grain.social is a photo sharing platform built on atproto.

Update layout

Expands navbar fullscreen for a more modern look. Same layout on mobile where the navbar flows with the scroll (not fixed).

![screenshot](https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:bcgltzqazw5tb6k2g3ttenbj/bafkreihssk6un6npe4apd6lxcwcik6a5gi6p2cp4fhjhaxcsoupt6lsfiu@jpeg)

+3 -4
src/app.tsx
··· 12 12 undefined; 13 13 const staticFilesHash = props.ctx.state.staticFilesHash; 14 14 return ( 15 - <html lang="en" class="w-full h-full"> 15 + <html lang="en" class="h-full"> 16 16 <head> 17 17 <meta charset="UTF-8" /> 18 18 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> ··· 56 56 /> 57 57 ))} 58 58 </head> 59 - <body class="h-full w-full dark:bg-zinc-950 dark:text-white"> 60 - <Layout id="layout" class="border-zinc-200 dark:border-zinc-800"> 59 + <body class="h-full dark:bg-zinc-950 dark:text-white"> 60 + <Layout id="layout"> 61 61 <Layout.Nav 62 62 heading={ 63 63 <h1 class="font-['Jersey_20'] text-4xl text-zinc-900 dark:text-white"> ··· 67 67 } 68 68 profile={profile} 69 69 hasNotifications={hasNotifications} 70 - class="border-zinc-200 dark:border-zinc-800" 71 70 /> 72 71 <Layout.Content>{props.children}</Layout.Content> 73 72 </Layout>
+1 -1
src/components/AvatarDialog.tsx
··· 7 7 profile, 8 8 }: Readonly<{ profile: Un$Typed<ProfileView> }>) { 9 9 return ( 10 - <Dialog> 10 + <Dialog class="z-100"> 11 11 <Dialog.X /> 12 12 <div 13 13 class="w-[400px] h-[400px] flex flex-col p-4 z-10"
+1 -1
src/components/GalleryCreateEditDialog.tsx
··· 6 6 gallery, 7 7 }: Readonly<{ gallery?: GalleryView | null }>) { 8 8 return ( 9 - <Dialog id="gallery-dialog" class="z-30"> 9 + <Dialog id="gallery-dialog" class="z-100"> 10 10 <Dialog.Content class="dark:bg-zinc-950 relative"> 11 11 <Dialog.X class="fill-zinc-950 dark:fill-zinc-50" /> 12 12 <Dialog.Title>
+1 -1
src/components/GallerySortDialog.tsx
··· 7 7 { gallery }: Readonly<{ gallery: GalleryView }>, 8 8 ) { 9 9 return ( 10 - <Dialog> 10 + <Dialog class="z-100"> 11 11 <Dialog.Content class="dark:bg-zinc-950 relative"> 12 12 <Dialog.X class="fill-zinc-950 dark:fill-zinc-50" /> 13 13 <Dialog.Title>Sort gallery</Dialog.Title>
+60 -55
src/components/Layout.tsx
··· 25 25 return ( 26 26 <div 27 27 class={cn( 28 - "h-full max-w-5xl mx-auto sm:border-x relative", 28 + "min-h-screen flex flex-col max-w-5xl mx-auto sm:border-x border-zinc-200 dark:border-zinc-800", 29 29 classProp, 30 30 )} 31 31 {...props} ··· 40 40 ) => { 41 41 return ( 42 42 <main 43 - class={cn("h-[calc(100vh-56px)] sm:overflow-y-auto", classProp)} 43 + class={cn( 44 + "flex-1 sm:pt-14", // pt-14 pushes content below the fixed nav 45 + classProp, 46 + )} 44 47 {...props} 45 48 > 46 49 {children} ··· 60 63 return ( 61 64 <nav 62 65 class={cn( 63 - "w-full border-b border-zinc-950 flex justify-between items-center px-4 h-14", 66 + "sm:fixed sm:top-0 sm:left-0 sm:right-0 h-14 z-50 bg-white dark:bg-zinc-950 border-b border-zinc-200 dark:border-zinc-800", 64 67 classProp, 65 68 )} 66 69 {...props} 67 70 > 68 - <div class="flex items-center space-x-4"> 69 - <a href="/"> 70 - {heading} 71 - </a> 72 - </div> 73 - <div class="space-x-2"> 74 - {profile 75 - ? ( 76 - <div class="flex items-center ts:space-x-1 sm:space-x-2"> 77 - <form hx-post="/logout" hx-swap="none" class="inline"> 78 - <Button type="submit" variant="secondary">Sign out</Button> 79 - </form> 80 - <Button 81 - asChild 82 - variant="secondary" 83 - class="relative pl-2" 84 - > 85 - <a href="/explore"> 86 - <i class="fas fa-search text-zinc-950 dark:text-zinc-50" /> 71 + <div class="mx-auto max-w-5xl h-full flex items-center justify-between px-4"> 72 + <div class="flex items-center space-x-4"> 73 + <a href="/"> 74 + {heading} 75 + </a> 76 + </div> 77 + <div class="flex space-x-2"> 78 + {profile 79 + ? ( 80 + <div class="flex items-center ts:space-x-1 sm:space-x-2"> 81 + <form hx-post="/logout" hx-swap="none" class="inline"> 82 + <Button type="submit" variant="secondary">Sign out</Button> 83 + </form> 84 + <Button 85 + asChild 86 + variant="secondary" 87 + class="relative pl-2" 88 + > 89 + <a href="/explore"> 90 + <i class="fas fa-search text-zinc-950 dark:text-zinc-50" /> 91 + </a> 92 + </Button> 93 + <Button 94 + asChild 95 + variant="secondary" 96 + class="relative pl-2" 97 + > 98 + <a href="/notifications"> 99 + <i class="fas fa-bell text-zinc-950 dark:text-zinc-50" /> 100 + {hasNotifications 101 + ? ( 102 + <span class="absolute inline-flex items-center justify-center w-3 h-3 text-xs font-bold text-white bg-sky-500 rounded-full top-1 right-1" /> 103 + ) 104 + : null} 105 + </a> 106 + </Button> 107 + <a href={`/profile/${profile.handle}`}> 108 + <ActorAvatar profile={profile} size={32} /> 87 109 </a> 88 - </Button> 89 - <Button 90 - asChild 91 - variant="secondary" 92 - class="relative pl-2" 93 - > 94 - <a href="/notifications"> 95 - <i class="fas fa-bell text-zinc-950 dark:text-zinc-50" /> 96 - {hasNotifications 97 - ? ( 98 - <span class="absolute inline-flex items-center justify-center w-3 h-3 text-xs font-bold text-white bg-sky-500 rounded-full top-1 right-1" /> 99 - ) 100 - : null} 101 - </a> 102 - </Button> 103 - <a href={`/profile/${profile.handle}`}> 104 - <ActorAvatar profile={profile} size={32} /> 105 - </a> 106 - </div> 107 - ) 108 - : ( 109 - <div class="flex items-center space-x-4"> 110 - <form hx-post="/signup" hx-swap="none" class="inline"> 111 - <Button variant="secondary" type="submit"> 112 - Create account 110 + </div> 111 + ) 112 + : ( 113 + <div class="flex items-center space-x-4"> 114 + <form hx-post="/signup" hx-swap="none" class="inline"> 115 + <Button variant="secondary" type="submit"> 116 + Create account 117 + </Button> 118 + </form> 119 + <Button variant="secondary" asChild> 120 + <a href="/login"> 121 + Sign in 122 + </a> 113 123 </Button> 114 - </form> 115 - <Button variant="secondary" asChild> 116 - <a href="/login"> 117 - Sign in 118 - </a> 119 - </Button> 120 - </div> 121 - )} 124 + </div> 125 + )} 126 + </div> 122 127 </div> 123 128 </nav> 124 129 );
+1 -1
src/components/LoginPage.tsx
··· 5 5 return ( 6 6 <div 7 7 id="login" 8 - class="flex justify-center items-center w-full h-full relative" 8 + class="flex justify-center items-center w-full h-[calc(100vh-56px)] relative" 9 9 style="background-image: url('https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:bcgltzqazw5tb6k2g3ttenbj/bafkreiewhwu3ro5dv7omedphb62db4koa7qtvyzfhiiypg3ru4tvuxkrjy@jpeg'); background-size: cover; background-position: center;" 10 10 > 11 11 <Login hx-target="#login" error={error} errorClass="text-white" />
+1 -1
src/components/PhotoAltDialog.tsx
··· 8 8 photo: PhotoView; 9 9 }>) { 10 10 return ( 11 - <Dialog id="photo-alt-dialog" class="z-30"> 11 + <Dialog id="photo-alt-dialog" class="z-100"> 12 12 <Dialog.Content class="dark:bg-zinc-950 relative"> 13 13 <Dialog.X class="fill-zinc-950 dark:fill-zinc-50" /> 14 14 <Dialog.Title>Add alt text</Dialog.Title>
+1 -1
src/components/PhotoDialog.tsx
··· 15 15 prevImage?: PhotoView; 16 16 }>) { 17 17 return ( 18 - <Dialog id="photo-dialog" class="bg-zinc-950 z-30"> 18 + <Dialog id="photo-dialog" class="bg-zinc-950 z-100"> 19 19 <Dialog.X /> 20 20 {nextImage 21 21 ? (
+1 -1
src/components/PhotoSelectDialog.tsx
··· 13 13 photos: PhotoView[]; 14 14 }>) { 15 15 return ( 16 - <Dialog id="photo-select-dialog" class="z-30"> 16 + <Dialog id="photo-select-dialog" class="z-100"> 17 17 <Dialog.Content class="w-full max-w-5xl dark:bg-zinc-950 sm:min-h-screen flex flex-col relative"> 18 18 <Dialog.X class="fill-zinc-950 dark:fill-zinc-50" /> 19 19 <Dialog.Title>Add photos</Dialog.Title>
+1 -1
src/components/ProfileDialog.tsx
··· 8 8 profile: ProfileView; 9 9 }>) { 10 10 return ( 11 - <Dialog> 11 + <Dialog class="z-100"> 12 12 <Dialog.Content class="dark:bg-zinc-950 relative"> 13 13 <Dialog.X class="fill-zinc-950 dark:fill-zinc-50" /> 14 14 <Dialog.Title>Edit my profile</Dialog.Title>
+1 -1
src/routes/explore.tsx
··· 53 53 <div class="my-4"> 54 54 <Input 55 55 name="q" 56 - class="dark:bg-zinc-800 dark:text-white" 56 + class="dark:bg-zinc-800 dark:text-white border-zinc-100 bg-zinc-100 dark:border-zinc-800" 57 57 placeholder="Search for users" 58 58 hx-get="/explore" 59 59 hx-target="#search-results"
+43 -8
static/styles.css
··· 198 198 .absolute { 199 199 position: absolute; 200 200 } 201 + .fixed { 202 + position: fixed; 203 + } 201 204 .relative { 202 205 position: relative; 203 206 } ··· 248 251 } 249 252 .z-30 { 250 253 z-index: 30; 254 + } 255 + .z-50 { 256 + z-index: 50; 257 + } 258 + .z-100 { 259 + z-index: 100; 251 260 } 252 261 .container { 253 262 width: 100%; ··· 354 363 } 355 364 .h-full { 356 365 height: 100%; 366 + } 367 + .min-h-screen { 368 + min-height: 100vh; 357 369 } 358 370 .w-1\/3 { 359 371 width: calc(1/3 * 100%); ··· 536 548 .border-zinc-900 { 537 549 border-color: var(--color-zinc-900); 538 550 } 539 - .border-zinc-950 { 540 - border-color: var(--color-zinc-950); 541 - } 542 551 .bg-black { 543 552 background-color: var(--color-black); 544 553 } ··· 550 559 } 551 560 .bg-sky-500 { 552 561 background-color: var(--color-sky-500); 562 + } 563 + .bg-white { 564 + background-color: var(--color-white); 553 565 } 554 566 .bg-zinc-100 { 555 567 background-color: var(--color-zinc-100); ··· 595 607 } 596 608 .pt-4 { 597 609 padding-top: calc(var(--spacing) * 4); 610 + } 611 + .pt-14 { 612 + padding-top: calc(var(--spacing) * 14); 598 613 } 599 614 .pb-4 { 600 615 padding-bottom: calc(var(--spacing) * 4); ··· 710 725 opacity: 50%; 711 726 } 712 727 } 728 + .sm\:fixed { 729 + @media (width >= 40rem) { 730 + position: fixed; 731 + } 732 + } 733 + .sm\:top-0 { 734 + @media (width >= 40rem) { 735 + top: calc(var(--spacing) * 0); 736 + } 737 + } 738 + .sm\:right-0 { 739 + @media (width >= 40rem) { 740 + right: calc(var(--spacing) * 0); 741 + } 742 + } 713 743 .sm\:right-1 { 714 744 @media (width >= 40rem) { 715 745 right: calc(var(--spacing) * 1); ··· 718 748 .sm\:bottom-1 { 719 749 @media (width >= 40rem) { 720 750 bottom: calc(var(--spacing) * 1); 751 + } 752 + } 753 + .sm\:left-0 { 754 + @media (width >= 40rem) { 755 + left: calc(var(--spacing) * 0); 721 756 } 722 757 } 723 758 .sm\:h-screen { ··· 789 824 } 790 825 } 791 826 } 792 - .sm\:overflow-y-auto { 793 - @media (width >= 40rem) { 794 - overflow-y: auto; 795 - } 796 - } 797 827 .sm\:border-x { 798 828 @media (width >= 40rem) { 799 829 border-inline-style: var(--tw-border-style); ··· 803 833 .sm\:px-0 { 804 834 @media (width >= 40rem) { 805 835 padding-inline: calc(var(--spacing) * 0); 836 + } 837 + } 838 + .sm\:pt-14 { 839 + @media (width >= 40rem) { 840 + padding-top: calc(var(--spacing) * 14); 806 841 } 807 842 } 808 843 .dark\:divide-zinc-800 {