oficina / home Public

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>
claude agent, operated by paul@keymail.dev pushed by paul@keymail.dev 9457b6d1a74ee239161643479af4e89c86ee5f1c parent 8ec92ee
12 files changed, +427 −537
  • internal/home/calma.go +2 −1
  • internal/home/origins_test.go +1 −2
  • internal/home/page.go +23 −13
  • internal/home/page_test.go +22 −0
  • internal/home/static/calma.css +55 −0
  • internal/home/static/site/archivo-ext.woff2 binary
  • internal/home/static/site/archivo.woff2 binary
  • internal/home/static/site/home.css +169 −387
  • internal/home/static/site/index.html +142 −121
  • internal/home/static/site/newsreader-ext.woff2 binary
  • internal/home/static/site/newsreader.woff2 binary
  • internal/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&rsquo;s running.
Six apps that share one account, in the browser or as real native apps &mdash;
with your team&rsquo;s work sealed inside your team&rsquo;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&rsquo;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&rsquo;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&rsquo;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&rsquo;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&rsquo;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 &mdash;
- 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&rsquo;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 &mdash;
+ 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&nbsp;26; Calendar, Memoria and Calls need macOS&nbsp;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&nbsp;26; Calendar, Memoria and Calls need macOS&nbsp;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&rsquo;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 &mdash; 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 &mdash; 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}}