Calma in Home: the front page, the door and your home
Home adopts estilo's Calma (estilo decisions-table row 8, landed 5b2ce83). - Theme and Onest from estilo; the suite's seeded sky behind every Home page and the front page's hero (never inside an app). - The front page is redrawn in Calma: same sections, same words, every link kept; each app's card in its own colour from estilo's ring (apps.css, generated from theme.AccentCSS); light and dark follow the system. Archivo and Newsreader are retired for Onest. - Pages at the door (sign-in, check your mail, passkeys, sign-out...) are one card on the sky with the Oficina mark; the hand-off page is not, since it submits itself mid-hop. - Your home is a grid of app tiles, with Mail and Files' new icons. - Calma icons distributed from estilo; credits list Onest. - Tests: the theme check is now "declares every token tokens.css uses"; the front page must link estilo's current versioned URLs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
12 files changed,
+427
−537
internal/home/calma.go+2 −1internal/home/origins_test.go+1 −2internal/home/page.go+23 −13internal/home/page_test.go+22 −0internal/home/static/calma.css+55 −0internal/home/static/site/archivo-ext.woff2binaryinternal/home/static/site/archivo.woff2binaryinternal/home/static/site/home.css+169 −387internal/home/static/site/index.html+142 −121internal/home/static/site/newsreader-ext.woff2binaryinternal/home/static/site/newsreader.woff2binaryinternal/home/templates/directory.html+13 −13
CI passed — run details
CI log
amadan: .amadan/timeout: this job's budget is 15m0s (this runner allows up to 30m0s) === vet === CGO_ENABLED=0 go vet ./... go: downloading amadan.net/oficina/estilo.git v0.0.0-20260929070530-5b2ce835e773 cd handoff && CGO_ENABLED=0 go vet ./... cd roster && CGO_ENABLED=0 go vet ./... cd connect && CGO_ENABLED=0 go vet ./... === fmt === === build === CGO_ENABLED=0 go build ./... CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build ./cmd/home ./cmd/oficina ./internal/operatorlaunch cd handoff && CGO_ENABLED=0 go build ./... cd roster && CGO_ENABLED=0 go build ./... cd connect && CGO_ENABLED=0 go build ./... === go-test === CGO_ENABLED=0 go test -count=1 ./... ? amadan.net/oficina/home/cmd/home [no test files] ok amadan.net/oficina/home/cmd/home-placeholder 0.493s ? amadan.net/oficina/home/cmd/oficina [no test files] ok amadan.net/oficina/home/internal/guardstest 0.027s ok amadan.net/oficina/home/internal/home 7.713s ok amadan.net/oficina/home/internal/home/directorydata 0.939s ok amadan.net/oficina/home/internal/home/logtest 0.005s ok amadan.net/oficina/home/internal/home/seal 0.003s ok amadan.net/oficina/home/internal/home/sealtest 0.003s ok amadan.net/oficina/home/internal/operator 4.978s ok amadan.net/oficina/home/internal/operatorlaunch 4.143s ok amadan.net/oficina/home/internal/provisioner 35.137s cd handoff && CGO_ENABLED=0 go test -count=1 ./... ok amadan.net/oficina/home/handoff 0.307s ok amadan.net/oficina/home/handoff/seal 0.002s cd roster && CGO_ENABLED=0 go test -count=1 ./... ok amadan.net/oficina/home/roster 5.072s cd connect && CGO_ENABLED=0 go test -count=1 ./... ok amadan.net/oficina/home/connect 0.055s ok amadan.net/oficina/home/connect/linkstore 0.212s === race === go test -race -count=1 ./internal/home/ ./internal/operator/ ./internal/operatorlaunch/ ./internal/provisioner/ ./internal/home/directorydata/ ok amadan.net/oficina/home/internal/home 92.958s ok amadan.net/oficina/home/internal/operator 63.954s ok amadan.net/oficina/home/internal/operatorlaunch 22.295s ok amadan.net/oficina/home/internal/provisioner 262.650s ok amadan.net/oficina/home/internal/home/directorydata 14.867s cd handoff && CGO_ENABLED=1 go test -race -count=1 ./... ok amadan.net/oficina/home/handoff 8.892s ok amadan.net/oficina/home/handoff/seal 1.009s cd roster && CGO_ENABLED=1 go test -race -count=1 ./... ok amadan.net/oficina/home/roster 6.548s cd connect && CGO_ENABLED=1 go test -race -count=1 ./... ok amadan.net/oficina/home/connect 1.614s ok amadan.net/oficina/home/connect/linkstore 3.676s
diff --git a/internal/home/calma.go b/internal/home/calma.go| index d0d6e6b..cde7f97 100644 |
| --- a/internal/home/calma.go |
| +++ b/internal/home/calma.go |
| @@ -28,6 +28,8 @@ var homeFuncs = map[string]any{ |
| // way in or out. The layout draws them as one card on the sky with the |
| // Oficina mark, as Calma's sign-in does; every other page keeps the full |
| // width (the directory lists apps; credits is a long page of notices). |
| +// Not assertion.html: it submits itself mid-hop and hides its own copy |
| +// for 1.5 s so nothing flashes; a card and a mark would flash anyway. |
| var doorPages = map[string]bool{ |
| "signin.html": true, |
| "check_your_mail.html": true, |
| @@ -36,7 +38,6 @@ var doorPages = map[string]bool{ |
| "passkey_confirm.html": true, |
| "passkey_enrol.html": true, |
| "recovery_codes.html": true, |
| - "assertion.html": true, |
| "handoff_failed.html": true, |
| "signout_confirm.html": true, |
| "directory_signed_out.html": true, |
diff --git a/internal/home/origins_test.go b/internal/home/origins_test.go| index 4d62f88..abae455 100644 |
| --- a/internal/home/origins_test.go |
| +++ b/internal/home/origins_test.go |
| @@ -21,8 +21,7 @@ var sharedPublicPaths = []string{ |
| "/static/brand/fichas.svg", "/static/brand/mail.svg", "/static/brand/files.svg", |
| // Calma: Onest, the sky and its stylesheet, and Home's rules for placing it. |
| "/static/estilo/fonts/v1/*", "/static/estilo/sky/v1/*", "/static/estilo/sky.css", "/static/calma.css", |
| - "/static/site/home.css", "/static/site/archivo.woff2", "/static/site/archivo-ext.woff2", |
| - "/static/site/newsreader.woff2", "/static/site/newsreader-ext.woff2", |
| + "/static/site/home.css", "/static/site/apps.css", |
| } |
| // examplePath is a real file under a wildcard route, so fetching it |
diff --git a/internal/home/page.go b/internal/home/page.go| index c6c8c38..984d5bd 100644 |
| --- a/internal/home/page.go |
| +++ b/internal/home/page.go |
| @@ -1,9 +1,11 @@ |
| package home |
| import ( |
| + "bytes" |
| "embed" |
| "net/http" |
| + "amadan.net/oficina/estilo.git/theme" |
| "github.com/go-chi/chi/v5" |
| ) |
| @@ -16,10 +18,11 @@ var siteFS embed.FS |
| // user input to escape — so it is served as bytes, with no template step |
| // standing between the file and the response. |
| // |
| -// It links one stylesheet and four font subsets, all from this origin. |
| -// SecurityPolicy's `default-src 'self'` already admits them, which is why |
| -// adding the page needed no change to the policy: fonts fall back to |
| -// default-src, and the stylesheet is covered by `style-src 'self'`. |
| +// It is drawn in Calma, the suite's look: estilo's theme and Onest, the |
| +// sky behind the hero, and each app's own colour from estilo's ring |
| +// (appsCSS). Everything it links is on this origin; SecurityPolicy's |
| +// `default-src 'self'` already admits the fonts and pictures, and the |
| +// stylesheets are covered by `style-src 'self'`. |
| // |
| // Everything the page points at has to exist. The app links go to the six |
| // live app hosts; there is no sign-up form, because there is no route that |
| @@ -36,19 +39,26 @@ func siteAsset(name string) []byte { |
| return b |
| } |
| +// appsCSS gives each app on the page its own accent: estilo's per-app |
| +// accent blocks, scoped from :root to [data-app="<slug>"], so a card for |
| +// Sheets tints green from the same ring the Sheets app itself uses, with |
| +// no colour copied here to drift. |
| +var appsCSS = func() []byte { |
| + var b bytes.Buffer |
| + b.WriteString("/* Each app's accent, from estilo's colour ring (theme.AccentCSS). */\n") |
| + for _, a := range theme.Apps() { |
| + block, _ := theme.AccentCSS(a.Slug) |
| + b.Write(bytes.Replace(block, []byte(":root {"), []byte(`[data-app="`+a.Slug+`"] {`), 1)) |
| + } |
| + return b.Bytes() |
| +}() |
| + |
| // Public on both Home origins, and a closed list for the same reason |
| // mountBrand keeps one: a request path must never be treated as a file |
| // name. |
| func mountSite(r chi.Router) { |
| - for name, contentType := range map[string]string{ |
| - "home.css": "text/css; charset=utf-8", |
| - "archivo.woff2": "font/woff2", |
| - "archivo-ext.woff2": "font/woff2", |
| - "newsreader.woff2": "font/woff2", |
| - "newsreader-ext.woff2": "font/woff2", |
| - } { |
| - r.Get("/static/site/"+name, serveAsset(siteAsset(name), contentType)) |
| - } |
| + r.Get("/static/site/home.css", serveAsset(siteAsset("home.css"), "text/css; charset=utf-8")) |
| + r.Get("/static/site/apps.css", serveAsset(appsCSS, "text/css; charset=utf-8")) |
| } |
| // handleIndex serves the front page. |
diff --git a/internal/home/page_test.go b/internal/home/page_test.go| index f2d9cc3..0ba3d48 100644 |
| --- a/internal/home/page_test.go |
| +++ b/internal/home/page_test.go |
| @@ -1,8 +1,12 @@ |
| package home |
| import ( |
| + "bytes" |
| "regexp" |
| "testing" |
| + |
| + "amadan.net/oficina/estilo.git/sky" |
| + "amadan.net/oficina/estilo.git/theme" |
| ) |
| // TestFrontPageLineBreaksKeepTheirSpace: home.css hides the hero's <br> on |
| @@ -14,3 +18,21 @@ func TestFrontPageLineBreaksKeepTheirSpace(t *testing.T) { |
| t.Fatalf("front page has <br> with no whitespace beside it: %q", glued) |
| } |
| } |
| + |
| +// The front page is static bytes, so it names estilo's versioned URLs |
| +// directly. When estilo moves one (a new sky drawing, a new font file), |
| +// this fails rather than the page quietly linking files that 404. |
| +func TestFrontPageLinksEstilosCurrentURLs(t *testing.T) { |
| + want := []string{theme.FontPrefix + "fonts.css"} |
| + for _, layer := range sky.For(sky.SuiteSeed).Layers { |
| + want = append(want, layer.Src) |
| + } |
| + for _, url := range want { |
| + if !bytes.Contains(indexPage, []byte(`"`+url+`"`)) { |
| + t.Errorf("front page does not link %s", url) |
| + } |
| + } |
| + if n := bytes.Count(indexPage, []byte(sky.Prefix)); n != len(sky.For(sky.SuiteSeed).Layers) { |
| + t.Errorf("front page links %d sky files, want exactly the suite sky's %d", n, len(sky.For(sky.SuiteSeed).Layers)) |
| + } |
| +} |
diff --git a/internal/home/static/calma.css b/internal/home/static/calma.css| index 9aa4ae2..28e556b 100644 |
| --- a/internal/home/static/calma.css |
| +++ b/internal/home/static/calma.css |
| @@ -44,3 +44,58 @@ main[home-door] h1 { |
| padding: 26px 22px 24px; |
| } |
| } |
| + |
| +/* Your home: the Oficina mark and a grid of every app, each one tile. An |
| + app you can open is a link across its whole tile; one you cannot yet |
| + is shown quieter, with its status saying why. */ |
| +[home-hub-head] { |
| + display: flex; |
| + align-items: center; |
| + justify-content: space-between; |
| + gap: 16px; |
| + margin: 28px 0 8px; |
| +} |
| +[home-hub-head] h1 { |
| + margin: 0; |
| + font-size: clamp(1.9rem, 4vw, 2.5rem); |
| + letter-spacing: -0.03em; |
| +} |
| +[home-hub-head] form { margin: 0; } |
| +[home-hub-lede] { |
| + margin: 0 0 40px; |
| + color: var(--rst-text-muted); |
| +} |
| +[home-hub-h] { |
| + margin: 0 0 12px; |
| + color: var(--rst-text-faint); |
| + font-size: 0.95rem; |
| + font-weight: 600; |
| +} |
| +[home-apps] { |
| + display: grid; |
| + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); |
| + gap: 12px; |
| + margin: 0 0 48px; |
| + padding: 0; |
| + list-style: none; |
| +} |
| +[home-app] { |
| + box-sizing: border-box; |
| + height: 100%; |
| + display: flex; |
| + flex-direction: column; |
| + align-items: center; |
| + gap: 8px; |
| + padding: 22px 12px 18px; |
| + border-radius: 18px; |
| + background: var(--rst-surface); |
| + box-shadow: 0 0 0 1px var(--rst-line); |
| + color: var(--rst-text); |
| + text-align: center; |
| + text-decoration: none; |
| +} |
| +a[home-app]:hover { box-shadow: 0 0 0 1px var(--rst-line), var(--rst-shadow-lift); } |
| +[home-app] img { margin-bottom: 4px; filter: drop-shadow(0 5px 9px rgba(20, 24, 33, 0.16)); } |
| +[home-app] b { font-weight: 620; } |
| +[home-app] small { color: var(--rst-text-faint); font-size: 0.8rem; } |
| +div[home-app] img { filter: saturate(0.6); opacity: 0.8; } |
diff --git a/internal/home/static/site/archivo-ext.woff2 b/internal/home/static/site/archivo-ext.woff2| deleted file mode 100644 |
| index 462b5cf..0000000 |
| Binary files a/internal/home/static/site/archivo-ext.woff2 and /dev/null differ |
diff --git a/internal/home/static/site/archivo.woff2 b/internal/home/static/site/archivo.woff2| deleted file mode 100644 |
| index 7af3f7d..0000000 |
| Binary files a/internal/home/static/site/archivo.woff2 and /dev/null differ |
diff --git a/internal/home/static/site/home.css b/internal/home/static/site/home.css| index 6b9136a..b075cf0 100644 |
| --- a/internal/home/static/site/home.css |
| +++ b/internal/home/static/site/home.css |
| @@ -1,401 +1,183 @@ |
| -/* Oficina — the front door. |
| - The suite's own theme (estilo) is deliberately unremarkable; this page is |
| - the one place that gets to have a voice. One family, two widths: Archivo |
| - expanded does the signage, Archivo normal does the interface, Newsreader |
| - does the prose. Saffron means "new, or not yet" and nothing else. */ |
| +/* oficina.works, the front page, in Calma (estilo AGENTS.md, row 8). |
| -@font-face { |
| - font-family: "Archivo"; |
| - src: url("/static/site/archivo.woff2") format("woff2"); |
| - font-weight: 400 800; |
| - font-stretch: 62% 125%; |
| - font-display: swap; |
| - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, |
| - U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, |
| - U+2212, U+2215, U+FEFF, U+FFFD; |
| -} |
| -@font-face { |
| - font-family: "Archivo"; |
| - src: url("/static/site/archivo-ext.woff2") format("woff2"); |
| - font-weight: 400 800; |
| - font-stretch: 62% 125%; |
| - font-display: swap; |
| - unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, |
| - U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, |
| - U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; |
| -} |
| -@font-face { |
| - font-family: "Newsreader"; |
| - src: url("/static/site/newsreader.woff2") format("woff2"); |
| - font-weight: 300 600; |
| - font-display: swap; |
| - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, |
| - U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, |
| - U+2212, U+2215, U+FEFF, U+FFFD; |
| -} |
| -@font-face { |
| - font-family: "Newsreader"; |
| - src: url("/static/site/newsreader-ext.woff2") format("woff2"); |
| - font-weight: 300 600; |
| - font-display: swap; |
| - unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, |
| - U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, |
| - U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; |
| -} |
| - |
| -:root { |
| - /* Contrast, measured against these exact values (WCAG 2.2): |
| - ink/paper 15.6 · muted/paper 5.2 · cobalt/paper 6.2 · ochre/paper 5.0 |
| - bone/ink 13.4 · dim/ink 7.5 · sky/ink 7.6 · saffron/ink 7.2 · ink/saffron 8.0 |
| - Put a colour on a surface not listed and recompute before you ship it. */ |
| - --ink: #151e3a; |
| - --ink-2: #1d2848; |
| - --ink-rule: #37436f; |
| - --paper: #eeede6; |
| - --paper-2: #e4e2d8; |
| - --paper-rule: #cfcdc0; |
| - --cobalt: #2748cc; |
| - --cobalt-deep: #1b36a8; |
| - --sky: #93aeff; |
| - --saffron: #e0a02c; |
| - --ochre: #8a5a06; |
| - --bone: #eae8df; |
| - --dim: #a7afc8; |
| - --muted: #5a6178; |
| - |
| - --sig: "Archivo", system-ui, sans-serif; /* signage + interface */ |
| - --prose: "Newsreader", Georgia, serif; /* running text */ |
| - |
| - color-scheme: light; |
| - |
| - --shell: 74rem; |
| - --gutter: clamp(1.25rem, 5vw, 4rem); |
| - --step: clamp(4rem, 9vw, 7.5rem); |
| -} |
| + Colour, type and shape are estilo's theme (theme.css, loaded first): |
| + this file only lays the page out. Each app's card, and the Memoria |
| + section, take their accent from estilo's colour ring through |
| + [data-app] (apps.css), so the page is every app's colour at once and |
| + each app is its own. The sky behind the hero is estilo's (sky.css), |
| + seeded as the suite's own. Light and dark follow the reader's system; |
| + the page runs no script to switch them. */ |
| *, *::before, *::after { box-sizing: border-box; } |
| - |
| -html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; } |
| -@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } } |
| - |
| body { |
| margin: 0; |
| - background: var(--paper); |
| - color: var(--ink); |
| - font-family: var(--sig); |
| - font-size: 1.0625rem; |
| - line-height: 1.55; |
| - font-synthesis-weight: none; |
| + background: var(--rst-bg); |
| + color: var(--rst-text); |
| + font: 400 16px/1.55 var(--rst-font); |
| + -webkit-font-smoothing: antialiased; |
| text-rendering: optimizeLegibility; |
| } |
| +img { display: block; } |
| +a { color: var(--rst-accent); } |
| +:focus-visible { outline: 2px solid var(--rst-accent); outline-offset: 3px; border-radius: 6px; } |
| +h1, h2, h3 { text-wrap: balance; } |
| -h1, h2, h3 { margin: 0; font-weight: 700; font-stretch: 112%; letter-spacing: -0.018em; line-height: 1.02; } |
| -p { margin: 0 0 1.1em; } |
| -ul { margin: 0; padding: 0; list-style: none; } |
| - |
| -a { color: var(--cobalt); text-decoration-thickness: 1px; text-underline-offset: 0.18em; } |
| -a:hover { color: var(--cobalt-deep); } |
| - |
| -:where(a, button, input, summary):focus-visible { |
| - outline: 2px solid var(--cobalt); |
| - outline-offset: 3px; |
| - border-radius: 2px; |
| -} |
| -.band-ink :where(a, button, input):focus-visible { outline-color: var(--sky); } |
| - |
| -.vh { |
| - position: absolute; width: 1px; height: 1px; margin: -1px; |
| - padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; |
| -} |
| - |
| -.skip { |
| - position: absolute; left: 0.75rem; top: -4rem; z-index: 10; |
| - background: var(--bone); color: var(--ink); padding: 0.6rem 1rem; |
| - border-radius: 3px; font-weight: 600; text-decoration: none; |
| -} |
| -.skip:focus { top: 0.75rem; } |
| - |
| -.shell { max-width: var(--shell); margin-inline: auto; padding-inline: var(--gutter); } |
| - |
| -/* ── Bands ────────────────────────────────────────────────────────────── */ |
| -.band { padding-block: var(--step); } |
| -.band-ink { background: var(--ink); color: var(--bone); } |
| -.band-quiet { background: var(--paper-2); } |
| -.band:not(.band-ink) + .band:not(.band-ink) { border-top: 1px solid var(--paper-rule); } |
| - |
| -.band-ink a { color: var(--sky); } |
| -.band-ink a:hover { color: #b9caff; } |
| -.band h2 { font-size: clamp(1.75rem, 3.4vw, 2.6rem); max-width: 18ch; } |
| -.band h2.wide { max-width: 24ch; margin-bottom: 1.5rem; } |
| - |
| -/* ── Bar ──────────────────────────────────────────────────────────────── */ |
| -.bar { background: var(--ink); color: var(--bone); } |
| -.bar-in { |
| - display: flex; align-items: center; gap: clamp(1rem, 4vw, 3rem); |
| - min-height: 4.25rem; |
| -} |
| -.mark { |
| - font-weight: 800; font-stretch: 118%; font-size: 1.32rem; |
| - letter-spacing: -0.02em; color: var(--bone); text-decoration: none; |
| - margin-right: auto; |
| -} |
| -.nav { display: flex; gap: clamp(0.9rem, 2.4vw, 1.9rem); } |
| -.nav a { |
| - color: var(--dim); text-decoration: none; font-size: 0.95rem; font-weight: 500; |
| -} |
| -.nav a:hover { color: var(--bone); } |
| -.bar-cta { flex: none; } |
| -@media (max-width: 46rem) { |
| - .bar-in { flex-wrap: wrap; padding-block: 0.9rem; row-gap: 0.75rem; } |
| - .nav { order: 3; width: 100%; } |
| - .bar-cta { order: 2; } |
| -} |
| - |
| -/* ── Hero ─────────────────────────────────────────────────────────────── */ |
| -.hero { padding-top: clamp(3rem, 7vw, 5.5rem); } |
| -.hero-h { |
| - font-size: clamp(2.2rem, 9.2vw, 6.6rem); |
| - font-weight: 800; |
| - font-stretch: 118%; |
| - letter-spacing: -0.03em; |
| - line-height: 0.92; |
| - margin-bottom: 1.4rem; |
| -} |
| -.hero-lead { |
| - font-family: var(--prose); |
| - font-size: clamp(1.1rem, 1.9vw, 1.3rem); |
| - line-height: 1.62; |
| - color: var(--dim); |
| - max-width: 56ch; |
| - margin-bottom: 2.6rem; |
| -} |
| - |
| -@media (max-width: 30rem) { |
| - .hero-h { font-stretch: 104%; letter-spacing: -0.022em; } |
| - .hero-h br { display: none; } |
| -} |
| - |
| -/* ── The plate: an engraved directory board ───────────────────────────── */ |
| -.plate { |
| - background: var(--ink-2); |
| - border: 1px solid var(--ink-rule); |
| - border-radius: 3px; |
| - padding: clamp(1rem, 2.6vw, 1.6rem) clamp(1rem, 2.6vw, 2rem); |
| -} |
| -.plate-flat { padding-block: 0.4rem; } |
| - |
| -.directory { width: 100%; border-collapse: collapse; } |
| -.directory caption { |
| - text-align: left; |
| - font-family: var(--prose); |
| - font-size: 0.98rem; |
| - color: var(--dim); |
| - padding-bottom: 0.9rem; |
| -} |
| -.directory thead th { |
| - text-align: right; |
| - font-size: 0.82rem; |
| - font-weight: 600; |
| - font-stretch: 100%; |
| - color: var(--dim); |
| - padding: 0 0 0.55rem; |
| - border-bottom: 1px solid var(--ink-rule); |
| - width: 7.5rem; |
| -} |
| -.directory thead th:first-child, .directory thead th:nth-child(2) { width: auto; } |
| - |
| -.directory tbody tr { border-bottom: 1px solid var(--ink-rule); } |
| -.directory tbody tr:last-child { border-bottom: 0; } |
| -.directory tbody th, .directory tbody td { padding-block: 0.95rem; vertical-align: baseline; } |
| +.shell { max-width: 1180px; margin: 0 auto; padding-inline: 32px; } |
| -.c-app { |
| - text-align: left; |
| - font-size: clamp(1.3rem, 2.3vw, 1.7rem); |
| - font-weight: 700; |
| - font-stretch: 115%; |
| - letter-spacing: -0.02em; |
| - color: var(--bone); |
| - white-space: nowrap; |
| - padding-right: 1.5rem; |
| -} |
| -.c-app em { |
| - font-style: normal; font-size: 0.68rem; font-weight: 700; font-stretch: 100%; |
| - letter-spacing: 0.04em; color: var(--ink); background: var(--saffron); |
| - border-radius: 2px; padding: 0.16em 0.42em; vertical-align: 0.42em; |
| - margin-left: 0.45rem; |
| -} |
| -.c-what { |
| - font-family: var(--prose); |
| - font-size: 1.03rem; |
| - color: var(--dim); |
| - padding-right: 1.5rem; |
| -} |
| -.c-get { text-align: right; white-space: nowrap; } |
| -.c-get a { font-weight: 600; font-size: 0.97rem; text-decoration: none; border-bottom: 1px solid rgba(147,174,255,.4); } |
| -.c-get a:hover { border-bottom-color: currentColor; } |
| -.c-none span { color: #7d86a0; } |
| -.c-get a.soon { color: var(--saffron); border-bottom-color: rgba(224,160,44,.45); } |
| +.skip { position: absolute; left: 16px; top: -48px; z-index: 10; padding: 10px 14px; border-radius: 10px; background: var(--rst-surface); box-shadow: var(--rst-shadow-lift); } |
| +.skip:focus { top: 12px; } |
| -.acts { display: flex; flex-wrap: wrap; gap: 0.85rem; margin-top: 2.4rem; } |
| +/* ── The bar ── */ |
| +.bar { position: relative; z-index: 2; } |
| +.bar-in { display: flex; align-items: center; gap: 28px; padding-block: 18px; } |
| +.mark { display: flex; align-items: center; gap: 10px; margin-right: auto; color: var(--rst-text); text-decoration: none; font: 650 19px/1 var(--rst-font); letter-spacing: -0.01em; } |
| +.nav { display: flex; gap: 26px; } |
| +.nav a { color: var(--rst-text-muted); text-decoration: none; font-size: 15px; } |
| +.nav a:hover { color: var(--rst-text); } |
| -/* ── Buttons ──────────────────────────────────────────────────────────── */ |
| +/* ── Buttons ── */ |
| .btn { |
| - display: inline-block; |
| - font: inherit; |
| - font-weight: 600; |
| - font-stretch: 104%; |
| - line-height: 1; |
| - padding: 0.82rem 1.35rem; |
| - border: 1px solid transparent; |
| - border-radius: 3px; |
| - text-decoration: none; |
| - cursor: pointer; |
| - transition: background-color .14s ease, color .14s ease, border-color .14s ease; |
| -} |
| -.btn-sm { padding: 0.6rem 1rem; font-size: 0.93rem; } |
| -.btn-bone { background: var(--bone); color: var(--ink); } |
| -.btn-bone:hover { background: #fff; color: var(--ink); } |
| -.btn-ghost { background: transparent; color: var(--sky); border-color: var(--ink-rule); } |
| -.btn-ghost:hover { border-color: var(--sky); color: var(--sky); } |
| -.btn-cobalt { background: var(--cobalt); color: #fff; } |
| -.btn-cobalt:hover { background: var(--cobalt-deep); color: #fff; } |
| -.btn-saffron { background: var(--saffron); color: var(--ink); } |
| -.btn-saffron:hover { background: #f0b444; color: var(--ink); } |
| -.band-ink .btn-bone, .band-ink .btn-saffron { color: var(--ink); } |
| -.bar .btn-bone { padding: 0.62rem 1.05rem; font-size: 0.95rem; } |
| - |
| -/* ── Two-column bands ─────────────────────────────────────────────────── */ |
| -.two { |
| - display: grid; |
| - grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); |
| - gap: clamp(2rem, 5vw, 4.5rem); |
| - align-items: start; |
| -} |
| -.two-tight { align-items: center; } |
| -@media (max-width: 56rem) { .two { grid-template-columns: 1fr; } } |
| - |
| -.prose { |
| - font-family: var(--prose); |
| - font-size: 1.12rem; |
| - line-height: 1.66; |
| - max-width: 60ch; |
| - color: #2b3350; |
| -} |
| -.prose.sm { font-size: 1rem; color: var(--muted); } |
| -.two-a h2 { margin-bottom: 1.3rem; } |
| + display: inline-flex; align-items: center; justify-content: center; gap: 8px; |
| + padding: 12px 18px; border-radius: 10px; |
| + background: var(--rst-accent); color: var(--rst-on-accent); |
| + font: 600 15px/1 var(--rst-font); text-decoration: none; |
| + transition: background-color .15s ease; |
| +} |
| +.btn:hover { background: var(--rst-accent-strong); } |
| +.btn-quiet { background: var(--rst-surface); color: var(--rst-text); box-shadow: inset 0 0 0 1px var(--rst-line); } |
| +.btn-quiet:hover { background: var(--rst-surface-2); } |
| +.btn-sm { padding: 9px 14px; font-size: 14px; } |
| + |
| +/* ── Hero: the sky behind the headline, the product beneath it ── */ |
| +.hero { --estilo-sky-top: -10%; --estilo-sky-height: 74%; } |
| +.hero-in { text-align: center; padding-block: 72px 0; } |
| +.hero-h { |
| + margin: 0 auto 22px; max-width: 13ch; |
| + font: 620 clamp(44px, 7.2vw, 78px)/1.02 var(--rst-font); letter-spacing: -0.035em; |
| +} |
| +.hero-lead { margin: 0 auto 32px; max-width: 46ch; color: var(--rst-text-muted); font-size: clamp(17px, 1.6vw, 19.5px); line-height: 1.55; } |
| +.acts { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; } |
| + |
| +.stage { |
| + position: relative; height: 470px; margin-top: 60px; |
| + border-radius: 24px 24px 0 0; overflow: hidden; |
| + background: color-mix(in oklab, var(--rst-accent) 7%, var(--rst-surface-2)); |
| +} |
| +.launch { |
| + position: absolute; left: 6%; top: 56px; width: 400px; |
| + padding: 18px 20px 22px; border-radius: 16px; |
| + background: var(--rst-surface); box-shadow: var(--rst-shadow-lift); |
| +} |
| +.launch-h { display: flex; align-items: center; gap: 10px; margin: 0 0 16px; font-size: 13px; } |
| +.launch-h b { font-size: 15px; font-weight: 620; } |
| +.launch-h span { margin-left: auto; color: var(--rst-text-faint); } |
| +.launch-apps { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin: 0; padding: 0; list-style: none; } |
| +.launch-apps li { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 14px 6px 12px; border-radius: 12px; font: 500 13px/1 var(--rst-font); } |
| +.doc { |
| + position: absolute; right: 5%; top: 92px; width: 540px; height: 420px; |
| + border-radius: 16px; overflow: hidden; |
| + background: var(--rst-surface); box-shadow: var(--rst-shadow-lift); |
| +} |
| +.doc-bar { display: flex; align-items: center; gap: 10px; margin: 0; padding: 10px 16px; border-bottom: 1px solid var(--rst-line); font-size: 13px; } |
| +.doc-bar b { font-weight: 620; } |
| +.doc-bar span:last-child { color: var(--rst-text-faint); overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } |
| +.lights { display: flex; gap: 6px; margin-right: 4px; } |
| +.lights i { width: 11px; height: 11px; border-radius: 50%; background: var(--rst-line); } |
| +.doc-page { padding: 26px 40px; } |
| +.doc-h { margin: 0 0 12px; font: 620 22px/1.2 var(--rst-font); letter-spacing: -0.02em; } |
| +.doc-page p:not(.doc-h) { margin: 0 0 10px; color: var(--rst-text-muted); font-size: 14px; line-height: 1.6; } |
| +.doc mark { background: var(--rst-accent-soft); color: inherit; border-radius: 3px; padding: 0 2px; box-shadow: inset 0 -2px 0 var(--rst-accent); } |
| + |
| +/* ── Bands ── */ |
| +.band { padding-block: 92px; } |
| +.band-quiet { background: var(--rst-surface); border-block: 1px solid var(--rst-line); } |
| +.band h2, .sec-head h2 { margin: 0 0 14px; font: 620 clamp(28px, 3.4vw, 40px)/1.1 var(--rst-font); letter-spacing: -0.03em; } |
| +.sec-head { max-width: 60ch; margin-bottom: 40px; } |
| +.prose { margin: 0 0 16px; max-width: 58ch; color: var(--rst-text-muted); font-size: 17px; line-height: 1.65; } |
| +.prose.sm { font-size: 15px; } |
| .lnk { font-weight: 600; } |
| - |
| -/* Memoria's demonstration */ |
| -.ask { |
| - background: var(--paper-2); |
| - border: 1px solid var(--paper-rule); |
| - border-left: 3px solid var(--ochre); |
| - border-radius: 3px; |
| - padding: clamp(1.3rem, 3vw, 1.9rem); |
| -} |
| -.ask-q { font-weight: 600; font-size: 1.06rem; margin-bottom: 1.1rem; } |
| -.ask-a { |
| - font-family: var(--prose); font-size: 1.14rem; line-height: 1.6; |
| - margin-bottom: 1.1rem; |
| -} |
| -.ask-cite { margin: 0; font-size: 0.94rem; } |
| -.ask-cite a { color: var(--ochre); font-weight: 600; } |
| -.ask-note { margin: 0.9rem 0 0; font-size: 0.92rem; color: var(--muted); } |
| - |
| -/* Isolation facts */ |
| -.facts { margin: 0; } |
| -.facts > div { padding: 1.05rem 0; border-top: 1px solid var(--paper-rule); } |
| -.facts > div:first-child { border-top: 0; padding-top: 0; } |
| -.facts dt { font-weight: 700; font-stretch: 108%; font-size: 1.02rem; } |
| -.facts dd { margin: 0.32rem 0 0; font-family: var(--prose); color: var(--muted); max-width: 48ch; } |
| - |
| -/* ── Downloads ────────────────────────────────────────────────────────── */ |
| -#native .hero-lead { margin-bottom: 2.2rem; } |
| -.dl-list { display: grid; grid-template-columns: 1fr 1fr; column-gap: clamp(1.5rem, 4vw, 3rem); } |
| -.dl-list li { |
| - display: flex; align-items: center; justify-content: space-between; |
| - gap: 1.5rem; padding: 1.05rem 0; border-bottom: 1px solid var(--ink-rule); |
| -} |
| -.dl-list li:nth-last-child(-n+2) { border-bottom: 0; } |
| -@media (max-width: 44rem) { |
| - .dl-list { grid-template-columns: 1fr; } |
| - .dl-list li:nth-last-child(2) { border-bottom: 1px solid var(--ink-rule); } |
| -} |
| -.dl-state { color: var(--dim); font-size: 0.95rem; font-family: var(--prose); } |
| -.dl-app { font-weight: 700; font-stretch: 112%; font-size: 1.18rem; color: var(--bone); } |
| -.dl-req { margin: 0; padding: 1rem 0 1.1rem; border-top: 1px solid var(--ink-rule); font-size: 0.92rem; color: var(--dim); } |
| - |
| -.tf { margin-top: clamp(2.5rem, 5vw, 3.5rem); max-width: 62ch; } |
| -.tf h3 { font-size: clamp(1.4rem, 2.6vw, 1.9rem); margin-bottom: 0.85rem; } |
| -.tf h3::after { |
| - content: ""; display: block; width: 3.5rem; height: 3px; |
| - background: var(--saffron); margin-top: 0.85rem; |
| -} |
| -.tf p { font-family: var(--prose); color: var(--dim); font-size: 1.08rem; line-height: 1.62; } |
| - |
| -/* ── Forms ────────────────────────────────────────────────────────────── */ |
| -.form { display: grid; gap: 1.1rem; } |
| -.form-inline { grid-template-columns: minmax(0, 1fr) auto; align-items: end; gap: 0.8rem; } |
| -@media (max-width: 34rem) { .form-inline { grid-template-columns: 1fr; align-items: stretch; } } |
| - |
| -.field { display: grid; gap: 0.4rem; } |
| -.field label { font-size: 0.88rem; font-weight: 600; color: var(--muted); } |
| -.band-ink .field label { color: var(--dim); } |
| -.field input { |
| - font: inherit; |
| - padding: 0.78rem 0.9rem; |
| - border: 1px solid var(--paper-rule); |
| - border-radius: 3px; |
| - background: #fff; |
| - color: var(--ink); |
| - min-width: 0; |
| -} |
| -.band-ink .field input { background: var(--ink-2); border-color: var(--ink-rule); color: var(--bone); } |
| -.field input::placeholder { color: #8d93a5; } |
| -.band-ink .field input::placeholder { color: #6e7793; } |
| -.field input:hover { border-color: var(--muted); } |
| -.hint { margin: 0; font-size: 0.88rem; color: var(--muted); } |
| -.hint-url { font-weight: 600; color: var(--ink); } |
| -#signup .form { align-content: start; } |
| -#signup .btn { justify-self: start; } |
| - |
| -/* ── Footer ───────────────────────────────────────────────────────────── */ |
| -.foot { background: var(--ink); color: var(--dim); padding-block: clamp(2.5rem, 5vw, 3.75rem); } |
| -.foot-in { display: grid; grid-template-columns: auto auto minmax(0, 1fr); gap: clamp(2rem, 5vw, 4rem); align-items: start; } |
| -@media (max-width: 46rem) { .foot-in { grid-template-columns: 1fr 1fr; } .colophon { grid-column: 1 / -1; } } |
| -.foot-h { font-size: 0.88rem; font-weight: 600; font-stretch: 100%; color: var(--bone); margin-bottom: 0.8rem; letter-spacing: 0; } |
| -.foot-col li { padding: 0.22rem 0; } |
| -.foot-col a { color: var(--dim); text-decoration: none; font-size: 0.95rem; } |
| -.foot-col a:hover { color: var(--bone); text-decoration: underline; } |
| -.colophon { |
| - margin: 0; font-family: var(--prose); font-size: 0.98rem; line-height: 1.6; |
| - max-width: 52ch; color: var(--dim); |
| -} |
| - |
| -/* ── The one orchestrated moment: the board fills in on arrival ───────── */ |
| -@media (prefers-reduced-motion: no-preference) { |
| - .directory tbody tr { |
| - animation: settle .5s cubic-bezier(.2, .7, .3, 1) backwards; |
| - animation-delay: calc(var(--row) * 70ms + 120ms); |
| - } |
| - @keyframes settle { |
| - from { opacity: 0; transform: translateY(6px); } |
| - to { opacity: 1; transform: none; } |
| - } |
| -} |
| - |
| -/* ── Small screens: the board becomes a stack of entries ──────────────── */ |
| -@media (max-width: 48rem) { |
| - .directory, .directory tbody, .directory tbody tr, .directory tbody th, .directory tbody td { display: block; } |
| - .directory thead, .directory caption { display: none; } |
| - .directory tbody tr { padding-block: 1.1rem; } |
| - .directory tbody th, .directory tbody td { padding-block: 0; } |
| - .c-app { padding-right: 0; } |
| - .c-what { padding: 0.15rem 0 0.7rem; } |
| - .directory tbody td.c-get { |
| - display: flex; justify-content: space-between; align-items: baseline; |
| - text-align: left; padding: 0.28rem 0; border-top: 1px solid rgba(55,67,111,.5); |
| - } |
| - .directory tbody td.c-get::before { content: attr(data-label); color: var(--dim); font-size: 0.88rem; } |
| - .r-new { border-left: 3px solid var(--saffron); padding-left: 0.9rem; } |
| +.two { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, .9fr); gap: 64px; align-items: start; } |
| + |
| +/* ── The family: one card per app, in its own colour ── */ |
| +.family { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin: 0; padding: 0; list-style: none; } |
| +.family li { |
| + padding: 24px 24px 22px; border-radius: 18px; |
| + background: var(--rst-surface); box-shadow: 0 0 0 1px var(--rst-line); |
| + transition: box-shadow .2s ease, transform .2s ease; |
| +} |
| +.family li:hover { box-shadow: 0 0 0 1px color-mix(in oklab, var(--rst-accent) 30%, var(--rst-line)), var(--rst-shadow-lift); } |
| +.family img { filter: drop-shadow(0 5px 9px rgba(20, 24, 33, .16)); } |
| +.family h3 { margin: 16px 0 4px; font: 620 18px/1.2 var(--rst-font); letter-spacing: -0.01em; } |
| +.family h3 em { |
| + margin-left: 6px; padding: 3px 8px; border-radius: 999px; vertical-align: 2px; |
| + background: var(--rst-accent-soft); color: var(--rst-accent); |
| + font: 600 11.5px/1 var(--rst-font); font-style: normal; |
| +} |
| +.family p { margin: 0; color: var(--rst-text-muted); font-size: 15px; } |
| +.family .get { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-top: 16px; } |
| +.get a { font-weight: 600; font-size: 14px; text-decoration: none; } |
| +.get a:hover { text-decoration: underline; } |
| +.get .soon { color: var(--rst-text-faint); font-weight: 500; } |
| +.get .none { color: var(--rst-text-faint); font-size: 14px; } |
| + |
| +/* ── Memoria's answer, and the sign-in card ── */ |
| +.ask { padding: 24px 28px; border-radius: 18px; background: var(--rst-bg); box-shadow: 0 0 0 1px var(--rst-line); } |
| +.band:not(.band-quiet) .ask { background: var(--rst-surface); } |
| +.ask-q { margin: 0 0 10px; font: 620 17px/1.35 var(--rst-font); } |
| +.ask-a { margin: 0 0 14px; color: var(--rst-text-muted); } |
| +.ask-cite { margin: 0; font-size: 14px; } |
| +.ask-cite a { font-weight: 600; text-decoration: none; } |
| +.ask-note { margin: 12px 0 0; color: var(--rst-text-faint); font-size: 14px; } |
| + |
| +/* ── Isolation ── */ |
| +.facts { display: grid; gap: 12px; margin: 0; } |
| +.facts div { padding: 18px 20px; border-radius: 16px; background: var(--rst-surface); box-shadow: 0 0 0 1px var(--rst-line); } |
| +.facts dt { font-weight: 620; } |
| +.facts dd { margin: 4px 0 0; color: var(--rst-text-muted); font-size: 15px; } |
| + |
| +/* ── Native apps ── */ |
| +/* One tile per Mac app: its icon and name, the download beneath. */ |
| +.dl-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(168px, 1fr)); gap: 12px; margin: 0; padding: 0; list-style: none; } |
| +.dl-list li { display: grid; grid-template-columns: auto 1fr; align-items: center; gap: 14px 12px; padding: 18px; border-radius: 18px; background: var(--rst-bg); box-shadow: 0 0 0 1px var(--rst-line); } |
| +.dl-app { font-weight: 620; } |
| +.dl-list .btn { grid-column: 1 / -1; } |
| +.dl-req { margin: 20px 0 0; color: var(--rst-text-faint); font-size: 14px; } |
| +.tf { margin-top: 44px; max-width: 62ch; } |
| +.tf h3 { margin: 0 0 10px; font: 620 20px/1.25 var(--rst-font); letter-spacing: -0.015em; } |
| +.tf p { margin: 0 0 10px; color: var(--rst-text-muted); } |
| + |
| +/* ── Footer ── */ |
| +.foot { padding-block: 56px 64px; border-top: 1px solid var(--rst-line); background: var(--rst-surface); } |
| +.foot-in { display: grid; grid-template-columns: auto auto 1fr; gap: 56px; } |
| +.foot-h { margin: 0 0 12px; color: var(--rst-text-faint); font: 650 13px/1 var(--rst-font); } |
| +.foot ul { display: grid; gap: 7px; margin: 0; padding: 0; list-style: none; } |
| +.foot a { color: var(--rst-text-muted); text-decoration: none; font-size: 14.5px; } |
| +.foot a:hover { color: var(--rst-text); } |
| +.colophon { justify-self: end; max-width: 52ch; margin: 0; color: var(--rst-text-faint); font-size: 13.5px; line-height: 1.6; } |
| + |
| +/* ── Narrower screens ── */ |
| +@media (max-width: 1060px) { |
| + .family { grid-template-columns: repeat(2, 1fr); } |
| + .doc { width: 460px; } |
| +} |
| +@media (max-width: 880px) { |
| + .stage { height: auto; padding: 18px 14px 0; } |
| + .launch { position: static; width: auto; } |
| + .doc { position: static; width: auto; height: 250px; margin-top: 14px; border-radius: 16px 16px 0 0; } |
| + .doc-page { padding: 18px 22px; } |
| + .two { grid-template-columns: 1fr; gap: 32px; } |
| + .foot-in { grid-template-columns: 1fr 1fr; gap: 32px; } |
| + .colophon { grid-column: 1 / -1; justify-self: start; } |
| +} |
| +@media (max-width: 640px) { |
| + .shell { padding-inline: 20px; } |
| + .nav { display: none; } |
| + .hero-in { padding-top: 44px; } |
| + .stage { margin-top: 40px; } |
| + .band { padding-block: 64px; } |
| + .family { grid-template-columns: 1fr; } |
| + .launch-apps li { padding: 10px 4px; } |
| +} |
| +@media (prefers-reduced-motion: reduce) { |
| + .btn, .family li { transition: none; } |
| } |
diff --git a/internal/home/static/site/index.html b/internal/home/static/site/index.html| index 7058f2e..23e596b 100644 |
| --- a/internal/home/static/site/index.html |
| +++ b/internal/home/static/site/index.html |
| @@ -8,105 +8,129 @@ |
| <link rel="icon" type="image/png" sizes="32x32" href="/static/brand/icon-32.png"> |
| <link rel="icon" type="image/svg+xml" href="/static/brand/icon.svg"> |
| <link rel="apple-touch-icon" sizes="180x180" href="/static/brand/apple-touch-icon.png"> |
| +<link rel="stylesheet" href="/static/estilo/fonts/v1/fonts.css"> |
| +<link rel="stylesheet" href="/static/theme.css"> |
| +<link rel="stylesheet" href="/static/estilo/sky.css"> |
| +<link rel="stylesheet" href="/static/site/apps.css"> |
| <link rel="stylesheet" href="/static/site/home.css"> |
| </head> |
| <body> |
| -<a class="skip" href="#directory">Skip to the app directory</a> |
| +<a class="skip" href="#directory">Skip to the apps</a> |
| <header class="bar"> |
| <div class="shell bar-in"> |
| - <a class="mark" href="/"><span>Oficina</span></a> |
| + <a class="mark" href="/"><img src="/static/brand/icon.svg" alt="" width="30" height="30"><span>Oficina</span></a> |
| <nav class="nav" aria-label="Sections of this page"> |
| <a href="#directory">Apps</a> |
| <a href="#memoria">Memoria</a> |
| <a href="#isolation">Privacy</a> |
| - <a href="#native">Native apps</a> |
| + <a href="#native">Mac apps</a> |
| </nav> |
| - <a class="btn btn-bone bar-cta" href="/auth/start?return_to=%2F">Sign in</a> |
| + <a class="btn btn-quiet" href="/auth/start?return_to=%2F">Sign in</a> |
| </div> |
| </header> |
| <main> |
| - <!-- ───────── Hero: the lobby directory ───────── --> |
| - <section class="band band-ink hero"> |
| - <div class="shell"> |
| - <h1 class="hero-h">Open your new<br> office suite.</h1> |
| + <!-- ───────── Hero: every app's colour, together, behind the headline ───────── --> |
| + <section class="hero" estilo-sky-host> |
| + <div estilo-sky aria-hidden="true"> |
| + <img estilo-sky-layer="haze" estilo-sky-tone="light" src="/static/estilo/sky/v1/fffadbd9c1aa2b94/haze-light.svg" alt="" loading="lazy" decoding="async"> |
| + <img estilo-sky-layer="haze" estilo-sky-tone="dark" src="/static/estilo/sky/v1/fffadbd9c1aa2b94/haze-dark.svg" alt="" loading="lazy" decoding="async"> |
| + <img estilo-sky-layer="clouds" estilo-sky-tone="light" src="/static/estilo/sky/v1/fffadbd9c1aa2b94/clouds-light.svg" alt="" loading="lazy" decoding="async"> |
| + <img estilo-sky-layer="clouds" estilo-sky-tone="dark" src="/static/estilo/sky/v1/fffadbd9c1aa2b94/clouds-dark.svg" alt="" loading="lazy" decoding="async"> |
| + <img estilo-sky-layer="filaments" src="/static/estilo/sky/v1/fffadbd9c1aa2b94/filaments.svg" alt="" loading="lazy" decoding="async"> |
| + </div> |
| + <div class="shell hero-in"> |
| + <h1 class="hero-h">Open your new office suite.</h1> |
| <p class="hero-lead">Oficina is fast, modern and light enough to forget it’s running. |
| Six apps that share one account, in the browser or as real native apps — |
| with your team’s work sealed inside your team’s own instance.</p> |
| + <div class="acts"> |
| + <a class="btn" href="https://memoria.oficina.works">Try Memoria</a> |
| + <a class="btn btn-quiet" href="/auth/start?return_to=%2F">Sign in</a> |
| + </div> |
| + </div> |
| - <div class="plate" id="directory"> |
| - <table class="directory"> |
| - <caption>Everything in Oficina, and where you can open it today. Every app runs in the browser, five have a Mac app, and the iPhone apps are close.</caption> |
| - <thead> |
| - <tr> |
| - <th scope="col"><span class="vh">App</span></th> |
| - <th scope="col"><span class="vh">What it does</span></th> |
| - <th scope="col">Web</th> |
| - <th scope="col">Mac</th> |
| - <th scope="col">iPhone</th> |
| - </tr> |
| - </thead> |
| - <tbody> |
| - <tr style="--row:1"> |
| - <th scope="row" class="c-app">Docs</th> |
| - <td class="c-what">Write together, in the same paragraph.</td> |
| - <td class="c-get" data-label="Web"><a href="https://docs.oficina.works">Open</a></td> |
| - <td class="c-get" data-label="Mac"><a href="https://raw.amadan.net/oficina/native-releases/a48d4751df238594c8ee056e53e278124754c3be/docs/releases/net.amadan.oficina.docs-7.zip">Download</a></td> |
| - <td class="c-get" data-label="iPhone"><a class="soon" href="#testflight">Soon</a></td> |
| - </tr> |
| - <tr style="--row:2"> |
| - <th scope="row" class="c-app">Sheets</th> |
| - <td class="c-what">Formulas, ranges and a grid that keeps up.</td> |
| - <td class="c-get" data-label="Web"><a href="https://sheets.oficina.works">Open</a></td> |
| - <td class="c-get" data-label="Mac"><a href="https://raw.amadan.net/oficina/native-releases/86c695b0ea23eace253a3a36133dc04b5ec4f82a/sheets/releases/net.amadan.oficina.sheets-9.zip">Download</a></td> |
| - <td class="c-get" data-label="iPhone"><a class="soon" href="#testflight">Soon</a></td> |
| - </tr> |
| - <tr style="--row:3"> |
| - <th scope="row" class="c-app">Slides</th> |
| - <td class="c-what">Build the deck, then stand up and present it.</td> |
| - <td class="c-get" data-label="Web"><a href="https://slides.oficina.works">Open</a></td> |
| - <td class="c-get c-none" data-label="Mac"><span aria-hidden="true">—</span><span class="vh">No Mac app</span></td> |
| - <td class="c-get c-none" data-label="iPhone"><span aria-hidden="true">—</span><span class="vh">No iPhone app</span></td> |
| - </tr> |
| - <tr style="--row:4"> |
| - <th scope="row" class="c-app">Calendar</th> |
| - <td class="c-what">Everyone’s week, without the reply-all.</td> |
| - <td class="c-get" data-label="Web"><a href="https://calendar.oficina.works">Open</a></td> |
| - <td class="c-get" data-label="Mac"><a href="https://raw.amadan.net/oficina/native-releases/main/calendar/releases/net.amadan.oficina.calendar-2.zip">Download</a></td> |
| - <td class="c-get" data-label="iPhone"><a class="soon" href="#testflight">Soon</a></td> |
| - </tr> |
| - <tr style="--row:5"> |
| - <th scope="row" class="c-app">Calls</th> |
| - <td class="c-what">Meet without installing anything first.</td> |
| - <td class="c-get" data-label="Web"><a href="https://calls.oficina.works">Open</a></td> |
| - <td class="c-get" data-label="Mac"><a href="https://raw.amadan.net/oficina/native-releases/2a78e04fdc6869e3c7edcccc1467e470b27c0df2/calls/releases/net.amadan.oficina.calls-18.zip">Download</a></td> |
| - <td class="c-get c-none" data-label="iPhone"><span aria-hidden="true">—</span><span class="vh">No iPhone app</span></td> |
| - </tr> |
| - <tr class="r-new" style="--row:6"> |
| - <th scope="row" class="c-app">Memoria <em>New</em></th> |
| - <td class="c-what">Ask your own documents a question.</td> |
| - <td class="c-get" data-label="Web"><a href="https://memoria.oficina.works">Open</a></td> |
| - <td class="c-get" data-label="Mac"><a href="https://raw.amadan.net/oficina/native-releases/main/memoria/releases/net.amadan.oficina.memoria-2.zip">Download</a></td> |
| - <td class="c-get" data-label="iPhone"><a class="soon" href="#testflight">Soon</a></td> |
| - </tr> |
| - </tbody> |
| - </table> |
| + <!-- The product is the picture: a launcher and a document, drawn in HTML. --> |
| + <div class="shell"> |
| + <div class="stage" aria-hidden="true"> |
| + <div class="launch"> |
| + <p class="launch-h"><img src="/static/brand/icon.svg" alt="" width="22" height="22"><b>Your team</b><span>6 apps</span></p> |
| + <ul class="launch-apps"> |
| + <li><img src="/static/brand/docs.svg" alt="" width="50" height="50">Docs</li> |
| + <li><img src="/static/brand/sheets.svg" alt="" width="50" height="50">Sheets</li> |
| + <li><img src="/static/brand/slides.svg" alt="" width="50" height="50">Slides</li> |
| + <li><img src="/static/brand/calendar.svg" alt="" width="50" height="50">Calendar</li> |
| + <li><img src="/static/brand/calls.svg" alt="" width="50" height="50">Calls</li> |
| + <li><img src="/static/brand/memoria.svg" alt="" width="50" height="50">Memoria</li> |
| + </ul> |
| + </div> |
| + <div class="doc" data-app="docs"> |
| + <p class="doc-bar"><span class="lights"><i></i><i></i><i></i></span><img src="/static/brand/docs.svg" alt="" width="20" height="20"><b>Docs</b><span>Packing list, Thursday site visit</span></p> |
| + <div class="doc-page"> |
| + <p class="doc-h">Packing list, Thursday site visit</p> |
| + <p>Leaving from the depot at 7:30, back by mid-afternoon. The forecast says overcast with rain after noon, so pack for both.</p> |
| + <p>Bring hard hats (two spares are already in the van), the folding tape measure rather than the laser, high-vis vests, the signed site access letter, spare camera batteries and a <mark>full water bottle each</mark>. There is no tap on site.</p> |
| + </div> |
| + </div> |
| </div> |
| + </div> |
| + </section> |
| - <div class="acts"> |
| - <a class="btn btn-bone" href="https://memoria.oficina.works">Try Memoria</a> |
| - <a class="btn btn-ghost" href="/auth/start?return_to=%2F">Sign in</a> |
| + <!-- ───────── The apps, and where to open each today ───────── --> |
| + <section class="band" id="directory"> |
| + <div class="shell"> |
| + <div class="sec-head"> |
| + <h2>Six apps, one family.</h2> |
| + <p class="prose">Every app runs in the browser, five have a Mac app, and the iPhone apps are close.</p> |
| </div> |
| + <ul class="family"> |
| + <li data-app="docs"> |
| + <img src="/static/brand/docs.svg" alt="" width="56" height="56"> |
| + <h3>Docs</h3> |
| + <p>Write together, in the same paragraph.</p> |
| + <p class="get"><a href="https://docs.oficina.works">Open</a><a href="https://raw.amadan.net/oficina/native-releases/a48d4751df238594c8ee056e53e278124754c3be/docs/releases/net.amadan.oficina.docs-7.zip">Download for Mac</a><a class="soon" href="#testflight">iPhone soon</a></p> |
| + </li> |
| + <li data-app="sheets"> |
| + <img src="/static/brand/sheets.svg" alt="" width="56" height="56"> |
| + <h3>Sheets</h3> |
| + <p>Formulas, ranges and a grid that keeps up.</p> |
| + <p class="get"><a href="https://sheets.oficina.works">Open</a><a href="https://raw.amadan.net/oficina/native-releases/86c695b0ea23eace253a3a36133dc04b5ec4f82a/sheets/releases/net.amadan.oficina.sheets-9.zip">Download for Mac</a><a class="soon" href="#testflight">iPhone soon</a></p> |
| + </li> |
| + <li data-app="slides"> |
| + <img src="/static/brand/slides.svg" alt="" width="56" height="56"> |
| + <h3>Slides</h3> |
| + <p>Build the deck, then stand up and present it.</p> |
| + <p class="get"><a href="https://slides.oficina.works">Open</a><span class="none">Browser only, for now</span></p> |
| + </li> |
| + <li data-app="calendar"> |
| + <img src="/static/brand/calendar.svg" alt="" width="56" height="56"> |
| + <h3>Calendar</h3> |
| + <p>Everyone’s week, without the reply-all.</p> |
| + <p class="get"><a href="https://calendar.oficina.works">Open</a><a href="https://raw.amadan.net/oficina/native-releases/main/calendar/releases/net.amadan.oficina.calendar-2.zip">Download for Mac</a><a class="soon" href="#testflight">iPhone soon</a></p> |
| + </li> |
| + <li data-app="calls"> |
| + <img src="/static/brand/calls.svg" alt="" width="56" height="56"> |
| + <h3>Calls</h3> |
| + <p>Meet without installing anything first.</p> |
| + <p class="get"><a href="https://calls.oficina.works">Open</a><a href="https://raw.amadan.net/oficina/native-releases/2a78e04fdc6869e3c7edcccc1467e470b27c0df2/calls/releases/net.amadan.oficina.calls-18.zip">Download for Mac</a></p> |
| + </li> |
| + <li data-app="memoria"> |
| + <img src="/static/brand/memoria.svg" alt="" width="56" height="56"> |
| + <h3>Memoria <em>New</em></h3> |
| + <p>Ask your own documents a question.</p> |
| + <p class="get"><a href="https://memoria.oficina.works">Open</a><a href="https://raw.amadan.net/oficina/native-releases/main/memoria/releases/net.amadan.oficina.memoria-2.zip">Download for Mac</a><a class="soon" href="#testflight">iPhone soon</a></p> |
| + </li> |
| + </ul> |
| </div> |
| </section> |
| <!-- ───────── Memoria ───────── --> |
| - <section class="band" id="memoria"> |
| - <div class="shell two two-tight"> |
| - <div class="two-a"> |
| + <section class="band band-quiet" id="memoria" data-app="memoria"> |
| + <div class="shell two"> |
| + <div> |
| <h2>Introducing Memoria, your organisation’s shared memory.</h2> |
| <p class="prose">Every team keeps its answers in places nobody can search: an old thread, |
| a deck from March, the contract nobody re-read. Memoria holds your team’s |
| @@ -117,8 +141,7 @@ |
| on your own hardware or ours.</p> |
| <p><a class="lnk" href="https://memoria.oficina.works">Try Memoria with your own documents</a></p> |
| </div> |
| - |
| - <div class="two-b"> |
| + <div> |
| <div class="ask"> |
| <p class="ask-q">What did we promise Vitoria about keeping their data?</p> |
| <p class="ask-a">Ninety days by default, extendable to a year on request. Agreed |
| @@ -131,55 +154,52 @@ |
| </section> |
| <!-- ───────── Isolation ───────── --> |
| - <section class="band band-quiet" id="isolation"> |
| - <div class="shell"> |
| - <h2 class="wide">Every team gets its own building.</h2> |
| - <div class="two"> |
| - <div class="two-a"> |
| - <p class="prose">Most suites put every customer in one enormous database and |
| - separate you with a column. Oficina doesn’t. Your team gets its own |
| - instance, its own store and its own key, so isolation is a property of |
| - the architecture rather than a promise about our query filters.</p> |
| - <p class="prose">It also means you can take it with you. Run Oficina on our |
| - infrastructure, in your own cloud, or on a machine in your office — |
| - the apps are the same either way.</p> |
| - </div> |
| - <dl class="facts two-b"> |
| - <div> |
| - <dt>One instance per team</dt> |
| - <dd>Your data has its own store and its own encryption key. There is no shared table to leak across.</dd> |
| - </div> |
| - <div> |
| - <dt>Nothing leaves to be indexed</dt> |
| - <dd>Search and Memoria build their indexes inside your instance. No document is sent out to be read.</dd> |
| - </div> |
| - <div> |
| - <dt>Sharing stops at your edge</dt> |
| - <dd>Documents are shared by name, inside your team, and stay that way until someone changes it.</dd> |
| - </div> |
| - </dl> |
| + <section class="band" id="isolation"> |
| + <div class="shell two"> |
| + <div> |
| + <h2>Every team gets its own building.</h2> |
| + <p class="prose">Most suites put every customer in one enormous database and |
| + separate you with a column. Oficina doesn’t. Your team gets its own |
| + instance, its own store and its own key, so isolation is a property of |
| + the architecture rather than a promise about our query filters.</p> |
| + <p class="prose">It also means you can take it with you. Run Oficina on our |
| + infrastructure, in your own cloud, or on a machine in your office — |
| + the apps are the same either way.</p> |
| </div> |
| + <dl class="facts"> |
| + <div> |
| + <dt>One instance per team</dt> |
| + <dd>Your data has its own store and its own encryption key. There is no shared table to leak across.</dd> |
| + </div> |
| + <div> |
| + <dt>Nothing leaves to be indexed</dt> |
| + <dd>Search and Memoria build their indexes inside your instance. No document is sent out to be read.</dd> |
| + </div> |
| + <div> |
| + <dt>Sharing stops at your edge</dt> |
| + <dd>Documents are shared by name, inside your team, and stay that way until someone changes it.</dd> |
| + </div> |
| + </dl> |
| </div> |
| </section> |
| <!-- ───────── Native apps ───────── --> |
| - <section class="band band-ink" id="native"> |
| + <section class="band band-quiet" id="native"> |
| <div class="shell"> |
| - <h2 class="wide">Native apps, not a browser in a costume.</h2> |
| - <p class="hero-lead">Five of the six edit the same documents as the web apps, |
| - with native editors rather than an embedded web view. They launch fast, they |
| - keep working when the connection drops, and they sync when it comes back.</p> |
| - |
| - <div class="plate plate-flat"> |
| - <ul class="dl-list"> |
| - <li><span class="dl-app">Docs</span><a class="btn btn-bone btn-sm" href="https://raw.amadan.net/oficina/native-releases/a48d4751df238594c8ee056e53e278124754c3be/docs/releases/net.amadan.oficina.docs-7.zip">Download</a></li> |
| - <li><span class="dl-app">Sheets</span><a class="btn btn-bone btn-sm" href="https://raw.amadan.net/oficina/native-releases/86c695b0ea23eace253a3a36133dc04b5ec4f82a/sheets/releases/net.amadan.oficina.sheets-9.zip">Download</a></li> |
| - <li><span class="dl-app">Calendar</span><a class="btn btn-bone btn-sm" href="https://raw.amadan.net/oficina/native-releases/main/calendar/releases/net.amadan.oficina.calendar-2.zip">Download</a></li> |
| - <li><span class="dl-app">Memoria</span><a class="btn btn-bone btn-sm" href="https://raw.amadan.net/oficina/native-releases/main/memoria/releases/net.amadan.oficina.memoria-2.zip">Download</a></li> |
| - <li><span class="dl-app">Calls</span><a class="btn btn-bone btn-sm" href="https://raw.amadan.net/oficina/native-releases/2a78e04fdc6869e3c7edcccc1467e470b27c0df2/calls/releases/net.amadan.oficina.calls-18.zip">Download</a></li> |
| - </ul> |
| - <p class="dl-req">Apple silicon, signed and notarized. Docs and Sheets need macOS 26; Calendar, Memoria and Calls need macOS 14. Slides runs in the browser and has no Mac app yet.</p> |
| + <div class="sec-head"> |
| + <h2>Native apps, not a browser in a costume.</h2> |
| + <p class="prose">Five of the six edit the same documents as the web apps, |
| + with native editors rather than an embedded web view. They launch fast, they |
| + keep working when the connection drops, and they sync when it comes back.</p> |
| </div> |
| + <ul class="dl-list"> |
| + <li data-app="docs"><img src="/static/brand/docs.svg" alt="" width="36" height="36"><span class="dl-app">Docs</span><a class="btn btn-sm" href="https://raw.amadan.net/oficina/native-releases/a48d4751df238594c8ee056e53e278124754c3be/docs/releases/net.amadan.oficina.docs-7.zip">Download</a></li> |
| + <li data-app="sheets"><img src="/static/brand/sheets.svg" alt="" width="36" height="36"><span class="dl-app">Sheets</span><a class="btn btn-sm" href="https://raw.amadan.net/oficina/native-releases/86c695b0ea23eace253a3a36133dc04b5ec4f82a/sheets/releases/net.amadan.oficina.sheets-9.zip">Download</a></li> |
| + <li data-app="calendar"><img src="/static/brand/calendar.svg" alt="" width="36" height="36"><span class="dl-app">Calendar</span><a class="btn btn-sm" href="https://raw.amadan.net/oficina/native-releases/main/calendar/releases/net.amadan.oficina.calendar-2.zip">Download</a></li> |
| + <li data-app="memoria"><img src="/static/brand/memoria.svg" alt="" width="36" height="36"><span class="dl-app">Memoria</span><a class="btn btn-sm" href="https://raw.amadan.net/oficina/native-releases/main/memoria/releases/net.amadan.oficina.memoria-2.zip">Download</a></li> |
| + <li data-app="calls"><img src="/static/brand/calls.svg" alt="" width="36" height="36"><span class="dl-app">Calls</span><a class="btn btn-sm" href="https://raw.amadan.net/oficina/native-releases/2a78e04fdc6869e3c7edcccc1467e470b27c0df2/calls/releases/net.amadan.oficina.calls-18.zip">Download</a></li> |
| + </ul> |
| + <p class="dl-req">Apple silicon, signed and notarized. Docs and Sheets need macOS 26; Calendar, Memoria and Calls need macOS 14. Slides runs in the browser and has no Mac app yet.</p> |
| <div class="tf" id="testflight"> |
| <h3>Unzip it and move it to Applications.</h3> |
| @@ -195,8 +215,8 @@ |
| <!-- ───────── Where to start ───────── --> |
| <section class="band" id="signup"> |
| - <div class="shell two two-tight"> |
| - <div class="two-a"> |
| + <div class="shell two"> |
| + <div> |
| <h2>Oficina isn’t open for sign-ups yet.</h2> |
| <p class="prose">Creating a team still means one of us provisioning the |
| instance by hand, so there is no form here that would do anything. The |
| @@ -204,7 +224,7 @@ |
| your time first.</p> |
| <p class="prose sm">When sign-ups open, the same page will say so.</p> |
| </div> |
| - <div class="two-b"> |
| + <div> |
| <div class="ask"> |
| <p class="ask-q">Already have an Oficina account?</p> |
| <p class="ask-a">Open any app above and sign in with your work email. |
| @@ -238,9 +258,10 @@ |
| <li><a href="https://amadan.net/oficina/docs/-/releases/v0.1.0">Releases</a></li> |
| </ul> |
| </div> |
| - <p class="colophon">This page is one HTML file, one stylesheet and two typefaces served |
| - from our own machines. It runs no scripts, sets no cookies and calls nothing |
| - third-party — which is why there is no banner asking your permission.</p> |
| + <p class="colophon">This page is one HTML file, a few stylesheets, one typeface and |
| + a sky drawn in SVG, all served from our own machines. It runs no scripts, sets no |
| + cookies and calls nothing third-party — which is why there is no banner |
| + asking your permission.</p> |
| </div> |
| </footer> |
diff --git a/internal/home/static/site/newsreader-ext.woff2 b/internal/home/static/site/newsreader-ext.woff2| deleted file mode 100644 |
| index 622a9f7..0000000 |
| Binary files a/internal/home/static/site/newsreader-ext.woff2 and /dev/null differ |
diff --git a/internal/home/static/site/newsreader.woff2 b/internal/home/static/site/newsreader.woff2| deleted file mode 100644 |
| index 8cd8eff..0000000 |
| Binary files a/internal/home/static/site/newsreader.woff2 and /dev/null differ |
diff --git a/internal/home/templates/directory.html b/internal/home/templates/directory.html| index 7647515..c5854b2 100644 |
| --- a/internal/home/templates/directory.html |
| +++ b/internal/home/templates/directory.html |
| @@ -1,22 +1,22 @@ |
| {{define "title"}}Oficina{{end}} |
| {{define "content"}} |
| -<h1 estilo-brand><img estilo-brand-icon src="/static/brand/icon.svg" alt="" width="28" height="28">Oficina</h1> |
| -<p>You are signed in.</p> |
| +<header home-hub-head> |
| +<h1 estilo-brand><img estilo-brand-icon src="/static/brand/icon.svg" alt="" width="40" height="40">Oficina</h1> |
| +<form method="post" action="/signout"><button type="submit" rst-btn>Sign out</button></form> |
| +</header> |
| +<p home-hub-lede>You are signed in. Every app below opens with this one account.</p> |
| <section aria-labelledby="apps-heading"> |
| -<h2 id="apps-heading">Apps</h2> |
| -<div rst-list> |
| +<h2 id="apps-heading" home-hub-h>Apps</h2> |
| +<ul home-apps> |
| {{range .Apps}} |
| -<div rst-row> |
| -{{if .Icon}}<img estilo-brand-icon="listing" src="{{.Icon}}" alt="" width="40" height="40">{{end}} |
| -<div rst-row-main> |
| -{{if .Href}}<a href="{{.Href}}">{{.Name}}</a>{{else}}<strong>{{.Name}}</strong>{{end}} |
| -<span rst-row-sub>{{.Status}}</span> |
| -</div> |
| -</div> |
| +<li>{{if .Href}}<a home-app href="{{.Href}}">{{else}}<div home-app>{{end}} |
| +{{if .Icon}}<img src="{{.Icon}}" alt="" width="56" height="56">{{end}} |
| +<b>{{.Name}}</b> |
| +<small>{{.Status}}</small> |
| +{{if .Href}}</a>{{else}}</div>{{end}}</li> |
| {{end}} |
| -</div> |
| +</ul> |
| </section> |
| -<form method="post" action="/signout" rst-form><button type="submit" rst-btn>Sign out</button></form> |
| {{end}} |
| {{define "help"}}{{template "home/help" .}}{{end}} |