: null}
@@ -322,47 +343,22 @@ export function PhoneTabs({ screen }: { screen: ScreenId }) {
);
}
-const MORE: Array<{ id: ScreenId | WindowId; title: string; hint: string; icon: IconName; dash?: boolean }> = [
- { id: "commune-new", title: "Create a commune", hint: "A home, a studio, or a trip", icon: "plus", dash: true },
- { id: "communes", title: "Places", hint: "Switch house, studio, or trip", icon: "hub" },
- { id: "search", title: "Search", hint: "Find expenses, notes and jobs across your communes", icon: "search" },
- { id: "notes", title: "Notes", hint: "Shared details to remember", icon: "notes" },
- { id: "funds", title: "Funds", hint: "Contributions and commitments", icon: "funds" },
- { id: "recurring", title: "Recurring", hint: "Regular bills, recorded after payment", icon: "refresh" },
- { id: "templates", title: "Saved splits", hint: "Participants, percentages and share weights", icon: "templates" },
- { id: "operations", title: "Operations", hint: "What’s late, what’s yours, then the week", icon: "operations" },
- { id: "members", title: "Members", hint: "Who’s here this cycle", icon: "members" },
- { id: "settings", title: "You & settings", hint: "Profile and preferences", icon: "settings" },
-];
-
export function MoreSheet() {
- const { setMoreOpen, go } = useApp();
+ const { moreOpen, setMoreOpen, screen } = useApp();
+ useEffect(() => {
+ if (!moreOpen) return;
+ const onKey = (event: KeyboardEvent) => {
+ // An open commune list inside the drawer closes first.
+ if (event.key === "Escape" && !document.querySelector(".a-drawer .a-hub-menu")) setMoreOpen(false);
+ };
+ document.addEventListener("keydown", onKey);
+ return () => document.removeEventListener("keydown", onKey);
+ }, [moreOpen, setMoreOpen]);
+ if (!moreOpen) return null;
return (
<>
setMoreOpen(false)} />
-
-
-
-
More
-
Notes, funds, and the rest of your commune. The four tabs stay the spine.
-
-
-
- {MORE.map((row) => (
-
- ))}
-
+
>
);
}
diff --git a/apps/web/src/styles/app.css b/apps/web/src/styles/app.css
index d02d8a3c..429d8a89 100644
--- a/apps/web/src/styles/app.css
+++ b/apps/web/src/styles/app.css
@@ -185,19 +185,6 @@
min-height: 0;
}
-.a-header-mark {
- display: none;
- width: 38px;
- height: 38px;
- padding: 10px;
- box-sizing: border-box;
- border-radius: 999px;
- border: 1px solid var(--border);
- background: #fff;
- flex-shrink: 0;
- object-fit: contain;
-}
-
.a-wordmark {
font-family: "DM Sans Variable", "DM Sans", sans-serif;
font-size: 34px;
@@ -1219,116 +1206,6 @@ div.a-set-row {
display: block;
}
-.a-more-head {
- display: flex;
- align-items: flex-start;
- gap: 12px;
- padding: 4px 18px 12px;
-}
-
-.a-more-kicker {
- margin: 0;
- font-size: 18px;
- font-weight: 750;
- letter-spacing: -0.02em;
-}
-
-.a-more-lede {
- margin: 4px 0 0;
- font-size: 13px;
- color: var(--ink-soft);
- line-height: 1.4;
-}
-
-.a-more-x {
- width: 32px;
- height: 32px;
- border: 0;
- border-radius: 999px;
- background: #1a1e2b;
- color: #fff;
- display: grid;
- place-items: center;
- cursor: pointer;
- flex-shrink: 0;
-}
-
-.a-more-x svg {
- width: 14px;
- height: 14px;
-}
-
-.a-more > button {
- display: flex;
- align-items: center;
- gap: 12px;
- width: 100%;
- margin: 0;
- padding: 12px 18px;
- border: 0;
- border-bottom: 1px solid rgba(23, 27, 36, 0.06);
- background: transparent;
- font: inherit;
- text-align: left;
- cursor: pointer;
- color: inherit;
-}
-
-.a-more > button:last-child {
- border-bottom: 0;
-}
-
-.a-more > button:hover {
- background: #f8f5f0;
-}
-
-.a-more-ic {
- width: 44px;
- height: 44px;
- border-radius: 12px;
- background: #e8f0eb;
- color: var(--sage);
- display: grid;
- place-items: center;
- flex-shrink: 0;
-}
-
-.a-more-ic-dash {
- background: transparent;
- border: 1px dashed rgba(26, 30, 43, 0.28);
- color: var(--ink-soft);
-}
-
-.a-more-ic svg {
- width: 18px;
- height: 18px;
-}
-
-.a-more-copy {
- flex: 1;
- min-width: 0;
-}
-
-.a-more-copy strong {
- display: block;
- font-size: 15px;
- font-weight: 650;
-}
-
-.a-more-copy span {
- display: block;
- font-size: 13px;
- color: var(--ink-soft);
- margin-top: 2px;
-}
-
-.a-more-chev {
- width: 16px;
- height: 16px;
- color: var(--ink-faint, #b8b3aa);
- flex-shrink: 0;
-}
-
.a-you-btn .avatar {
width: 100%;
height: 100%;
@@ -2978,7 +2855,7 @@ button.a-now-row {
align-items: stretch;
}
-#home .a-week {
+:is(#home, #home-inbox) .a-week {
margin-top: 0;
}
@@ -3666,14 +3543,10 @@ button.a-item {
letter-spacing: -0.02em;
overflow-wrap: anywhere;
}
-
-.a-bottom,
-.a-more {
+.a-bottom {
display: none;
}
-
.a-body.is-gate .a-bottom,
-.a-body.is-gate .a-more,
.a-body.is-gate .a-sheet-scrim,
.a-body.is-gate .a-windows {
display: none !important;
@@ -3723,7 +3596,9 @@ button.a-item {
display: none;
}
.a-header {
- padding: 12px 16px;
+ min-height: 56px;
+ gap: 8px;
+ padding: 8px 12px;
background: #1a1e2b;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
color: #f5f1ea;
@@ -3736,15 +3611,6 @@ button.a-item {
white-space: normal;
overflow: visible;
}
- .a-header-mark {
- display: block;
- width: 38px;
- height: 38px;
- padding: 10px;
- border-radius: 999px;
- border: 1px solid rgba(255, 255, 255, 0.12);
- background: rgba(255, 255, 255, 0.06);
- }
.a-header .a-icon-btn {
background: rgba(255, 255, 255, 0.06);
border-color: rgba(255, 255, 255, 0.12);
@@ -3865,129 +3731,6 @@ button.a-item {
grid-template-columns: 1fr;
gap: 16px;
}
- .a-body.more-open .a-more {
- display: flex;
- flex-direction: column;
- position: fixed;
- left: 0;
- right: 0;
- bottom: 0;
- top: auto;
- z-index: 41;
- width: 100%;
- max-height: min(86vh, 640px);
- overflow: auto;
- border: 0;
- border-radius: var(--radius-surface) var(--radius-surface) 0 0;
- padding: 10px 0 calc(18px + env(safe-area-inset-bottom, 0px));
- box-shadow: 0 -18px 50px rgba(26, 30, 43, 0.28);
- background: #fff;
- }
- .a-more::before {
- content: "";
- display: block;
- width: 36px;
- height: 4px;
- border-radius: 99px;
- background: var(--border);
- margin: 4px auto 6px;
- flex-shrink: 0;
- }
- .a-more-head {
- display: flex;
- align-items: flex-start;
- gap: 12px;
- padding: 4px 18px 12px;
- }
- .a-more-kicker {
- margin: 0;
- font-size: 18px;
- font-weight: 750;
- letter-spacing: -0.02em;
- }
- .a-more-lede {
- margin: 4px 0 0;
- font-size: 13px;
- color: var(--ink-soft);
- line-height: 1.4;
- }
- .a-more-x {
- width: 32px;
- height: 32px;
- border: 0;
- border-radius: 999px;
- background: #1a1e2b;
- color: #fff;
- display: grid;
- place-items: center;
- cursor: pointer;
- flex-shrink: 0;
- }
- .a-more-x svg {
- width: 14px;
- height: 14px;
- }
- .a-more > button {
- display: flex;
- align-items: center;
- gap: 12px;
- width: 100%;
- margin: 0;
- padding: 12px 18px;
- border: 0;
- border-bottom: 1px solid rgba(23, 27, 36, 0.06);
- background: transparent;
- font: inherit;
- text-align: left;
- cursor: pointer;
- color: inherit;
- }
- .a-more > button:last-child {
- border-bottom: 0;
- }
- .a-more > button:hover {
- background: #f8f5f0;
- }
- .a-more-ic {
- width: 44px;
- height: 44px;
- border-radius: 12px;
- background: #e8f0eb;
- color: var(--sage);
- display: grid;
- place-items: center;
- flex-shrink: 0;
- }
- .a-more-ic-dash {
- background: transparent;
- border: 1px dashed rgba(26, 30, 43, 0.28);
- color: var(--ink-soft);
- }
- .a-more-ic svg {
- width: 18px;
- height: 18px;
- }
- .a-more-copy {
- flex: 1;
- min-width: 0;
- }
- .a-more-copy strong {
- display: block;
- font-size: 15px;
- font-weight: 650;
- }
- .a-more-copy span {
- display: block;
- font-size: 13px;
- color: var(--ink-soft);
- margin-top: 2px;
- }
- .a-more-chev {
- width: 16px;
- height: 16px;
- color: var(--ink-faint, #b8b3aa);
- flex-shrink: 0;
- }
}
.a-body.is-native {
@@ -4024,16 +3767,6 @@ button.a-item {
overflow: visible;
}
-.a-body.is-native .a-header-mark {
- display: block;
- width: 38px;
- height: 38px;
- padding: 10px;
- border-radius: 999px;
- border: 1px solid rgba(255, 255, 255, 0.12);
- background: rgba(255, 255, 255, 0.06);
-}
-
.a-body.is-native .a-header .a-icon-btn {
background: rgba(255, 255, 255, 0.06);
border-color: rgba(255, 255, 255, 0.12);
@@ -4064,40 +3797,7 @@ button.a-item {
right: auto;
transform: translateX(-50%);
}
-
-.a-body.is-native.more-open .a-more {
- display: flex;
- flex-direction: column;
- position: fixed;
- left: 50%;
- right: auto;
- bottom: 0;
- top: auto;
- z-index: 41;
- width: min(390px, 100%);
- max-height: min(86vh, 640px);
- overflow: auto;
- border: 0;
- border-radius: var(--radius-surface) var(--radius-surface) 0 0;
- padding: 10px 0 28px;
- transform: translateX(-50%);
- box-shadow: 0 -18px 50px rgba(26, 30, 43, 0.28);
- background: #fff;
-}
-
-.a-body.is-native.more-open .a-more::before {
- content: "";
- display: block;
- width: 36px;
- height: 4px;
- border-radius: 99px;
- background: var(--border);
- margin: 4px auto 6px;
- flex-shrink: 0;
-}
-
-.a-body.is-native.is-gate .a-sheet-scrim,
-.a-body.is-native.is-gate .a-more {
+.a-body.is-native.is-gate .a-sheet-scrim {
display: none !important;
}
diff --git a/apps/web/src/styles/design-system.css b/apps/web/src/styles/design-system.css
index ae6f4164..7b9c2b25 100644
--- a/apps/web/src/styles/design-system.css
+++ b/apps/web/src/styles/design-system.css
@@ -55,7 +55,23 @@
letter-spacing: -0.5px;
}
@media (max-width: 560px) {
- .a-header-home { flex-wrap: wrap; }
+ /* Phone top bar: one slim row of menu, title and icon actions. The page
+ description stays on larger screens; Home keeps its greeting below. */
+ .a-header .a-icon-btn,
+ .a-header .a-more-btn { width: 40px; height: 40px; }
+ .a-header .c-search-trigger {
+ width: 40px; min-height: 40px; height: 40px; padding: 0; justify-content: center; align-items: center;
+ background: rgba(255, 255, 255, 0.06); border-color: rgba(255, 255, 255, 0.12); color: #f5f1ea;
+ }
+ .a-header-actions { margin-left: 0; }
+ .a-header:not(.a-header-home) .a-header-title h1 { font-size: 18px; }
+ .a-header:not(.a-header-home) .a-header-title p { display: none; }
+ .a-header-home { flex-wrap: wrap; row-gap: 10px; padding-bottom: 14px; }
+ .a-body.is-native .a-header-home .a-header-actions { margin-left: auto; }
+ .notes-mobile-search.c-record-filter { width: 100%; }
+ /* iOS Safari zooms the whole page into any field under 16px when it gets
+ focus, and stays zoomed. Every text field on a phone is 16px. */
+ :is(input:not([type="checkbox"], [type="radio"], [type="range"], [type="file"]), select, textarea) { font-size: 16px !important; }
.a-header-home .a-header-title { order: 1; flex-basis: 100%; }
.a-header-home .a-header-title h1 { white-space: normal; overflow-wrap: anywhere; }
.a-header-home .c-search-trigger { margin-left: auto; }
@@ -420,3 +436,38 @@
@media (max-width: 560px) {
.c-split-hint { min-height: 4.8em; }
}
+
+/* The phone menu is the desktop sidebar, opened as a drawer from the left. */
+.a-side-head { display: flex; align-items: flex-start; }
+.a-side-head .a-brand { flex: 1; min-width: 0; }
+.a-drawer-x {
+ display: grid; place-items: center; flex-shrink: 0; width: 40px; height: 40px; margin-top: -2px;
+ border: 1px solid rgba(255, 255, 255, 0.12); border-radius: var(--radius-control);
+ background: rgba(255, 255, 255, 0.06); color: #f5f1ea; cursor: pointer;
+}
+.a-body .a-side.a-drawer {
+ display: flex;
+ position: fixed;
+ inset: 0 auto 0 0;
+ z-index: 41;
+ width: min(300px, 84vw);
+ height: 100dvh;
+ overflow-y: auto;
+ overscroll-behavior: contain;
+ padding: calc(18px + env(safe-area-inset-top, 0px)) 14px calc(16px + env(safe-area-inset-bottom, 0px));
+ box-shadow: 18px 0 50px rgba(26, 30, 43, 0.28);
+ animation: a-drawer-in 180ms ease-out;
+}
+.a-drawer .a-brand { padding-bottom: 24px; }
+.a-drawer .a-wordmark { font-size: 28px; }
+@keyframes a-drawer-in { from { transform: translateX(-100%); } to { transform: none; } }
+@media (prefers-reduced-motion: reduce) { .a-body .a-side.a-drawer { animation: none; } }
+
+/* List toolbars on narrow screens: status chips get their own scrollable
+ row, then the filter and the main action share the next one. */
+@media (max-width: 850px) {
+ .a-toolbar:has(> .c-record-filter) { flex-wrap: wrap; row-gap: 10px; }
+ .a-toolbar:has(> .c-record-filter) > .a-filters { flex: 1 0 100%; overflow-x: auto; scrollbar-width: none; }
+ .a-toolbar > .c-record-filter { flex: 1 1 0; min-width: 0; }
+}
+.c-expense-amount strong { white-space: nowrap; }
diff --git a/apps/web/src/styles/overrides.css b/apps/web/src/styles/overrides.css
index a9b8353d..c1ad0cb0 100644
--- a/apps/web/src/styles/overrides.css
+++ b/apps/web/src/styles/overrides.css
@@ -58,7 +58,6 @@ body,
.a-window-frame,
.a-window-bar,
.a-note-fact,
- .a-more,
.a-owe-card.a-card
) {
background: #242b39;
@@ -72,7 +71,7 @@ body,
border-color: var(--border-strong);
}
-.a-body.is-dark :is(.a-table tbody tr:hover td, .a-more > button:hover) {
+.a-body.is-dark .a-table tbody tr:hover td {
background: rgba(217, 235, 229, 0.07);
}
@@ -160,22 +159,26 @@ body,
/* Home keeps one featured commune with smaller places beneath it. */
.a-home-featured { margin-bottom: 16px; }
.a-home-places { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; margin-bottom: 22px; }
-#home .a-home-greet h1 { font-size: 24px; font-weight: 550; }
-#home .a-home-greet p { font-size: 13px; color: var(--ink-soft); }
-#home .a-place { box-shadow: none; border-radius: var(--radius-surface); }
-#home .a-place-hero { min-height: 208px; padding: 18px; }
-#home .a-place-hero h3 { font-size: 21px; font-weight: 500; letter-spacing: -.6px; }
-#home .a-week { grid-template-columns: repeat(2,minmax(0,1fr)); gap: 22px; }
-#home .a-card { background: var(--white); box-shadow: none; border: 1px solid var(--border); border-radius: var(--radius-surface); padding: 23px; min-width: 0; }
-#home .a-card h2 { font-size: 18px; font-weight: 550; }
-#home .a-card strong:not(.c-calendar-number) { font-weight: 500; }
-#home .hub-feed-row { grid-template-columns: minmax(0,1fr) auto; }
-#home .hub-feed-row > .pill { grid-column: 1 / -1; justify-self: start; }
-#home .hub-feed-row:has(> .c-calendar-date) { grid-template-columns: auto minmax(0,1fr) auto; }
+:is(#home, #home-inbox) .a-home-greet h1 { font-size: 24px; font-weight: 550; }
+:is(#home, #home-inbox) .a-home-greet p { font-size: 13px; color: var(--ink-soft); }
+:is(#home, #home-inbox) .a-place { box-shadow: none; border-radius: var(--radius-surface); }
+:is(#home, #home-inbox) .a-place-hero { min-height: 208px; padding: 18px; }
+:is(#home, #home-inbox) .a-place-hero h3 { font-size: 21px; font-weight: 500; letter-spacing: -.6px; }
+:is(#home, #home-inbox) .a-week { grid-template-columns: repeat(2,minmax(0,1fr)); gap: 22px; }
+:is(#home, #home-inbox) .a-card { background: var(--white); box-shadow: none; border: 1px solid var(--border); border-radius: var(--radius-surface); padding: 23px; min-width: 0; }
+:is(#home, #home-inbox) .a-card h2 { font-size: 18px; font-weight: 550; }
+:is(#home, #home-inbox) .a-card strong:not(.c-calendar-number) { font-weight: 500; }
+:is(#home, #home-inbox) .hub-feed-row { grid-template-columns: minmax(0,1fr) auto; }
+:is(#home, #home-inbox) .hub-feed-row > .pill { grid-column: 1 / -1; justify-self: start; }
+:is(#home, #home-inbox) .hub-feed-row:has(> .c-calendar-date) { grid-template-columns: auto minmax(0,1fr) auto; }
.a-search:focus-within { outline: 2px solid var(--sage); outline-offset: 2px; }
@media (max-width: 850px) {
.a-home-places { grid-template-columns: repeat(2, minmax(0, 1fr)); }
- #home .a-week { grid-template-columns: 1fr; }
+ :is(#home, #home-inbox) .a-week { grid-template-columns: 1fr; }
+}
+/* A full-width search row only where the trigger still shows its label;
+ narrower screens use the icon button from QuickSearch.css. */
+@media (min-width: 701px) and (max-width: 850px) {
.a-body:not(.is-native) .a-header { flex-wrap: wrap; height: auto; min-height: 60px; }
.a-body:not(.is-native) .a-search { display: flex; order: 3; flex: 1 0 100%; width: 100%; max-width: none; margin-top: 8px; background: var(--white); color: var(--ink-soft); }
}
diff --git a/apps/web/tests/design-tokens.test.ts b/apps/web/tests/design-tokens.test.ts
index 8b388485..748aab60 100644
--- a/apps/web/tests/design-tokens.test.ts
+++ b/apps/web/tests/design-tokens.test.ts
@@ -87,13 +87,10 @@ describe("shared corner contracts", () => {
expect(declarations("app", ".a-icon-btn.a-you-btn")).toContain("border-radius: var(--radius-pill);");
});
- it("uses the surface token for the top corners of narrow dialog and menu sheets", () => {
+ it("uses the surface token for the top corners of narrow dialog sheets", () => {
expect(declarations("app", ".a-body.is-native .a-window-frame")).toContain(
"border-radius: var(--radius-surface) var(--radius-surface) 0 0;",
);
- expect(declarations("app", ".a-body.is-native.more-open .a-more")).toContain(
- "border-radius: var(--radius-surface) var(--radius-surface) 0 0;",
- );
expect(styles.app.includes("border-radius: 20px 20px 0 0;")).toBe(false);
});
});