objective categorical abstract machine language personal data server

Nicer root page

futur.blue 604d88cd 118d429f

verified
+100 -72
+2 -1
frontend/client/Router.mlx
··· 13 13 type route = {path: string; template: (module Template)} 14 14 15 15 let routes = 16 - [ {path= "/oauth/authorize"; template= (module OauthAuthorizePage)} 16 + [ {path= "/"; template= (module RootPage)} 17 + ; {path= "/oauth/authorize"; template= (module OauthAuthorizePage)} 17 18 ; {path= "/account/login"; template= (module LoginPage)} 18 19 ; {path= "/account/signup"; template= (module SignupPage)} 19 20 ; {path= "/account"; template= (module AccountPage)}
+29 -31
frontend/src/components/HandleInput.mlx
··· 2 2 3 3 open React 4 4 5 - type handle_status = 6 - | Idle 7 - | Checking 8 - | Valid 9 - | Invalid of string 5 + type handle_status = Idle | Checking | Valid | Invalid of string 10 6 11 7 let[@react.component] make ~name ?(label = "handle") ?(sr_only = false) 12 - ?(required = false) ?(showIndicator = true) ?(subdomainOnly = false) ?placeholder ?hostname ?value ?onChange () = 8 + ?(required = false) ?(showIndicator = true) ?(subdomainOnly = false) 9 + ?placeholder ?hostname ?value ?onChange () = 13 10 let internalValue, setInternalValue = useState (fun () -> "") in 14 11 let handleValue = Option.value value ~default:internalValue in 15 12 let handleStatus, setHandleStatus = useState (fun () -> Idle) in ··· 20 17 setHandleStatus (fun _ -> Checking) ; 21 18 let fullHandle = 22 19 match hostname with 23 - | Some host -> handle ^ "." ^ host 24 - | None -> handle 20 + | Some host -> 21 + handle ^ "." ^ host 22 + | None -> 23 + handle 25 24 in 26 25 let _ = 27 26 Fetch.fetch 28 - ("/account/signup/check-handle?handle=" 27 + ( "/account/signup/check-handle?handle=" 29 28 ^ Js.Global.encodeURIComponent handle ) 30 29 |> Js.Promise.then_ (fun response -> 31 - if Fetch.Response.ok response then 32 - Fetch.Response.json response 30 + if Fetch.Response.ok response then Fetch.Response.json response 33 31 else Js.Promise.reject (Js.Exn.raiseError "Request failed") ) 34 32 |> Js.Promise.then_ (fun json -> 35 33 let valid = ··· 42 40 |> Option.map Obj.magic 43 41 |> Option.value ~default:false 44 42 in 45 - let error = Option.bind 46 - (Js.Dict.get (Obj.magic json) "error" 47 - |> Option.map Obj.magic) 48 - (fun x -> 49 - if Js.Nullable.isNullable (Obj.magic x) then None 50 - else Some x ) 43 + let error = 44 + Option.bind 45 + ( Js.Dict.get (Obj.magic json) "error" 46 + |> Option.map Obj.magic ) 47 + (fun x -> 48 + if Js.Nullable.isNullable (Obj.magic x) then None 49 + else Some x ) 51 50 in 52 - ( if valid && available then setHandleStatus (fun _ -> Valid) 53 - else 54 - setHandleStatus (fun _ -> 55 - Invalid (Option.value error ~default:"Invalid handle") 56 - ) ) ; 51 + if valid && available then setHandleStatus (fun _ -> Valid) 52 + else 53 + setHandleStatus (fun _ -> 54 + Invalid (Option.value error ~default:"Invalid handle") ) ; 57 55 Js.Promise.resolve () ) 58 56 |> Js.Promise.catch (fun _ -> 59 57 setHandleStatus (fun _ -> ··· 67 65 fun e -> 68 66 let inputValue = (Event.Form.target e)##value in 69 67 ( match onChange with 70 - | Some f -> f e 71 - | None -> setInternalValue (fun _ -> inputValue) ) ; 68 + | Some f -> 69 + f e 70 + | None -> 71 + setInternalValue (fun _ -> inputValue) ) ; 72 72 ( match checkTimeoutRef.current with 73 73 | Some id -> 74 74 Js.Global.clearTimeout id ··· 84 84 let trailing = 85 85 match hostname with 86 86 | Some h -> 87 - Some ( 87 + Some 88 88 <span className="font-serif text-mist-80 whitespace-nowrap"> 89 89 (string ("." ^ h)) 90 90 </span> 91 - ) 92 - | _ -> None 91 + | _ -> 92 + None 93 93 in 94 94 <div> 95 95 <Input ··· 110 110 (string "Checking availability...") 111 111 </span> 112 112 | Valid -> 113 - <span 114 - className="inline-flex items-center text-mana-100 text-sm mt-1"> 113 + <span className="inline-flex items-center text-mana-100 text-sm mt-1"> 115 114 <CheckmarkIcon className="w-4 h-4 mr-1" /> 116 115 (string "Handle is available") 117 116 </span> 118 117 | Invalid msg -> 119 118 <span 120 119 className="inline-flex items-center text-phoenix-100 text-sm mt-1"> 121 - <CircleAlertIcon className="w-4 h-4 mr-1" /> 122 - (string msg) 120 + <CircleAlertIcon className="w-4 h-4 mr-1" /> (string msg) 123 121 </span> 124 122 | Idle -> 125 123 null )
+3 -2
frontend/src/components/Input.mlx
··· 4 4 let req_marker = " *" 5 5 6 6 let[@react.component] make ?id ~name ?(className = "") ?(type_ = "text") ?label 7 - ?(sr_only = false) ?value ?defaultValue ?placeholder ?autoComplete ?(required = false) 8 - ?(disabled = false) ?trailing ?(showIndicator = true) ?onChange () = 7 + ?(sr_only = false) ?value ?defaultValue ?placeholder ?autoComplete 8 + ?(required = false) ?(disabled = false) ?trailing ?(showIndicator = true) 9 + ?onChange () = 9 10 let id = Option.value id ~default:name in 10 11 let placeholder = if label <> None && sr_only then label else placeholder in 11 12 let input =
+2 -1
frontend/src/templates/Layout.mlx
··· 1 1 open React 2 2 3 - let[@react.component] make ?(title = "Pegasus") ?(children = null) ?(favicon = "/public/favicon.ico") () = 3 + let[@react.component] make ?(title = "Pegasus") ?(children = null) 4 + ?(favicon = "/public/favicon.ico") () = 4 5 <html lang="en"> 5 6 <head> 6 7 <meta charSet="utf-8" />
+2 -1
frontend/src/templates/OauthAuthorizePage.mlx
··· 562 562 in 563 563 let add_account_url = "/account/login" ^ query_string in 564 564 let favicon_url, set_favicon_url = 565 - useState (fun () -> (Option.value logo_uri ~default:("https://" ^ host ^ "/favicon.ico")) ) 565 + useState (fun () -> 566 + Option.value logo_uri ~default:("https://" ^ host ^ "/favicon.ico") ) 566 567 in 567 568 <form className="w-full h-auto max-w-lg px-4 sm:px-0"> 568 569 <h1 className="text-2xl font-serif text-mana-200 mb-2">
+60
frontend/src/templates/RootPage.mlx
··· 1 + open Melange_json.Primitives 2 + open React 3 + 4 + let pegasus = 5 + string 6 + {js| 7 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⢀⣠⣴⡆⠀⠀⠀⠀⠀⠀ 8 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣤⣤⣶⣶⠿⠿⢛⡛⠉⠀⠀⠀⠀⠀⠀⠀ 9 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⣤⣤⣴⣶⣿⢻⣿⣿⣶⣶⣢⣆⠀⠀⠀⣀⣀⣤⣤⣶⣶⣾⣿⡿⠿⣛⣛⣭⣵⣶⠾⢟⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀ 10 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⣧⣾⣿⡟⣻⣿⣿⣿⣿⣿⣿⣭⣭⢠⣾⣿⣯⣭⣤⡬⣩⣭⣽⡶⠿⢟⣛⣯⣭⡶⣶⡛⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 11 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠋⠉⠁⠿⣿⡿⣿⣿⣿⣿⣿⣯⡄⣿⣿⣿⣿⡟⠃⠈⠱⠶⠾⠛⢋⣭⣿⠶⣟⡛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 12 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠞⣡⣾⣿⣿⣿⣿⣟⡓⢠⣾⣿⠿⣛⣛⡛⣟⣛⣛⡛⢶⣶⣶⣶⣶⣶⣥⣤⣤⣤⣤⣄⣀⣀⣀⣀⠀⠀⠀ 13 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡞⠁⣴⣿⣿⣿⣿⣿⡿⠯⢁⣿⢟⣴⣿⣿⡿⢳⣿⣿⣿⠏⠰⠶⠶⢾⣽⣭⣭⣭⣙⣛⣛⣛⣛⡛⠿⠿⠿⠿⠋ 14 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⡟⢀⣾⣿⣿⣿⣿⣿⣿⣿⠋⣾⡟⣼⣿⣿⢟⣴⣿⡿⢏⣀⣙⣛⣛⣛⡳⠶⠶⠶⣶⣭⣭⣭⣭⣭⣝⠛⠋⠀⠀⠀ 15 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⡿⠀⣼⣿⣿⣿⣿⣿⣿⠟⣂⣼⡟⣱⣿⣿⢏⣼⣿⣿⠁⠬⣭⣙⣛⣛⣛⣛⠛⠿⠿⠿⠶⠶⠶⠶⠆⠀⠀⠀⠀⠀⠀ 16 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⠟⢁⣤⣾⣿⣿⣿⣷⣶⣶⣾⣟⣫⣾⣿⣿⡏⣾⣿⣿⡅⢶⣶⣦⣤⣤⣬⣭⣭⣉⠛⠛⠛⠒⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀ 17 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⠟⠛⠱⣿⣛⣋⣤⣴⣶⣶⣦⣭⡉⠉⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 18 + ⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣄⡀⠀⠀⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 19 + ⠀⠀⠀⠀⠀⠀⠀⠀⠻⠿⠿⣿⡿⠶⠜⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇⢻⣿⠿⢿⣶⣶⣦⣤⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀ 20 + ⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣶⣶⣶⣿⣷⣶⣾⣿⣿⣿⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⣿⠀⠀⠉⢿⣿⣿⣿⣿⣿⣷⣦⣄⠀⠀⠀⠀ 21 + ⠀⠀⠀⠀⢀⣼⠟⠉⠀⢸⣿⠛⠛⠛⠿⠿⢿⣿⠟⣠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⡙⢻⣿⣿⣿⣿⣿⣿⡎⣿⡇⠀⠀⠀⠙⠉⠻⣿⣿⣿⣿⣿⣷⡄⠀⠀ 22 + ⠀⠀⠀⣠⣿⠏⠀⠀⠀⠘⣿⣧⡀⠀⠀⠀⠀⠀⠀⠈⠉⠙⠛⠻⠿⠿⠿⠛⠛⠛⠉⠉⠉⠁⠀⠀⠻⣿⣿⣿⣿⣿⣿⣿⣇⠀⠀⠀⠀⠀⠀⠀⠹⣿⣿⣿⣿⡷⠀⠀ 23 + ⠀⢀⣾⡿⠁⠀⠀⠀⠀⠀⠈⠻⢿⣶⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⣿⣿⣿⣿⣿⣿⣼⣶⠀⠀⠀⠀⠀⠀⠛⠿⠿⠟⠁⠀⠀ 24 + ⣰⣿⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠻⠿⣿⣿⣷⣭⣁⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 25 + ⠿⠥⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠻⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 26 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⣇⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀ 27 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⡿⢿⣶⠀⠀⠀⠀⠀⠀⠀ 28 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀ 29 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠿⣷⠄⠀⠀⠀⠀⠀⠀ 30 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⠿⠆⠀⠀⠀⠀⠀⠀ 31 + |js} 32 + 33 + type props = unit [@@deriving json] 34 + 35 + let[@react.component] make ~props:(_ : props) () = 36 + <main className="text-mist-100"> 37 + <pre className="text-mana-100">pegasus</pre> 38 + <p> 39 + (string "this is ") 40 + <a 41 + href="https://tangled.org/futur.blue/pegasus" 42 + className="text-mana-100 underline hover:text-mana-200"> 43 + (string "pegasus") 44 + </a> 45 + (string ", an atproto personal data server") 46 + </p> 47 + <p> 48 + (string "manage your account at ") 49 + <a 50 + href="/account" className="text-mana-100 underline hover:text-mana-200"> 51 + (string "/account") 52 + </a> 53 + </p> 54 + <p> 55 + (string "admin panel at ") 56 + <a href="/admin" className="text-mana-100 underline hover:text-mana-200"> 57 + (string "/admin") 58 + </a> 59 + </p> 60 + </main>
+1 -5
frontend/src/templates/SignupPage.mlx
··· 23 23 <input type_="hidden" name="dream.csrf" value=csrf_token /> 24 24 ( if invite_required then 25 25 <Input 26 - sr_only=true 27 - name="invite_code" 28 - type_="text" 29 - label="invite code" 30 - /> 26 + sr_only=true name="invite_code" type_="text" label="invite code" /> 31 27 else null ) 32 28 <HandleInput sr_only=true name="handle" hostname /> 33 29 <Input sr_only=true name="email" type_="email" label="email" />
+1 -31
pegasus/lib/api/root.ml
··· 1 1 let handler = 2 2 Xrpc.handler (fun _ -> 3 - Dream.respond 4 - ~headers:[("Content-Type", "text/plain; charset=utf-8")] 5 - {| 6 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⢀⣠⣴⡆⠀⠀⠀⠀⠀⠀ 7 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣤⣤⣶⣶⠿⠿⢛⡛⠉⠀⠀⠀⠀⠀⠀⠀ 8 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⣤⣤⣴⣶⣿⢻⣿⣿⣶⣶⣢⣆⠀⠀⠀⣀⣀⣤⣤⣶⣶⣾⣿⡿⠿⣛⣛⣭⣵⣶⠾⢟⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀ 9 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⣧⣾⣿⡟⣻⣿⣿⣿⣿⣿⣿⣭⣭⢠⣾⣿⣯⣭⣤⡬⣩⣭⣽⡶⠿⢟⣛⣯⣭⡶⣶⡛⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 10 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠋⠉⠁⠿⣿⡿⣿⣿⣿⣿⣿⣯⡄⣿⣿⣿⣿⡟⠃⠈⠱⠶⠾⠛⢋⣭⣿⠶⣟⡛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 11 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠞⣡⣾⣿⣿⣿⣿⣟⡓⢠⣾⣿⠿⣛⣛⡛⣟⣛⣛⡛⢶⣶⣶⣶⣶⣶⣥⣤⣤⣤⣤⣄⣀⣀⣀⣀⠀⠀⠀ 12 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡞⠁⣴⣿⣿⣿⣿⣿⡿⠯⢁⣿⢟⣴⣿⣿⡿⢳⣿⣿⣿⠏⠰⠶⠶⢾⣽⣭⣭⣭⣙⣛⣛⣛⣛⡛⠿⠿⠿⠿⠋ 13 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⡟⢀⣾⣿⣿⣿⣿⣿⣿⣿⠋⣾⡟⣼⣿⣿⢟⣴⣿⡿⢏⣀⣙⣛⣛⣛⡳⠶⠶⠶⣶⣭⣭⣭⣭⣭⣝⠛⠋⠀⠀⠀ 14 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⡿⠀⣼⣿⣿⣿⣿⣿⣿⠟⣂⣼⡟⣱⣿⣿⢏⣼⣿⣿⠁⠬⣭⣙⣛⣛⣛⣛⠛⠿⠿⠿⠶⠶⠶⠶⠆⠀⠀⠀⠀⠀⠀ 15 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⠟⢁⣤⣾⣿⣿⣿⣷⣶⣶⣾⣟⣫⣾⣿⣿⡏⣾⣿⣿⡅⢶⣶⣦⣤⣤⣬⣭⣭⣉⠛⠛⠛⠒⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀ 16 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⠟⠛⠱⣿⣛⣋⣤⣴⣶⣶⣦⣭⡉⠉⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 17 - ⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣄⡀⠀⠀⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 18 - ⠀⠀⠀⠀⠀⠀⠀⠀⠻⠿⠿⣿⡿⠶⠜⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇⢻⣿⠿⢿⣶⣶⣦⣤⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀ 19 - ⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣶⣶⣶⣿⣷⣶⣾⣿⣿⣿⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⣿⠀⠀⠉⢿⣿⣿⣿⣿⣿⣷⣦⣄⠀⠀⠀⠀ 20 - ⠀⠀⠀⠀⢀⣼⠟⠉⠀⢸⣿⠛⠛⠛⠿⠿⢿⣿⠟⣠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⡙⢻⣿⣿⣿⣿⣿⣿⡎⣿⡇⠀⠀⠀⠙⠉⠻⣿⣿⣿⣿⣿⣷⡄⠀⠀ 21 - ⠀⠀⠀⣠⣿⠏⠀⠀⠀⠘⣿⣧⡀⠀⠀⠀⠀⠀⠀⠈⠉⠙⠛⠻⠿⠿⠿⠛⠛⠛⠉⠉⠉⠁⠀⠀⠻⣿⣿⣿⣿⣿⣿⣿⣇⠀⠀⠀⠀⠀⠀⠀⠹⣿⣿⣿⣿⡷⠀⠀ 22 - ⠀⢀⣾⡿⠁⠀⠀⠀⠀⠀⠈⠻⢿⣶⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⣿⣿⣿⣿⣿⣿⣼⣶⠀⠀⠀⠀⠀⠀⠛⠿⠿⠟⠁⠀⠀ 23 - ⣰⣿⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠻⠿⣿⣿⣷⣭⣁⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 24 - ⠿⠥⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠻⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 25 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⣇⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀ 26 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⡿⢿⣶⠀⠀⠀⠀⠀⠀⠀ 27 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀ 28 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠿⣷⠄⠀⠀⠀⠀⠀⠀ 29 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⠿⠆⠀⠀⠀⠀⠀⠀ 30 - 31 - this is [pegasus](https://tangled.org/@futur.blue/pegasus) 32 - manage your account at /account 33 - |} ) 3 + Util.render_html ~title:"Pegasus" (module Frontend.RootPage) ~props:() )