feat: initial project setup with implementation plan

- Add PLAN.md with 40-step checklist across 10 phases
- Add CLAUDE.md with project-specific instructions
- Set up nix flake with FastHTML/MonsterUI dependencies
- Create Python package skeleton (src/animaltrack)
- Vendor FastHTML and MonsterUI documentation
- Add Docker build configuration

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-27 17:37:16 +00:00
parent 852107794b
commit c0b939627b
61 changed files with 18076 additions and 0 deletions

150
docs/vendor/monsterui/examples/forms.md vendored Normal file
View File

@@ -0,0 +1,150 @@
"""FrankenUI Forms Example built with MonsterUI (original design by ShadCN)"""
from fasthtml.common import *
from monsterui.all import *
from fasthtml.svg import *
app, rt = fast_app(hdrs=Theme.blue.headers())
def HelpText(c): return P(c,cls=TextPresets.muted_sm)
def heading():
return Div(cls="space-y-5")(
H2("Settings"),
Subtitle("Manage your account settings and set e-mail preferences."),
DividerSplit())
sidebar = NavContainer(
*map(lambda x: Li(A(x)), ("Profile", "Account", "Appearance", "Notifications", "Display")),
uk_switcher="connect: #component-nav; animation: uk-animation-fade",
cls=(NavT.secondary,"space-y-4 p-4 w-1/5"))
def FormSectionDiv(*c, cls='space-y-2', **kwargs): return Div(*c, cls=cls, **kwargs)
def FormLayout(title, subtitle, *content, cls='space-y-3 mt-4'): return Container(Div(H3(title), Subtitle(subtitle), DividerLine(), Form(*content, cls=cls)))
def profile_form():
content = (FormSectionDiv(
LabelInput("Username", placeholder='sveltecult', id='username'),
HelpText("This is your public display name. It can be your real name or a pseudonym. You can only change this once every 30 days.")),
FormSectionDiv(
LabelSelect(
Option("Select a verified email to display", value="", selected=True, disabled=True),
*[Option(o, value=o) for o in ('m@example.com', 'm@yahoo.com', 'm@cloud.com')],
label="Email", id="email"),
HelpText("You can manage verified email addresses in your email settings.")),
FormSectionDiv(
LabelTextArea("Bio", id="bio", placeholder="Tell us a little bit about yourself"),
HelpText("You can @mention other users and organizations to link to them."),
P("String must contain at least 4 character(s)", cls="text-destructive")),
FormSectionDiv(
FormLabel("URLs"),
HelpText("Add links to your website, blog, or social media profiles."),
Input(value="https://www.franken-ui.dev"),
Input(value="https://github.com/sveltecult/franken-ui"),
Button("Add URL")),
Button('Update profile', cls=ButtonT.primary))
return FormLayout('Profile', 'This is how others will see you on the site.', *content)
def account_form():
content = (
FormSectionDiv(
LabelInput("Name", placeholder="Your name", id="name"),
HelpText("This is the name that will be displayed on your profile and in emails.")),
FormSectionDiv(
LabelInput("Date of Birth", type="date", placeholder="Pick a date", id="date_of_birth"),
HelpText("Your date of birth is used to calculate your age.")),
FormSectionDiv(
LabelSelect(*Options("Select a language", "English", "French", "German", "Spanish", "Portuguese", selected_idx=1, disabled_idxs={0}),
label='Language', id="language"),
HelpText("This is the language that will be used in the dashboard.")),
Button('Update profile', cls=ButtonT.primary))
return FormLayout('Account', 'Update your account settings. Set your preferred language and timezone.', *content)
def appearance_form():
def theme_item(bg_color, content_bg, text_bg):
common_content = f"space-y-2 rounded-md {content_bg} p-2 shadow-sm"
item_row = lambda: Div(cls=f"flex items-center space-x-2 {common_content}")(
Div(cls=f"h-4 w-4 rounded-full {text_bg}"),
Div(cls=f"h-2 w-[100px] rounded-lg {text_bg}"))
return Div(cls=f"space-y-2 rounded-sm {bg_color} p-2")(
Div(cls=common_content)(
Div(cls=f"h-2 w-[80px] rounded-lg {text_bg}"),
Div(cls=f"h-2 w-[100px] rounded-lg {text_bg}")),
item_row(),
item_row())
common_toggle_cls = "block cursor-pointer items-center rounded-md border-2 border-muted p-1 ring-ring"
content = (
FormSectionDiv(
LabelSelect(*Options('Select a font family', 'Inter', 'Geist', 'Open Sans', selected_idx=2, disabled_idxs={0}),
label='Font Family', id='font_family'),
HelpText("Set the font you want to use in the dashboard.")),
FormSectionDiv(
FormLabel("Theme"),
HelpText("Select the theme for the dashboard."),
Grid(
A(id="theme-toggle-light", cls=common_toggle_cls)(theme_item("bg-[#ecedef]", "bg-white", "bg-[#ecedef]")),
A(id="theme-toggle-dark", cls=f"{common_toggle_cls} bg-popover")(theme_item("bg-slate-950", "bg-slate-800", "bg-slate-400")),
cols_max=2,cls=('max-w-md','gap-8'))),
Button('Update preferences', cls=ButtonT.primary))
return FormLayout('Appearance', 'Customize the appearance of the app. Automatically switch between day and night themes.', *content)
notification_items = [
{"title": "Communication emails", "description": "Receive emails about your account activity.", "checked": False, "disabled": False},
{"title": "Marketing emails", "description": "Receive emails about new products, features, and more.", "checked": False, "disabled": False},
{"title": "Social emails", "description": "Receive emails for friend requests, follows, and more.", "checked": True, "disabled": False},
{"title": "Security emails", "description": "Receive emails about your account activity and security.", "checked": True, "disabled": True}]
def notifications_form():
def RadioLabel(label): return DivLAligned(Radio(name="notification", checked=(label=="Nothing")), FormLabel(label))
def NotificationCard(item):
return Card(
Div(cls="space-y-0.5")(
FormLabel(Strong(item['title'], cls=TextT.sm),
HelpText(item['description']))))
content = Div(
FormSectionDiv(
FormLabel("Notify me about"),
*map(RadioLabel, ["All new messages", "Direct messages and mentions", "Nothing"])),
Div(
H4("Email Notifications", cls="mb-4"),
Grid(*map(NotificationCard, notification_items), cols=1)),
LabelCheckboxX("Use different settings for my mobile devices", id="notification_mobile"),
HelpText("You can manage your mobile notifications in the mobile settings page."),
Button('Update notifications', cls=ButtonT.primary))
return FormLayout('Notifications', 'Configure how you receive notifications.', *content)
def display_form():
content = (
Div(cls="space-y-2")(
Div(cls="mb-4")(
H5("Sidebar"),
Subtitle("Select the items you want to display in the sidebar.")),
*[Div(CheckboxX(id=f"display_{i}", checked=i in [0, 1, 2]),FormLabel(label))
for i, label in enumerate(["Recents", "Home", "Applications", "Desktop", "Downloads", "Documents"])]),
Button('Update display', cls=ButtonT.primary))
return FormLayout('Display', 'Turn items on or off to control what\'s displayed in the app.', *content)
@rt
def index():
return Title("Forms Example"),Container(
heading(),
Div(cls="flex gap-x-12")(
sidebar,
Ul(id="component-nav", cls="uk-switcher max-w-2xl")(
Li(cls="uk-active")(profile_form(),
*map(Li, [account_form(), appearance_form(), notifications_form(), display_form()])))))
serve()