koyoapp 0.1.0__tar.gz

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.
Files changed (46) hide show
  1. koyoapp-0.1.0/MANIFEST.in +3 -0
  2. koyoapp-0.1.0/PKG-INFO +122 -0
  3. koyoapp-0.1.0/README.md +102 -0
  4. koyoapp-0.1.0/koyoapp/__init__.py +3 -0
  5. koyoapp-0.1.0/koyoapp/__main__.py +4 -0
  6. koyoapp-0.1.0/koyoapp/_placeholders.py +56 -0
  7. koyoapp-0.1.0/koyoapp/app.py +224 -0
  8. koyoapp-0.1.0/koyoapp/assets/apple-touch-icon.png +0 -0
  9. koyoapp-0.1.0/koyoapp/assets/favicon-96x96.png +0 -0
  10. koyoapp-0.1.0/koyoapp/assets/favicon.ico +0 -0
  11. koyoapp-0.1.0/koyoapp/assets/favicon.svg +1 -0
  12. koyoapp-0.1.0/koyoapp/assets/htmx.min.js +1 -0
  13. koyoapp-0.1.0/koyoapp/assets/icon.png +0 -0
  14. koyoapp-0.1.0/koyoapp/assets/logo.png +0 -0
  15. koyoapp-0.1.0/koyoapp/assets/site.webmanifest +21 -0
  16. koyoapp-0.1.0/koyoapp/assets/web-app-manifest-192x192.png +0 -0
  17. koyoapp-0.1.0/koyoapp/assets/web-app-manifest-512x512.png +0 -0
  18. koyoapp-0.1.0/koyoapp/build.py +82 -0
  19. koyoapp-0.1.0/koyoapp/cli.py +128 -0
  20. koyoapp-0.1.0/koyoapp/config.py +31 -0
  21. koyoapp-0.1.0/koyoapp/deps.py +312 -0
  22. koyoapp-0.1.0/koyoapp/dev.py +189 -0
  23. koyoapp-0.1.0/koyoapp/html.py +183 -0
  24. koyoapp-0.1.0/koyoapp/log.py +70 -0
  25. koyoapp-0.1.0/koyoapp/meta.py +51 -0
  26. koyoapp-0.1.0/koyoapp/net.py +35 -0
  27. koyoapp-0.1.0/koyoapp/reload.py +92 -0
  28. koyoapp-0.1.0/koyoapp/router.py +238 -0
  29. koyoapp-0.1.0/koyoapp/scaffold.py +60 -0
  30. koyoapp-0.1.0/koyoapp/serve.py +11 -0
  31. koyoapp-0.1.0/koyoapp/session.py +85 -0
  32. koyoapp-0.1.0/koyoapp/state.py +110 -0
  33. koyoapp-0.1.0/koyoapp/tailwind.py +86 -0
  34. koyoapp-0.1.0/koyoapp/templates.py +475 -0
  35. koyoapp-0.1.0/koyoapp/venv.py +17 -0
  36. koyoapp-0.1.0/koyoapp.egg-info/SOURCES.txt +43 -0
  37. koyoapp-0.1.0/pyproject.toml +40 -0
  38. koyoapp-0.1.0/setup.cfg +4 -0
  39. koyoapp-0.1.0/tests/test_build.py +47 -0
  40. koyoapp-0.1.0/tests/test_deps.py +249 -0
  41. koyoapp-0.1.0/tests/test_dev.py +83 -0
  42. koyoapp-0.1.0/tests/test_html.py +94 -0
  43. koyoapp-0.1.0/tests/test_install.py +151 -0
  44. koyoapp-0.1.0/tests/test_net.py +35 -0
  45. koyoapp-0.1.0/tests/test_router.py +374 -0
  46. koyoapp-0.1.0/tests/test_session.py +95 -0
