├── src ├── views │ ├── test.js │ ├── encrypted │ │ └── spiderman.gif │ ├── About.vue │ └── Home.vue ├── assets │ └── logo.png ├── main.js ├── App.vue ├── router.js ├── registerServiceWorker.js └── components │ └── EncryptedPage.vue ├── public ├── robots.txt ├── favicon.ico ├── img │ └── icons │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── apple-touch-icon.png │ │ ├── mstile-150x150.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon-60x60.png │ │ ├── apple-touch-icon-76x76.png │ │ ├── apple-touch-icon-120x120.png │ │ ├── apple-touch-icon-152x152.png │ │ ├── apple-touch-icon-180x180.png │ │ ├── msapplication-icon-144x144.png │ │ └── safari-pinned-tab.svg ├── manifest.json ├── index.html └── key.asc ├── babel.config.js ├── .gitignore ├── README.md ├── vue.config.js ├── encryption ├── webpack-chunk-encryption-plugin.js ├── vue-chunk-decryption-plugin.js ├── encryption-key.asc └── decryption-key.asc └── package.json /src/views/test.js: -------------------------------------------------------------------------------- 1 | export default 'foo' 2 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@vue/app'], 3 | } 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/znck/pgp-encrypted-pages-with-vue/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/znck/pgp-encrypted-pages-with-vue/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /public/img/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/znck/pgp-encrypted-pages-with-vue/HEAD/public/img/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/img/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/znck/pgp-encrypted-pages-with-vue/HEAD/public/img/icons/favicon-32x32.png -------------------------------------------------------------------------------- /src/views/encrypted/spiderman.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/znck/pgp-encrypted-pages-with-vue/HEAD/src/views/encrypted/spiderman.gif -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/znck/pgp-encrypted-pages-with-vue/HEAD/public/img/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /public/img/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/znck/pgp-encrypted-pages-with-vue/HEAD/public/img/icons/mstile-150x150.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/znck/pgp-encrypted-pages-with-vue/HEAD/public/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/znck/pgp-encrypted-pages-with-vue/HEAD/public/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/znck/pgp-encrypted-pages-with-vue/HEAD/public/img/icons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/znck/pgp-encrypted-pages-with-vue/HEAD/public/img/icons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/znck/pgp-encrypted-pages-with-vue/HEAD/public/img/icons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/znck/pgp-encrypted-pages-with-vue/HEAD/public/img/icons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/znck/pgp-encrypted-pages-with-vue/HEAD/public/img/icons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /public/img/icons/msapplication-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/znck/pgp-encrypted-pages-with-vue/HEAD/public/img/icons/msapplication-icon-144x144.png -------------------------------------------------------------------------------- /src/views/About.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import DecryptChunk from '../encryption/vue-chunk-decryption-plugin' 3 | import privateKey from '../encryption/decryption-key.asc' 4 | 5 | Vue.use(DecryptChunk, { privateKey }) 6 | 7 | import App from './App.vue' 8 | import router from './router' 9 | import './registerServiceWorker' 10 | 11 | Vue.config.productionTip = false 12 | Vue.config.devtools = true 13 | 14 | new Vue({ 15 | router, 16 | render: h => h(App), 17 | }).$mount('#app') 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Encrypted Pages 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Run your tests 19 | ``` 20 | npm run test 21 | ``` 22 | 23 | ### Lints and fixes files 24 | ``` 25 | npm run lint 26 | ``` 27 | 28 | ### Customize configuration 29 | See [Configuration Reference](https://cli.vuejs.org/config/). 30 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "poulami", 3 | "short_name": "poulami", 4 | "icons": [ 5 | { 6 | "src": "./img/icons/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "./img/icons/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "start_url": "./index.html", 17 | "display": "standalone", 18 | "background_color": "#000000", 19 | "theme_color": "#4DBA87" 20 | } 21 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | poulami 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 32 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Home from './views/Home.vue' 4 | import EncryptedPage from './components/EncryptedPage.vue' 5 | 6 | Vue.use(Router) 7 | 8 | export default new Router({ 9 | mode: 'history', 10 | base: process.env.BASE_URL, 11 | routes: [ 12 | { 13 | path: '/', 14 | name: 'home', 15 | component: Home, 16 | }, 17 | { 18 | path: '/about', 19 | name: 'about', 20 | props: { 21 | component: () => 22 | import(/* webpackChunkName: "about.enc" */ './views/About.vue'), 23 | }, 24 | component: EncryptedPage, 25 | }, 26 | ], 27 | }) 28 | -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import { register } from 'register-service-worker' 4 | 5 | if (process.env.NODE_ENV === 'production') { 6 | register(`${process.env.BASE_URL}service-worker.js`, { 7 | ready() { 8 | console.log( 9 | 'App is being served from cache by a service worker.\n' + 10 | 'For more details, visit https://goo.gl/AFskqB' 11 | ) 12 | }, 13 | registered() { 14 | console.log('Service worker has been registered.') 15 | }, 16 | cached() { 17 | console.log('Content has been cached for offline use.') 18 | }, 19 | updatefound() { 20 | console.log('New content is downloading.') 21 | }, 22 | updated() { 23 | console.log('New content is available; please refresh.') 24 | }, 25 | offline() { 26 | console.log( 27 | 'No internet connection found. App is running in offline mode.' 28 | ) 29 | }, 30 | error(error) { 31 | console.error('Error during service worker registration:', error) 32 | }, 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | 4 | module.exports = { 5 | /** @param {import('webpack-chain')} config */ 6 | chainWebpack(config) { 7 | config.module.rule('images').exclude.add(/encrypted/) 8 | config.module.rule('media').exclude.add(/encrypted/) 9 | 10 | config.module 11 | .rule('inlined-images') 12 | .include.add(/encrypted/) 13 | .end() 14 | .test(/\.(png|jpe?g|gif|webp)(\?.*)?$/) 15 | .use('url-loader') 16 | .loader('url-loader') 17 | .options({ 18 | limit: Infinity, 19 | }) 20 | 21 | config.module 22 | .rule('inlined-media') 23 | .include.add(/encrypted/) 24 | .end() 25 | .test(/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/) 26 | .use('url-loader') 27 | .loader('url-loader') 28 | .options({ 29 | limit: Infinity, 30 | }) 31 | 32 | config.module 33 | .rule('key') 34 | .test(/\.asc$/) 35 | .use('raw-loader') 36 | .loader('raw-loader') 37 | 38 | config 39 | .plugin('encrypt') 40 | .use(require('./encryption/webpack-chunk-encryption-plugin'), [ 41 | { 42 | publicKey: fs 43 | .readFileSync( 44 | path.resolve(__dirname, './encryption/encryption-key.asc') 45 | ) 46 | .toString(), 47 | }, 48 | ]) 49 | 50 | if (process.env.NODE_ENV === 'production') config.devtool(false) 51 | }, 52 | } 53 | -------------------------------------------------------------------------------- /encryption/webpack-chunk-encryption-plugin.js: -------------------------------------------------------------------------------- 1 | const openpgp = require('openpgp') 2 | 3 | module.exports = class ChunkEncryptionPlugin { 4 | constructor({ publicKey }) { 5 | this.publicKey = publicKey 6 | } 7 | apply(compiler) { 8 | compiler.hooks.emit.tapAsync( 9 | 'ChunkEncryptionPlugin', 10 | async (compilation, done) => { 11 | const names = Object.keys(compilation.assets).filter(name => 12 | /\.enc.([a-f0-9]+\.)?js$/.test(name) 13 | ) 14 | 15 | await Promise.all( 16 | names.map(async name => { 17 | const chunk = compilation.assets[name] 18 | const content = decrypt( 19 | await encrypt(chunk.source(), this.publicKey) 20 | ) 21 | 22 | compilation.assets[name] = { 23 | source() { 24 | return content 25 | }, 26 | size() { 27 | return content.length 28 | }, 29 | } 30 | }) 31 | ) 32 | 33 | done() 34 | } 35 | ) 36 | } 37 | } 38 | 39 | async function encrypt(message, key) { 40 | const result = await openpgp.encrypt({ 41 | message: openpgp.message.fromText(message), 42 | publicKeys: (await openpgp.key.readArmored(key)).keys, 43 | }) 44 | 45 | return result.data 46 | } 47 | 48 | function decrypt(content) { 49 | return `window.__decrypt_webpack_chunk__('${content 50 | .split(/\r?\n/g) 51 | .join('\\n')}')` 52 | } 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "encrypted-pages", 3 | "private": true, 4 | "scripts": { 5 | "serve": "vue-cli-service serve", 6 | "build": "vue-cli-service build", 7 | "lint": "vue-cli-service lint", 8 | "predeploy": "npm run build", 9 | "deploy": "cp dist/index.html dist/200.html && surge dist encrypted-pages-with-vue.surge.sh" 10 | }, 11 | "dependencies": { 12 | "openpgp": "^4.4.7", 13 | "register-service-worker": "^1.5.2", 14 | "vue": "^2.6.6", 15 | "vue-router": "^3.0.1" 16 | }, 17 | "devDependencies": { 18 | "@vue/cli-plugin-babel": "^3.4.0", 19 | "@vue/cli-plugin-eslint": "^3.4.0", 20 | "@vue/cli-plugin-pwa": "^3.4.0", 21 | "@vue/cli-service": "^3.4.0", 22 | "@vue/eslint-config-prettier": "^4.0.1", 23 | "babel-eslint": "^10.0.1", 24 | "eslint": "^5.8.0", 25 | "eslint-plugin-vue": "^5.0.0", 26 | "raw-loader": "^1.0.0", 27 | "vue-template-compiler": "^2.5.21" 28 | }, 29 | "eslintConfig": { 30 | "root": true, 31 | "env": { 32 | "node": true 33 | }, 34 | "extends": [ 35 | "plugin:vue/essential", 36 | "@vue/prettier" 37 | ], 38 | "rules": {}, 39 | "parserOptions": { 40 | "parser": "babel-eslint" 41 | } 42 | }, 43 | "postcss": { 44 | "plugins": { 45 | "autoprefixer": {} 46 | } 47 | }, 48 | "browserslist": [ 49 | "> 1%", 50 | "last 2 versions", 51 | "not ie <= 8" 52 | ], 53 | "prettier": { 54 | "trailingComma": "es5", 55 | "singleQuote": true, 56 | "semi": false 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/components/EncryptedPage.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 63 | -------------------------------------------------------------------------------- /encryption/vue-chunk-decryption-plugin.js: -------------------------------------------------------------------------------- 1 | import { decrypt, decryptKey, key as KEY, message } from 'openpgp' 2 | 3 | export default function ChunkDecryptionPlugin(Vue, { privateKey }) { 4 | let current = {} 5 | 6 | window.__decrypt_webpack_chunk__ = async function __decrypt_webpack_chunk__( 7 | payload 8 | ) { 9 | if (!current.vm) { 10 | setTimeout(window.__decrypt_webpack_chunk__, 1000, payload) 11 | return 12 | } 13 | 14 | const { vm, password } = current 15 | 16 | try { 17 | const passphrase = await password() 18 | vm.$emit('decryption:start') 19 | const { key } = await decryptKey({ 20 | privateKey: (await KEY.readArmored(privateKey)).keys[0], 21 | passphrase, 22 | }) 23 | const content = await decrypt({ 24 | message: await message.readArmored(payload), 25 | privateKeys: [key], 26 | }) 27 | const script = document.createElement('script') 28 | script.innerHTML = content.data 29 | document.head.appendChild(script) 30 | vm.$emit('decryption:loaded') 31 | } catch (error) { 32 | vm.$emit('decryption:error', error, () => 33 | __decrypt_webpack_chunk__(payload) 34 | ) 35 | console.error(error) // eslint-disable-line 36 | } 37 | } 38 | const warnHandler = Vue.config.warnHandler 39 | Vue.config.warnHandler = (error, vm, info) => { 40 | if (/Loading chunk .*?\.enc failed/.test(error.message)) return 41 | if (warnHandler) return warnHandler(error, vm, info) 42 | } 43 | 44 | Vue.mixin({ 45 | created() { 46 | if (this.$options.password) { 47 | current = { 48 | vm: this, 49 | password: () => this.$options.password.call(this), 50 | } 51 | } 52 | }, 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /encryption/encryption-key.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | 3 | mQENBFxxmWgBCAC/Pw4z89e+/ScvYDZ5tM7YST5KoXd08hq8+VwwWVT21mumIfQT 4 | jJ4QZWVKn0WZ6Xq5uLqyWVwAoEGU3wlzQ+Xi6FwBoZ2nnz0sEf6QGUQDPWmvxIvr 5 | PTIjUJ47xCdzdhmT/EUjRzs9au4Ts0ZBPfPAKCy22V88dKZjx869NW4gFBPIU8xB 6 | lIk67oD65HZhLfeAaKRBmLFBUtgyMl3/DwnPv1+UJN44tU21UG4svzNi+XdDiVxF 7 | 78gMmF0NnjNmg73zCYLuc6xTUpEfdObQ1PI486OTgpBlj550tkdbRQW+1KY1FQjW 8 | k2Jqwa7fwb5IRQE1gvRXVN+1wfOA+wwxOlURABEBAAG0G0phbmUgRG9lIDxqYW5l 9 | QGV4YW1wbGUuY29tPokBVAQTAQgAPhYhBIO27fmRDmPJjhn1uQX1cqyUdXGBBQJc 10 | cZloAhsDBQkHhh+ABQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEAX1cqyUdXGB 11 | /P4H/ib5qSYS9Z/PsL/+/FPrNHko4AHsdsDBrEa9jzK+aFC4K3CIdCkmQK4SK7LE 12 | p5vNO5JXXhnjXJpjmP/xhwtMN3cn0ZZY9T6O4PEe4Xms+AdTh60hsbJMJZ+qaj0c 13 | cHIMRfRV6vWbaSPEYo1azTc5ZpE+4YdeUBEHLKvdQtyIhIwmit/RSsPYg6ZuZ1gn 14 | eqMSjC7BNESnxCRR43mW0aPNQg5haAARjiHkt67l+fylLXkMDsKjPY/AhOXXXmH4 15 | /4qtjhQuzY5h0PYi406g1g85DFlGy3gCAYz3t+g+Kr4/m8zxF3ADacl/Y/zt+0W3 16 | dy5dBIiIx3v4IVL9RYR4x2i+6lW5AQ0EXHGZaAEIAM9BK9vExjZANp6fqoR+BjXw 17 | gpI9iuXPyR3kOUzeUsdgq7+tCXIvPwHCyrzAOB/DSf4AzNUgTLRommlXBRW9OF+n 18 | tsabqKF25QPEKKURA4PmEojranGFAh5bsnjPvK8JSHzoroFvIIYcvHaCd0P7sEwP 19 | 9MNgK0OzMi/qjtC8RrEPCpjR7mdGPNZcH0cEKZtGgp83qbtLwcNWNoor7gYycLMm 20 | oEHzIMmP5tIHE1CRzL1YQQ/BfeEqtpOYb+6P/YBDbhoraTUQferAMPiuWWHFPZNk 21 | 7Wil6Ler9/l7cHWLDQSuaRegiDGucplybEt9isBIyuwmSX86ui3lgsiBQF8GL0EA 22 | EQEAAYkBPAQYAQgAJhYhBIO27fmRDmPJjhn1uQX1cqyUdXGBBQJccZloAhsMBQkH 23 | hh+AAAoJEAX1cqyUdXGB2+oIAJ4F+b5mW/nR5nyQ7dV7LMydRmJuBW4097+UhqFj 24 | 8+0k13IyUUzlyEJ7IBazZiM2Fw4w4i5rr4LGuVVLZon9I4pC13T0rNxk9UmffmKs 25 | PtMGSqyypD/BIXPnWf0e6mWzOOfupPgFGMeI3/zq37MRPkIyzlM/HUvDVhcMwzFe 26 | Y0YJyCbJAvvODVTpIp7jMyblDFWkEOx2isQULtb22TKOZNjcIi7RXirEkFDvHXJK 27 | qkWI+Z5lwguq9+1ONvRvkK+BpRG1eImcX6BP28J09g12mkQxKX5olKJB0JoxX8z4 28 | SIF+sXBqiyw7J9LA3fMYEB+VdAgCJ1z9ClZnM2BCvr30x+Y= 29 | =vxE7 30 | -----END PGP PUBLIC KEY BLOCK----- -------------------------------------------------------------------------------- /encryption/decryption-key.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PRIVATE KEY BLOCK----- 2 | 3 | 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 58 | =PLLx 59 | -----END PGP PRIVATE KEY BLOCK----- 60 | -------------------------------------------------------------------------------- /public/key.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PRIVATE KEY BLOCK----- 2 | 3 | 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 58 | =PLLx 59 | -----END PGP PRIVATE KEY BLOCK----- 60 | -----BEGIN PGP PUBLIC KEY BLOCK----- 61 | 62 | mQENBFxxmWgBCAC/Pw4z89e+/ScvYDZ5tM7YST5KoXd08hq8+VwwWVT21mumIfQT 63 | jJ4QZWVKn0WZ6Xq5uLqyWVwAoEGU3wlzQ+Xi6FwBoZ2nnz0sEf6QGUQDPWmvxIvr 64 | PTIjUJ47xCdzdhmT/EUjRzs9au4Ts0ZBPfPAKCy22V88dKZjx869NW4gFBPIU8xB 65 | lIk67oD65HZhLfeAaKRBmLFBUtgyMl3/DwnPv1+UJN44tU21UG4svzNi+XdDiVxF 66 | 78gMmF0NnjNmg73zCYLuc6xTUpEfdObQ1PI486OTgpBlj550tkdbRQW+1KY1FQjW 67 | k2Jqwa7fwb5IRQE1gvRXVN+1wfOA+wwxOlURABEBAAG0G0phbmUgRG9lIDxqYW5l 68 | QGV4YW1wbGUuY29tPokBVAQTAQgAPhYhBIO27fmRDmPJjhn1uQX1cqyUdXGBBQJc 69 | cZloAhsDBQkHhh+ABQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEAX1cqyUdXGB 70 | /P4H/ib5qSYS9Z/PsL/+/FPrNHko4AHsdsDBrEa9jzK+aFC4K3CIdCkmQK4SK7LE 71 | p5vNO5JXXhnjXJpjmP/xhwtMN3cn0ZZY9T6O4PEe4Xms+AdTh60hsbJMJZ+qaj0c 72 | cHIMRfRV6vWbaSPEYo1azTc5ZpE+4YdeUBEHLKvdQtyIhIwmit/RSsPYg6ZuZ1gn 73 | eqMSjC7BNESnxCRR43mW0aPNQg5haAARjiHkt67l+fylLXkMDsKjPY/AhOXXXmH4 74 | /4qtjhQuzY5h0PYi406g1g85DFlGy3gCAYz3t+g+Kr4/m8zxF3ADacl/Y/zt+0W3 75 | dy5dBIiIx3v4IVL9RYR4x2i+6lW5AQ0EXHGZaAEIAM9BK9vExjZANp6fqoR+BjXw 76 | gpI9iuXPyR3kOUzeUsdgq7+tCXIvPwHCyrzAOB/DSf4AzNUgTLRommlXBRW9OF+n 77 | tsabqKF25QPEKKURA4PmEojranGFAh5bsnjPvK8JSHzoroFvIIYcvHaCd0P7sEwP 78 | 9MNgK0OzMi/qjtC8RrEPCpjR7mdGPNZcH0cEKZtGgp83qbtLwcNWNoor7gYycLMm 79 | oEHzIMmP5tIHE1CRzL1YQQ/BfeEqtpOYb+6P/YBDbhoraTUQferAMPiuWWHFPZNk 80 | 7Wil6Ler9/l7cHWLDQSuaRegiDGucplybEt9isBIyuwmSX86ui3lgsiBQF8GL0EA 81 | EQEAAYkBPAQYAQgAJhYhBIO27fmRDmPJjhn1uQX1cqyUdXGBBQJccZloAhsMBQkH 82 | hh+AAAoJEAX1cqyUdXGB2+oIAJ4F+b5mW/nR5nyQ7dV7LMydRmJuBW4097+UhqFj 83 | 8+0k13IyUUzlyEJ7IBazZiM2Fw4w4i5rr4LGuVVLZon9I4pC13T0rNxk9UmffmKs 84 | PtMGSqyypD/BIXPnWf0e6mWzOOfupPgFGMeI3/zq37MRPkIyzlM/HUvDVhcMwzFe 85 | Y0YJyCbJAvvODVTpIp7jMyblDFWkEOx2isQULtb22TKOZNjcIi7RXirEkFDvHXJK 86 | qkWI+Z5lwguq9+1ONvRvkK+BpRG1eImcX6BP28J09g12mkQxKX5olKJB0JoxX8z4 87 | SIF+sXBqiyw7J9LA3fMYEB+VdAgCJ1z9ClZnM2BCvr30x+Y= 88 | =vxE7 89 | -----END PGP PUBLIC KEY BLOCK----- 90 | -------------------------------------------------------------------------------- /public/img/icons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 148 | 149 | 150 | --------------------------------------------------------------------------------