Browse Source

fix coloring

imwald
Silberengel 4 weeks ago
parent
commit
d29894a15f
  1. 23
      index.html
  2. BIN
      public/apple-touch-icon.png
  3. BIN
      public/banner.png
  4. BIN
      public/brand-atmosphere-dark.png
  5. BIN
      public/brand-atmosphere-light.png
  6. BIN
      public/favicon-96x96.png
  7. BIN
      public/favicon.png
  8. BIN
      public/og-image.png
  9. BIN
      public/pwa-192x192.png
  10. BIN
      public/pwa-512x512.png
  11. BIN
      public/pwa-maskable-192x192.png
  12. BIN
      public/pwa-maskable-512x512.png
  13. BIN
      resources/banner.png
  14. BIN
      resources/favicon.png
  15. 1
      resources/logo-dark.svg
  16. 1
      resources/logo-light.svg
  17. BIN
      resources/og-image.png
  18. 16
      resources/open-sats-logo.svg
  19. 31
      src/PageManager.tsx
  20. 32
      src/assets/Icon.tsx
  21. 67
      src/assets/Logo.tsx
  22. 2
      src/components/AboutInfoDialog/index.tsx
  23. 2
      src/components/BottomNavigationBar/index.tsx
  24. 2
      src/components/Note/index.tsx
  25. 2
      src/components/Sidebar/PostButton.tsx
  26. 3
      src/components/Sidebar/SidebarItem.tsx
  27. 55
      src/components/Sidebar/index.tsx
  28. 4
      src/components/Titlebar/index.tsx
  29. 3
      src/components/ui/button.tsx
  30. 169
      src/index.css
  31. 11
      src/layouts/SecondaryPageLayout/index.tsx
  32. 9
      src/pages/primary/NoteListPage/index.tsx
  33. 26
      src/pages/secondary/NotePage/index.tsx
  34. 24
      src/pages/secondary/ProfilePage/index.tsx
  35. 2
      src/services/nip89.service.ts
  36. 2
      src/services/web.service.ts
  37. 12
      tailwind.config.js

23
index.html

@ -3,8 +3,14 @@ @@ -3,8 +3,14 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400..900;1,400..900&display=swap"
rel="stylesheet"
/>
<title>Imwald 🌲</title>
<title>Imwald</title>
<meta
name="description"
content="Imwald — a user-friendly Nostr client focused on relay feed browsing, publications, and relay discovery"
@ -17,26 +23,27 @@ @@ -17,26 +23,27 @@
<meta name="apple-mobile-web-app-title" content="Imwald" />
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22%3E%3Ctext y=%22.9em%22 font-size=%2290%22%3E🌲%3C/text%3E%3C/svg%3E" type="image/svg+xml" />
<meta name="theme-color" content="#171717" media="(prefers-color-scheme: dark)" />
<meta name="theme-color" content="#FFFFFF" media="(prefers-color-scheme: light)" />
<link rel="icon" href="/favicon.png" type="image/png" sizes="216x215" />
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<meta name="theme-color" content="#121e18" media="(prefers-color-scheme: dark)" />
<meta name="theme-color" content="#f4f7f4" media="(prefers-color-scheme: light)" />
<meta property="og:url" content="https://jumble.imwald.eu" />
<meta property="og:type" content="website" />
<meta property="og:title" content="Imwald 🌲" />
<meta property="og:title" content="Imwald" />
<meta
property="og:description"
content="Imwald — a user-friendly Nostr client focused on relay feed browsing, publications, and relay discovery."
/>
<meta
property="og:image"
content="https://jumble.imwald.eu/pwa-512x512.png"
content="https://jumble.imwald.eu/og-image.png"
/>
<meta property="og:site_name" content="Imwald" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Imwald 🌲" />
<meta name="twitter:title" content="Imwald" />
<meta name="twitter:description" content="jumble.imwald.eu — a user-friendly Nostr client focused on relay feed browsing and relay discovery." />
<meta name="twitter:image" content="https://jumble.imwald.eu/pwa-512x512.png" />
<meta name="twitter:image" content="https://jumble.imwald.eu/og-image.png" />
</head>
<body>
<div id="root">

BIN
public/apple-touch-icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 54 KiB

BIN
public/banner.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

BIN
public/brand-atmosphere-dark.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

BIN
public/brand-atmosphere-light.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

BIN
public/favicon-96x96.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 18 KiB

BIN
public/favicon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

BIN
public/og-image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 KiB

BIN
public/pwa-192x192.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 61 KiB

BIN
public/pwa-512x512.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 252 KiB

BIN
public/pwa-maskable-192x192.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 669 B

After

Width:  |  Height:  |  Size: 61 KiB

BIN
public/pwa-maskable-512x512.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 252 KiB

BIN
resources/banner.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

BIN
resources/favicon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

1
resources/logo-dark.svg

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.9 KiB

1
resources/logo-light.svg

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.9 KiB

BIN
resources/og-image.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 794 KiB

16
resources/open-sats-logo.svg

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 10 KiB

