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 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" /> <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 <meta
name="description" name="description"
content="Imwald — a user-friendly Nostr client focused on relay feed browsing, publications, and relay discovery" content="Imwald — a user-friendly Nostr client focused on relay feed browsing, publications, and relay discovery"
@ -17,26 +23,27 @@
<meta name="apple-mobile-web-app-title" content="Imwald" /> <meta name="apple-mobile-web-app-title" content="Imwald" />
<link rel="manifest" href="/manifest.webmanifest" /> <link rel="manifest" href="/manifest.webmanifest" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" /> <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" /> <link rel="icon" href="/favicon.png" type="image/png" sizes="216x215" />
<meta name="theme-color" content="#171717" media="(prefers-color-scheme: dark)" /> <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<meta name="theme-color" content="#FFFFFF" media="(prefers-color-scheme: light)" /> <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:url" content="https://jumble.imwald.eu" />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:title" content="Imwald 🌲" /> <meta property="og:title" content="Imwald" />
<meta <meta
property="og:description" property="og:description"
content="Imwald — a user-friendly Nostr client focused on relay feed browsing, publications, and relay discovery." content="Imwald — a user-friendly Nostr client focused on relay feed browsing, publications, and relay discovery."
/> />
<meta <meta
property="og:image" 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 property="og:site_name" content="Imwald" />
<meta name="twitter:card" content="summary_large_image" /> <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: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> </head>
<body> <body>
<div id="root"> <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'
import { ChevronLeft } from 'lucide-react' import { ChevronLeft } from 'lucide-react'
import { NavigationService } from '@/services/navigation.service' import { NavigationService } from '@/services/navigation.service'
// Page imports needed for primary note view // Page imports needed for primary note view
import { ImwaldBrandBar } from '@/assets/Logo'
import LiveActivitiesStrip from '@/components/LiveActivitiesStrip' import LiveActivitiesStrip from '@/components/LiveActivitiesStrip'
import NoteDrawer from '@/components/NoteDrawer' import NoteDrawer from '@/components/NoteDrawer'
import storage from '@/services/local-storage.service' import storage from '@/services/local-storage.service'
@ -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). // flex + min-h-0 + min-w-0 so primary pages get a real height in flex parents and can shrink horizontally (double-pane).
return ( 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 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 ? ( {primaryNoteView ? (
// Show note view with back button // Show note view with back button
<div className="flex h-full min-h-0 min-w-0 w-full flex-col"> <div className="flex h-full min-h-0 min-w-0 w-full flex-col">
<div className="flex justify-center py-1 border-b"> <ImwaldBrandBar />
<span className="text-green-600 dark:text-green-500 font-semibold text-sm"> <div className="flex gap-1 border-b border-border p-1 items-center justify-between font-semibold">
Imwald
</span>
</div>
<div className="flex gap-1 p-1 items-center justify-between font-semibold border-b">
<div className="flex items-center flex-1 w-0"> <div className="flex items-center flex-1 w-0">
<Button <Button
className="flex gap-1 items-center w-fit max-w-full justify-start pl-2 pr-3" 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 }) {
}} }}
> >
<NoteDrawerContext.Provider value={{ openDrawer, closeDrawer, isDrawerOpen: drawerOpen, drawerNoteId, drawerInitialEvent }}> <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" /> <LiveActivitiesStrip placement="mobile" />
{primaryNoteView ? ( {primaryNoteView ? (
// Show primary note view with back button on mobile // 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 min-h-0 flex-1 flex-col h-full w-full">
<div className="flex justify-center py-1 border-b"> <ImwaldBrandBar />
<span className="text-green-600 dark:text-green-500 font-semibold text-sm"> <div className="flex gap-1 border-b border-border p-1 items-center justify-between font-semibold">
Imwald
</span>
</div>
<div className="flex gap-1 p-1 items-center justify-between font-semibold border-b">
<div className="flex min-w-0 flex-1 items-center"> <div className="flex min-w-0 flex-1 items-center">
<Button <Button
className="flex min-w-0 max-w-full gap-1 justify-start pl-2 pr-3" 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 }) {
onClick={goBack} onClick={goBack}
> >
<ChevronLeft /> <ChevronLeft />
<div className="truncate text-lg font-semibold"> <div className="truncate font-display text-lg font-semibold">
{primaryViewType === 'settings' || primaryViewType === 'settings-sub' {primaryViewType === 'settings' || primaryViewType === 'settings-sub'
? 'Settings' ? 'Settings'
: primaryViewType === 'profile' : primaryViewType === 'profile'
@ -2158,9 +2151,9 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
}} }}
> >
<NoteDrawerContext.Provider value={{ openDrawer, closeDrawer, isDrawerOpen: drawerOpen, drawerNoteId, drawerInitialEvent }}> <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 <div
className="flex h-[var(--vh)] w-full bg-surface-background" className="flex h-[var(--vh)] w-full bg-content-canvas"
style={{ style={{
maxWidth: '1920px' maxWidth: '1920px'
}} }}
@ -2174,7 +2167,7 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
return ( return (
<div className="flex min-h-0 min-w-0 flex-1 overflow-hidden"> <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 */} {/* 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 <MainContentArea
primaryPages={primaryPages} primaryPages={primaryPages}
currentPrimaryPage={currentPrimaryPage} currentPrimaryPage={currentPrimaryPage}
@ -2185,7 +2178,7 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
/> />
</div> </div>
{/* Right: secondary stack — max width so left pane keeps space on small desktops */} {/* 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.length > 0 ? (
secondaryStack.map((item, index) => { secondaryStack.map((item, index) => {
const isLast = index === secondaryStack.length - 1 const isLast = index === secondaryStack.length - 1

32
src/assets/Icon.tsx

@ -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 }) { export default function Icon({ className }: { className?: string }) {
return ( return (
<svg <img
viewBox="0 0 1080 1228" src="/favicon.png"
version="1.1" alt=""
xmlns="http://www.w3.org/2000/svg" width={216}
xmlnsXlink="http://www.w3.org/1999/xlink" height={215}
xmlSpace="preserve" decoding="async"
style={{ className={cn('mx-auto size-10 object-contain shrink-0', className)}
fill: 'currentcolor', role="presentation"
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>
) )
} }

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
const content = ( const content = (
<> <>
<div className="text-xl font-semibold">Imwald 🌲</div> <div className="text-xl font-semibold">Imwald</div>
<div className="text-muted-foreground"> <div className="text-muted-foreground">
A user-friendly Nostr client focused on relay feed browsing, publications, and relay discovery A user-friendly Nostr client focused on relay feed browsing, publications, and relay discovery
</div> </div>

2
src/components/BottomNavigationBar/index.tsx

@ -11,7 +11,7 @@ export default function BottomNavigationBar() {
return ( return (
<div <div
className={cn( 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={{ style={{
height: 'calc(3rem + env(safe-area-inset-bottom))', height: 'calc(3rem + env(safe-area-inset-bottom))',

2
src/components/Note/index.tsx

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

2
src/components/Sidebar/PostButton.tsx

@ -29,7 +29,7 @@ export default function PostButton() {
}) })
}} }}
variant="default" 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} /> <PencilLine strokeWidth={3} />
</SidebarItem> </SidebarItem>

3
src/components/Sidebar/SidebarItem.tsx

@ -13,7 +13,8 @@ const SidebarItem = forwardRef<
<Button <Button
className={cn( 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', '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 className
)} )}
variant="ghost" variant="ghost"

55
src/components/Sidebar/index.tsx

@ -23,37 +23,38 @@ export default function PrimaryPageSidebar() {
if (isSmallScreen) return null if (isSmallScreen) return null
return ( 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="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="space-y-2"> <div className="imwald-sidebar__atmosphere" aria-hidden />
<div className="px-3 xl:px-4 mb-6 w-full"> <div className="relative z-[1] flex min-h-0 flex-1 flex-col justify-between">
<Icon className="xl:hidden" /> <div className="space-y-2">
<div className="max-xl:hidden"> <div className="mb-6 w-full min-w-0">
<Logo /> <Icon className="mx-auto xl:hidden" />
<div className="text-green-600 dark:text-green-500 font-semibold text-sm mt-1 text-center"> {/* Full-bleed banner at xl: span entire sidebar column (undo pl-4 + pr-6) */}
Im Wald <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>
</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> </div>
<ReadOnlySessionIndicator variant="sidebar" /> <div className="space-y-2">
<div className="max-xl:hidden w-full min-w-0 px-1"> <HelpAndAccountMenu variant="sidebar" />
<LiveActivitiesStrip placement="sidebar" /> <PaneModeToggle />
<DownloadDesktopSidebarButton />
</div> </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>
</div> </div>
) )

