drops-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -0
- package/SETUP.md +83 -0
- package/SKILL.md +93 -0
- package/brand/badge.html +15 -0
- package/brand/brand.json +13 -0
- package/brand/download-page.html +110 -0
- package/brand/favicon.png +0 -0
- package/brand/gate.html +327 -0
- package/brand/logo-black.png +0 -0
- package/brand/logo-white.png +0 -0
- package/brand/meta.html +13 -0
- package/drop.mjs +745 -0
- package/install.mjs +70 -0
- package/mcp.mjs +145 -0
- package/package.json +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# drops-mcp
|
|
2
|
+
|
|
3
|
+
**Open-source artifact sharing — publish HTML/Markdown/files as branded, password-protected,
|
|
4
|
+
zero-knowledge links on your own domain, from any AI agent.** CLI + MCP server. The open-source,
|
|
5
|
+
self-hosted [Stacktree](https://stacktr.ee) alternative.
|
|
6
|
+
|
|
7
|
+
→ Site & docs: **[drops.maxtechera.dev](https://drops.maxtechera.dev)** · Source: [github.com/maxtechera/drops-share](https://github.com/maxtechera/drops-share)
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx drops-install # wire the MCP into Claude Code / Codex / Cursor / OpenCode
|
|
13
|
+
# or one-line installer:
|
|
14
|
+
curl -fsSL https://drops.maxtechera.dev/install.sh | sh
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Use
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
drop report.html --managed # zero-setup: publish to the managed tier (no account, auto-expires 24h)
|
|
21
|
+
drop report.html # your own domain (after `drop init` + `drop setup`)
|
|
22
|
+
drop notes.md # markdown → branded HTML
|
|
23
|
+
drop site.zip # multi-file static site
|
|
24
|
+
drop list | rm <slug> | gc # manage drops
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## MCP
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
claude mcp add drops -- npx -y drops-mcp
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Tools: `publish_html`, `publish_file`, `update_site`, `list_sites`, `delete_site`.
|
|
34
|
+
|
|
35
|
+
## How it works
|
|
36
|
+
|
|
37
|
+
Branding + AES-256 encryption happen **client-side** (StatiCrypt) before upload to Vercel Blob; an
|
|
38
|
+
edge proxy serves it from your domain with the right headers. The server only ever stores ciphertext.
|
|
39
|
+
|
|
40
|
+
MIT © [Max Techera](https://maxtechera.dev)
|
package/SETUP.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# drop — backend setup (stand up your own deployment)
|
|
2
|
+
|
|
3
|
+
`drop` serves from **your** domain via a Vercel Blob store behind this repo's edge proxy. Do this
|
|
4
|
+
once; afterwards every machine just needs `drop setup` (the token) — the backend is shared.
|
|
5
|
+
|
|
6
|
+
## What you're building
|
|
7
|
+
|
|
8
|
+
- A **Vercel project** deploying this repo (`index.html` landing + `middleware.js` edge proxy + `vercel.json`).
|
|
9
|
+
- A **Vercel Blob store** that holds the uploaded drops.
|
|
10
|
+
- A **domain** (e.g. `drops.yoursite.com`) pointed at the project.
|
|
11
|
+
- A **token** (`BLOB_READ_WRITE_TOKEN`) the CLI uses to upload/list/delete.
|
|
12
|
+
|
|
13
|
+
## Steps
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# 0. clone + deploy this repo to Vercel
|
|
17
|
+
git clone https://github.com/maxtechera/drops-share && cd drops-share
|
|
18
|
+
vercel link --yes # creates .vercel/project.json (note the projectId + orgId)
|
|
19
|
+
|
|
20
|
+
# 1. add a Blob store named "drops" and pull its token
|
|
21
|
+
vercel blob store add drops # link to this project, all environments
|
|
22
|
+
vercel env pull .env.local --environment=production # contains BLOB_READ_WRITE_TOKEN
|
|
23
|
+
|
|
24
|
+
# 2. discover your PUBLIC blob host: do one upload and read the returned URL's host.
|
|
25
|
+
# e.g. https://<id>.public.blob.vercel-storage.com → host = <id>.public.blob.vercel-storage.com
|
|
26
|
+
# middleware.js reads BLOB host from the BLOB constant — set it there (and vercel.json fallback rewrite).
|
|
27
|
+
|
|
28
|
+
# 3. deploy + attach your domain
|
|
29
|
+
vercel deploy --prod --yes
|
|
30
|
+
vercel domains add drops.yoursite.com
|
|
31
|
+
|
|
32
|
+
# 4. point the skill at this deployment
|
|
33
|
+
node skill/drop.mjs init \
|
|
34
|
+
--domain drops.yoursite.com \
|
|
35
|
+
--blob-host <id>.public.blob.vercel-storage.com \
|
|
36
|
+
--project prj_xxx --org team_xxx # projectId/orgId from .vercel/project.json
|
|
37
|
+
|
|
38
|
+
# 5. give the skill the token (or run `vercel login` and let setup pull it)
|
|
39
|
+
node skill/drop.mjs setup --token "$(grep BLOB_READ_WRITE_TOKEN .env.local | cut -d= -f2- | tr -d '\"')"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Wiring the blob host
|
|
43
|
+
|
|
44
|
+
`drop` itself doesn't need the blob host (the SDK returns blob URLs), but the **serving side** does.
|
|
45
|
+
Set your host in two places so encrypted HTML renders correctly:
|
|
46
|
+
|
|
47
|
+
**`middleware.js`** — the edge proxy that fixes Blob's `Content-Disposition: attachment` + CSP:
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
const BLOB = "https://<id>.public.blob.vercel-storage.com";
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**`vercel.json`** — the fallback rewrite:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"rewrites": [
|
|
58
|
+
{ "source": "/", "destination": "/index.html" },
|
|
59
|
+
{ "source": "/(.*)", "destination": "https://<id>.public.blob.vercel-storage.com/$1" }
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Why both: `vercel.json` routes requests to the blob, and `middleware.js` rewrites the response
|
|
65
|
+
headers so password-protected HTML **decrypts and renders in-browser** instead of downloading, and so
|
|
66
|
+
CSP doesn't block StatiCrypt's unlock script. Serving is otherwise a dumb transparent proxy — all
|
|
67
|
+
branding/encryption happens at upload time in `drop.mjs`.
|
|
68
|
+
|
|
69
|
+
## Branding
|
|
70
|
+
|
|
71
|
+
Edit `skill/brand/brand.json` (name, colors, owner, social links) and replace
|
|
72
|
+
`skill/brand/logo-white.png` + `skill/brand/favicon.png`. These flow into the unlock gate, the corner
|
|
73
|
+
badge, download pages, and link-preview cards automatically.
|
|
74
|
+
|
|
75
|
+
## New machine (after the backend exists)
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
git clone https://github.com/maxtechera/drops-share && cd drops-share
|
|
79
|
+
node skill/drop.mjs setup # installs deps + writes ~/.drop/.env (pulls token from Vercel if logged in)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`drop list`/`rm` read the store directly, so they work from any machine; passwords are kept locally in
|
|
83
|
+
`~/.drop/manifest.json`.
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: drop
|
|
3
|
+
description: "Publish/share an artifact (HTML, Markdown, PDF, file, or whole site) as a private, branded, password-protected link on your own domain. Use when the user says 'publish this', 'share this page', 'drop this', 'send them a link', or whenever you've generated an HTML artifact they'll want to open in a browser. Zero-knowledge AES-256, ~1s. Zero-setup via --managed; self-host on your own domain via 'drop init'."
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# drop
|
|
8
|
+
|
|
9
|
+
Open-source artifact sharing on **your own domain**. Drop a file → it's branded, optionally
|
|
10
|
+
password-locked (AES-256, client-side), and live at a clean URL in ~1 second. No git, no
|
|
11
|
+
deploy-per-file — it's object storage (Vercel Blob) behind a one-line edge proxy.
|
|
12
|
+
|
|
13
|
+
## When to use
|
|
14
|
+
|
|
15
|
+
Reach for `drop` when the user asks to **publish / share / host / send** something, or whenever
|
|
16
|
+
you've generated an HTML page (report, dashboard, mockup, doc) they'll want to view in a browser.
|
|
17
|
+
Pipe the artifact to `drop` and hand back the URL (and password, if locked).
|
|
18
|
+
|
|
19
|
+
## Two ways to run
|
|
20
|
+
|
|
21
|
+
- **Zero-setup (managed):** `drop file.html --managed` — no token, no Vercel. Publishes to the
|
|
22
|
+
managed tier (HTML/markdown, auto-expires in 24h). Easiest start.
|
|
23
|
+
- **Your own domain:** `drop init` once (BYO Vercel Blob + domain, see `SETUP.md`), then `drop file.html`.
|
|
24
|
+
|
|
25
|
+
## Default posture
|
|
26
|
+
|
|
27
|
+
Locked by default (AES-256, client-side — the server only stores ciphertext). Every drop is served
|
|
28
|
+
`noindex` so leaked URLs never get indexed. Use a long password for anything sensitive.
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
drop report.html # brand + lock (auto password) + upload → https://<domain>/report-a1b2
|
|
34
|
+
drop notes.md # markdown → rendered, branded HTML page
|
|
35
|
+
drop report.html -p secret # use your own password
|
|
36
|
+
drop report.html --no-lock # branded, no password (renders for anyone with the link)
|
|
37
|
+
drop report.html --expire 7d # auto-expire (7d/24h/2w/date); enforce deletion with `drop gc`
|
|
38
|
+
drop bundle.zip # raw file, unguessable URL → /bundle-x7f2k9.zip
|
|
39
|
+
drop file.pdf --page # generate a branded download page wrapping the file
|
|
40
|
+
drop file.pdf --page -p secret # password-protect that download page
|
|
41
|
+
drop -s q3-deck deck.html # force the slug → /q3-deck
|
|
42
|
+
drop list # list live drops + their passwords
|
|
43
|
+
drop rm q3-deck # delete a drop
|
|
44
|
+
drop gc # delete drops whose --expire has passed (cron-friendly)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**MCP server:** `node skill/mcp.mjs` exposes `publish_html`, `publish_file`, `update_site`,
|
|
48
|
+
`list_sites`, `delete_site` over stdio. Wire it into agents with `node skill/install.mjs`.
|
|
49
|
+
|
|
50
|
+
The URL (and password, if locked) is printed and **copied to the clipboard**.
|
|
51
|
+
|
|
52
|
+
## How it works (per file)
|
|
53
|
+
|
|
54
|
+
1. **HTML** → inject branded `<head>` (favicon + OG/Twitter card so pasted links show a branded
|
|
55
|
+
preview) + a **subtle corner badge** before `</body>`.
|
|
56
|
+
2. If locking: run **StatiCrypt** (AES-256, client-side) with the branded unlock gate. The badge is
|
|
57
|
+
baked into the content *before* encryption, so it survives decryption.
|
|
58
|
+
3. Upload to **Vercel Blob** under a clean key; served via `middleware.js` on the configured domain
|
|
59
|
+
(rewrites Blob headers so encrypted HTML renders instead of downloading).
|
|
60
|
+
|
|
61
|
+
## Configuration
|
|
62
|
+
|
|
63
|
+
- **Branding** → `brand/brand.json` (name, colors, owner, social links) + swap `brand/logo-white.png`
|
|
64
|
+
and `brand/favicon.png`. Applied automatically to gate, badge, download pages, and link cards.
|
|
65
|
+
- **Infra** → `~/.drop/config.json` (domain, blob host, Vercel project/org), written by `drop init`.
|
|
66
|
+
- **Token** → `~/.drop/.env` → `BLOB_READ_WRITE_TOKEN`, written by `drop setup`.
|
|
67
|
+
- **Env overrides** → `DROP_DOMAIN`, `DROP_BLOB_HOST`.
|
|
68
|
+
|
|
69
|
+
## Setup (your own deployment)
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
drop init --domain drops.yoursite.com \
|
|
73
|
+
--blob-host <id>.public.blob.vercel-storage.com --project prj_xxx --org team_xxx
|
|
74
|
+
drop setup --token vercel_blob_rw_... # or: vercel login, then `drop setup`
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`drop setup` auto-installs `@vercel/blob` and verifies the store. See `SETUP.md` to stand up the
|
|
78
|
+
Vercel Blob store + domain rewrite first. The backend is shared — set up once, then `drop list`/`rm`
|
|
79
|
+
work from any machine; passwords stay local in `~/.drop/manifest.json`.
|
|
80
|
+
|
|
81
|
+
## Security notes
|
|
82
|
+
|
|
83
|
+
- **Locked HTML** is genuinely AES-256 encrypted client-side — strong against casual access. The
|
|
84
|
+
encrypted blob is downloadable, so use long passwords for anything sensitive (it's not a vault).
|
|
85
|
+
- **Raw files** are **not** encrypted — protection is the unguessable random slug. For real
|
|
86
|
+
protection on a file, use `--page -p <pw>` (gated page).
|
|
87
|
+
- The password manifest at `~/.drop/manifest.json` is plaintext on this machine only.
|
|
88
|
+
|
|
89
|
+
## Requirements
|
|
90
|
+
|
|
91
|
+
- `node` ≥ 18, `npx` (StatiCrypt is pulled on demand); `vercel` CLI for `drop setup` token pull
|
|
92
|
+
- `@vercel/blob` — auto-installed on first run (gitignored, not committed)
|
|
93
|
+
- Clipboard helper (optional): `pbcopy` (mac), `clip`/`clip.exe` (windows/WSL), `xclip`/`wl-copy` (linux)
|
package/brand/badge.html
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!-- drop: subtle corner badge (injected before </body>) -->
|
|
2
|
+
<a id="drop-badge" href="https://__DROP_DOMAIN__" target="_blank" rel="noopener noreferrer"
|
|
3
|
+
style="position:fixed;right:14px;bottom:14px;z-index:2147483646;display:flex;align-items:center;gap:7px;
|
|
4
|
+
padding:7px 12px 7px 9px;border-radius:999px;text-decoration:none;
|
|
5
|
+
font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
|
|
6
|
+
font-size:12px;font-weight:600;line-height:1;color:#fff;
|
|
7
|
+
background:rgba(20,20,22,0.62);border:1px solid rgba(255,255,255,0.14);
|
|
8
|
+
box-shadow:0 6px 22px -8px rgba(0,0,0,0.55);
|
|
9
|
+
-webkit-backdrop-filter:blur(12px) saturate(160%);backdrop-filter:blur(12px) saturate(160%);
|
|
10
|
+
transition:transform .12s ease,background .12s ease;"
|
|
11
|
+
onmouseover="this.style.transform='translateY(-1px)';this.style.background='rgba(20,20,22,0.78)'"
|
|
12
|
+
onmouseout="this.style.transform='none';this.style.background='rgba(20,20,22,0.62)'">
|
|
13
|
+
<img src="__DROP_LOGO__" alt="" style="height:15px;width:auto;display:block;" />
|
|
14
|
+
<span style="opacity:.78;">__DROP_DOMAIN__</span>
|
|
15
|
+
</a>
|
package/brand/brand.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "drops",
|
|
3
|
+
"owner": "Max Techera",
|
|
4
|
+
"domain": "drops.maxtechera.dev",
|
|
5
|
+
"tagline": "Share anything. Branded. On your own domain.",
|
|
6
|
+
"primaryColor": "#ff6b35",
|
|
7
|
+
"accentColor": "#ea580c",
|
|
8
|
+
"links": {
|
|
9
|
+
"website": "https://maxtechera.dev",
|
|
10
|
+
"github": "https://github.com/maxtechera",
|
|
11
|
+
"instagram": "https://instagram.com/maxtechera"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>__DROP_TITLE__</title>
|
|
7
|
+
__DROP_META__
|
|
8
|
+
<style>
|
|
9
|
+
:root {
|
|
10
|
+
--drop-primary: #ff6b35;
|
|
11
|
+
--drop-accent: #ea580c;
|
|
12
|
+
}
|
|
13
|
+
* { box-sizing: border-box; }
|
|
14
|
+
html, body { height: 100%; margin: 0; }
|
|
15
|
+
body {
|
|
16
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
17
|
+
-webkit-font-smoothing: antialiased;
|
|
18
|
+
color: #f5f5f5;
|
|
19
|
+
display: flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
justify-content: center;
|
|
22
|
+
padding: 24px;
|
|
23
|
+
background:
|
|
24
|
+
radial-gradient(1200px 600px at 50% -10%, rgba(255, 107, 53, 0.22), transparent 60%),
|
|
25
|
+
radial-gradient(900px 500px at 90% 110%, rgba(234, 88, 12, 0.16), transparent 60%),
|
|
26
|
+
#0b0b0d;
|
|
27
|
+
}
|
|
28
|
+
.card {
|
|
29
|
+
width: 100%;
|
|
30
|
+
max-width: 440px;
|
|
31
|
+
background: rgba(255, 255, 255, 0.06);
|
|
32
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
33
|
+
border-radius: 20px;
|
|
34
|
+
padding: 36px 32px 30px;
|
|
35
|
+
text-align: center;
|
|
36
|
+
backdrop-filter: blur(18px) saturate(160%);
|
|
37
|
+
-webkit-backdrop-filter: blur(18px) saturate(160%);
|
|
38
|
+
box-shadow: 0 24px 70px -24px rgba(0, 0, 0, 0.7), inset 0 0.5px 0 rgba(255, 255, 255, 0.12);
|
|
39
|
+
}
|
|
40
|
+
.logo { height: 38px; margin-bottom: 24px; opacity: 0.95; }
|
|
41
|
+
h1 {
|
|
42
|
+
font-size: 1.35em;
|
|
43
|
+
font-weight: 650;
|
|
44
|
+
letter-spacing: -0.01em;
|
|
45
|
+
margin: 0 0 6px;
|
|
46
|
+
word-break: break-word;
|
|
47
|
+
}
|
|
48
|
+
.sub { color: rgba(245, 245, 245, 0.55); font-size: 0.9em; margin: 0 0 26px; }
|
|
49
|
+
.file {
|
|
50
|
+
display: flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
gap: 14px;
|
|
53
|
+
text-align: left;
|
|
54
|
+
background: rgba(0, 0, 0, 0.28);
|
|
55
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
56
|
+
border-radius: 13px;
|
|
57
|
+
padding: 14px 16px;
|
|
58
|
+
margin-bottom: 20px;
|
|
59
|
+
}
|
|
60
|
+
.file .ico {
|
|
61
|
+
flex: 0 0 auto;
|
|
62
|
+
width: 40px; height: 40px;
|
|
63
|
+
border-radius: 10px;
|
|
64
|
+
display: flex; align-items: center; justify-content: center;
|
|
65
|
+
background: linear-gradient(135deg, var(--drop-primary), var(--drop-accent));
|
|
66
|
+
font-size: 18px;
|
|
67
|
+
}
|
|
68
|
+
.file .meta { min-width: 0; }
|
|
69
|
+
.file .name { font-weight: 600; font-size: 0.95em; overflow-wrap: anywhere; }
|
|
70
|
+
.file .size { color: rgba(245, 245, 245, 0.5); font-size: 0.82em; margin-top: 2px; }
|
|
71
|
+
.btn {
|
|
72
|
+
display: block;
|
|
73
|
+
width: 100%;
|
|
74
|
+
background: linear-gradient(135deg, var(--drop-primary), var(--drop-accent));
|
|
75
|
+
color: #fff;
|
|
76
|
+
text-decoration: none;
|
|
77
|
+
text-align: center;
|
|
78
|
+
text-transform: uppercase;
|
|
79
|
+
letter-spacing: 0.04em;
|
|
80
|
+
font-weight: 650;
|
|
81
|
+
font-size: 14px;
|
|
82
|
+
padding: 15px;
|
|
83
|
+
border-radius: 12px;
|
|
84
|
+
transition: filter 0.15s ease, transform 0.05s ease;
|
|
85
|
+
}
|
|
86
|
+
.btn:hover { filter: brightness(108%); }
|
|
87
|
+
.btn:active { transform: translateY(1px); }
|
|
88
|
+
.footer { margin: 22px 0 0; font-size: 0.78em; color: rgba(245, 245, 245, 0.4); }
|
|
89
|
+
.footer a { color: rgba(255, 107, 53, 0.85); text-decoration: none; }
|
|
90
|
+
</style>
|
|
91
|
+
</head>
|
|
92
|
+
<body>
|
|
93
|
+
<div class="card">
|
|
94
|
+
<img class="logo" src="__DROP_LOGO__" alt="logo" />
|
|
95
|
+
<h1>__DROP_TITLE__</h1>
|
|
96
|
+
<p class="sub">__DROP_SUBTITLE__</p>
|
|
97
|
+
<div class="file">
|
|
98
|
+
<div class="ico">↓</div>
|
|
99
|
+
<div class="meta">
|
|
100
|
+
<div class="name">__DROP_FILE_NAME__</div>
|
|
101
|
+
<div class="size">__DROP_FILE_SIZE__</div>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
<a class="btn" href="__DROP_FILE_URL__" download>Download</a>
|
|
105
|
+
<p class="footer">Shared via <a href="https://__DROP_DOMAIN__">__DROP_DOMAIN__</a><br />
|
|
106
|
+
<span style="opacity:.8;">made by <a href="__DROP_WEBSITE__">__DROP_OWNER__</a> · <a href="__DROP_GITHUB__">github</a> · <a href="__DROP_INSTAGRAM__">instagram</a></span>
|
|
107
|
+
</p>
|
|
108
|
+
</div>
|
|
109
|
+
</body>
|
|
110
|
+
</html>
|
|
Binary file
|