31
src/PageManager.tsx

@ -5,6 +5,7 @@ import logger from '@/lib/logger' @@ -5,6 +5,7 @@ import logger from '@/lib/logger'
import { ChevronLeft } from 'lucide-react'
import { NavigationService } from '@/services/navigation.service'
// Page imports needed for primary note view
import { ImwaldBrandBar } from '@/assets/Logo'
import LiveActivitiesStrip from '@/components/LiveActivitiesStrip'
import NoteDrawer from '@/components/NoteDrawer'
import storage from '@/services/local-storage.service'
@ -921,16 +922,12 @@ function MainContentArea({ @@ -921,16 +922,12 @@ function MainContentArea({
// flex + min-h-0 + min-w-0 so primary pages get a real height in flex parents and can shrink horizontally (double-pane).
return (
<div className="flex min-h-0 min-w-0 flex-1 flex-col w-full pr-2 py-2">
<div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden rounded-lg bg-background shadow-lg">
<div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden rounded-lg border border-border bg-card shadow-lg">
{primaryNoteView ? (
// Show note view with back button
<div className="flex h-full min-h-0 min-w-0 w-full flex-col">
<div className="flex justify-center py-1 border-b">
<span className="text-green-600 dark:text-green-500 font-semibold text-sm">
Imwald
</span>
</div>
<div className="flex gap-1 p-1 items-center justify-between font-semibold border-b">
<ImwaldBrandBar />
<div className="flex gap-1 border-b border-border p-1 items-center justify-between font-semibold">
<div className="flex items-center flex-1 w-0">
<Button
className="flex gap-1 items-center w-fit max-w-full justify-start pl-2 pr-3"
@ -2038,17 +2035,13 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) { @@ -2038,17 +2035,13 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
}}
>
<NoteDrawerContext.Provider value={{ openDrawer, closeDrawer, isDrawerOpen: drawerOpen, drawerNoteId, drawerInitialEvent }}>
<div className="flex min-h-0 min-w-0 flex-1 flex-col">
<div className="flex min-h-0 min-w-0 flex-1 flex-col bg-content-canvas min-h-[var(--vh)]">
<LiveActivitiesStrip placement="mobile" />
{primaryNoteView ? (
// Show primary note view with back button on mobile
<div className="flex min-h-0 flex-1 flex-col h-full w-full">
<div className="flex justify-center py-1 border-b">
<span className="text-green-600 dark:text-green-500 font-semibold text-sm">
Imwald
</span>
</div>
<div className="flex gap-1 p-1 items-center justify-between font-semibold border-b">
<ImwaldBrandBar />
<div className="flex gap-1 border-b border-border p-1 items-center justify-between font-semibold">
<div className="flex min-w-0 flex-1 items-center">
<Button
className="flex min-w-0 max-w-full gap-1 justify-start pl-2 pr-3"
@ -2058,7 +2051,7 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) { @@ -2058,7 +2051,7 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
onClick={goBack}
>
<ChevronLeft />
<div className="truncate text-lg font-semibold">
<div className="truncate font-display text-lg font-semibold">
{primaryViewType === 'settings' || primaryViewType === 'settings-sub'
? 'Settings'
: primaryViewType === 'profile'
@ -2158,9 +2151,9 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) { @@ -2158,9 +2151,9 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
}}
>
<NoteDrawerContext.Provider value={{ openDrawer, closeDrawer, isDrawerOpen: drawerOpen, drawerNoteId, drawerInitialEvent }}>
<div className="flex flex-col items-center bg-surface-background">
<div className="flex flex-col items-center bg-content-canvas">
<div
className="flex h-[var(--vh)] w-full bg-surface-background"
className="flex h-[var(--vh)] w-full bg-content-canvas"
style={{
maxWidth: '1920px'
}}
@ -2174,7 +2167,7 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) { @@ -2174,7 +2167,7 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
return (
<div className="flex min-h-0 min-w-0 flex-1 overflow-hidden">
{/* Left: primary column — must be a flex column so MainContentArea flex-1 gets height */}
<div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden border-r">
<div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden border-r border-border">
<MainContentArea
primaryPages={primaryPages}
currentPrimaryPage={currentPrimaryPage}
@ -2185,7 +2178,7 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) { @@ -2185,7 +2178,7 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
/>
</div>
{/* Right: secondary stack — max width so left pane keeps space on small desktops */}
<div className="flex h-full min-h-0 w-[min(1042px,50vw)] shrink-0 flex-col overflow-hidden border-l border-border/60 bg-muted/20">
<div className="flex h-full min-h-0 w-[min(1042px,50vw)] shrink-0 flex-col overflow-hidden border-l border-border bg-muted/25">
{secondaryStack.length > 0 ? (
secondaryStack.map((item, index) => {
const isLast = index === secondaryStack.length - 1

32
src/assets/Icon.tsx

@ -1,24 +1,16 @@ @@ -1,24 +1,16 @@
import { cn } from '@/lib/utils'
/** Compact mark for narrow sidebar (from `public/favicon.png`). */
export default function Icon({ className }: { className?: string }) {
return (
<svg
viewBox="0 0 1080 1228"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
xmlSpace="preserve"
style={{
fill: 'currentcolor',
fillRule: 'evenodd',
clipRule: 'evenodd',
strokeLinejoin: 'round',
strokeMiterlimit: 2
}}
className={className}
>
<path
id="Icon-Curve-Cut"
d="M360.047,1225.75c-31.046,-3.901 -75.11,-14.46 -106.756,-25.58c-101.676,-35.727 -175.164,-93.066 -215.387,-168.055c-12.079,-22.521 -30.071,-71.422 -27.297,-74.195c0.736,-0.736 11.648,5.578 24.249,14.031c135.436,90.86 301.047,169.043 465.056,219.547l32.77,10.091l-20.27,7.416c-43.455,15.896 -105.159,22.678 -152.365,16.745Zm166.293,-59.234c-168.523,-50.004 -331.475,-126.514 -481.755,-226.196c-37.737,-25.031 -41.489,-28.372 -43.419,-38.663c-3.585,-19.109 1.498,-83.894 9.798,-124.886c7.343,-36.266 27.664,-106.034 32.278,-110.818c2.023,-2.099 217.924,48.207 221.274,51.557c0.975,0.975 -1.132,11.339 -4.682,23.032c-24.542,80.842 -27.217,127.586 -9.935,173.593c22.507,59.917 114.521,99.888 177.281,77.012c29.23,-10.654 56.593,-41.085 82.629,-91.894c29.288,-57.155 32.348,-64.988 196.483,-503.076c81.138,-216.562 148.499,-394.821 149.692,-396.131c2.1,-2.304 217.949,76.926 223.076,81.884c2.056,1.988 -262.476,712.505 -307.806,826.747c-18.422,46.426 -56.939,123.045 -77.918,154.993c-10.157,15.469 -30.753,40.901 -45.769,56.515c-27.821,28.93 -66.46,58.952 -75.447,58.621c-2.738,-0.106 -23.339,-5.631 -45.78,-12.29Z"
/>
</svg>
<img
src="/favicon.png"
alt=""
width={216}
height={215}
decoding="async"
className={cn('mx-auto size-10 object-contain shrink-0', className)}
role="presentation"
/>
)
}

67
src/assets/Logo.tsx

File diff suppressed because one or more lines are too long

2
src/components/AboutInfoDialog/index.tsx

@ -43,7 +43,7 @@ export default function AboutInfoDialog({ children }: { children: React.ReactNod @@ -43,7 +43,7 @@ export default function AboutInfoDialog({ children }: { children: React.ReactNod
const content = (
<>
<div className="text-xl font-semibold">Imwald 🌲</div>
<div className="text-xl font-semibold">Imwald</div>
<div className="text-muted-foreground">
A user-friendly Nostr client focused on relay feed browsing, publications, and relay discovery
</div>

2
src/components/BottomNavigationBar/index.tsx

@ -11,7 +11,7 @@ export default function BottomNavigationBar() { @@ -11,7 +11,7 @@ export default function BottomNavigationBar() {
return (
<div
className={cn(
'fixed bottom-0 w-full z-40 bg-background border-t flex items-center justify-around [&_svg]:size-4 [&_svg]:shrink-0'
'fixed bottom-0 z-40 flex w-full items-center justify-around border-t border-[hsl(var(--sidebar-border))] bg-background [&_svg]:size-4 [&_svg]:shrink-0'
)}
style={{
height: 'calc(3rem + env(safe-area-inset-bottom))',

2
src/components/Note/index.tsx

@ -457,7 +457,7 @@ export default function Note({ @@ -457,7 +457,7 @@ export default function Note({
}`}
>
<img
src="/pwa-192x192.png"
src="/favicon.png"
alt=""
className="w-full h-full object-cover"
width={size === 'small' ? 36 : 40}

2
src/components/Sidebar/PostButton.tsx

@ -29,7 +29,7 @@ export default function PostButton() { @@ -29,7 +29,7 @@ export default function PostButton() {
})
}}
variant="default"
className="bg-primary xl:justify-center gap-2"
className="bg-primary-active hover:bg-primary-hover active:bg-primary-active xl:justify-center gap-2"
>
<PencilLine strokeWidth={3} />
</SidebarItem>

3
src/components/Sidebar/SidebarItem.tsx

@ -13,7 +13,8 @@ const SidebarItem = forwardRef< @@ -13,7 +13,8 @@ const SidebarItem = forwardRef<
<Button
className={cn(
'flex shadow-none items-center transition-colors duration-500 bg-transparent w-12 h-12 xl:w-full xl:h-auto xl:min-w-0 p-3 m-0 xl:py-2 xl:pl-3 xl:pr-4 rounded-lg xl:justify-start gap-3 text-lg font-semibold [&_svg]:size-full xl:[&_svg]:size-4 xl:[&_svg]:shrink-0',
active && 'text-primary hover:text-primary bg-primary/10 hover:bg-primary/10',
active &&
'text-primary hover:text-primary bg-primary/10 hover:bg-primary/10 xl:shadow-[inset_3px_0_0_0_hsl(var(--primary)),0_0_14px_-4px_hsl(var(--primary)/0.45)]',
className
)}
variant="ghost"

55
src/components/Sidebar/index.tsx

@ -23,37 +23,38 @@ export default function PrimaryPageSidebar() { @@ -23,37 +23,38 @@ export default function PrimaryPageSidebar() {
if (isSmallScreen) return null
return (
<div className="w-[4.8rem] xl:w-[15.6rem] flex flex-col pb-2 pt-4 px-2 xl:pl-4 xl:pr-6 justify-between h-full shrink-0">
<div className="space-y-2">
<div className="px-3 xl:px-4 mb-6 w-full">
<Icon className="xl:hidden" />
<div className="max-xl:hidden">
<Logo />
<div className="text-green-600 dark:text-green-500 font-semibold text-sm mt-1 text-center">
Im Wald
<div className="imwald-sidebar w-[4.8rem] xl:w-[15.6rem] flex flex-col pb-2 pt-4 px-2 xl:pl-4 xl:pr-6 justify-between h-full shrink-0">
<div className="imwald-sidebar__atmosphere" aria-hidden />
<div className="relative z-[1] flex min-h-0 flex-1 flex-col justify-between">
<div className="space-y-2">
<div className="mb-6 w-full min-w-0">
<Icon className="mx-auto xl:hidden" />
{/* Full-bleed banner at xl: span entire sidebar column (undo pl-4 + pr-6) */}
<div className="max-xl:hidden -ml-4 -mr-6 w-[calc(100%+2.5rem)] min-w-0">
<Logo className="h-auto w-full max-h-[5.5rem] max-w-full object-contain object-center" />
</div>
</div>
<ReadOnlySessionIndicator variant="sidebar" />
<div className="max-xl:hidden w-full min-w-0 px-1">
<LiveActivitiesStrip placement="sidebar" />
</div>
<HomeButton />
<FeedButton />
<DiscussionsButton />
<NotificationButton />
<SearchButton />
<FollowsLatestButton />
<FavoritesButton />
<SpellsButton />
<RssButton />
<FavoriteRelaysActiveStripSidebar />
<PostButton />
</div>
<ReadOnlySessionIndicator variant="sidebar" />
<div className="max-xl:hidden w-full min-w-0 px-1">
<LiveActivitiesStrip placement="sidebar" />
<div className="space-y-2">
<HelpAndAccountMenu variant="sidebar" />
<PaneModeToggle />
<DownloadDesktopSidebarButton />
</div>
<HomeButton />
<FeedButton />
<DiscussionsButton />
<NotificationButton />
<SearchButton />
<FollowsLatestButton />
<FavoritesButton />
<SpellsButton />
<RssButton />
<FavoriteRelaysActiveStripSidebar />
<PostButton />
</div>
<div className="space-y-2">
<HelpAndAccountMenu variant="sidebar" />
<PaneModeToggle />
<DownloadDesktopSidebarButton />
</div>
</div>
)

4
src/components/Titlebar/index.tsx

@ -12,8 +12,8 @@ export function Titlebar({ @@ -12,8 +12,8 @@ export function Titlebar({
return (
<div
className={cn(
'sticky top-0 w-full h-12 z-40 bg-background [&_svg]:size-5 [&_svg]:shrink-0 select-none',
!hideBottomBorder && 'border-b',
'imwald-titlebar-fog sticky top-0 w-full h-12 z-40 bg-background [&_svg]:size-5 [&_svg]:shrink-0 select-none',
!hideBottomBorder && 'border-b border-border',
className
)}
>

3
src/components/ui/button.tsx

@ -9,7 +9,8 @@ const buttonVariants = cva( @@ -9,7 +9,8 @@ const buttonVariants = cva(
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
default:
'bg-primary text-primary-foreground shadow-sm shadow-black/10 ring-1 ring-inset ring-white/15 dark:ring-white/10 hover:bg-primary-hover active:bg-primary-active [@media(hover:hover)_and_(pointer:fine)]:md:hover:-translate-y-px [@media(hover:hover)_and_(pointer:fine)]:md:active:translate-y-0',
destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
outline:
'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',

169
src/index.css

@ -90,27 +90,36 @@ @@ -90,27 +90,36 @@
}
:root {
--surface-background: 0 0% 98%;
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
/* Moss / forest (approx. #2f6f4f, #3f8a63, #1f4d38) */
--surface-background: 95 10% 97%;
--content-canvas: 92 9% 96%;
--background: 90 8% 99%;
--foreground: 155 25% 12%;
--card: 90 12% 99%;
--card-foreground: 155 25% 12%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 140 70% 28%;
--primary-hover: 140 70% 35%;
--popover-foreground: 155 25% 12%;
--primary: 152 41% 31%;
--primary-hover: 154 37% 39%;
--primary-active: 155 42% 21%;
--primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 94%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 94%;
--muted-foreground: 240 5% 35%;
--accent: 240 4.8% 94%;
--accent-foreground: 240 5.9% 10%;
--secondary: 95 12% 94%;
--secondary-foreground: 155 22% 18%;
--muted: 95 10% 93%;
--muted-foreground: 150 8% 38%;
--accent: 95 14% 92%;
--accent-foreground: 155 22% 18%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 85%;
--input: 240 5.9% 90%;
--ring: 140 70% 28%;
--border: 130 16% 86%;
--input: 130 12% 90%;
--ring: 152 41% 31%;
--sidebar-top: 95 14% 95%;
--sidebar-bottom: 100 16% 92%;
--sidebar-border: 130 18% 82%;
--brand-wordmark: 155 32% 22%;
--prose-link: 152 38% 28%;
--highlight: 152 45% 36%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
@ -119,27 +128,35 @@ @@ -119,27 +128,35 @@
--radius: 0.5rem;
}
.dark {
--surface-background: 240 10% 3.9%;
--background: 0 0% 9%;
--foreground: 0 0% 98%;
--card: 0 0% 9%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 9%;
--popover-foreground: 0 0% 98%;
--primary: 140 70% 40%;
--primary-hover: 140 70% 50%;
--primary-foreground: 240 5.9% 10%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 75%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--surface-background: 152 22% 9%;
--content-canvas: 150 20% 8%;
--background: 150 18% 10%;
--foreground: 100 12% 96%;
--card: 150 16% 12%;
--card-foreground: 100 10% 95%;
--popover: 150 18% 10%;
--popover-foreground: 100 12% 96%;
--primary: 152 41% 38%;
--primary-hover: 154 37% 46%;
--primary-active: 155 42% 28%;
--primary-foreground: 0 0% 98%;
--secondary: 150 14% 16%;
--secondary-foreground: 100 10% 95%;
--muted: 150 12% 16%;
--muted-foreground: 140 8% 72%;
--accent: 150 14% 18%;
--accent-foreground: 100 10% 95%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 240 3.7% 25%;
--input: 240 3.7% 15.9%;
--ring: 140 70% 40%;
--border: 150 12% 22%;
--input: 150 12% 18%;
--ring: 152 41% 42%;
--sidebar-top: 152 24% 12%;
--sidebar-bottom: 154 22% 7%;
--sidebar-border: 150 14% 20%;
--brand-wordmark: 145 28% 91%;
--prose-link: 145 35% 72%;
--highlight: 150 40% 48%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
@ -147,6 +164,15 @@ @@ -147,6 +164,15 @@
--chart-5: 340 75% 55%;
}
:where(.prose) a {
color: hsl(var(--prose-link));
text-underline-offset: 2px;
}
:where(.prose) a:hover {
color: hsl(var(--primary));
}
.dark input[type='datetime-local']::-webkit-calendar-picker-indicator {
filter: invert(1) brightness(1.5);
}
@ -172,6 +198,77 @@ @@ -172,6 +198,77 @@
}
}
@layer components {
.imwald-sidebar {
position: relative;
isolation: isolate;
background: linear-gradient(
180deg,
hsl(var(--sidebar-top)) 0%,
hsl(var(--sidebar-bottom)) 100%
);
border-right: 1px solid hsl(var(--sidebar-border));
}
.imwald-sidebar__atmosphere {
position: absolute;
inset: 0;
z-index: 0;
pointer-events: none;
overflow: hidden;
}
.imwald-sidebar__atmosphere::after {
content: '';
position: absolute;
left: -32%;
right: -32%;
top: -32%;
bottom: -32%;
background-image: url(/brand-atmosphere-light.png);
background-size: cover;
background-position: center;
opacity: 0.055;
filter: blur(42px) saturate(1.05);
}
.dark .imwald-sidebar__atmosphere::after {
background-image: url(/brand-atmosphere-dark.png);
opacity: 0.075;
filter: blur(42px) saturate(1.08) brightness(0.9);
}
.imwald-titlebar-fog {
position: relative;
}
.imwald-titlebar-fog::before {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
z-index: 0;
background: radial-gradient(
ellipse 100% 85% at 50% -35%,
hsl(var(--primary) / 0.08),
transparent 52%
);
}
.dark .imwald-titlebar-fog::before {
background: radial-gradient(
ellipse 100% 85% at 50% -35%,
hsl(var(--primary) / 0.11),
transparent 52%
);
}
.imwald-titlebar-fog > * {
position: relative;
z-index: 1;
}
}
/* Font Size Adjustments */
.font-size-small {
font-size: 87.5% !important; /* 14px base */

11
src/layouts/SecondaryPageLayout/index.tsx

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
import { ImwaldBrandBar } from '@/assets/Logo'
import ScrollToTopButton from '@/components/ScrollToTopButton'
import { ReadOnlySessionIndicator } from '@/components/ReadOnlySessionIndicator'
import { Titlebar } from '@/components/Titlebar'
@ -117,11 +118,7 @@ const SecondaryPageLayout = forwardRef( @@ -117,11 +118,7 @@ const SecondaryPageLayout = forwardRef(
<div className="flex h-full min-h-0 min-w-0 flex-col">
{title && (
<>
<div className="flex justify-center py-1 border-b">
<span className="text-green-600 dark:text-green-500 font-semibold text-sm">
Imwald
</span>
</div>
<ImwaldBrandBar />
<SecondaryPageTitlebar
title={title}
controls={controls}
@ -180,7 +177,7 @@ export function SecondaryPageTitlebar({ @@ -180,7 +177,7 @@ export function SecondaryPageTitlebar({
<ReadOnlySessionIndicator variant="titlebar" />
<div className="flex min-w-0 flex-1 items-center justify-between gap-1">
{hideBackButton ? (
<div className="flex gap-2 items-center pl-2 w-fit truncate text-lg font-semibold">
<div className="flex gap-2 items-center pl-2 w-fit truncate font-display text-lg font-semibold">
{title}
</div>
) : (
@ -207,7 +204,7 @@ function BackButton({ children }: { children?: React.ReactNode }) { @@ -207,7 +204,7 @@ function BackButton({ children }: { children?: React.ReactNode }) {
onClick={() => pop()}
>
<ChevronLeft />
<div className="truncate text-lg font-semibold">{children}</div>
<div className="truncate font-display text-lg font-semibold">{children}</div>
</Button>
)
}

9
src/pages/primary/NoteListPage/index.tsx

@ -26,6 +26,7 @@ import { useTranslation } from 'react-i18next' @@ -26,6 +26,7 @@ import { useTranslation } from 'react-i18next'
import { FavoriteRelaysActiveStripMobileBar } from '@/components/FavoriteRelaysActiveStrip'
import FavoriteRelaysFeedPicker from '@/components/FavoriteRelaysFeedPicker'
import HelpAndAccountMenu from '@/components/HelpAndAccountMenu'
import Logo from '@/assets/Logo'
import FollowingFeed from './FollowingFeed'
import RelaysFeed from './RelaysFeed'
import { usePrimaryPage } from '@/contexts/primary-page-context'
@ -290,16 +291,18 @@ function NoteListPageTitlebar({ @@ -290,16 +291,18 @@ function NoteListPageTitlebar({
)}
</div>
{isSmallScreen && (
<div className="absolute left-1/2 transform -translate-x-1/2 z-10">
<div className="absolute left-1/2 z-10 -translate-x-1/2 transform">
<button
className="text-green-600 dark:text-green-500 font-semibold text-sm hover:text-green-700 dark:hover:text-green-400 transition-colors cursor-pointer"
type="button"
className="flex max-h-10 max-w-[min(72vw,14rem)] cursor-pointer items-center justify-center overflow-hidden rounded-xl bg-card px-1.5 ring-1 ring-border/50"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
setPrimaryNoteView(null)
}}
aria-label="Imwald"
>
Im Wald
<Logo className="max-h-8 w-full object-contain object-center" />
</button>
</div>
)}

26
src/pages/secondary/NotePage/index.tsx

@ -259,18 +259,18 @@ const NotePage = forwardRef(({ id, index, hideTitlebar = false, initialEvent }: @@ -259,18 +259,18 @@ const NotePage = forwardRef(({ id, index, hideTitlebar = false, initialEvent }:
// Reset to default meta tags with richer information
const defaultUrl = window.location.href
const truncatedDefaultUrl = defaultUrl.length > 150 ? defaultUrl.substring(0, 147) + '...' : defaultUrl
updateMetaTag('og:title', 'Imwald 🌲')
updateMetaTag('og:title', 'Imwald')
updateMetaTag('og:description', `${truncatedDefaultUrl} - A user-friendly Nostr client focused on relay feed browsing and relay discovery. The Imwald edition focuses on publications and articles.`)
updateMetaTag('og:image', 'https://jumble.imwald.eu/pwa-512x512.png')
updateMetaTag('og:image', 'https://jumble.imwald.eu/og-image.png')
updateMetaTag('og:type', 'website')
updateMetaTag('og:url', window.location.href)
updateMetaTag('og:site_name', 'Imwald 🌲')
updateMetaTag('og:site_name', 'Imwald')
// Twitter card meta tags
updateMetaTag('twitter:card', 'summary_large_image')
updateMetaTag('twitter:title', 'Imwald 🌲')
updateMetaTag('twitter:title', 'Imwald')
updateMetaTag('twitter:description', `${truncatedDefaultUrl} - A user-friendly Nostr client focused on relay feed browsing and relay discovery. The Imwald edition focuses on publications and articles.`)
updateMetaTag('twitter:image', 'https://jumble.imwald.eu/pwa-512x512.png')
updateMetaTag('twitter:image', 'https://jumble.imwald.eu/og-image.png')
// Remove article:tag if it exists
const articleTagMeta = document.querySelector('meta[property="article:tag"]')
@ -359,7 +359,7 @@ const NotePage = forwardRef(({ id, index, hideTitlebar = false, initialEvent }: @@ -359,7 +359,7 @@ const NotePage = forwardRef(({ id, index, hideTitlebar = false, initialEvent }:
}
if (!image) {
// Use default OG image with green forest theme
image = 'https://jumble.imwald.eu/pwa-512x512.png'
image = 'https://jumble.imwald.eu/og-image.png'
}
const tags = eventMetadata?.tags || []
@ -370,8 +370,8 @@ const NotePage = forwardRef(({ id, index, hideTitlebar = false, initialEvent }: @@ -370,8 +370,8 @@ const NotePage = forwardRef(({ id, index, hideTitlebar = false, initialEvent }:
// Enhanced title with profile info
const ogTitle = authorName
? `${eventTitle} by @${authorName} - Imwald 🌲`
: `${eventTitle} - Imwald 🌲`
? `${eventTitle} by @${authorName} - Imwald `
: `${eventTitle} - Imwald `
updateMetaTag('og:title', ogTitle)
updateMetaTag('og:description', ogDescription)
@ -381,7 +381,7 @@ const NotePage = forwardRef(({ id, index, hideTitlebar = false, initialEvent }: @@ -381,7 +381,7 @@ const NotePage = forwardRef(({ id, index, hideTitlebar = false, initialEvent }:
updateMetaTag('og:image:alt', `${eventTitle}${authorName ? ` by @${authorName}` : ''} on Imwald`)
updateMetaTag('og:type', ogType)
updateMetaTag('og:url', window.location.href)
updateMetaTag('og:site_name', 'Imwald 🌲')
updateMetaTag('og:site_name', 'Imwald ')
// Add profile data - always include if available
if (authorProfile) {
@ -434,12 +434,12 @@ const NotePage = forwardRef(({ id, index, hideTitlebar = false, initialEvent }: @@ -434,12 +434,12 @@ const NotePage = forwardRef(({ id, index, hideTitlebar = false, initialEvent }:
// Reset to default on unmount with richer information
const cleanupUrl = window.location.href
const truncatedCleanupUrl = cleanupUrl.length > 150 ? cleanupUrl.substring(0, 147) + '...' : cleanupUrl
updateMetaTag('og:title', 'Imwald 🌲')
updateMetaTag('og:title', 'Imwald ')
updateMetaTag('og:description', `${truncatedCleanupUrl} - A user-friendly Nostr client focused on relay feed browsing and relay discovery. The Imwald edition focuses on publications and articles.`)
updateMetaTag('og:image', 'https://jumble.imwald.eu/pwa-512x512.png')
updateMetaTag('og:image', 'https://jumble.imwald.eu/og-image.png')
updateMetaTag('og:type', 'website')
updateMetaTag('og:url', window.location.href)
updateMetaTag('og:site_name', 'Imwald 🌲')
updateMetaTag('og:site_name', 'Imwald ')
// Remove article:tag meta tags
document.querySelectorAll('meta[property="article:tag"]').forEach(meta => meta.remove())
@ -448,7 +448,7 @@ const NotePage = forwardRef(({ id, index, hideTitlebar = false, initialEvent }: @@ -448,7 +448,7 @@ const NotePage = forwardRef(({ id, index, hideTitlebar = false, initialEvent }:
authorMeta.remove()
}
document.title = 'Imwald 🌲'
document.title = 'Imwald '
}
}, [finalEvent, articleMetadata, authorProfile])

24
src/pages/secondary/ProfilePage/index.tsx

@ -48,25 +48,25 @@ const ProfilePage = forwardRef(({ id, index, hideTitlebar = false }: { id?: stri @@ -48,25 +48,25 @@ const ProfilePage = forwardRef(({ id, index, hideTitlebar = false }: { id?: stri
// Reset to default meta tags
const defaultUrl = window.location.href
const truncatedDefaultUrl = defaultUrl.length > 150 ? defaultUrl.substring(0, 147) + '...' : defaultUrl
updateMetaTag('og:title', 'Imwald 🌲')
updateMetaTag('og:title', 'Imwald ')
updateMetaTag('og:description', `${truncatedDefaultUrl} - A user-friendly Nostr client focused on relay feed browsing and relay discovery. The Imwald edition focuses on publications and articles.`)
updateMetaTag('og:image', 'https://jumble.imwald.eu/pwa-512x512.png')
updateMetaTag('og:image', 'https://jumble.imwald.eu/og-image.png')
updateMetaTag('og:type', 'profile')
updateMetaTag('og:url', window.location.href)
updateMetaTag('og:site_name', 'Imwald 🌲')
updateMetaTag('og:site_name', 'Imwald ')
// Twitter card meta tags
updateMetaTag('twitter:card', 'summary')
updateMetaTag('twitter:title', 'Imwald 🌲')
updateMetaTag('twitter:title', 'Imwald ')
updateMetaTag('twitter:description', `${truncatedDefaultUrl} - Profile`)
updateMetaTag('twitter:image', 'https://jumble.imwald.eu/pwa-512x512.png')
updateMetaTag('twitter:image', 'https://jumble.imwald.eu/og-image.png')
return
}
// Build description matching fallback card: username, hostname, URL
const username = profile.username || ''
const ogTitle = username ? `@${username} - Imwald 🌲` : 'Profile - Imwald 🌲'
const ogTitle = username ? `@${username} - Imwald ` : 'Profile - Imwald '
// Truncate URL to 150 chars
const fullUrl = window.location.href
@ -86,7 +86,7 @@ const ProfilePage = forwardRef(({ id, index, hideTitlebar = false }: { id?: stri @@ -86,7 +86,7 @@ const ProfilePage = forwardRef(({ id, index, hideTitlebar = false }: { id?: stri
// Use profile avatar or default image with green theme
const image = profile.avatar
? `https://jumble.imwald.eu/api/avatar/${profile.pubkey}`
: 'https://jumble.imwald.eu/pwa-512x512.png'
: 'https://jumble.imwald.eu/og-image.png'
updateMetaTag('og:title', ogTitle)
updateMetaTag('og:description', ogDescription)
@ -96,7 +96,7 @@ const ProfilePage = forwardRef(({ id, index, hideTitlebar = false }: { id?: stri @@ -96,7 +96,7 @@ const ProfilePage = forwardRef(({ id, index, hideTitlebar = false }: { id?: stri
updateMetaTag('og:image:alt', `${username ? `@${username}` : 'Profile'} on Imwald`)
updateMetaTag('og:type', 'profile')
updateMetaTag('og:url', window.location.href)
updateMetaTag('og:site_name', 'Imwald 🌲')
updateMetaTag('og:site_name', 'Imwald ')
// Add profile-specific meta tags
if (profile.username) {
@ -121,13 +121,13 @@ const ProfilePage = forwardRef(({ id, index, hideTitlebar = false }: { id?: stri @@ -121,13 +121,13 @@ const ProfilePage = forwardRef(({ id, index, hideTitlebar = false }: { id?: stri
// Reset to default on unmount
const cleanupUrl = window.location.href
const truncatedCleanupUrl = cleanupUrl.length > 150 ? cleanupUrl.substring(0, 147) + '...' : cleanupUrl
updateMetaTag('og:title', 'Imwald 🌲')
updateMetaTag('og:title', 'Imwald ')
updateMetaTag('og:description', `${truncatedCleanupUrl} - A user-friendly Nostr client focused on relay feed browsing and relay discovery. The Imwald edition focuses on publications and articles.`)
updateMetaTag('og:image', 'https://jumble.imwald.eu/pwa-512x512.png')
updateMetaTag('og:image', 'https://jumble.imwald.eu/og-image.png')
updateMetaTag('og:type', 'website')
updateMetaTag('og:url', window.location.href)
updateMetaTag('og:site_name', 'Imwald 🌲')
document.title = 'Imwald 🌲'
updateMetaTag('og:site_name', 'Imwald ')
document.title = 'Imwald '
}
}, [profile])

2
src/services/nip89.service.ts

@ -207,7 +207,7 @@ class Nip89Service { @@ -207,7 +207,7 @@ class Nip89Service {
name: 'Imwald',
description: 'A modern Nostr client with advanced features for content discovery, discussions, and community building.',
website: 'https://jumble.imwald.eu',
picture: 'https://jumble.imwald.eu/pwa-512x512.png',
picture: 'https://jumble.imwald.eu/og-image.png',
supportedKinds: [
kinds.ShortTextNote,
kinds.Repost,

2
src/services/web.service.ts

@ -125,7 +125,7 @@ function parseOpenGraphFromHtml(html: string, pageUrl: string): TWebMetadata { @@ -125,7 +125,7 @@ function parseOpenGraphFromHtml(html: string, pageUrl: string): TWebMetadata {
const urlObj = new URL(pageUrl)
const isAppCanonicalHost = urlObj.hostname === 'jumble.imwald.eu'
const isAppDefaultTitle =
title?.includes('Imwald 🌲') ||
title?.includes('Imwald ') ||
title?.includes('Jumble - Imwald Edition') ||
title?.includes('Jumble Imwald Edition')
const isAppDefaultDesc = description?.includes(

12
tailwind.config.js

@ -4,6 +4,9 @@ export default { @@ -4,6 +4,9 @@ export default {
content: ['./index.html', './src/**/*.{ts,tsx}'],
theme: {
extend: {
fontFamily: {
display: ['"Playfair Display"', 'Georgia', 'serif']
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
@ -13,6 +16,12 @@ export default { @@ -13,6 +16,12 @@ export default {
surface: {
background: 'hsl(var(--surface-background))'
},
content: {
canvas: 'hsl(var(--content-canvas))'
},
brand: {
wordmark: 'hsl(var(--brand-wordmark))'
},
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
card: {
@ -26,7 +35,8 @@ export default { @@ -26,7 +35,8 @@ export default {
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))',
hover: 'hsl(var(--primary-hover))'
hover: 'hsl(var(--primary-hover))',
active: 'hsl(var(--primary-active))'
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',

Loading…
Cancel
Save