4
src/components/Titlebar/index.tsx

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

3
src/components/ui/button.tsx

@ -9,7 +9,8 @@ const buttonVariants = cva(
{ {
variants: { variants: {
variant: { 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', destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
outline: outline:
'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground', 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',

169
src/index.css

@ -90,27 +90,36 @@
} }
:root { :root {
--surface-background: 0 0% 98%; /* Moss / forest (approx. #2f6f4f, #3f8a63, #1f4d38) */
--background: 0 0% 100%; --surface-background: 95 10% 97%;
--foreground: 240 10% 3.9%; --content-canvas: 92 9% 96%;
--card: 0 0% 100%; --background: 90 8% 99%;
--card-foreground: 240 10% 3.9%; --foreground: 155 25% 12%;
--card: 90 12% 99%;
--card-foreground: 155 25% 12%;
--popover: 0 0% 100%; --popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%; --popover-foreground: 155 25% 12%;
--primary: 140 70% 28%; --primary: 152 41% 31%;
--primary-hover: 140 70% 35%; --primary-hover: 154 37% 39%;
--primary-active: 155 42% 21%;
--primary-foreground: 0 0% 98%; --primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 94%; --secondary: 95 12% 94%;
--secondary-foreground: 240 5.9% 10%; --secondary-foreground: 155 22% 18%;
--muted: 240 4.8% 94%; --muted: 95 10% 93%;
--muted-foreground: 240 5% 35%; --muted-foreground: 150 8% 38%;
--accent: 240 4.8% 94%; --accent: 95 14% 92%;
--accent-foreground: 240 5.9% 10%; --accent-foreground: 155 22% 18%;
--destructive: 0 84.2% 60.2%; --destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%; --destructive-foreground: 0 0% 98%;
--border: 240 5.9% 85%; --border: 130 16% 86%;
--input: 240 5.9% 90%; --input: 130 12% 90%;
--ring: 140 70% 28%; --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-1: 12 76% 61%;
--chart-2: 173 58% 39%; --chart-2: 173 58% 39%;
--chart-3: 197 37% 24%; --chart-3: 197 37% 24%;
@ -119,27 +128,35 @@
--radius: 0.5rem; --radius: 0.5rem;
} }
.dark { .dark {
--surface-background: 240 10% 3.9%; --surface-background: 152 22% 9%;
--background: 0 0% 9%; --content-canvas: 150 20% 8%;
--foreground: 0 0% 98%; --background: 150 18% 10%;
--card: 0 0% 9%; --foreground: 100 12% 96%;
--card-foreground: 0 0% 98%; --card: 150 16% 12%;
--popover: 0 0% 9%; --card-foreground: 100 10% 95%;
--popover-foreground: 0 0% 98%; --popover: 150 18% 10%;
--primary: 140 70% 40%; --popover-foreground: 100 12% 96%;
--primary-hover: 140 70% 50%; --primary: 152 41% 38%;
--primary-foreground: 240 5.9% 10%; --primary-hover: 154 37% 46%;
--secondary: 240 3.7% 15.9%; --primary-active: 155 42% 28%;
--secondary-foreground: 0 0% 98%; --primary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%; --secondary: 150 14% 16%;
--muted-foreground: 240 5% 75%; --secondary-foreground: 100 10% 95%;
--accent: 240 3.7% 15.9%; --muted: 150 12% 16%;
--accent-foreground: 0 0% 98%; --muted-foreground: 140 8% 72%;
--accent: 150 14% 18%;
--accent-foreground: 100 10% 95%;
--destructive: 0 62.8% 30.6%; --destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%; --destructive-foreground: 0 0% 98%;
--border: 240 3.7% 25%; --border: 150 12% 22%;
--input: 240 3.7% 15.9%; --input: 150 12% 18%;
--ring: 140 70% 40%; --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-1: 220 70% 50%;
--chart-2: 160 60% 45%; --chart-2: 160 60% 45%;
--chart-3: 30 80% 55%; --chart-3: 30 80% 55%;
@ -147,6 +164,15 @@
--chart-5: 340 75% 55%; --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 { .dark input[type='datetime-local']::-webkit-calendar-picker-indicator {
filter: invert(1) brightness(1.5); filter: invert(1) brightness(1.5);
} }
@ -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 Adjustments */
.font-size-small { .font-size-small {
font-size: 87.5% !important; /* 14px base */ font-size: 87.5% !important; /* 14px base */

11
src/layouts/SecondaryPageLayout/index.tsx

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

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

@ -26,6 +26,7 @@ import { useTranslation } from 'react-i18next'
import { FavoriteRelaysActiveStripMobileBar } from '@/components/FavoriteRelaysActiveStrip' import { FavoriteRelaysActiveStripMobileBar } from '@/components/FavoriteRelaysActiveStrip'
import FavoriteRelaysFeedPicker from '@/components/FavoriteRelaysFeedPicker' import FavoriteRelaysFeedPicker from '@/components/FavoriteRelaysFeedPicker'
import HelpAndAccountMenu from '@/components/HelpAndAccountMenu' import HelpAndAccountMenu from '@/components/HelpAndAccountMenu'
import Logo from '@/assets/Logo'
import FollowingFeed from './FollowingFeed' import FollowingFeed from './FollowingFeed'
import RelaysFeed from './RelaysFeed' import RelaysFeed from './RelaysFeed'
import { usePrimaryPage } from '@/contexts/primary-page-context' import { usePrimaryPage } from '@/contexts/primary-page-context'
@ -290,16 +291,18 @@ function NoteListPageTitlebar({
)} )}
</div> </div>
{isSmallScreen && ( {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 <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) => { onClick={(e) => {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
setPrimaryNoteView(null) setPrimaryNoteView(null)
}} }}
aria-label="Imwald"
> >
Im Wald <Logo className="max-h-8 w-full object-contain object-center" />
</button> </button>
</div> </div>
)} )}

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

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

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

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

2
src/services/nip89.service.ts

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

2
src/services/web.service.ts

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

12
tailwind.config.js

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

Loading…
Cancel
Save