dgwyer commited on
Commit
06f5c28
1 Parent(s): b07d38d

deploy at 2024-09-15 17:15:22.995399

Browse files
Dockerfile ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10
2
+ WORKDIR /code
3
+ COPY --link --chown=1000 . .
4
+ RUN mkdir -p /tmp/cache/
5
+ RUN chmod a+rwx -R /tmp/cache/
6
+ ENV HF_HUB_CACHE=HF_HOME
7
+ RUN pip install --no-cache-dir -r requirements.txt
8
+
9
+ ENV PYTHONUNBUFFERED=1 PORT=7860
10
+ CMD ["python", "main.py"]
README.md CHANGED
@@ -1,10 +1,59 @@
1
- ---
2
- title: Match Cards
3
- emoji: 🐠
4
- colorFrom: purple
5
- colorTo: yellow
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # FastHTML Card Matching Memory Game
2
+
3
+ This example app demonstrates a simple memory card game using TailwindCSS, AlpineJS inside a FastHTML app.
4
+
5
+ <img src="../assets/02-card-game.png" width="500" alt="Card matching game">
6
+
7
+ ## Running Locally
8
+
9
+ To run the app locally:
10
+
11
+ 1. Clone the repository
12
+ 2. Navigate to the project directory
13
+ 3. [optional] Install the standalone TailwindCSS CLI (see below).
14
+ 4. Create a new Python environment if you wish.
15
+ 5. Install the project dependencies: `pip install -r requirements.txt`
16
+ 6. In one terminal start the Python server: `python main.py`
17
+ 7. If you wish to edit the Tailwind styles then, in another terminal watch and compile the app CSS file: `tailwindcss -i ./src/app.css -o ./public/app.css --watch`
18
+
19
+ ## Installing the TailwindCSS Standalone CLI
20
+
21
+ There are three main methods for installing the standalone CLI.
22
+
23
+ ### 1. PyPi
24
+
25
+ `pip install pytailwindcss`
26
+
27
+ I had issues getting this to work on Windows WSL though.
28
+
29
+ ### 2. Installing Manually
30
+
31
+ You can download the standalone CLI manually with the following commands:
32
+
33
+ ```
34
+ wget https://github.com/tailwindlabs/tailwindcss/releases/download/v3.4.10/tailwindcss-linux-x64
35
+ chmod +x tailwindcss-linux-x64
36
+ sudo mv tailwindcss-linux-x64 /usr/local/bin/tailwindcss
37
+ ```
38
+
39
+ ### 3. NPM
40
+
41
+ Perhaps the easiest method is to just install TailwindCSS via npm, if you don't mind using npm that is. It's only for local development and there is no need to run npm/Node on the server when you deploy your app in production.
42
+
43
+ ```
44
+ npm install -D tailwindcss
45
+ ```
46
+
47
+ ### Testing TailwindCSS CLI
48
+
49
+ Once intstalled, test that you can run the TailwindCSS standalone CLI via:
50
+
51
+ ```
52
+ tailwindcss
53
+ ```
54
+
55
+ You should see the current TailwindCSS CLI version and some usage instructions.
56
+
57
+ ### Installing TailwindCSS Plugins
58
+
59
+ If you use the standalone TailwindCSS CLI then you can still use any 3rd party Tailwind plugin such as DasiyUI. Simply install the plugin via npm in the usual way and update `tailwind.config.js` accordingly.
assets/card_imgs/1.png ADDED
assets/card_imgs/10.png ADDED
assets/card_imgs/11.png ADDED
assets/card_imgs/12.png ADDED
assets/card_imgs/2.png ADDED
assets/card_imgs/3.png ADDED
assets/card_imgs/4.png ADDED
assets/card_imgs/5.png ADDED
assets/card_imgs/6.png ADDED
assets/card_imgs/7.png ADDED
assets/card_imgs/8.png ADDED
assets/card_imgs/9.png ADDED
fasthtml_hf/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ from .deploy import *
2
+ from .backup import *
fasthtml_hf/backup.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os, shutil
2
+ os.environ['HF_HUB_DISABLE_PROGRESS_BARS'] = '1'
3
+ import time
4
+ from fastcore.utils import *
5
+ from datetime import datetime
6
+ from huggingface_hub import snapshot_download, upload_folder, create_repo, repo_exists, whoami
7
+
8
+ __all__ = ['download', 'upload', 'setup_hf_backup']
9
+ def _token(): return os.getenv("HF_TOKEN")
10
+
11
+ def get_cfg():
12
+ return Config('.', 'config.ini',
13
+ types=dict(dataset_id=str, db_dir=str, private_backup=bool, interval=int),
14
+ create=dict(dataset_id='space-backup', db_dir='data', private_backup=True, interval=15))
15
+
16
+ def get_dataset_id(cfg):
17
+ did = cfg.dataset_id
18
+ if "/" in did or _token() is None: return did
19
+ return f"{whoami(_token())['name']}/{did}"
20
+
21
+ def download():
22
+ cfg = get_cfg()
23
+ did = get_dataset_id(cfg)
24
+ upload_on_schedule()
25
+ if os.getenv("SPACE_ID") and repo_exists(did, repo_type="dataset", token=_token()):
26
+ cache_path = snapshot_download(repo_id=did, repo_type='dataset', token=_token())
27
+ shutil.copytree(cache_path, cfg.db_dir, dirs_exist_ok=True)
28
+
29
+ def upload():
30
+ cfg = get_cfg()
31
+ if not os.getenv("SPACE_ID"): return
32
+ did = get_dataset_id(cfg)
33
+ create_repo(did, token=_token(), private=cfg.private_backup, repo_type='dataset', exist_ok=True)
34
+ upload_folder(folder_path=cfg.db_dir, token=_token(), repo_id=did,
35
+ repo_type='dataset', commit_message=f"backup {datetime.now()}")
36
+
37
+
38
+ @threaded
39
+ def upload_on_schedule():
40
+ cfg = get_cfg()
41
+ while True:
42
+ time.sleep(cfg.interval*60)
43
+ upload()
44
+
45
+
46
+ def setup_hf_backup(app):
47
+ app.on_event("startup")(download)
48
+ app.on_event("shutdown")(upload)
49
+
fasthtml_hf/deploy.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import datetime
2
+ from huggingface_hub import create_repo, upload_folder, add_space_secret, whoami
3
+ from fastcore.utils import *
4
+ from fastcore.script import *
5
+
6
+ def _mk_docker(python_ver):
7
+ fn = Path('Dockerfile')
8
+ if fn.exists(): return
9
+ packages = Path('packages.txt')
10
+ pkg_line = ''
11
+ reqs = Path('requirements.txt')
12
+ if not reqs.exists(): reqs.write_text('python-fasthtml\nfasthtml-hf\n')
13
+ req_line = f'RUN pip install --no-cache-dir -r requirements.txt'
14
+ if packages.exists():
15
+ pkglist = ' '.join(packages.readlines())
16
+ pkg_line = f'RUN apt-get update -y && apt-get install -y {pkglist}'
17
+
18
+ cts = f"""FROM python:{python_ver}
19
+ WORKDIR /code
20
+ COPY --link --chown=1000 . .
21
+ RUN mkdir -p /tmp/cache/
22
+ RUN chmod a+rwx -R /tmp/cache/
23
+ ENV HF_HUB_CACHE=HF_HOME
24
+ {req_line}
25
+ {pkg_line}
26
+ ENV PYTHONUNBUFFERED=1 PORT=7860
27
+ CMD ["python", "main.py"]
28
+ """
29
+ fn.write_text(cts)
30
+
31
+
32
+ def _mk_README(space_id, termination_grace_period):
33
+ fn = Path('README.md')
34
+ if fn.exists(): return
35
+ cts = f"""
36
+ ---
37
+ title: {space_id}
38
+ emoji: 🚀
39
+ colorFrom: purple
40
+ colorTo: red
41
+ sdk: docker
42
+ app_file: app.py
43
+ pinned: false
44
+ termination_grace_period: {termination_grace_period}
45
+ ---
46
+ """
47
+ fn.write_text(cts)
48
+
49
+ @call_parse
50
+ def deploy(
51
+ space_id:str, # ID of the space to upload to
52
+ token:str=None, # Hugging Face token for authentication
53
+ python_ver:str='3.10', # Version of python to use
54
+ upload:bool_arg=True, # Set to `false` to skip uploading files
55
+ private:bool_arg=False,
56
+ termination_grace_period:str="2m"): # Make the repository private
57
+ "Upload current directory to Hugging Face Spaces"
58
+ if not token: token=os.getenv('HF_TOKEN')
59
+ if not token: return print('No token available')
60
+ if "/" not in space_id: space_id = f"{whoami(token)['name']}/{space_id}"
61
+ _mk_docker(python_ver)
62
+ _mk_README(space_id, termination_grace_period)
63
+ private = bool(private) # `private` can be 0,1 or False. As `create_repo` expects private to be True/False we cast it.
64
+ url = create_repo(space_id, token=token, repo_type='space',
65
+ space_sdk="docker", private=private, exist_ok=True)
66
+ if not upload: return print('Repo created; upload skipped')
67
+ upload_folder(folder_path=Path("."),
68
+ repo_id=space_id, repo_type='space',
69
+ ignore_patterns=['__pycache__/*', '.sesskey', 'deploy_hf.py', 'data/*'],
70
+ commit_message=f"deploy at {datetime.datetime.now()}",
71
+ token=token)
72
+ add_space_secret(space_id, token=token, key="HF_TOKEN", value=token)
73
+ print(f"Deployed space at {url}")
74
+
main.py ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fasthtml.common import *
2
+ from fasthtml.svg import *
3
+ from svgs import fasthtml_logo
4
+ from fasthtml_hf import setup_hf_backup
5
+
6
+ app,rt = fast_app(
7
+ #live=True,
8
+ id=int,
9
+ title=str,
10
+ done=bool,
11
+ pk='id',
12
+ hdrs=(Script(src="https://unpkg.com/alpinejs", defer=True), Link(rel="stylesheet", href="/public/app.css", type="text/css"),),
13
+ pico=False,
14
+ debug=True,
15
+ )
16
+
17
+ js = """
18
+ function game() {
19
+ const cds = [];
20
+ let id = 1;
21
+ for (let i = 1; i <= 12; i++) {
22
+ cds.push({ id: id++, flipped: false, cleared: false, card: i });
23
+ cds.push({ id: id++, flipped: false, cleared: false, card: i });
24
+ }
25
+ cds.sort((a, b) => 0.5 - Math.random());
26
+
27
+ return {
28
+ cheats: 3,
29
+ cards: cds,
30
+ get flippedCards() {
31
+ return this.cards.filter(card => card.flipped);
32
+ },
33
+ flipCard(card) {
34
+ if( card.cleared ) { return; }
35
+ if( this.flippedCards.length <= 1 ) { card.flipped = ! card.flipped; }
36
+
37
+ if( this.flippedCards.length === 2 ) {
38
+ if( this.flippedCards[0].card === this.flippedCards[1].card ) {
39
+ this.flippedCards.forEach(card => {
40
+ card.cleared = true;
41
+ card.flipped = false;
42
+ });
43
+ } else {
44
+ if( card.id !== this.flippedCards[0].id && card.id !== this.flippedCards[1].id ) {
45
+ this.flippedCards.forEach(card => {
46
+ card.flipped = false;
47
+ });
48
+ card.flipped = ! card.flipped;
49
+ } else {
50
+ setTimeout((cd, fl) => {
51
+ fl.forEach(card => {
52
+ card.flipped = false;
53
+ });
54
+ }, 1000, card, this.flippedCards);
55
+ }
56
+ }
57
+ }
58
+ },
59
+ restart() {
60
+ this.cards.forEach(c => {
61
+ c.flipped = false;
62
+ c.cleared = false;
63
+ });
64
+ this.cheats = 3;
65
+ setTimeout(() => {
66
+ this.cards.sort((a, b) => 0.5 - Math.random());
67
+ }, 500);
68
+ },
69
+ cheat() {
70
+ if( this.cheats >= 1 ) {
71
+ this.cheats -= 1;
72
+ this.cards.forEach(c => {
73
+ c.flipped = true;
74
+ });
75
+ setTimeout(() => {
76
+ this.cards.forEach(c => {
77
+ c.flipped = false;
78
+ });
79
+ }, 500);
80
+ }
81
+ },
82
+ };
83
+ }
84
+ """
85
+
86
+ def cards():
87
+ return Template(
88
+ Div(
89
+ Div(
90
+ Div(
91
+ NotStr(fasthtml_logo),
92
+ cls='flip-card-front'
93
+ ),
94
+ Div(
95
+ Img(
96
+ #src='/assets/card_imgs/' + card_num + '.png',
97
+ **{":src": "'/assets/card_imgs/' + card.card + '.png'"},
98
+ ),
99
+ cls='flip-card-back'
100
+ ),
101
+ cls='flip-card-inner',
102
+ **{":class": "{'flip-rotate-y-180': card.flipped || card.cleared}"},
103
+ ),
104
+ tabindex='0',
105
+ cls='flip-card',
106
+ **{"@click": "flipCard(card)"},
107
+ **{":class": "{'flip-cleared': card.cleared}"},
108
+ ),
109
+ x_for='card in cards',
110
+ id="fr",
111
+ data_something="123"
112
+ )
113
+
114
+ @rt('/')
115
+ def get(): return Title('Card Memory Game in FastHTML'), Div(
116
+ Section(
117
+ Div(
118
+ H1('Card Memory Game', cls='text-4xl md:text-6xl font-bold mb-4'),
119
+ P('Built with FastHTML, HTMX, TailwindCSS, and AlpineJS.', cls='text-lg md:text-2xl'),
120
+ cls='text-center text-white px-6 md:px-12 drop-shadow-lg'
121
+ ),
122
+ cls='min-h-[300px] flex items-center justify-center bg-gradient-to-r from-[#3cdd8c] to-[#ffdb6d]'
123
+ ),
124
+ Section(
125
+ Div(
126
+ Div(
127
+ Div(
128
+ cards(),
129
+ cls='grid grid-cols-6 gap-4 w-[944px]'
130
+ ),
131
+ ),
132
+ cls='flex items-center justify-center my-20'
133
+ ),
134
+ Div(
135
+ Div(
136
+ A(
137
+ 'Restart Game',
138
+ cls='bg-white text-[#3cdd8c] font-semibold px-6 py-3 rounded-lg shadow-lg hover:bg-gray-100 transition',
139
+ **{"x-on:click.prevent": "restart()"},
140
+ ),
141
+ A(
142
+ cls='bg-white text-[#3cdd8c] font-semibold px-6 py-3 rounded-lg shadow-lg hover:bg-gray-100 transition',
143
+ **{"x-on:click.prevent": "cheat()"},
144
+ **{"x-text": "'Cheat Mode! (' + cheats + ')'"},
145
+ ),
146
+ cls='flex items-center justify-center gap-4'
147
+ ),
148
+ P(
149
+ 'Cheat mode reveals all tiles! But you only have three per game so use wisely!',
150
+ cls='mt-6 flex items-center justify-center gap-4'
151
+ ),
152
+ ),
153
+ cls='mb-20',
154
+ x_data='game()',
155
+ ),
156
+ Footer(
157
+ A('Made by David Gwyer - Follow me on X', href="https://x.com/dgwyer", _target="_blank", cls="drop-shadow-lg text-lg"),
158
+ cls='bg-gray-800 text-white text-center py-8'
159
+ ),
160
+ cls='bg-gray-100 text-gray-800'
161
+ ), Script(js)
162
+
163
+ setup_hf_backup(app)
164
+
165
+ serve(reload_includes=["*.css"])
public/app.css ADDED
@@ -0,0 +1,796 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* app.css */
2
+
3
+ /* ! tailwindcss v3.2.4 | MIT License | https://tailwindcss.com */
4
+
5
+ /*
6
+ 1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
7
+ 2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
8
+ */
9
+
10
+ *,
11
+ ::before,
12
+ ::after {
13
+ box-sizing: border-box;
14
+ /* 1 */
15
+ border-width: 0;
16
+ /* 2 */
17
+ border-style: solid;
18
+ /* 2 */
19
+ border-color: #e5e7eb;
20
+ /* 2 */
21
+ }
22
+
23
+ ::before,
24
+ ::after {
25
+ --tw-content: '';
26
+ }
27
+
28
+ /*
29
+ 1. Use a consistent sensible line-height in all browsers.
30
+ 2. Prevent adjustments of font size after orientation changes in iOS.
31
+ 3. Use a more readable tab size.
32
+ 4. Use the user's configured `sans` font-family by default.
33
+ 5. Use the user's configured `sans` font-feature-settings by default.
34
+ */
35
+
36
+ html {
37
+ line-height: 1.5;
38
+ /* 1 */
39
+ -webkit-text-size-adjust: 100%;
40
+ /* 2 */
41
+ -moz-tab-size: 4;
42
+ /* 3 */
43
+ -o-tab-size: 4;
44
+ tab-size: 4;
45
+ /* 3 */
46
+ font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
47
+ /* 4 */
48
+ font-feature-settings: normal;
49
+ /* 5 */
50
+ }
51
+
52
+ /*
53
+ 1. Remove the margin in all browsers.
54
+ 2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
55
+ */
56
+
57
+ body {
58
+ margin: 0;
59
+ /* 1 */
60
+ line-height: inherit;
61
+ /* 2 */
62
+ }
63
+
64
+ /*
65
+ 1. Add the correct height in Firefox.
66
+ 2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
67
+ 3. Ensure horizontal rules are visible by default.
68
+ */
69
+
70
+ hr {
71
+ height: 0;
72
+ /* 1 */
73
+ color: inherit;
74
+ /* 2 */
75
+ border-top-width: 1px;
76
+ /* 3 */
77
+ }
78
+
79
+ /*
80
+ Add the correct text decoration in Chrome, Edge, and Safari.
81
+ */
82
+
83
+ abbr:where([title]) {
84
+ -webkit-text-decoration: underline dotted;
85
+ text-decoration: underline dotted;
86
+ }
87
+
88
+ /*
89
+ Remove the default font size and weight for headings.
90
+ */
91
+
92
+ h1,
93
+ h2,
94
+ h3,
95
+ h4,
96
+ h5,
97
+ h6 {
98
+ font-size: inherit;
99
+ font-weight: inherit;
100
+ }
101
+
102
+ /*
103
+ Reset links to optimize for opt-in styling instead of opt-out.
104
+ */
105
+
106
+ a {
107
+ color: inherit;
108
+ text-decoration: inherit;
109
+ }
110
+
111
+ /*
112
+ Add the correct font weight in Edge and Safari.
113
+ */
114
+
115
+ b,
116
+ strong {
117
+ font-weight: bolder;
118
+ }
119
+
120
+ /*
121
+ 1. Use the user's configured `mono` font family by default.
122
+ 2. Correct the odd `em` font sizing in all browsers.
123
+ */
124
+
125
+ code,
126
+ kbd,
127
+ samp,
128
+ pre {
129
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
130
+ /* 1 */
131
+ font-size: 1em;
132
+ /* 2 */
133
+ }
134
+
135
+ /*
136
+ Add the correct font size in all browsers.
137
+ */
138
+
139
+ small {
140
+ font-size: 80%;
141
+ }
142
+
143
+ /*
144
+ Prevent `sub` and `sup` elements from affecting the line height in all browsers.
145
+ */
146
+
147
+ sub,
148
+ sup {
149
+ font-size: 75%;
150
+ line-height: 0;
151
+ position: relative;
152
+ vertical-align: baseline;
153
+ }
154
+
155
+ sub {
156
+ bottom: -0.25em;
157
+ }
158
+
159
+ sup {
160
+ top: -0.5em;
161
+ }
162
+
163
+ /*
164
+ 1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
165
+ 2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
166
+ 3. Remove gaps between table borders by default.
167
+ */
168
+
169
+ table {
170
+ text-indent: 0;
171
+ /* 1 */
172
+ border-color: inherit;
173
+ /* 2 */
174
+ border-collapse: collapse;
175
+ /* 3 */
176
+ }
177
+
178
+ /*
179
+ 1. Change the font styles in all browsers.
180
+ 2. Remove the margin in Firefox and Safari.
181
+ 3. Remove default padding in all browsers.
182
+ */
183
+
184
+ button,
185
+ input,
186
+ optgroup,
187
+ select,
188
+ textarea {
189
+ font-family: inherit;
190
+ /* 1 */
191
+ font-size: 100%;
192
+ /* 1 */
193
+ font-weight: inherit;
194
+ /* 1 */
195
+ line-height: inherit;
196
+ /* 1 */
197
+ color: inherit;
198
+ /* 1 */
199
+ margin: 0;
200
+ /* 2 */
201
+ padding: 0;
202
+ /* 3 */
203
+ }
204
+
205
+ /*
206
+ Remove the inheritance of text transform in Edge and Firefox.
207
+ */
208
+
209
+ button,
210
+ select {
211
+ text-transform: none;
212
+ }
213
+
214
+ /*
215
+ 1. Correct the inability to style clickable types in iOS and Safari.
216
+ 2. Remove default button styles.
217
+ */
218
+
219
+ button,
220
+ [type='button'],
221
+ [type='reset'],
222
+ [type='submit'] {
223
+ -webkit-appearance: button;
224
+ /* 1 */
225
+ background-color: transparent;
226
+ /* 2 */
227
+ background-image: none;
228
+ /* 2 */
229
+ }
230
+
231
+ /*
232
+ Use the modern Firefox focus style for all focusable elements.
233
+ */
234
+
235
+ :-moz-focusring {
236
+ outline: auto;
237
+ }
238
+
239
+ /*
240
+ Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
241
+ */
242
+
243
+ :-moz-ui-invalid {
244
+ box-shadow: none;
245
+ }
246
+
247
+ /*
248
+ Add the correct vertical alignment in Chrome and Firefox.
249
+ */
250
+
251
+ progress {
252
+ vertical-align: baseline;
253
+ }
254
+
255
+ /*
256
+ Correct the cursor style of increment and decrement buttons in Safari.
257
+ */
258
+
259
+ ::-webkit-inner-spin-button,
260
+ ::-webkit-outer-spin-button {
261
+ height: auto;
262
+ }
263
+
264
+ /*
265
+ 1. Correct the odd appearance in Chrome and Safari.
266
+ 2. Correct the outline style in Safari.
267
+ */
268
+
269
+ [type='search'] {
270
+ -webkit-appearance: textfield;
271
+ /* 1 */
272
+ outline-offset: -2px;
273
+ /* 2 */
274
+ }
275
+
276
+ /*
277
+ Remove the inner padding in Chrome and Safari on macOS.
278
+ */
279
+
280
+ ::-webkit-search-decoration {
281
+ -webkit-appearance: none;
282
+ }
283
+
284
+ /*
285
+ 1. Correct the inability to style clickable types in iOS and Safari.
286
+ 2. Change font properties to `inherit` in Safari.
287
+ */
288
+
289
+ ::-webkit-file-upload-button {
290
+ -webkit-appearance: button;
291
+ /* 1 */
292
+ font: inherit;
293
+ /* 2 */
294
+ }
295
+
296
+ /*
297
+ Add the correct display in Chrome and Safari.
298
+ */
299
+
300
+ summary {
301
+ display: list-item;
302
+ }
303
+
304
+ /*
305
+ Removes the default spacing and border for appropriate elements.
306
+ */
307
+
308
+ blockquote,
309
+ dl,
310
+ dd,
311
+ h1,
312
+ h2,
313
+ h3,
314
+ h4,
315
+ h5,
316
+ h6,
317
+ hr,
318
+ figure,
319
+ p,
320
+ pre {
321
+ margin: 0;
322
+ }
323
+
324
+ fieldset {
325
+ margin: 0;
326
+ padding: 0;
327
+ }
328
+
329
+ legend {
330
+ padding: 0;
331
+ }
332
+
333
+ ol,
334
+ ul,
335
+ menu {
336
+ list-style: none;
337
+ margin: 0;
338
+ padding: 0;
339
+ }
340
+
341
+ /*
342
+ Prevent resizing textareas horizontally by default.
343
+ */
344
+
345
+ textarea {
346
+ resize: vertical;
347
+ }
348
+
349
+ /*
350
+ 1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
351
+ 2. Set the default placeholder color to the user's configured gray 400 color.
352
+ */
353
+
354
+ input::-moz-placeholder, textarea::-moz-placeholder {
355
+ opacity: 1;
356
+ /* 1 */
357
+ color: #9ca3af;
358
+ /* 2 */
359
+ }
360
+
361
+ input::placeholder,
362
+ textarea::placeholder {
363
+ opacity: 1;
364
+ /* 1 */
365
+ color: #9ca3af;
366
+ /* 2 */
367
+ }
368
+
369
+ /*
370
+ Set the default cursor for buttons.
371
+ */
372
+
373
+ button,
374
+ [role="button"] {
375
+ cursor: pointer;
376
+ }
377
+
378
+ /*
379
+ Make sure disabled buttons don't get the pointer cursor.
380
+ */
381
+
382
+ :disabled {
383
+ cursor: default;
384
+ }
385
+
386
+ /*
387
+ 1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
388
+ 2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
389
+ This can trigger a poorly considered lint error in some tools but is included by design.
390
+ */
391
+
392
+ img,
393
+ svg,
394
+ video,
395
+ canvas,
396
+ audio,
397
+ iframe,
398
+ embed,
399
+ object {
400
+ display: block;
401
+ /* 1 */
402
+ vertical-align: middle;
403
+ /* 2 */
404
+ }
405
+
406
+ /*
407
+ Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
408
+ */
409
+
410
+ img,
411
+ video {
412
+ max-width: 100%;
413
+ height: auto;
414
+ }
415
+
416
+ /* Make elements with the HTML hidden attribute stay hidden by default */
417
+
418
+ [hidden] {
419
+ display: none;
420
+ }
421
+
422
+ *, ::before, ::after {
423
+ --tw-border-spacing-x: 0;
424
+ --tw-border-spacing-y: 0;
425
+ --tw-translate-x: 0;
426
+ --tw-translate-y: 0;
427
+ --tw-rotate: 0;
428
+ --tw-skew-x: 0;
429
+ --tw-skew-y: 0;
430
+ --tw-scale-x: 1;
431
+ --tw-scale-y: 1;
432
+ --tw-pan-x: ;
433
+ --tw-pan-y: ;
434
+ --tw-pinch-zoom: ;
435
+ --tw-scroll-snap-strictness: proximity;
436
+ --tw-ordinal: ;
437
+ --tw-slashed-zero: ;
438
+ --tw-numeric-figure: ;
439
+ --tw-numeric-spacing: ;
440
+ --tw-numeric-fraction: ;
441
+ --tw-ring-inset: ;
442
+ --tw-ring-offset-width: 0px;
443
+ --tw-ring-offset-color: #fff;
444
+ --tw-ring-color: rgb(59 130 246 / 0.5);
445
+ --tw-ring-offset-shadow: 0 0 #0000;
446
+ --tw-ring-shadow: 0 0 #0000;
447
+ --tw-shadow: 0 0 #0000;
448
+ --tw-shadow-colored: 0 0 #0000;
449
+ --tw-blur: ;
450
+ --tw-brightness: ;
451
+ --tw-contrast: ;
452
+ --tw-grayscale: ;
453
+ --tw-hue-rotate: ;
454
+ --tw-invert: ;
455
+ --tw-saturate: ;
456
+ --tw-sepia: ;
457
+ --tw-drop-shadow: ;
458
+ --tw-backdrop-blur: ;
459
+ --tw-backdrop-brightness: ;
460
+ --tw-backdrop-contrast: ;
461
+ --tw-backdrop-grayscale: ;
462
+ --tw-backdrop-hue-rotate: ;
463
+ --tw-backdrop-invert: ;
464
+ --tw-backdrop-opacity: ;
465
+ --tw-backdrop-saturate: ;
466
+ --tw-backdrop-sepia: ;
467
+ }
468
+
469
+ ::backdrop {
470
+ --tw-border-spacing-x: 0;
471
+ --tw-border-spacing-y: 0;
472
+ --tw-translate-x: 0;
473
+ --tw-translate-y: 0;
474
+ --tw-rotate: 0;
475
+ --tw-skew-x: 0;
476
+ --tw-skew-y: 0;
477
+ --tw-scale-x: 1;
478
+ --tw-scale-y: 1;
479
+ --tw-pan-x: ;
480
+ --tw-pan-y: ;
481
+ --tw-pinch-zoom: ;
482
+ --tw-scroll-snap-strictness: proximity;
483
+ --tw-ordinal: ;
484
+ --tw-slashed-zero: ;
485
+ --tw-numeric-figure: ;
486
+ --tw-numeric-spacing: ;
487
+ --tw-numeric-fraction: ;
488
+ --tw-ring-inset: ;
489
+ --tw-ring-offset-width: 0px;
490
+ --tw-ring-offset-color: #fff;
491
+ --tw-ring-color: rgb(59 130 246 / 0.5);
492
+ --tw-ring-offset-shadow: 0 0 #0000;
493
+ --tw-ring-shadow: 0 0 #0000;
494
+ --tw-shadow: 0 0 #0000;
495
+ --tw-shadow-colored: 0 0 #0000;
496
+ --tw-blur: ;
497
+ --tw-brightness: ;
498
+ --tw-contrast: ;
499
+ --tw-grayscale: ;
500
+ --tw-hue-rotate: ;
501
+ --tw-invert: ;
502
+ --tw-saturate: ;
503
+ --tw-sepia: ;
504
+ --tw-drop-shadow: ;
505
+ --tw-backdrop-blur: ;
506
+ --tw-backdrop-brightness: ;
507
+ --tw-backdrop-contrast: ;
508
+ --tw-backdrop-grayscale: ;
509
+ --tw-backdrop-hue-rotate: ;
510
+ --tw-backdrop-invert: ;
511
+ --tw-backdrop-opacity: ;
512
+ --tw-backdrop-saturate: ;
513
+ --tw-backdrop-sepia: ;
514
+ }
515
+
516
+ .absolute {
517
+ position: absolute;
518
+ }
519
+
520
+ .relative {
521
+ position: relative;
522
+ }
523
+
524
+ .my-20 {
525
+ margin-top: 5rem;
526
+ margin-bottom: 5rem;
527
+ }
528
+
529
+ .mb-4 {
530
+ margin-bottom: 1rem;
531
+ }
532
+
533
+ .mt-4 {
534
+ margin-top: 1rem;
535
+ }
536
+
537
+ .mb-20 {
538
+ margin-bottom: 5rem;
539
+ }
540
+
541
+ .mt-8 {
542
+ margin-top: 2rem;
543
+ }
544
+
545
+ .mt-6 {
546
+ margin-top: 1.5rem;
547
+ }
548
+
549
+ .flex {
550
+ display: flex;
551
+ }
552
+
553
+ .grid {
554
+ display: grid;
555
+ }
556
+
557
+ .hidden {
558
+ display: none;
559
+ }
560
+
561
+ .min-h-\[300px\] {
562
+ min-height: 300px;
563
+ }
564
+
565
+ .w-\[944px\] {
566
+ width: 944px;
567
+ }
568
+
569
+ .transform {
570
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
571
+ }
572
+
573
+ .grid-cols-6 {
574
+ grid-template-columns: repeat(6, minmax(0, 1fr));
575
+ }
576
+
577
+ .items-center {
578
+ align-items: center;
579
+ }
580
+
581
+ .justify-center {
582
+ justify-content: center;
583
+ }
584
+
585
+ .gap-4 {
586
+ gap: 1rem;
587
+ }
588
+
589
+ .rounded-lg {
590
+ border-radius: 0.5rem;
591
+ }
592
+
593
+ .bg-white {
594
+ --tw-bg-opacity: 1;
595
+ background-color: rgb(255 255 255 / var(--tw-bg-opacity));
596
+ }
597
+
598
+ .bg-gray-800 {
599
+ --tw-bg-opacity: 1;
600
+ background-color: rgb(31 41 55 / var(--tw-bg-opacity));
601
+ }
602
+
603
+ .bg-gray-100 {
604
+ --tw-bg-opacity: 1;
605
+ background-color: rgb(243 244 246 / var(--tw-bg-opacity));
606
+ }
607
+
608
+ .bg-gradient-to-r {
609
+ background-image: linear-gradient(to right, var(--tw-gradient-stops));
610
+ }
611
+
612
+ .from-\[\#3cdd8c\] {
613
+ --tw-gradient-from: #3cdd8c;
614
+ --tw-gradient-to: rgb(60 221 140 / 0);
615
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
616
+ }
617
+
618
+ .to-\[\#ffdb6d\] {
619
+ --tw-gradient-to: #ffdb6d;
620
+ }
621
+
622
+ .px-6 {
623
+ padding-left: 1.5rem;
624
+ padding-right: 1.5rem;
625
+ }
626
+
627
+ .py-3 {
628
+ padding-top: 0.75rem;
629
+ padding-bottom: 0.75rem;
630
+ }
631
+
632
+ .py-6 {
633
+ padding-top: 1.5rem;
634
+ padding-bottom: 1.5rem;
635
+ }
636
+
637
+ .py-8 {
638
+ padding-top: 2rem;
639
+ padding-bottom: 2rem;
640
+ }
641
+
642
+ .text-center {
643
+ text-align: center;
644
+ }
645
+
646
+ .text-4xl {
647
+ font-size: 2.25rem;
648
+ line-height: 2.5rem;
649
+ }
650
+
651
+ .text-lg {
652
+ font-size: 1.125rem;
653
+ line-height: 1.75rem;
654
+ }
655
+
656
+ .font-bold {
657
+ font-weight: 700;
658
+ }
659
+
660
+ .font-semibold {
661
+ font-weight: 600;
662
+ }
663
+
664
+ .text-white {
665
+ --tw-text-opacity: 1;
666
+ color: rgb(255 255 255 / var(--tw-text-opacity));
667
+ }
668
+
669
+ .text-\[\#3cdd8c\] {
670
+ --tw-text-opacity: 1;
671
+ color: rgb(60 221 140 / var(--tw-text-opacity));
672
+ }
673
+
674
+ .text-gray-800 {
675
+ --tw-text-opacity: 1;
676
+ color: rgb(31 41 55 / var(--tw-text-opacity));
677
+ }
678
+
679
+ .shadow-lg {
680
+ --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
681
+ --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
682
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
683
+ }
684
+
685
+ .outline {
686
+ outline-style: solid;
687
+ }
688
+
689
+ .drop-shadow-lg {
690
+ --tw-drop-shadow: drop-shadow(0 10px 8px rgb(0 0 0 / 0.04)) drop-shadow(0 4px 3px rgb(0 0 0 / 0.1));
691
+ filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
692
+ }
693
+
694
+ .filter {
695
+ filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
696
+ }
697
+
698
+ .transition {
699
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
700
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
701
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
702
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
703
+ transition-duration: 150ms;
704
+ }
705
+
706
+ /* Card flip styles based on this CodePen example: https: //codepen.io/ananyaneogi/pen/Ezmyeb */
707
+
708
+ .flip-card {
709
+ background-color: transparent;
710
+ width: 144px;
711
+ height: 144px;
712
+ perspective: 1000px;
713
+ cursor: pointer;
714
+ }
715
+
716
+ .flip-card-inner {
717
+ position: relative;
718
+ width: 100%;
719
+ height: 100%;
720
+ text-align: center;
721
+ transition: transform 0.4s;
722
+ transform-style: preserve-3d;
723
+ -webkit-backface-visibility: hidden;
724
+ backface-visibility: hidden;
725
+ -moz-backface-visibility: hidden;
726
+ }
727
+
728
+ .flip-card:focus {
729
+ outline: 0;
730
+ }
731
+
732
+ /*.flip-card:hover .flip-card-inner {
733
+ transform: rotateY(180deg);
734
+ }*/
735
+
736
+ .flip-rotate-y-180 {
737
+ transform: rotateY(180deg);
738
+ }
739
+
740
+ .flip-cleared {
741
+ opacity: 0.6;
742
+ }
743
+
744
+ .flip-card-front,
745
+ .flip-card-back {
746
+ position: absolute;
747
+ width: 100%;
748
+ height: 100%;
749
+ }
750
+
751
+ .flip-card-front,
752
+ .flip-card-back,
753
+ .flip-card-back img {
754
+ border-radius: 20px;
755
+ }
756
+
757
+ .flip-card-front {
758
+ background: #3cdd8c;
759
+ color: white;
760
+ z-index: 2;
761
+ display: flex;
762
+ justify-content: center;
763
+ align-items: center;
764
+ }
765
+
766
+ .flip-card-back {
767
+ background: linear-gradient(to right, #4364f7, #6fb1fc);
768
+ color: white;
769
+ transform: rotateY(180deg);
770
+ z-index: 1;
771
+ display: flex;
772
+ justify-content: center;
773
+ align-items: center;
774
+ }
775
+
776
+ .hover\:bg-gray-100:hover {
777
+ --tw-bg-opacity: 1;
778
+ background-color: rgb(243 244 246 / var(--tw-bg-opacity));
779
+ }
780
+
781
+ @media (min-width: 768px) {
782
+ .md\:px-12 {
783
+ padding-left: 3rem;
784
+ padding-right: 3rem;
785
+ }
786
+
787
+ .md\:text-6xl {
788
+ font-size: 3.75rem;
789
+ line-height: 1;
790
+ }
791
+
792
+ .md\:text-2xl {
793
+ font-size: 1.5rem;
794
+ line-height: 2rem;
795
+ }
796
+ }
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ python-fasthtml
2
+ uvicorn>=0.29
3
+ python-multipart
4
+ sqlite-utils
5
+ huggingface-hub>=0.20.0
6
+ fasthtml-hf
src/app.css ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* app.css */
2
+ @tailwind base;
3
+ @tailwind components;
4
+ @tailwind utilities;
5
+
6
+ /* Card flip styles based on this CodePen example: https: //codepen.io/ananyaneogi/pen/Ezmyeb */
7
+ .flip-card {
8
+ background-color: transparent;
9
+ width: 144px;
10
+ height: 144px;
11
+ perspective: 1000px;
12
+ cursor: pointer;
13
+ }
14
+
15
+ .flip-card-inner {
16
+ position: relative;
17
+ width: 100%;
18
+ height: 100%;
19
+ text-align: center;
20
+ transition: transform 0.4s;
21
+ transform-style: preserve-3d;
22
+ backface-visibility: hidden;
23
+ -moz-backface-visibility: hidden;
24
+ }
25
+
26
+ .flip-card:focus {
27
+ outline: 0;
28
+ }
29
+
30
+ /*.flip-card:hover .flip-card-inner {
31
+ transform: rotateY(180deg);
32
+ }*/
33
+
34
+ .flip-rotate-y-180 {
35
+ transform: rotateY(180deg);
36
+ }
37
+
38
+ .flip-cleared {
39
+ opacity: 0.6;
40
+ }
41
+
42
+ .flip-card-front,
43
+ .flip-card-back {
44
+ position: absolute;
45
+ width: 100%;
46
+ height: 100%;
47
+ }
48
+
49
+ .flip-card-front,
50
+ .flip-card-back,
51
+ .flip-card-back img {
52
+ border-radius: 20px;
53
+ }
54
+
55
+ .flip-card-front {
56
+ background: #3cdd8c;
57
+ color: white;
58
+ z-index: 2;
59
+ display: flex;
60
+ justify-content: center;
61
+ align-items: center;
62
+ }
63
+
64
+ .flip-card-back {
65
+ background: linear-gradient(to right, #4364f7, #6fb1fc);
66
+ color: white;
67
+ transform: rotateY(180deg);
68
+ z-index: 1;
69
+ display: flex;
70
+ justify-content: center;
71
+ align-items: center;
72
+ }
svgs.py ADDED
@@ -0,0 +1 @@
 
 
1
+ fasthtml_logo = '<svg xmlns="http://www.w3.org/2000/svg" width="100" height="24" viewBox="0 0 105 24" fill="none"><path d="M7.76172 16.5034V2.35254H16.9498V4.32569H9.91425V8.63074H16.5911V10.5441H9.91425V16.5034H7.76172Z" fill="white"/><path d="M21.7078 16.7426C19.5154 16.7426 18.0804 15.6862 18.0804 13.8526C18.0804 12.0389 19.1567 11.0623 21.4687 10.6039L25.116 9.90631C25.116 8.27199 24.3586 7.45482 22.8439 7.45482C21.4886 7.45482 20.7312 8.07268 20.4522 9.22866L18.2598 9.08915C18.6584 6.97648 20.3127 5.66105 22.8439 5.66105C25.7139 5.66105 27.2287 7.27545 27.2287 10.0259V14.1516C27.2287 14.6698 27.408 14.8292 27.8067 14.8292H28.1853V16.5034C28.0458 16.5433 27.707 16.5632 27.408 16.5632C26.1923 16.5632 25.4747 16.1048 25.2954 14.8691C24.7572 15.9852 23.3621 16.7426 21.7078 16.7426ZM22.0666 15.0684C23.96 15.0684 25.116 13.8725 25.116 12.1784V11.5008L22.0267 12.0987C20.7511 12.3379 20.2728 12.8361 20.2728 13.6533C20.2728 14.5701 20.9305 15.0684 22.0666 15.0684Z" fill="white"/><path d="M33.6104 16.7426C30.6208 16.7426 29.0064 15.2478 28.8469 13.155L31.0393 13.0554C31.2187 14.2313 31.9761 14.9687 33.6104 14.9687C34.9258 14.9687 35.7629 14.5701 35.7629 13.6334C35.7629 12.8561 35.3643 12.5172 33.2317 12.1585C30.0827 11.6004 29.106 10.6836 29.106 8.94963C29.106 7.01635 30.6009 5.66105 33.3513 5.66105C36.0619 5.66105 37.4969 7.21565 37.7959 9.14894L35.6234 9.26853C35.4241 8.17233 34.6468 7.43489 33.3314 7.43489C31.9562 7.43489 31.2984 8.03282 31.2984 8.86991C31.2984 9.80666 31.9362 10.1853 33.6702 10.4644C36.9588 11.0025 37.9553 11.8795 37.9553 13.6134C37.9553 15.6065 36.1416 16.7426 33.6104 16.7426Z" fill="white"/><path d="M43.1099 16.5034C41.0969 16.5034 40.1602 15.6264 40.1602 13.6334V7.6342H38.5458V5.90022H40.1602V3.40887H42.2728V5.90022H45.0831V7.6342H42.2728V13.4739C42.2728 14.4705 42.6914 14.7694 43.5683 14.7694H45.0831V16.5034H43.1099Z" fill="white"/><path d="M46.7794 16.5034V2.35254H48.9319V8.4115H55.4692V2.35254H57.6217V16.5034H55.4692V10.3647H48.9319V16.5034H46.7794Z" fill="white"/><path d="M63.4977 16.5034V4.32569H59.1528V2.35254H69.9952V4.32569H65.6503V16.5034H63.4977Z" fill="white"/><path d="M71.5239 16.5034V2.35254H74.4737L78.6791 13.9722L82.8845 2.35254H85.8342V16.5034H83.6817V6.13939L79.8749 16.4835H77.4832L73.6764 6.13939V16.5034H71.5239Z" fill="white"/><path d="M88.5577 16.5034V2.35254H90.7103V14.5303H97.6063V16.5034H88.5577Z" fill="white"/><path fill-rule="evenodd" clip-rule="evenodd" d="M5.41016 18.8567V0H19.3041V4.09997C20.3686 3.55927 21.58 3.30851 22.8453 3.30851C24.7148 3.30851 26.4889 3.84237 27.7888 5.15988C27.8722 5.24434 27.9522 5.33066 28.029 5.41873C28.2342 5.18079 28.4641 4.95753 28.7187 4.75151C29.9754 3.73486 31.6171 3.30851 33.3527 3.30851C34.3763 3.30851 35.3335 3.4765 36.1942 3.8116V3.54768H37.8086V1.05633H44.4278V0H51.2862V6.05896H53.1176V0H76.1258L78.6805 7.05865L81.2351 0H93.0646V12.1777H99.9607V18.8567H81.3302V18.8368H79.4223L78.6805 20.8865L77.9386 18.8368H76.0308V18.8567H69.1724V6.67903H68.0046V18.8567H61.1462V6.67903H59.976V18.8567H53.1176V12.7181H51.2862V18.8567H43.1113C41.8211 18.8567 40.3615 18.5832 39.2484 17.5101C39.1269 17.3929 39.0148 17.2714 38.9116 17.1462C38.6596 17.4098 38.3786 17.6486 38.0731 17.8602C36.7925 18.747 35.1905 19.0959 33.6118 19.0959C32.3386 19.0959 31.1374 18.879 30.0836 18.4089L28.8331 18.7662C28.5051 18.8599 28.1672 18.8855 28.0255 18.8956C27.8215 18.9102 27.6054 18.9165 27.4094 18.9165C26.6793 18.9165 25.7892 18.8049 24.9651 18.334C23.9647 18.8453 22.8206 19.0959 21.7092 19.0959C20.2681 19.0959 18.795 18.752 17.6268 17.8413C16.3879 16.8756 15.7289 15.4506 15.7289 13.853C15.7289 13.5305 15.7519 13.211 15.8005 12.8974H12.2686V18.8567H5.41016ZM28.1867 16.5038V14.8296H27.808C27.7727 14.8296 27.7392 14.8284 27.7073 14.8257C27.5827 14.8154 27.484 14.7839 27.4094 14.723C27.3644 14.6862 27.3281 14.6387 27.3003 14.5786C27.2582 14.4877 27.2355 14.3679 27.2309 14.213C27.2308 14.2095 27.2307 14.2059 27.2306 14.2023C27.2302 14.1859 27.23 14.1691 27.23 14.152V10.0263C27.23 9.98619 27.2297 9.94633 27.2291 9.90671C27.2236 9.56996 27.1949 9.25076 27.1433 8.95004C27.13 8.87248 27.1151 8.79616 27.0988 8.72108C27.0364 8.43497 26.952 8.16693 26.8459 7.91781C26.7724 7.74533 26.6884 7.58191 26.5941 7.42786C26.5354 7.33194 26.4727 7.23965 26.406 7.15106C25.6806 6.18766 24.4838 5.66145 22.8453 5.66145C21.4268 5.66145 20.2837 6.07456 19.4827 6.80958C19.2817 6.99399 19.1022 7.19866 18.9454 7.42216C18.6949 7.7791 18.502 8.18405 18.3709 8.63114C18.3623 8.66068 18.3539 8.69039 18.3458 8.72029C18.3132 8.84047 18.2849 8.96359 18.2612 9.08955L20.4536 9.22907C20.5378 8.88016 20.6656 8.58027 20.8402 8.3316C21.2443 7.75639 21.899 7.45523 22.8453 7.45523C23.334 7.45523 23.744 7.54031 24.075 7.71048C24.4189 7.88726 24.6777 8.15585 24.8514 8.51626C24.9373 8.69447 25.0024 8.89514 25.0467 9.11825C25.0893 9.33305 25.1127 9.56865 25.1167 9.82507C25.1172 9.85205 25.1174 9.87927 25.1174 9.90671L21.47 10.6043C21.0565 10.6863 20.6825 10.7849 20.3471 10.9018C19.8786 11.0652 19.4855 11.2646 19.1652 11.5048C19.0877 11.5629 19.0144 11.6235 18.9454 11.6865C18.582 12.0183 18.336 12.4185 18.2023 12.8974C18.1372 13.1305 18.0988 13.3822 18.0863 13.6537C18.0833 13.719 18.0818 13.7854 18.0818 13.853C18.0818 15.6866 19.5168 16.743 21.7092 16.743C21.8303 16.743 21.9499 16.7389 22.068 16.731C22.5131 16.701 22.9353 16.6155 23.3217 16.483C24.0543 16.2317 24.6589 15.8113 25.0498 15.2788C25.1453 15.1488 25.228 15.0121 25.2968 14.8695C25.3739 15.4011 25.5507 15.7888 25.8238 16.0581C25.8271 16.0613 25.8304 16.0646 25.8338 16.0678C26.0649 16.2913 26.3638 16.4316 26.7286 16.5038C26.8266 16.5232 26.9292 16.5376 27.0366 16.5475C27.1551 16.5584 27.2794 16.5636 27.4094 16.5636C27.5058 16.5636 27.6063 16.5615 27.7029 16.5574C27.7386 16.5559 27.7738 16.554 27.808 16.552C27.9687 16.5421 28.1088 16.5261 28.1867 16.5038ZM25.1174 11.5012V12.1788C25.1174 12.2204 25.1167 12.2617 25.1153 12.3027C25.1016 12.7084 25.0202 13.0836 24.8771 13.4184C24.7849 13.634 24.6672 13.8329 24.5254 14.0124C24.4307 14.1323 24.3252 14.2435 24.2095 14.3453C23.6886 14.8033 22.9601 15.0688 22.068 15.0688C21.9425 15.0688 21.8228 15.0627 21.7092 15.0507C20.8718 14.9618 20.3648 14.5482 20.2852 13.853C20.2779 13.789 20.2742 13.7225 20.2742 13.6537C20.2742 12.895 20.6865 12.4112 21.7665 12.1544C21.77 12.1536 21.7734 12.1528 21.7769 12.152C21.857 12.1331 21.9407 12.1155 22.0281 12.0991L25.1174 11.5012ZM16.5924 10.5445V8.63114H9.91562V4.32609H16.9512V2.35294H7.7631V16.5038H9.91562V10.5445H16.5924ZM40.2354 14.5131C40.4722 15.7805 41.2828 16.4034 42.7315 16.4926C42.8536 16.5001 42.9801 16.5038 43.1113 16.5038H45.0845V14.7698H43.5697C43.3998 14.7698 43.2471 14.7586 43.1113 14.7333C42.5851 14.635 42.313 14.3248 42.2781 13.6338C42.2755 13.5827 42.2742 13.5296 42.2742 13.4743V7.6346H45.0845V5.90062H42.2742V3.40927H40.1615V5.90062H38.5472V7.6346H40.1615V13.6338C40.1615 13.9557 40.186 14.2484 40.2354 14.5131ZM28.8483 13.1554C28.8543 13.2333 28.8622 13.3104 28.8722 13.3866C28.907 13.6531 28.9665 13.9089 29.0504 14.152C29.1327 14.3907 29.2387 14.6172 29.368 14.8296C29.5073 15.0584 29.6737 15.2708 29.8671 15.4645C30.0635 15.6613 30.2878 15.8389 30.5397 15.9946C31.3086 16.4702 32.3347 16.743 33.6118 16.743C35.7793 16.743 37.4206 15.91 37.8476 14.4191C37.9176 14.1747 37.9549 13.9127 37.9566 13.6338C37.9567 13.6271 37.9567 13.6205 37.9567 13.6138C37.9567 13.2446 37.9115 12.9142 37.8086 12.6176C37.6633 12.199 37.4031 11.8479 36.9926 11.5501C36.6674 11.3143 36.2479 11.1119 35.7166 10.936C35.1629 10.7528 34.4879 10.5983 33.6716 10.4648C33.6261 10.4575 33.5813 10.4501 33.5373 10.4426C33.5322 10.4417 33.5272 10.4409 33.5223 10.44C33.4958 10.4355 33.4696 10.431 33.4437 10.4264C31.9286 10.1588 31.3359 9.78804 31.3014 8.95004C31.3003 8.92392 31.2998 8.89735 31.2998 8.87031C31.2998 8.03322 31.9575 7.4353 33.3328 7.4353C33.3394 7.4353 33.3461 7.43531 33.3527 7.43535C34.2582 7.4405 34.9065 7.79708 35.2844 8.38003C35.4504 8.63616 35.5643 8.936 35.6248 9.26893L37.7973 9.14934C37.7834 9.05965 37.7671 8.97078 37.7483 8.88284C37.6533 8.43781 37.4953 8.01666 37.2726 7.6346C37.1073 7.35112 36.9064 7.08916 36.669 6.85495C36.5243 6.71216 36.3661 6.57968 36.1942 6.45892C35.4844 5.96032 34.5412 5.66145 33.3527 5.66145C33.346 5.66145 33.3394 5.66146 33.3328 5.66148C31.1663 5.66665 29.7827 6.51523 29.2996 7.82483C29.1807 8.14728 29.1163 8.49767 29.1083 8.87031C29.1077 8.89678 29.1074 8.92335 29.1074 8.95004C29.1074 9.35049 29.1595 9.70736 29.2784 10.0263C29.3527 10.2255 29.453 10.4099 29.583 10.5808C29.6296 10.6422 29.6801 10.7018 29.7345 10.7598C30.0252 11.0692 30.4295 11.3312 30.9725 11.5554C31.5556 11.7961 32.2987 11.9933 33.2331 12.1589C33.2507 12.1618 33.2681 12.1648 33.2854 12.1677C33.3207 12.1738 33.3556 12.1798 33.39 12.1858C33.3915 12.1861 33.393 12.1863 33.3945 12.1866C35.3684 12.5323 35.7575 12.8705 35.7642 13.6138C35.7643 13.6205 35.7643 13.6271 35.7643 13.6338C35.7643 14.5705 34.9272 14.9691 33.6118 14.9691C32.6052 14.9691 31.9312 14.6894 31.5201 14.1997C31.2638 13.8944 31.1096 13.5074 31.0407 13.0558L28.8483 13.1554ZM46.7808 16.5038H48.9333V10.3651H55.4706V16.5038H57.6231V2.35294H55.4706V8.4119H48.9333V2.35294H46.7808V16.5038ZM63.4991 16.5038H65.6516V4.32609H69.9966V2.35294H59.1542V4.32609H63.4991V16.5038ZM71.5253 16.5038H73.6778V6.13979L77.4846 16.4839H79.8763L83.6831 6.13979V16.5038H85.8356V2.35294H82.8859L78.6805 13.9726L74.4751 2.35294H71.5253V16.5038ZM90.7117 2.35294H88.5591V16.5038H97.6077V14.5307H90.7117V2.35294Z" fill="black"/><path d="M7.76172 16.5034V2.35254H16.9498V4.32569H9.91425V8.63074H16.5911V10.5441H9.91425V16.5034H7.76172Z" fill="#3CDD8C"/><path d="M21.7078 16.7426C19.5154 16.7426 18.0804 15.6862 18.0804 13.8526C18.0804 12.0389 19.1567 11.0623 21.4687 10.6039L25.116 9.90631C25.116 8.27199 24.3586 7.45482 22.8439 7.45482C21.4886 7.45482 20.7312 8.07268 20.4522 9.22866L18.2598 9.08915C18.6584 6.97648 20.3127 5.66105 22.8439 5.66105C25.7139 5.66105 27.2287 7.27545 27.2287 10.0259V14.1516C27.2287 14.6698 27.408 14.8292 27.8067 14.8292H28.1853V16.5034C28.0458 16.5433 27.707 16.5632 27.408 16.5632C26.1923 16.5632 25.4747 16.1048 25.2954 14.8691C24.7572 15.9852 23.3621 16.7426 21.7078 16.7426ZM22.0666 15.0684C23.96 15.0684 25.116 13.8725 25.116 12.1784V11.5008L22.0267 12.0987C20.7511 12.3379 20.2728 12.8361 20.2728 13.6533C20.2728 14.5701 20.9305 15.0684 22.0666 15.0684Z" fill="#3CDD8C"/><path d="M33.6104 16.7426C30.6208 16.7426 29.0064 15.2478 28.8469 13.155L31.0393 13.0554C31.2187 14.2313 31.9761 14.9687 33.6104 14.9687C34.9258 14.9687 35.7629 14.5701 35.7629 13.6334C35.7629 12.8561 35.3643 12.5172 33.2317 12.1585C30.0827 11.6004 29.106 10.6836 29.106 8.94963C29.106 7.01635 30.6009 5.66105 33.3513 5.66105C36.0619 5.66105 37.4969 7.21565 37.7959 9.14894L35.6234 9.26853C35.4241 8.17233 34.6468 7.43489 33.3314 7.43489C31.9562 7.43489 31.2984 8.03282 31.2984 8.86991C31.2984 9.80666 31.9362 10.1853 33.6702 10.4644C36.9588 11.0025 37.9553 11.8795 37.9553 13.6134C37.9553 15.6065 36.1416 16.7426 33.6104 16.7426Z" fill="#3CDD8C"/><path d="M43.1099 16.5034C41.0969 16.5034 40.1602 15.6264 40.1602 13.6334V7.6342H38.5458V5.90022H40.1602V3.40887H42.2728V5.90022H45.0831V7.6342H42.2728V13.4739C42.2728 14.4705 42.6914 14.7694 43.5683 14.7694H45.0831V16.5034H43.1099Z" fill="#3CDD8C"/><path d="M46.7794 16.5034V2.35254H48.9319V8.4115H55.4692V2.35254H57.6217V16.5034H55.4692V10.3647H48.9319V16.5034H46.7794Z" fill="#3CDD8C"/><path d="M63.4977 16.5034V4.32569H59.1528V2.35254H69.9952V4.32569H65.6503V16.5034H63.4977Z" fill="#3CDD8C"/><path d="M71.5239 16.5034V2.35254H74.4737L78.6791 13.9722L82.8845 2.35254H85.8342V16.5034H83.6817V6.13939L79.8749 16.4835H77.4832L73.6764 6.13939V16.5034H71.5239Z" fill="#3CDD8C"/><path d="M88.5577 16.5034V2.35254H90.7103V14.5303H97.6063V16.5034H88.5577Z" fill="#3CDD8C"/><path d="M100 18.8232H5.41176L-3.72529e-07 23.9997H94.5882L100 18.8232Z" fill="black"/><path d="M59.5312 6.11816H61.8842V18.824H59.5312V6.11816Z" fill="black"/><path d="M68 6.11816H70.3529V18.824H68V6.11816Z" fill="black"/><path d="M50.5898 11.7646H53.6487V18.8235H50.5898V11.7646Z" fill="black"/><path d="M11.5312 12.4707H17.6489V19.5295H11.5312V12.4707Z" fill="black"/><path d="M13.4102 17.8818H19.5278V19.5289H13.4102V17.8818Z" fill="black"/><path d="M22.3516 17.1768H52.2339V19.5297H22.3516V17.1768Z" fill="black"/><path d="M5.53125 0L0.00183856 5.05882V23.8824L5.53125 18.8235V0Z" fill="black"/><path d="M7.83124 16.3535V2.20265H17.0193V4.1758H9.98376V8.48085H16.6606V10.3942H9.98376V16.3535H7.83124ZM21.7773 16.5927C19.585 16.5927 18.1499 15.5364 18.1499 13.7027C18.1499 11.889 19.2262 10.9124 21.5382 10.454L25.1855 9.75642C25.1855 8.1221 24.4281 7.30493 22.9134 7.30493C21.5581 7.30493 20.8007 7.92279 20.5217 9.07877L18.3293 8.93926C18.7279 6.82659 20.3822 5.51116 22.9134 5.51116C25.7834 5.51116 27.2982 7.12556 27.2982 9.87601V14.0017C27.2982 14.5199 27.4776 14.6793 27.8762 14.6793H28.2549V16.3535C28.1153 16.3934 27.7765 16.4133 27.4776 16.4133C26.2618 16.4133 25.5443 15.9549 25.3649 14.7192C24.8268 15.8353 23.4316 16.5927 21.7773 16.5927ZM22.1361 14.9185C24.0295 14.9185 25.1855 13.7227 25.1855 12.0285V11.3509L22.0962 11.9488C20.8207 12.188 20.3423 12.6862 20.3423 13.5034C20.3423 14.4202 21 14.9185 22.1361 14.9185ZM33.6799 16.5927C30.6903 16.5927 29.0759 15.0979 28.9165 13.0051L31.1089 12.9055C31.2882 14.0814 32.0456 14.8188 33.6799 14.8188C34.9954 14.8188 35.8325 14.4202 35.8325 13.4835C35.8325 12.7062 35.4338 12.3674 33.3012 12.0086C30.1522 11.4505 29.1756 10.5337 29.1756 8.79974C29.1756 6.86646 30.6704 5.51116 33.4208 5.51116C36.1314 5.51116 37.5664 7.06576 37.8654 8.99905L35.6929 9.11864C35.4936 8.02244 34.7163 7.285 33.4009 7.285C32.0257 7.285 31.368 7.88293 31.368 8.72002C31.368 9.65677 32.0057 10.0355 33.7397 10.3145C37.0283 10.8526 38.0248 11.7296 38.0248 13.4635C38.0248 15.4566 36.2111 16.5927 33.6799 16.5927ZM43.1794 16.3535C41.1664 16.3535 40.2297 15.4766 40.2297 13.4835V7.48431H38.6153V5.75033H40.2297V3.25898H42.3423V5.75033H45.1526V7.48431H42.3423V13.324C42.3423 14.3206 42.7609 14.6195 43.6378 14.6195H45.1526V16.3535H43.1794Z" fill="white"/><path d="M46.8489 16.3535V2.20265H49.0014V8.26161H55.5387V2.20265H57.6912V16.3535H55.5387V10.2148H49.0014V16.3535H46.8489Z" fill="#FFCCCC"/><path d="M63.5672 16.3535V4.1758H59.2223V2.20265H70.0647V4.1758H65.7198V16.3535H63.5672Z" fill="#3CDD8C"/><path d="M71.5934 16.3535V2.20265H74.5432L78.7486 13.8223L82.954 2.20265H85.9038V16.3535H83.7512V5.9895L79.9444 16.3336H77.5527L73.746 5.9895V16.3535H71.5934Z" fill="#FFC435"/><path d="M88.6273 16.3535V2.20265H90.7798V14.3804H97.6758V16.3535H88.6273Z" fill="#8C8CF3"/></svg>'
tailwind.config.js ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('tailwindcss').Config} */
2
+ module.exports = {
3
+ content: ["**/*.py", "src/*.css"],
4
+ theme: {
5
+ extend: {},
6
+ },
7
+ plugins: [],
8
+ }