| 1 | package home |
| 2 | |
| 3 | import ( |
| 4 | "amadan.net/oficina/estilo.git/busy" |
| 5 | _ "embed" |
| 6 | |
| 7 | "amadan.net/oficina/estilo.git/chrome" |
| 8 | "amadan.net/oficina/estilo.git/sky" |
| 9 | "amadan.net/oficina/estilo.git/theme" |
| 10 | "github.com/go-chi/chi/v5" |
| 11 | ) |
| 12 | |
| 13 | // Calma is the suite's look (estilo AGENTS.md, decisions table, row 8). |
| 14 | // Home is where the suite meets you — the front page, the sign-in, your |
| 15 | // home — so it is where the sky goes; the apps themselves never carry it. |
| 16 | |
| 17 | //go:embed static/calma.css |
| 18 | var calmaCSS []byte |
| 19 | |
| 20 | // homeFuncs are the template helpers Home's layout calls. suiteSky is the |
| 21 | // sky for pages that belong to no one team: all of Home's, today, since |
| 22 | // the directory lists apps rather than one team's. |
| 23 | var homeFuncs = map[string]any{ |
| 24 | // v links a stylesheet or picture by its content version (assetversion.go). |
| 25 | "v": versioned, |
| 26 | "suiteSky": func() sky.View { return sky.For(sky.SuiteSeed) }, |
| 27 | // Onest's stylesheet, under estilo's versioned prefix. |
| 28 | "fontsCSS": func() string { return theme.FontPrefix + "fonts.css" }, |
| 29 | // The door's words and tab titles are estilo's (chrome.English), the |
| 30 | // one copy set every app's door speaks, so the hub's door cannot |
| 31 | // drift from theirs. Home draws its own templates, so it reads the |
| 32 | // strings from estilo's types rather than rendering estilo-signin. |
| 33 | "signinDoor": func() chrome.SignIn { return chrome.SignIn{App: suiteName} }, |
| 34 | "sentDoor": func(again string) chrome.SignInNotice { |
| 35 | return chrome.SignInNotice{App: suiteName}.Sent(again) |
| 36 | }, |
| 37 | "signedOutDoor": func() chrome.SignInNotice { |
| 38 | return chrome.SignInNotice{App: suiteName}.SignedOut("/auth/start") |
| 39 | }, |
| 40 | "linkLifetime": func() int { return int(chrome.LinkLifetime.Minutes()) }, |
| 41 | // doorView is estilo-door-open's data: the suite's sky behind one |
| 42 | // card, the Oficina mark and name at its head. |
| 43 | "doorView": func() doorView { |
| 44 | return doorView{Sky: sky.For(sky.SuiteSeed), Icon: versioned("/static/brand/icon.svg"), App: suiteName} |
| 45 | }, |
| 46 | // hopSky is the sky a hop page keeps: the inline placeholder alone, a |
| 47 | // data: URI, so the moment between two skies is not a blank page and |
| 48 | // still fetches nothing it will not show (r3 C2). |
| 49 | "hopSky": func() sky.View { |
| 50 | var v sky.View |
| 51 | for _, l := range sky.For(sky.SuiteSeed).Layers { |
| 52 | if l.Inline != "" { |
| 53 | v.Layers = append(v.Layers, l) |
| 54 | } |
| 55 | } |
| 56 | return v |
| 57 | }, |
| 58 | // Onest's file, preloaded so a cold first load paints in it rather |
| 59 | // than swapping in after (r3 C4). |
| 60 | "fontPreload": func() string { return theme.FontPrefix + "onest-latin.woff2" }, |
| 61 | } |
| 62 | |
| 63 | // doorView is estilo-door-open's data (chrome/templates/signin.html). |
| 64 | type doorView struct { |
| 65 | Sky sky.View |
| 66 | Icon, App string |
| 67 | } |
| 68 | |
| 69 | // suiteName is what the hub calls itself to people: the suite, never |
| 70 | // "Home" (Paul, 2026-09-23). |
| 71 | const suiteName = "Oficina" |
| 72 | |
| 73 | // doorPages are the pages at the door — signing in, and everything on the |
| 74 | // way in or out, and id.*'s account pages. The layout draws them as |
| 75 | // estilo's door: one card on the sky with the Oficina mark, under the bar. |
| 76 | // The sky goes only where Calma puts it — the door, the team home's header |
| 77 | // and the front page's hero — so the team chooser, Open source and the |
| 78 | // error pages are plain pages under the bar. |
| 79 | // Not assertion.html or docs_continue.html: they are hops (hopPages). |
| 80 | var doorPages = map[string]bool{ |
| 81 | "signin.html": true, |
| 82 | "check_your_mail.html": true, |
| 83 | "signin_confirm.html": true, |
| 84 | "signin_failed.html": true, |
| 85 | "passkey_confirm.html": true, |
| 86 | "passkey_enrol.html": true, |
| 87 | "recovery_codes.html": true, |
| 88 | "handoff_failed.html": true, |
| 89 | "signout_confirm.html": true, |
| 90 | "directory_signed_out.html": true, |
| 91 | } |
| 92 | |
| 93 | // hopPages move on by themselves (an auto-submit, a 0 s refresh). They |
| 94 | // keep the bar and the sky's inline placeholder, so the moment between the |
| 95 | // door and the directory is not a blank page (r3 C2), but load no script, |
| 96 | // no sky layer and no card: the card shows only if the hop stalls. |
| 97 | var hopPages = map[string]bool{ |
| 98 | "assertion.html": true, |
| 99 | "docs_continue.html": true, |
| 100 | } |
| 101 | |
| 102 | // mountCalma serves the Calma pieces Home's pages link: Onest, the sky's |
| 103 | // layers and stylesheet, and Home's own few rules for placing it. Public |
| 104 | // on both origins; every handler here serves a closed set. |
| 105 | func mountCalma(r chi.Router) { |
| 106 | r.Handle(theme.FontPrefix+"*", theme.FontHandler()) |
| 107 | r.Handle(sky.Prefix+"*", sky.Handler()) |
| 108 | mountCSS(r, "/static/estilo/sky.css", sky.CSS()) |
| 109 | mountChrome(r) |
| 110 | mountCSS(r, "/static/calma.css", calmaCSS) |
| 111 | // estilo's busy rule: a submit button's label gives way to a spinner |
| 112 | // for at least 650ms, the button disables, the form refuses a second |
| 113 | // submit, and Back resets it. A progressive enhancement: every form |
| 114 | // works without it. |
| 115 | mountCSS(r, "/static/busy.css", busy.CSS()) |
| 116 | versionAsset("/static/busy.js", busy.JS()) |
| 117 | r.Get("/static/busy.js", serveAsset(busy.JS(), "text/javascript; charset=utf-8")) |
| 118 | } |
| 119 | |