├── 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 |
2 |
3 |
This is an about page
4 |

5 |
6 |
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 |
2 |
3 |

4 |
Encrypted Pages
5 |
6 | The about page is encrypted and password is password.
7 |
8 |
9 |
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 |
2 |
3 |
4 | Home |
5 | About
6 |
7 |
8 |
9 |
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 |
50 |
57 |
58 | Loading...
59 |
60 |
61 |
62 |
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 |
150 |
--------------------------------------------------------------------------------