diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 00000000..ab03508b Binary files /dev/null and b/screenshot.png differ diff --git a/src/app.css b/src/app.css index 1adb888c..0e95b78e 100644 --- a/src/app.css +++ b/src/app.css @@ -2,10 +2,15 @@ @config "../tailwind.config.js"; +@theme { + --font-sans: "Lato", ui-sans-serif, system-ui, sans-serif; + --font-display: "Baloo 2", "Lato", ui-rounded, system-ui, sans-serif; +} + /* root */ :root { - font-family: Lato; + font-family: var(--font-sans); --sait: var(--safe-area-inset-top, env(safe-area-inset-top)); --saib: var(--safe-area-inset-bottom, env(safe-area-inset-bottom)); --sail: var(--safe-area-inset-left, env(safe-area-inset-left)); @@ -153,15 +158,15 @@ } @utility heading { - @apply text-center text-2xl; + @apply font-display text-center text-2xl font-bold tracking-tight; } -@utility subheading { - @apply text-center text-xl; +@utility brand { + @apply font-display text-primary font-bold tracking-tight; } -@utility superheading { - @apply text-center text-4xl; +@utility label { + @apply font-display text-sm font-semibold tracking-wider uppercase opacity-70; } @utility link { @@ -215,8 +220,19 @@ @font-face { font-family: "Lato"; - font-style: bold; - font-weight: 600; + font-style: normal; + font-weight: 300; + src: + local(""), + url("/fonts/Lato-Light.ttf") format("truetype"); + } + + /* Lato ships Regular + Bold only; map 600 (semibold) and 700 (bold) to the + Bold file so the browser never synthesizes a faux-bold. */ + @font-face { + font-family: "Lato"; + font-style: normal; + font-weight: 600 700; src: local(""), url("/fonts/Lato-Bold.ttf") format("truetype"); @@ -228,13 +244,38 @@ font-weight: 400; src: local(""), - url("/fonts/Italic.ttf") format("truetype"); + url("/fonts/Lato-Italic.ttf") format("truetype"); + } + + /* Baloo 2 — rounded, friendly display face (self-hosted, Latin subset). */ + @font-face { + font-family: "Baloo 2"; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url("/fonts/Baloo2-Medium.woff2") format("woff2"); + } + + @font-face { + font-family: "Baloo 2"; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url("/fonts/Baloo2-SemiBold.woff2") format("woff2"); + } + + @font-face { + font-family: "Baloo 2"; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url("/fonts/Baloo2-Bold.woff2") format("woff2"); } /* root */ :root { - font-family: Lato; + font-family: var(--font-sans); text-size-adjust: 100%; --sait: var(--safe-area-inset-top, env(safe-area-inset-top)); --saib: var(--safe-area-inset-bottom, env(safe-area-inset-bottom)); @@ -284,7 +325,7 @@ opacity: 0.5; } -/* tiptap */ +/* editors */ .input-editor, .chat-editor, @@ -323,7 +364,11 @@ } .chat-editor .tiptap { - @apply rounded-box bg-base-300 pr-12; + @apply bg-base-300 rounded-[1.5rem] pr-12 transition-shadow; +} + +.chat-editor:focus-within .tiptap { + box-shadow: 0 0 0 2px color-mix(in oklab, var(--color-primary), transparent 55%); } .note-editor .tiptap { @@ -448,3 +493,149 @@ body.keyboard-open .chat__compose { .chat__scroll-down { @apply pb-sai z-feature fixed right-4 bottom-28 md:bottom-16; } + +/* shape, depth & motion */ + +/* Accessibility: neutralize all motion when the user asks for it. Decorative + motion is otherwise opt-in via `motion-safe:` and the guards below. */ +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} + +/* Soft, diffuse elevation — replaces ad-hoc hard `shadow-md` uses. */ +@utility shadow-soft { + box-shadow: + 0 4px 16px -4px oklch(0% 0 0 / 0.18), + 0 1px 3px oklch(0% 0 0 / 0.08); +} + +/* Organic "hand-drawn" avatar masks. The image (or gradient fallback) fills + the blob; three variants are chosen deterministically by pubkey hash so a + person's shape stays stable across the app. */ +@utility avatar-blob { + border-radius: 42% 58% 54% 46% / 58% 46% 54% 42%; +} +@utility avatar-blob-2 { + border-radius: 60% 40% 46% 54% / 43% 57% 43% 57%; +} +@utility avatar-blob-3 { + border-radius: 47% 53% 62% 38% / 50% 62% 38% 50%; +} + +/* Friendly rounded-square for space / relay / room tiles. */ +@utility squircle { + border-radius: 30%; +} + +/* Every DaisyUI button speaks in the rounded display voice and presses in. */ +.btn { + font-family: var(--font-display); + font-weight: 600; + letter-spacing: -0.01em; +} +@media (prefers-reduced-motion: no-preference) { + .btn { + transition: + transform 150ms ease, + box-shadow 150ms ease, + background-color 150ms ease, + border-color 150ms ease; + } + .btn:active { + transform: scale(0.96); + } +} + +/* ---- Motion vocabulary ---- */ +@keyframes nav-button-pop { + 0% { + transform: scale(0.9); + opacity: 0; + } + 100% { + transform: scale(1); + opacity: 1; + } +} +@keyframes button-pop { + 0% { + transform: scale(0.97); + } + 40% { + transform: scale(1.02); + } + 100% { + transform: scale(1); + } +} +@keyframes pop { + 0% { + transform: scale(0); + } + 70% { + transform: scale(1.1); + } + 100% { + transform: scale(1); + } +} +@keyframes reaction-pop { + 0% { + transform: scale(0.6); + } + 60% { + transform: scale(1.15); + } + 100% { + transform: scale(1); + } +} +@keyframes float { + 0%, + 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-6px); + } +} +@keyframes wiggle { + 0%, + 100% { + transform: rotate(0deg); + } + 25% { + transform: rotate(-4deg); + } + 75% { + transform: rotate(4deg); + } +} +@keyframes shimmer { + 0% { + background-position: -200% 0; + } + 100% { + background-position: 200% 0; + } +} + +@utility animate-pop { + animation: pop 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); +} +@utility animate-reaction-pop { + animation: reaction-pop 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); +} +@utility animate-float { + animation: float 6s ease-in-out infinite; +} +@utility animate-wiggle { + animation: wiggle 0.4s ease-in-out; +} diff --git a/src/app/components/ChatCompose.svelte b/src/app/components/ChatCompose.svelte index a38a2336..09cda3f5 100644 --- a/src/app/components/ChatCompose.svelte +++ b/src/app/components/ChatCompose.svelte @@ -127,7 +127,7 @@ {/each} {#each groupedReactions.entries() as [key, events]} @@ -179,17 +179,17 @@ data-tip={tooltip} class={cx( reactionClass, - "flex-inline btn btn-outline btn-neutral btn-xs gap-1 rounded-full font-normal bg-alt", + "flex-inline btn btn-xs gap-1 rounded-full border font-normal transition-transform motion-safe:hover:scale-110 motion-safe:active:scale-95", { tooltip: !noTooltip && !isMobile, - "border-neutral-content/20": !isOwn, - "btn-primary": isOwn, + "bg-alt border-base-content/15": !isOwn, + "border-primary/50 bg-primary/15 text-primary": isOwn, }, )} onclick={stopPropagation(preventDefault(onClick))}> {#if events.length > 1} - {events.length} + {events.length} {/if} {/each} diff --git a/src/app/components/RecentConversation.svelte b/src/app/components/RecentConversation.svelte index 4ae6a510..f33f93ae 100644 --- a/src/app/components/RecentConversation.svelte +++ b/src/app/components/RecentConversation.svelte @@ -10,6 +10,7 @@ import NoteContentMinimal from "@app/components/NoteContentMinimal.svelte" import ProfileCircle from "@app/components/ProfileCircle.svelte" import RoomNameWithImage from "@app/components/RoomNameWithImage.svelte" + import {getColor} from "@app/theme" import {makeRoomPath, makeSpaceChatPath} from "@app/routes" type Props = { @@ -25,7 +26,9 @@ const onClick = () => goto(h ? makeRoomPath(url, h) : makeSpaceChatPath(url)) - diff --git a/src/lib/components/Divider.svelte b/src/lib/components/Divider.svelte index 51b89ca2..6a92117d 100644 --- a/src/lib/components/Divider.svelte +++ b/src/lib/components/Divider.svelte @@ -8,10 +8,18 @@ const {children, ...props}: Props = $props() -
-
+
{#if children} -

{@render children?.()}

-
+
+
+

+ {@render children?.()} +

+
+
+ {:else} +
+
{/if}
diff --git a/src/lib/components/FAB.svelte b/src/lib/components/FAB.svelte index abcc35d3..1b2c5cdf 100644 --- a/src/lib/components/FAB.svelte +++ b/src/lib/components/FAB.svelte @@ -16,7 +16,7 @@
{:else} {@render children?.()} {#if !active && notification} -
+
+
{/if}
{/if} diff --git a/src/lib/components/SecondaryNav.svelte b/src/lib/components/SecondaryNav.svelte index 66405ca5..059088cb 100644 --- a/src/lib/components/SecondaryNav.svelte +++ b/src/lib/components/SecondaryNav.svelte @@ -13,7 +13,7 @@
diff --git a/src/lib/components/SecondaryNavHeader.svelte b/src/lib/components/SecondaryNavHeader.svelte index d15f77ff..0fdcb2dd 100644 --- a/src/lib/components/SecondaryNavHeader.svelte +++ b/src/lib/components/SecondaryNavHeader.svelte @@ -6,6 +6,6 @@ const {children}: Props = $props() -
+
{@render children?.()}
diff --git a/src/lib/components/SecondaryNavItem.svelte b/src/lib/components/SecondaryNavItem.svelte index 7dc02c66..880daf4d 100644 --- a/src/lib/components/SecondaryNavItem.svelte +++ b/src/lib/components/SecondaryNavItem.svelte @@ -36,11 +36,15 @@ const active = $derived($page.url.pathname === href) const wrapperClass = $derived( - cx(restProps.class, "relative flex shrink-0 items-center gap-3 text-left transition-all", { - "hover:bg-base-100 hover:text-base-content": true, - "text-base-content bg-base-100": active, - "tooltip tooltip-right": title, - }), + cx( + restProps.class, + "group relative flex shrink-0 items-center gap-3 rounded-xl text-left transition-all", + { + "hover:bg-base-100": true, + "bg-primary/15 text-primary font-semibold": active, + "tooltip tooltip-right": title, + }, + ), ) @@ -51,16 +55,19 @@ data-tip={title} data-sveltekit-replacestate={replaceState} class={wrapperClass}> + {#if active} +
+ {/if} {@render children?.()} {#if notification} -
+
{/if} {:else}
{/each} {:else if !term} -

You haven't joined any spaces yet.

+

+ You haven't joined any spaces yet — pick one below to dive in! +

{/if} {filteredUserUrls.length > 0 ? "More Spaces" : "Browse Spaces"} {#each otherSpaces.slice(0, limit) as relay (relay.url)} diff --git a/src/routes/spaces/[relay]/calendar/+page.svelte b/src/routes/spaces/[relay]/calendar/+page.svelte index b1815195..71a9f1c3 100644 --- a/src/routes/spaces/[relay]/calendar/+page.svelte +++ b/src/routes/spaces/[relay]/calendar/+page.svelte @@ -17,6 +17,7 @@ import SpaceBar from "@app/components/SpaceBar.svelte" import CalendarEventItem from "@app/components/CalendarEventItem.svelte" import CalendarEventCreate from "@app/components/CalendarEventCreate.svelte" + import EmptyState from "@app/components/EmptyState.svelte" import {pushModal} from "@app/modal" import {decodeRelay} from "@app/relays" import {makeCommentFilter} from "@app/content" @@ -147,7 +148,9 @@ Looking for events...

{:else if items.length === 0} -

No events found.

+ + Planning a meetup or call? Add an event so everyone knows when to show up. + {:else}

That's all!

{/if} diff --git a/src/routes/spaces/[relay]/goals/+page.svelte b/src/routes/spaces/[relay]/goals/+page.svelte index cf21a559..56e53b68 100644 --- a/src/routes/spaces/[relay]/goals/+page.svelte +++ b/src/routes/spaces/[relay]/goals/+page.svelte @@ -16,6 +16,7 @@ import SpaceBar from "@app/components/SpaceBar.svelte" import GoalItem from "@app/components/GoalItem.svelte" import GoalCreate from "@app/components/GoalCreate.svelte" + import EmptyState from "@app/components/EmptyState.svelte" import {decodeRelay} from "@app/relays" import {makeCommentFilter} from "@app/content" import {makeFeed} from "@app/feeds" @@ -83,15 +84,15 @@
{/each} -

- - {#if loading} - Looking for goals... - {:else if items.length === 0} - No goals found. - {:else} - That's all! - {/if} - -

+ {#if loading} +

+ Looking for goals... +

+ {:else if items.length === 0} + + Rallying the community around something? Set a goal and watch the support roll in. + + {:else} +

That's all!

+ {/if} diff --git a/src/routes/spaces/[relay]/polls/+page.svelte b/src/routes/spaces/[relay]/polls/+page.svelte index 4438daac..41764b48 100644 --- a/src/routes/spaces/[relay]/polls/+page.svelte +++ b/src/routes/spaces/[relay]/polls/+page.svelte @@ -16,6 +16,7 @@ import SpaceBar from "@app/components/SpaceBar.svelte" import PollItem from "@app/components/PollItem.svelte" import PollCreate from "@app/components/PollCreate.svelte" + import EmptyState from "@app/components/EmptyState.svelte" import {decodeRelay} from "@app/relays" import {makeCommentFilter} from "@app/content" import {makeFeed} from "@app/feeds" @@ -83,15 +84,15 @@
{/each} -

- - {#if loading} - Looking for polls... - {:else if items.length === 0} - No polls found. - {:else} - That's all! - {/if} - -

+ {#if loading} +

+ Looking for polls... +

+ {:else if items.length === 0} + + Want to take the room's temperature? Create the first poll and let people weigh in. + + {:else} +

That's all!

+ {/if} diff --git a/src/routes/spaces/[relay]/threads/+page.svelte b/src/routes/spaces/[relay]/threads/+page.svelte index c36c3542..cebefca5 100644 --- a/src/routes/spaces/[relay]/threads/+page.svelte +++ b/src/routes/spaces/[relay]/threads/+page.svelte @@ -16,6 +16,7 @@ import SpaceBar from "@app/components/SpaceBar.svelte" import ThreadItem from "@app/components/ThreadItem.svelte" import ThreadCreate from "@app/components/ThreadCreate.svelte" + import EmptyState from "@app/components/EmptyState.svelte" import {decodeRelay} from "@app/relays" import {makeCommentFilter} from "@app/content" import {makeFeed} from "@app/feeds" @@ -83,15 +84,15 @@
{/each} -

- - {#if loading} - Looking for threads... - {:else if items.length === 0} - No threads found. - {:else} - That's all! - {/if} - -

+ {#if loading} +

+ Looking for threads... +

+ {:else if items.length === 0} + + Threads keep longer conversations tidy and easy to follow. Be the first to start one! + + {:else} +

That's all!

+ {/if} diff --git a/static/fonts/Baloo2-Bold.woff2 b/static/fonts/Baloo2-Bold.woff2 new file mode 100644 index 00000000..1d2e2cc4 Binary files /dev/null and b/static/fonts/Baloo2-Bold.woff2 differ diff --git a/static/fonts/Baloo2-Medium.woff2 b/static/fonts/Baloo2-Medium.woff2 new file mode 100644 index 00000000..91a0a456 Binary files /dev/null and b/static/fonts/Baloo2-Medium.woff2 differ diff --git a/static/fonts/Baloo2-SemiBold.woff2 b/static/fonts/Baloo2-SemiBold.woff2 new file mode 100644 index 00000000..9fdcfa45 Binary files /dev/null and b/static/fonts/Baloo2-SemiBold.woff2 differ diff --git a/tailwind.config.js b/tailwind.config.js index d312ad05..8d961711 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -33,22 +33,52 @@ export default { daisyTheme({ name: "dark", ...themes["night"], - "--color-base-content": "oklch(75% 0.029 256.847)", + // Warm charcoal ramp with a wide (~8.5%) lightness spread so stacked + // surfaces (rail / panel / page / card / bubble) read as layered paper + // instead of one flat slab. Hue nudged from cold 265 toward brand 280. + "--color-base-100": "oklch(24% 0.025 280)", + "--color-base-200": "oklch(20% 0.024 280)", + "--color-base-300": "oklch(15.5% 0.022 280)", + "--color-base-content": "oklch(89% 0.02 280)", "--color-primary": process.env.VITE_PLATFORM_ACCENT, "--color-primary-content": process.env.VITE_PLATFORM_ACCENT_CONTENT || "#EAE7FF", "--color-secondary": process.env.VITE_PLATFORM_SECONDARY, "--color-secondary-content": process.env.VITE_PLATFORM_SECONDARY_CONTENT || "#EAE7FF", + // Amber completes the purple -> orange -> amber brand triad (was off-brand pink). + "--color-accent": "#FDB833", + "--color-accent-content": "oklch(22% 0.04 70)", + // Shape + depth: rounder geometry, friendlier borders, and DaisyUI's built-in + // soft top-highlight/bottom-shadow on btn/card/input/badge. + "--depth": "1", + "--radius-box": "1.25rem", + "--radius-field": "0.75rem", + "--radius-selector": "1rem", + "--border": "1.5px", }), daisyTheme({ name: "light", ...themes["winter"], - "--color-neutral": "#F2F7FF", + // Warm paper ramp (hue ~70) replaces winter's clinical blue-white, with a + // gentle layering step and a warm near-ink text color carrying a faint + // purple undertone so type feels tied to the brand. + "--color-base-100": "oklch(99.5% 0.006 70)", + "--color-base-200": "oklch(97% 0.012 70)", + "--color-base-300": "oklch(93.5% 0.016 75)", + "--color-base-content": "oklch(32% 0.03 285)", + "--color-neutral": "oklch(96% 0.01 70)", "--color-neutral-content": "var(--color-base-content)", "--color-warning": "#FD8D0B", "--color-primary": process.env.VITE_PLATFORM_ACCENT, "--color-primary-content": process.env.VITE_PLATFORM_ACCENT_CONTENT || "#EAE7FF", "--color-secondary": process.env.VITE_PLATFORM_SECONDARY, "--color-secondary-content": process.env.VITE_PLATFORM_SECONDARY_CONTENT || "#EAE7FF", + "--color-accent": "#FDB833", + "--color-accent-content": "oklch(30% 0.05 70)", + "--depth": "1", + "--radius-box": "1.25rem", + "--radius-field": "0.75rem", + "--radius-selector": "1rem", + "--border": "1.5px", }), ], }