@@ -0,0 +1,3 @@
1
+ # Keep local-only files and secrets out of the built distribution.
2
+ exclude .koyotoken state.md state2.md state3.md state4.md state5.md
3
+ global-exclude *.egg-info/*
koyoapp-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,122 @@
1
+ Metadata-Version: 2.4
2
+ Name: koyoapp
3
+ Version: 0.1.0
4
+ Summary: Koyo is a Python web framework that brings the Next.js app directory experience to pure Python.
5
+ Author: Koyo Developers
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.11
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: starlette>=0.37.0
10
+ Requires-Dist: uvicorn>=0.30.0
11
+ Requires-Dist: watchfiles>=0.21.0
12
+ Requires-Dist: typer>=0.12.0
13
+ Requires-Dist: tomlkit>=0.12.0
14
+ Provides-Extra: test
15
+ Requires-Dist: pytest>=8.0; extra == "test"
16
+ Requires-Dist: httpx>=0.27; extra == "test"
17
+ Provides-Extra: dev
18
+ Requires-Dist: build>=1.0.0; extra == "dev"
19
+ Requires-Dist: twine>=5.0.0; extra == "dev"
20
+
21
+ # Koyo
22
+
23
+ Koyo is a Python web framework that mirrors the developer experience of
24
+ Next.js (the app directory convention, file based routing, layouts,
25
+ reusable components) but is written entirely in Python and runs as a normal
26
+ Python web server. Since it is a normal Python process under the hood, any
27
+ package installed with pip is available immediately, with no build step or
28
+ compiler touching the Python code.
29
+
30
+ This is v1, frontend and routing only. No backend API layer, no database,
31
+ no auth, no deployment automation.
32
+
33
+ ## Install
34
+
35
+ Koyo requires Python 3.11 or newer.
36
+
37
+ ```
38
+ pip install koyoapp
39
+ ```
40
+
41
+ ## Quickstart
42
+
43
+ ```
44
+ koyoapp create my-app
45
+ cd my-app
46
+ koyoapp dev
47
+ ```
48
+
49
+ Open http://127.0.0.1:2309. The home page renders out of the box with an
50
+ animated hero, a call to action linking to the Koyo documentation, and a
51
+ live session counter driven by htmx. Edit any file under `app/`, `public/`,
52
+ or `styles/` and the dev server restarts and the browser reloads
53
+ automatically.
54
+
55
+ ## CLI
56
+
57
+ - `koyoapp create .` scaffolds a Koyo project into the current directory.
58
+ - `koyoapp create my-app` creates `my-app` and scaffolds inside it.
59
+ - `koyoapp dev` starts the dev server on port 2309 by default, override
60
+ with `--port`.
61
+ - `koyoapp build` prerenders every static route into `.koyo/build/site`.
62
+
63
+ ## Project structure
64
+
65
+ ```
66
+ my-app/
67
+ app/
68
+ layout.py
69
+ page.py
70
+ components/
71
+ counter.py
72
+ landing.py
73
+ site.py
74
+ public/
75
+ styles/
76
+ koyo.config.py
77
+ pyproject.toml
78
+ ```
79
+
80
+ Routing rules follow the Next.js app directory convention. Folders wrapped
81
+ in square brackets become dynamic path parameters. A page file must export
82
+ a function named `page`, and a layout file must export a function named
83
+ `layout(children)`. Layouts nest outward to inward, exactly like Next.js.
84
+
85
+ ## Component system
86
+
87
+ `koyoapp.html` exposes function based HTML elements. Text content is HTML
88
+ escaped by default; use `Markup` (or `raw()`) for raw unescaped output.
89
+
90
+ ```python
91
+ from koyoapp.html import div, h1, p
92
+
93
+ def Card(title: str, body: str):
94
+ return div(class_="p-4 rounded-lg shadow bg-white")[
95
+ h1(class_="text-xl font-bold")[title],
96
+ p(class_="text-gray-600")[body],
97
+ ]
98
+ ```
99
+
100
+ ## Styles
101
+
102
+ `koyoapp dev` runs the Tailwind CLI in watch mode against the project and
103
+ compiles `styles/globals.css` to `styles/koyo.css`, which the root layout
104
+ links automatically. Any other `.css` file under `styles/` is served
105
+ untouched from `/styles/` and can be linked directly with a normal `link`
106
+ tag. The scaffold ships a light/dark theme switch: `public/theme.js`
107
+ applies the saved or system-preferred theme before first paint, the hero
108
+ button toggles it and stores the choice in `localStorage`, and dark mode
109
+ is class based (`darkMode: "class"`) so any element can use `dark:`
110
+ variants.
111
+
112
+ ## Packages
113
+
114
+ There is no custom package manager. Activate the project virtualenv and use
115
+ plain pip:
116
+
117
+ ```
118
+ .venv/bin/pip install <package>
119
+ ```
120
+
121
+ The package is importable in any `page.py`, `layout.py`, or component file
122
+ immediately, since Koyo runs as a normal Python process.
@@ -0,0 +1,102 @@
1
+ # Koyo
2
+
3
+ Koyo is a Python web framework that mirrors the developer experience of
4
+ Next.js (the app directory convention, file based routing, layouts,
5
+ reusable components) but is written entirely in Python and runs as a normal
6
+ Python web server. Since it is a normal Python process under the hood, any
7
+ package installed with pip is available immediately, with no build step or
8
+ compiler touching the Python code.
9
+
10
+ This is v1, frontend and routing only. No backend API layer, no database,
11
+ no auth, no deployment automation.
12
+
13
+ ## Install
14
+
15
+ Koyo requires Python 3.11 or newer.
16
+
17
+ ```
18
+ pip install koyoapp
19
+ ```
20
+
21
+ ## Quickstart
22
+
23
+ ```
24
+ koyoapp create my-app
25
+ cd my-app
26
+ koyoapp dev
27
+ ```
28
+
29
+ Open http://127.0.0.1:2309. The home page renders out of the box with an
30
+ animated hero, a call to action linking to the Koyo documentation, and a
31
+ live session counter driven by htmx. Edit any file under `app/`, `public/`,
32
+ or `styles/` and the dev server restarts and the browser reloads
33
+ automatically.
34
+
35
+ ## CLI
36
+
37
+ - `koyoapp create .` scaffolds a Koyo project into the current directory.
38
+ - `koyoapp create my-app` creates `my-app` and scaffolds inside it.
39
+ - `koyoapp dev` starts the dev server on port 2309 by default, override
40
+ with `--port`.
41
+ - `koyoapp build` prerenders every static route into `.koyo/build/site`.
42
+
43
+ ## Project structure
44
+
45
+ ```
46
+ my-app/
47
+ app/
48
+ layout.py
49
+ page.py
50
+ components/
51
+ counter.py
52
+ landing.py
53
+ site.py
54
+ public/
55
+ styles/
56
+ koyo.config.py
57
+ pyproject.toml
58
+ ```
59
+
60
+ Routing rules follow the Next.js app directory convention. Folders wrapped
61
+ in square brackets become dynamic path parameters. A page file must export
62
+ a function named `page`, and a layout file must export a function named
63
+ `layout(children)`. Layouts nest outward to inward, exactly like Next.js.
64
+
65
+ ## Component system
66
+
67
+ `koyoapp.html` exposes function based HTML elements. Text content is HTML
68
+ escaped by default; use `Markup` (or `raw()`) for raw unescaped output.
69
+
70
+ ```python
71
+ from koyoapp.html import div, h1, p
72
+
73
+ def Card(title: str, body: str):
74
+ return div(class_="p-4 rounded-lg shadow bg-white")[
75
+ h1(class_="text-xl font-bold")[title],
76
+ p(class_="text-gray-600")[body],
77
+ ]
78
+ ```
79
+
80
+ ## Styles
81
+
82
+ `koyoapp dev` runs the Tailwind CLI in watch mode against the project and
83
+ compiles `styles/globals.css` to `styles/koyo.css`, which the root layout
84
+ links automatically. Any other `.css` file under `styles/` is served
85
+ untouched from `/styles/` and can be linked directly with a normal `link`
86
+ tag. The scaffold ships a light/dark theme switch: `public/theme.js`
87
+ applies the saved or system-preferred theme before first paint, the hero
88
+ button toggles it and stores the choice in `localStorage`, and dark mode
89
+ is class based (`darkMode: "class"`) so any element can use `dark:`
90
+ variants.
91
+
92
+ ## Packages
93
+
94
+ There is no custom package manager. Activate the project virtualenv and use
95
+ plain pip:
96
+
97
+ ```
98
+ .venv/bin/pip install <package>
99
+ ```
100
+
101
+ The package is importable in any `page.py`, `layout.py`, or component file
102
+ immediately, since Koyo runs as a normal Python process.
@@ -0,0 +1,3 @@
1
+ """Koyo, a Python web framework with file based routing."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,4 @@
1
+ from .cli import app
2
+
3
+ if __name__ == "__main__":
4
+ app()
@@ -0,0 +1,56 @@
1
+ """Generate simple placeholder PNG and ICO files with no dependencies."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import struct
6
+ import zlib
7
+
8
+ LOGO_RGB = (79, 70, 229)
9
+ ICON_RGB = (15, 118, 110)
10
+ FAVICON_RGB = (30, 27, 75)
11
+
12
+
13
+ def _png_chunk(kind: bytes, data: bytes) -> bytes:
14
+ block = kind + data
15
+ crc = zlib.crc32(block) & 0xFFFFFFFF
16
+ return struct.pack(">I", len(data)) + block + struct.pack(">I", crc)
17
+
18
+
19
+ def make_png(width: int, height: int, rgb: tuple[int, int, int]) -> bytes:
20
+ ihdr = struct.pack(">IIBBBBB", width, height, 8, 2, 0, 0, 0)
21
+ row = bytes(rgb) * width
22
+ scanlines = b"".join(b"\x00" + row for _ in range(height))
23
+ idat = zlib.compress(scanlines, 9)
24
+ return (
25
+ b"\x89PNG\r\n\x1a\n"
26
+ + _png_chunk(b"IHDR", ihdr)
27
+ + _png_chunk(b"IDAT", idat)
28
+ + _png_chunk(b"IEND", b"")
29
+ )
30
+
31
+
32
+ def make_ico(entries: list[tuple[int, bytes]]) -> bytes:
33
+ count = len(entries)
34
+ header = struct.pack("<HHH", 0, 1, count)
35
+ offset = 6 + 16 * count
36
+ directory = b""
37
+ payload = b""
38
+ for size, png in entries:
39
+ width_byte = size if size < 256 else 0
40
+ directory += struct.pack(
41
+ "<BBBBHHII", width_byte, width_byte, 0, 0, 1, 32, len(png), offset
42
+ )
43
+ payload += png
44
+ offset += len(png)
45
+ return header + directory + payload
46
+
47
+
48
+ def make_favicon_ico() -> bytes:
49
+ return make_ico(
50
+ [
51
+ (16, make_png(16, 16, FAVICON_RGB)),
52
+ (32, make_png(32, 32, FAVICON_RGB)),
53
+ (48, make_png(48, 48, FAVICON_RGB)),
54
+ (256, make_png(256, 256, FAVICON_RGB)),
55
+ ]
56
+ )
@@ -0,0 +1,224 @@
1
+ """Starlette application construction for a Koyo project."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ import traceback
7
+ from pathlib import Path
8
+
9
+ from starlette.applications import Starlette
10
+ from starlette.responses import HTMLResponse, Response
11
+ from starlette.routing import Mount, Route
12
+ from starlette.staticfiles import StaticFiles
13
+
14
+ from .html import Markup, a, body, div, h1, head, html, meta, p, pre, style, title
15
+ from .reload import current_token, dev_mode, inject_dev_reload
16
+ from .router import (
17
+ RouteEntry,
18
+ RouteError,
19
+ module_has_fragment,
20
+ module_has_page,
21
+ render_fragment,
22
+ render_route,
23
+ reset_project_modules,
24
+ scan_app_dir,
25
+ )
26
+ from .session import SessionMiddleware
27
+ from .state import STATE_URL_PREFIX, render_state_action
28
+
29
+ _ERROR_CSS = """\
30
+ body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
31
+ background: #f1f5f9; color: #0f172a; }
32
+ .container { display: flex; min-height: 100vh; align-items: center; justify-content: center; padding: 1.5rem; }
33
+ .card { max-width: 40rem; width: 100%; background: #ffffff; border: 1px solid #e2e8f0;
34
+ border-radius: 12px; padding: 2rem; box-shadow: 0 4px 12px rgba(15, 23, 42, 0.06); }
35
+ h1 { font-size: 1.5rem; margin: 0 0 0.5rem; }
36
+ p { color: #475569; line-height: 1.6; }
37
+ a { color: #4f46e5; }
38
+ .eyebrow { font-size: 0.75rem; letter-spacing: 0.08em; text-transform: uppercase; color: #ef4444;
39
+ font-weight: 600; margin: 0 0 0.25rem; }
40
+ .message { margin: 0; }
41
+ .where { margin-top: 1rem; color: #b45309; font-family: ui-monospace, monospace;
42
+ background: #fef3c7; border-radius: 8px; padding: 0.5rem 0.75rem; }
43
+ pre.trace { overflow-x: auto; background: #0f172a; color: #e2e8f0; padding: 1rem;
44
+ border-radius: 8px; font-size: 0.8rem; line-height: 1.5; }
45
+ """
46
+
47
+
48
+ def _error_page(status: int, heading: str, message: str, detail: str = "") -> str:
49
+ children = [h1()[heading], p()[message]]
50
+ if detail:
51
+ children.append(pre(class_="trace")[detail])
52
+ children.append(p(class_="home")[a(href="/")["Back to home"]])
53
+ page = html(lang="en")[
54
+ head()[
55
+ meta(charset="utf-8"),
56
+ title()[f"{status} {heading}"],
57
+ style()[Markup(_ERROR_CSS)],
58
+ ],
59
+ body()[
60
+ div(class_="container")[
61
+ div(class_="card")[children]
62
+ ]
63
+ ],
64
+ ]
65
+ return "<!doctype html>\n" + str(page)
66
+
67
+
68
+ def _dev_error_page(exc: BaseException, route_path: str) -> str:
69
+ tb = getattr(exc, "__traceback__", None)
70
+ if tb is not None:
71
+ trace_text = "".join(traceback.format_exception(type(exc), exc, tb))
72
+ else:
73
+ trace_text = f"{type(exc).__name__}: {exc}"
74
+ frames = traceback.extract_tb(tb) if tb is not None else []
75
+ location = ""
76
+ if frames:
77
+ frame = frames[-1]
78
+ location = f"{frame.filename}:{frame.lineno} in {frame.name}"
79
+ heading = type(exc).__name__
80
+ message = str(exc) or heading
81
+ children = [
82
+ p(class_="eyebrow")[f"Error in {route_path}"],
83
+ h1()[heading],
84
+ p(class_="message")[message],
85
+ ]
86
+ if location:
87
+ children.append(p(class_="where")[location])
88
+ children.append(pre(class_="trace")[trace_text])
89
+ children.append(p(class_="home")[a(href="/")["Back to home"]])
90
+ page = html(lang="en")[
91
+ head()[
92
+ meta(charset="utf-8"),
93
+ title()[f"{heading} on {route_path}"],
94
+ style()[Markup(_ERROR_CSS)],
95
+ ],
96
+ body()[
97
+ div(class_="container")[
98
+ div(class_="card")[children]
99
+ ]
100
+ ],
101
+ ]
102
+ return "<!doctype html>\n" + str(page)
103
+
104
+
105
+ def _html_response(content: str, status_code: int = 200) -> HTMLResponse:
106
+ return HTMLResponse(inject_dev_reload(content), status_code=status_code)
107
+
108
+
109
+ async def _not_found(request, exc) -> HTMLResponse:
110
+ message = f"There is no route or public file for {request.url.path}."
111
+ return _html_response(_error_page(404, "Not Found", message), 404)
112
+
113
+
114
+ def _request_wants_fragment(entry: RouteEntry, request) -> bool:
115
+ if not module_has_fragment(entry):
116
+ return False
117
+ if not module_has_page(entry):
118
+ return True
119
+ return (request.headers.get("hx-request") or "").lower() == "true"
120
+
121
+
122
+ def _make_endpoint(entry: RouteEntry):
123
+ async def endpoint(request):
124
+ status = 200
125
+ try:
126
+ params = dict(request.path_params)
127
+ if _request_wants_fragment(entry, request):
128
+ return HTMLResponse(render_fragment(entry, params, request))
129
+ return _html_response(render_route(entry, params, request))
130
+ except RouteError as exc:
131
+ if dev_mode():
132
+ content = _dev_error_page(exc, entry.path)
133
+ status = 500
134
+ else:
135
+ content = _error_page(
136
+ 500, "Internal Server Error", f"Rendering {entry.path} failed."
137
+ )
138
+ status = 500
139
+ except Exception:
140
+ if dev_mode():
141
+ content = _dev_error_page(sys.exc_info()[1], entry.path)
142
+ else:
143
+ content = _error_page(
144
+ 500,
145
+ "Internal Server Error",
146
+ f"Rendering {entry.path} raised an unexpected error.",
147
+ )
148
+ status = 500
149
+ return _html_response(content, status_code=status)
150
+
151
+ return endpoint
152
+
153
+
154
+ async def _state_endpoint(request):
155
+ token = request.path_params["token"]
156
+ try:
157
+ content, status = render_state_action(request, token)
158
+ return HTMLResponse(content, status_code=status)
159
+ except Exception:
160
+ if dev_mode():
161
+ content = _dev_error_page(sys.exc_info()[1], f"{STATE_URL_PREFIX}/{token}")
162
+ status = 500
163
+ else:
164
+ content = _error_page(
165
+ 500,
166
+ "Internal Server Error",
167
+ "The session state update failed.",
168
+ )
169
+ status = 500
170
+ return HTMLResponse(content, status_code=status)
171
+
172
+
173
+ async def _reload_probe(request) -> Response:
174
+ project_dir = request.app.state.koyo_project_dir
175
+ token = current_token(project_dir)
176
+ since = request.query_params.get("since", "")
177
+ headers = {"Cache-Control": "no-store", "X-Koyo-Token": token}
178
+ if not since or since == token:
179
+ return Response(status_code=204, headers=headers)
180
+ return Response(token, status_code=200, headers=headers)
181
+
182
+
183
+ def build_app(project_dir: str | Path) -> Starlette:
184
+ project_dir = Path(project_dir).resolve()
185
+ root = str(project_dir)
186
+ if sys.path and sys.path[0] != root:
187
+ sys.path.insert(0, root)
188
+ reset_project_modules()
189
+
190
+ app_dir = project_dir / "app"
191
+ public_dir = project_dir / "public"
192
+ styles_dir = project_dir / "styles"
193
+
194
+ public_dir.mkdir(parents=True, exist_ok=True)
195
+ styles_dir.mkdir(parents=True, exist_ok=True)
196
+
197
+ entries = scan_app_dir(app_dir)
198
+
199
+ routes = [
200
+ Route(
201
+ f"{STATE_URL_PREFIX}/{{token}}",
202
+ _state_endpoint,
203
+ methods=["GET", "HEAD", "POST"],
204
+ )
205
+ ]
206
+ if dev_mode():
207
+ routes.append(Route("/__koyo-reload", _reload_probe))
208
+ for entry in entries:
209
+ routes.append(
210
+ Route(
211
+ entry.path,
212
+ _make_endpoint(entry),
213
+ methods=["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"],
214
+ )
215
+ )
216
+ if styles_dir.is_dir():
217
+ routes.append(Mount("/styles", StaticFiles(directory=str(styles_dir))))
218
+ if public_dir.is_dir():
219
+ routes.append(Mount("/", StaticFiles(directory=str(public_dir))))
220
+
221
+ application = Starlette(routes=routes, exception_handlers={404: _not_found})
222
+ application.add_middleware(SessionMiddleware)
223
+ application.state.koyo_project_dir = str(project_dir)
224
+ return application
Binary file
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="1000" height="1000"><metadata><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/"><rdf:Description><dc:creator>RealFaviconGenerator</dc:creator><dc:source>https://realfavicongenerator.net</dc:source></rdf:Description></rdf:RDF></metadata><g clip-path="url(#SvgjsClipPath1125)"><rect width="1000" height="1000" fill="#1800ad"></rect><g transform="matrix(25,0,0,25,0,0)"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="40" height="40" viewBox="0 0 40 40"><image width="40" height="40" xlink:href="data:image/png;base64,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"></image></svg></g></g><defs><clipPath id="SvgjsClipPath1125"><rect width="1000" height="1000" x="0" y="0" rx="500" ry="500"></rect></clipPath></defs></svg>