@meith/theme-default 0.33.2 → 0.33.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,19 +1,31 @@
1
1
  import type { HeaderModel, SlotCopy } from '@meith/theme-kit'
2
2
  import { fromSlotCopy, linkTarget } from '@meith/theme-kit'
3
3
 
4
- import { PAGE } from '../shared'
4
+ import { BELOW_HEADER, PAGE } from '../shared'
5
5
 
6
6
  const DESKTOP_LINK =
7
- 'inline-flex h-10 items-center rounded-md px-3 font-medium text-muted-foreground transition-colors pointer-coarse:h-11 hover:bg-primary/8 hover:text-primary'
7
+ 'inline-flex h-9 items-center rounded-full px-3 text-sm font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground'
8
8
 
9
9
  const MOBILE_LINK =
10
- 'flex min-h-11 items-center rounded-md px-3 text-sm font-medium text-muted-foreground hover:bg-primary/8 hover:text-primary'
10
+ 'flex min-h-11 items-center rounded-lg px-3 text-sm font-medium text-muted-foreground hover:bg-muted hover:text-foreground'
11
11
 
12
12
  const SUMMARY =
13
13
  'cursor-default list-none select-none [&::-webkit-details-marker]:hidden [&::marker]:content-none'
14
14
 
15
15
  function BoardMark({ boardTitle, logo }: Pick<HeaderModel, 'boardTitle' | 'logo'>) {
16
- if (logo === undefined) return <>{boardTitle}</>
16
+ if (logo === undefined) {
17
+ return (
18
+ <>
19
+ <span
20
+ aria-hidden="true"
21
+ className="flex size-8 shrink-0 items-center justify-center rounded-lg bg-primary text-sm font-bold text-primary-foreground shadow-sm"
22
+ >
23
+ {(Array.from(boardTitle.trim())[0] ?? '?').toUpperCase()}
24
+ </span>
25
+ <span className="truncate">{boardTitle}</span>
26
+ </>
27
+ )
28
+ }
17
29
 
18
30
  const image = (
19
31
  <img
@@ -38,13 +50,13 @@ function Submenu({ items }: { items: HeaderModel['navigation'][number]['submenu'
38
50
  if (items === undefined || items.length === 0) return null
39
51
 
40
52
  return (
41
- <ul className="invisible absolute top-full left-0 z-30 min-w-48 rounded-md border border-border bg-card p-1 opacity-0 shadow-elevation transition-opacity group-focus-within:visible group-focus-within:opacity-100 group-hover:visible group-hover:opacity-100">
53
+ <ul className="invisible absolute top-full left-0 z-30 mt-1 min-w-52 rounded-xl border border-border bg-card p-1.5 opacity-0 shadow-elevation transition-opacity group-focus-within:visible group-focus-within:opacity-100 group-hover:visible group-hover:opacity-100">
42
54
  {items.map((child) => (
43
55
  <li key={child.href}>
44
56
  <a
45
57
  href={child.href}
46
58
  {...linkTarget(child)}
47
- className="flex items-center rounded-sm px-2.5 py-1.5 text-muted-foreground pointer-coarse:min-h-11 hover:bg-primary/8 hover:text-primary"
59
+ className="flex items-center rounded-lg px-2.5 py-1.5 text-sm text-muted-foreground pointer-coarse:min-h-11 hover:bg-muted hover:text-foreground"
48
60
  >
49
61
  {child.label}
50
62
  </a>
@@ -103,7 +115,7 @@ function MobileNavItem({ item }: { item: HeaderModel['navigation'][number] }) {
103
115
  <li>
104
116
  <details data-nav-disclosure name="default-header-submenu" className="group/submenu">
105
117
  <summary
106
- className={`${SUMMARY} flex min-h-11 items-center justify-between gap-2 rounded-md pl-3 text-sm font-medium text-muted-foreground hover:bg-muted hover:text-foreground`}
118
+ className={`${SUMMARY} flex min-h-11 items-center justify-between gap-2 rounded-lg pl-3 text-sm font-medium text-muted-foreground hover:bg-muted hover:text-foreground`}
107
119
  >
108
120
  <a
109
121
  href={item.href}
@@ -141,30 +153,67 @@ export function Header({
141
153
  const c = (key: string) => fromSlotCopy(copy, `default.header.${key}`)
142
154
 
143
155
  const hasNavigation = navigation.length > 0
156
+ const opensMenus = navigation.some(
157
+ (item) => item.submenu !== undefined && item.submenu.length > 0,
158
+ )
144
159
 
145
160
  return (
146
- <header className="relative border-b border-b-border bg-card">
147
- <div className={`${PAGE} flex flex-wrap items-center gap-x-4 gap-y-3 py-3`}>
161
+ <header
162
+ data-header-peek
163
+ className="sticky top-0 z-40 border-b border-b-border bg-card/95 backdrop-blur motion-safe:transition-transform motion-safe:duration-200 data-peek:-translate-y-full supports-[backdrop-filter]:bg-card/85"
164
+ >
165
+ <div className={`${PAGE} flex min-h-14 items-center gap-x-3 sm:gap-x-4`}>
148
166
  <a
149
167
  href={homeHref}
150
- className="me-auto inline-flex min-h-9 min-w-0 items-center text-xl font-semibold tracking-tight text-primary transition-colors hover:text-primary-hover"
168
+ className="flex min-w-0 shrink-0 items-center gap-2.5 text-lg font-semibold tracking-tight text-foreground transition-colors hover:text-primary"
151
169
  >
152
170
  <BoardMark boardTitle={boardTitle} logo={logo} />
153
171
  </a>
154
172
 
155
- {children}
173
+ {hasNavigation && (
174
+ <nav
175
+ aria-label={c('sections')}
176
+ className={
177
+ opensMenus
178
+ ? 'hidden min-w-0 flex-1 lg:block'
179
+ : 'hidden min-w-0 flex-1 overflow-x-auto [mask-image:linear-gradient(to_right,black_calc(100%-1.5rem),transparent)] [scrollbar-width:none] lg:block'
180
+ }
181
+ >
182
+ <ul
183
+ data-nav-view="desktop"
184
+ className={
185
+ opensMenus
186
+ ? 'flex flex-wrap items-center gap-0.5 py-1'
187
+ : 'flex items-center gap-0.5 pr-6 whitespace-nowrap'
188
+ }
189
+ >
190
+ {navigation.map((item) => (
191
+ <li key={item.href} className="group relative shrink-0">
192
+ <a href={item.href} {...linkTarget(item)} className={DESKTOP_LINK}>
193
+ {item.label}
194
+ </a>
195
+ <Submenu items={item.submenu} />
196
+ </li>
197
+ ))}
198
+ </ul>
199
+ </nav>
200
+ )}
201
+
202
+ <div className="ms-auto flex min-w-0 shrink-0 items-center gap-2 sm:gap-3">{children}</div>
156
203
 
157
204
  {hasNavigation && (
158
205
  <nav aria-label={c('sections')} data-nav-view="mobile" className="lg:hidden">
159
206
  <details data-nav-disclosure className="group/menu">
160
207
  <summary
161
- className={`${SUMMARY} flex size-11 items-center justify-center rounded-md border border-border text-foreground transition-colors hover:border-primary/40 hover:text-primary group-open/menu:border-primary/40 group-open/menu:bg-primary/8 group-open/menu:text-primary`}
208
+ className={`${SUMMARY} flex size-10 items-center justify-center rounded-lg border border-border bg-card text-foreground transition-colors hover:border-primary/40 hover:text-primary group-open/menu:border-primary/40 group-open/menu:bg-primary/8 group-open/menu:text-primary`}
162
209
  >
163
210
  <MenuGlyph />
164
211
  <span className="sr-only">{c('sections')}</span>
165
212
  </summary>
166
213
 
167
- <div className="absolute inset-x-0 top-full z-30 max-h-[calc(100dvh-5rem)] overflow-y-auto border-b border-border bg-card shadow-elevation">
214
+ <div
215
+ className={`absolute inset-x-0 ${BELOW_HEADER} z-30 max-h-[calc(100dvh-3.5rem)] overflow-y-auto border-b border-border bg-card shadow-elevation`}
216
+ >
168
217
  <ul className={`${PAGE} flex flex-col gap-0.5 py-2`}>
169
218
  {navigation.map((item) => (
170
219
  <MobileNavItem key={item.href} item={item} />
@@ -175,26 +224,6 @@ export function Header({
175
224
  </nav>
176
225
  )}
177
226
  </div>
178
-
179
- {hasNavigation && (
180
- <nav aria-label={c('sections')} className="hidden border-t border-border lg:block">
181
- <div className={PAGE}>
182
- <ul
183
- data-nav-view="desktop"
184
- className="-mx-3 flex flex-wrap items-center gap-0.5 py-1 text-sm"
185
- >
186
- {navigation.map((item) => (
187
- <li key={item.href} className="group relative shrink-0">
188
- <a href={item.href} {...linkTarget(item)} className={DESKTOP_LINK}>
189
- {item.label}
190
- </a>
191
- <Submenu items={item.submenu} />
192
- </li>
193
- ))}
194
- </ul>
195
- </div>
196
- </nav>
197
- )}
198
227
  </header>
199
228
  )
200
229
  }
@@ -1,6 +1,7 @@
1
1
  import type { LatestPostsModel, SlotCopy } from '@meith/theme-kit'
2
2
  import { fromSlotCopy } from '@meith/theme-kit'
3
3
  import {
4
+ Avatar,
4
5
  Card,
5
6
  CardContent,
6
7
  CardHeader,
@@ -10,15 +11,15 @@ import {
10
11
  EmptyTitle,
11
12
  } from '@meith/ui'
12
13
 
13
- import { LINK, MUTED_LINK, NUMERIC, PRIMARY_HEADER, Stamp, UserRef } from '../shared'
14
+ import { LINK, MUTED_LINK, NUMERIC, Stamp, UserRef } from '../shared'
14
15
 
15
16
  export function LatestPosts({ posts, capturedAt, copy }: LatestPostsModel & { copy: SlotCopy }) {
16
17
  const c = (key: string) => fromSlotCopy(copy, `default.latestPosts.${key}`)
17
18
 
18
19
  return (
19
- <Card aria-labelledby="latest-posts-heading">
20
- <CardHeader className={PRIMARY_HEADER}>
21
- <CardTitle id="latest-posts-heading" className="text-primary">
20
+ <Card aria-labelledby="latest-posts-heading" className="rounded-xl">
21
+ <CardHeader className="bg-card">
22
+ <CardTitle id="latest-posts-heading" className="text-sm">
22
23
  {c('heading')}
23
24
  </CardTitle>
24
25
  <p className={`text-xs text-muted-foreground ${NUMERIC}`}>
@@ -35,25 +36,36 @@ export function LatestPosts({ posts, capturedAt, copy }: LatestPostsModel & { co
35
36
  <CardContent className="px-0 py-0">
36
37
  <ul className="divide-y divide-border">
37
38
  {posts.map((post) => (
38
- <li key={post.href} className="flex flex-col gap-0.5 px-4 py-2">
39
- <a
40
- href={post.href}
41
- className={`truncate text-sm font-medium text-foreground ${LINK}`}
42
- >
43
- {post.threadTitle}
44
- </a>
39
+ <li
40
+ key={post.href}
41
+ className="flex gap-3 px-4 py-2.5 transition-colors hover:bg-muted/50"
42
+ >
43
+ <Avatar
44
+ src={null}
45
+ name={post.author.username}
46
+ size={28}
47
+ className="mt-0.5 rounded-full"
48
+ />
49
+ <div className="flex min-w-0 flex-1 flex-col gap-0.5">
50
+ <a
51
+ href={post.href}
52
+ className={`truncate text-sm font-medium text-foreground ${LINK}`}
53
+ >
54
+ {post.threadTitle}
55
+ </a>
45
56
 
46
- {post.excerpt !== '' && (
47
- <p className="line-clamp-2 text-xs text-muted-foreground">{post.excerpt}</p>
48
- )}
57
+ {post.excerpt !== '' && (
58
+ <p className="line-clamp-2 text-xs text-muted-foreground">{post.excerpt}</p>
59
+ )}
49
60
 
50
- <p className="truncate text-xs text-muted-foreground">
51
- <UserRef user={post.author} className="font-normal" /> {c('in')}{' '}
52
- <a href={post.forum.href} className={MUTED_LINK}>
53
- {post.forum.label}
54
- </a>{' '}
55
- {c('dot')} <Stamp at={post.postedAt} />
56
- </p>
61
+ <p className="truncate text-xs text-muted-foreground">
62
+ <UserRef user={post.author} className="font-normal" /> {c('in')}{' '}
63
+ <a href={post.forum.href} className={MUTED_LINK}>
64
+ {post.forum.label}
65
+ </a>{' '}
66
+ {c('dot')} <Stamp at={post.postedAt} />
67
+ </p>
68
+ </div>
57
69
  </li>
58
70
  ))}
59
71
  </ul>
@@ -10,7 +10,7 @@ import {
10
10
  EmptyTitle,
11
11
  } from '@meith/ui'
12
12
 
13
- import { LINK, MUTED_LINK, NUMERIC, PRIMARY_HEADER, Stamp, UserRef } from '../shared'
13
+ import { LINK, MUTED_LINK, NUMERIC, PILL, Stamp, UserRef } from '../shared'
14
14
 
15
15
  export function LatestThreads({
16
16
  threads,
@@ -20,9 +20,9 @@ export function LatestThreads({
20
20
  const c = (key: string) => fromSlotCopy(copy, `default.latestThreads.${key}`)
21
21
 
22
22
  return (
23
- <Card aria-labelledby="latest-threads-heading">
24
- <CardHeader className={PRIMARY_HEADER}>
25
- <CardTitle id="latest-threads-heading" className="text-primary">
23
+ <Card aria-labelledby="latest-threads-heading" className="rounded-xl">
24
+ <CardHeader className="bg-card">
25
+ <CardTitle id="latest-threads-heading" className="text-sm">
26
26
  {c('heading')}
27
27
  </CardTitle>
28
28
  <p className={`text-xs text-muted-foreground ${NUMERIC}`}>
@@ -39,17 +39,23 @@ export function LatestThreads({
39
39
  <CardContent className="px-0 py-0">
40
40
  <ul className="divide-y divide-border">
41
41
  {threads.map((thread) => (
42
- <li key={thread.href} className="flex flex-col gap-0.5 px-4 py-2">
43
- <div className="flex items-baseline justify-between gap-2">
42
+ <li
43
+ key={thread.href}
44
+ className="flex flex-col gap-0.5 px-4 py-2.5 transition-colors hover:bg-muted/50"
45
+ >
46
+ <div className="flex items-start justify-between gap-2">
44
47
  <a
45
48
  href={thread.href}
46
- className={`truncate text-sm font-medium text-foreground ${LINK}`}
49
+ className={`line-clamp-2 text-sm font-medium text-foreground ${LINK}`}
47
50
  >
48
51
  {thread.title}
49
52
  </a>
50
- <span className={`shrink-0 text-xs text-muted-foreground ${NUMERIC}`}>
51
- <span className="font-medium text-primary">{thread.replyCount.label}</span>{' '}
52
- {thread.replyCount.value === 1 ? c('reply.one') : c('reply.other')}
53
+ <span className={`${PILL} shrink-0 ${NUMERIC}`}>
54
+ {thread.replyCount.label}
55
+ <span className="sr-only">
56
+ {' '}
57
+ {thread.replyCount.value === 1 ? c('reply.one') : c('reply.other')}
58
+ </span>
53
59
  </span>
54
60
  </div>
55
61
 
@@ -22,22 +22,33 @@ export function MemberProfile({
22
22
 
23
23
  return (
24
24
  <div className={PAGE_BODY}>
25
- <Card>
26
- <CardContent className="flex flex-col gap-4 p-5 sm:flex-row sm:items-start sm:justify-between">
25
+ <Card className="rounded-xl">
26
+ <CardContent className="flex flex-col gap-4 px-5 py-5 sm:flex-row sm:items-center sm:justify-between sm:px-6">
27
27
  <div className="flex min-w-0 items-center gap-4">
28
- <Avatar src={avatarUrl} name={user.username} size={72} />
28
+ <Avatar src={avatarUrl} name={user.username} size={80} className="rounded-2xl" />
29
29
 
30
30
  <div className="min-w-0">
31
31
  <h1
32
- className={cn('text-2xl font-semibold tracking-tight break-words', user.nameClass)}
32
+ className={cn(
33
+ 'font-heading text-2xl font-semibold tracking-tight break-words',
34
+ user.nameClass,
35
+ )}
33
36
  >
34
37
  {user.username}
35
38
  </h1>
36
- {groupTags(groups, title).map((group) => (
37
- <p key={group.title} className="mt-0.5 text-sm text-muted-foreground">
38
- <span className={group.nameClass ?? undefined}>{group.title}</span>
39
- </p>
40
- ))}
39
+ <p className="mt-1.5 flex flex-wrap gap-1.5">
40
+ {groupTags(groups, title).map((group) => (
41
+ <span
42
+ key={group.title}
43
+ className={cn(
44
+ 'inline-flex items-center rounded-full bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground',
45
+ group.nameClass,
46
+ )}
47
+ >
48
+ {group.title}
49
+ </span>
50
+ ))}
51
+ </p>
41
52
  </div>
42
53
  </div>
43
54
 
@@ -62,18 +73,20 @@ export function MemberProfile({
62
73
  )}
63
74
  </CardContent>
64
75
 
65
- <dl className="grid grid-cols-3 gap-3 border-t border-border px-5 py-4">
66
- <div>
67
- <dd className={`text-lg font-semibold text-primary ${NUMERIC}`}>{postCount.label}</dd>
76
+ <dl className="grid grid-cols-3 divide-x divide-border border-t border-border">
77
+ <div className="px-5 py-3.5 sm:px-6">
78
+ <dd className={`text-lg font-semibold text-foreground ${NUMERIC}`}>
79
+ {postCount.label}
80
+ </dd>
68
81
  <dt className="text-xs text-muted-foreground">{c('postsLabel')}</dt>
69
82
  </div>
70
- <div>
83
+ <div className="px-5 py-3.5 sm:px-6">
71
84
  <dd className="text-sm font-medium text-foreground">
72
85
  <Stamp at={joinedAt} />
73
86
  </dd>
74
87
  <dt className="text-xs text-muted-foreground">{c('joinedLabel')}</dt>
75
88
  </div>
76
- <div>
89
+ <div className="px-5 py-3.5 sm:px-6">
77
90
  <dd className="text-sm font-medium text-foreground">
78
91
  {lastVisitAt === null ? c('never') : <Stamp at={lastVisitAt} />}
79
92
  </dd>
@@ -83,18 +96,18 @@ export function MemberProfile({
83
96
  </Card>
84
97
 
85
98
  {fields.length > 0 && (
86
- <Card aria-labelledby="profile-fields-heading">
87
- <CardHeader>
99
+ <Card aria-labelledby="profile-fields-heading" className="rounded-xl">
100
+ <CardHeader className="bg-card">
88
101
  <CardTitle id="profile-fields-heading" className="text-sm">
89
102
  {c('about')}
90
103
  </CardTitle>
91
104
  </CardHeader>
92
- <CardContent>
93
- <dl className="grid gap-x-6 gap-y-3 sm:grid-cols-2">
105
+ <CardContent className="px-5 py-4">
106
+ <dl className="grid gap-x-6 gap-y-4 sm:grid-cols-2">
94
107
  {fields.map((field) => (
95
108
  <div key={field.label}>
96
109
  <dt className="text-xs text-muted-foreground">{field.label}</dt>
97
- <dd className="text-sm break-words">{field.value}</dd>
110
+ <dd className="mt-0.5 text-sm break-words">{field.value}</dd>
98
111
  </div>
99
112
  ))}
100
113
  </dl>
@@ -103,13 +116,13 @@ export function MemberProfile({
103
116
  )}
104
117
 
105
118
  {signatureHtml !== null && (
106
- <Card aria-labelledby="profile-signature-heading">
107
- <CardHeader>
119
+ <Card aria-labelledby="profile-signature-heading" className="rounded-xl">
120
+ <CardHeader className="bg-card">
108
121
  <CardTitle id="profile-signature-heading" className="text-sm">
109
122
  {c('signature')}
110
123
  </CardTitle>
111
124
  </CardHeader>
112
- <CardContent>
125
+ <CardContent className="px-5 py-4">
113
126
  <div
114
127
  className="prose-md text-sm text-muted-foreground"
115
128
  dangerouslySetInnerHTML={{ __html: signatureHtml }}
@@ -16,21 +16,21 @@ export function Navigation({ items, copy }: NavigationModel & { copy: SlotCopy }
16
16
  return (
17
17
  <li key={item.href} className="flex shrink-0 items-center">
18
18
  {index > 0 && (
19
- <span aria-hidden="true" className="px-0.5 text-border select-none">
19
+ <span aria-hidden="true" className="px-1 text-muted-foreground/50 select-none">
20
20
  {c('separator')}
21
21
  </span>
22
22
  )}
23
23
  {isLast ? (
24
24
  <span
25
25
  aria-current="page"
26
- className="max-w-[24ch] truncate px-1.5 py-1 font-medium text-foreground sm:max-w-none"
26
+ className="max-w-[28ch] truncate px-1.5 py-1 font-medium text-foreground sm:max-w-none"
27
27
  >
28
28
  {item.label}
29
29
  </span>
30
30
  ) : (
31
31
  <a
32
32
  href={item.href}
33
- className="rounded-md px-1.5 py-1 transition-colors hover:bg-primary/8 hover:text-primary"
33
+ className="rounded-md px-1.5 py-1 transition-colors hover:bg-muted hover:text-foreground"
34
34
  >
35
35
  {item.label}
36
36
  </a>
@@ -17,7 +17,7 @@ export function Pagination({
17
17
 
18
18
  const c = (key: string) => fromSlotCopy(copy, `default.pagination.${key}`)
19
19
 
20
- const step = cn(buttonVariants({ variant: 'outline', size: 'sm' }), 'min-w-20')
20
+ const step = cn(buttonVariants({ variant: 'outline', size: 'sm' }), 'min-w-20 rounded-full')
21
21
  const stepDisabled = cn(step, 'pointer-events-none opacity-40')
22
22
 
23
23
  return (
@@ -53,7 +53,7 @@ export function Pagination({
53
53
  variant: entry.isCurrent ? 'primary' : 'ghost',
54
54
  size: 'sm',
55
55
  }),
56
- 'min-w-8 px-2',
56
+ 'min-w-8 rounded-full px-2',
57
57
  NUMERIC,
58
58
  entry.isCurrent ? '' : 'hidden sm:inline-flex',
59
59
  )}
@@ -3,14 +3,14 @@ import { fromSlotCopy } from '@meith/theme-kit'
3
3
  import { cn, Disclosure } from '@meith/ui'
4
4
 
5
5
  import { PanelIcon } from '../panel-icons'
6
+ import { BELOW_HEADER } from '../shared'
6
7
 
7
8
  const ITEM =
8
- 'relative flex items-center gap-2.5 rounded-lg px-2.5 py-2 transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
9
+ 'group/item relative flex items-center gap-3 rounded-lg px-3 py-2 transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
9
10
 
10
- const HERE =
11
- 'bg-secondary font-semibold text-foreground before:absolute before:top-1.5 before:bottom-1.5 before:-left-2.5 before:w-0.5 before:rounded-full before:bg-primary'
12
- const OPEN = 'font-semibold text-foreground hover:bg-accent'
13
- const ELSEWHERE = 'text-muted-foreground hover:bg-accent hover:text-foreground'
11
+ const HERE = 'bg-primary/10 font-semibold text-primary [&_svg]:text-primary'
12
+ const OPEN = 'font-semibold text-foreground hover:bg-muted'
13
+ const ELSEWHERE = 'text-muted-foreground hover:bg-muted hover:text-foreground'
14
14
 
15
15
  function Count({ count, copy }: { count: number; copy: SlotCopy }) {
16
16
  const c = (key: string) => fromSlotCopy(copy, `default.panelNav.${key}`)
@@ -19,7 +19,7 @@ function Count({ count, copy }: { count: number; copy: SlotCopy }) {
19
19
  <>
20
20
  <span
21
21
  aria-hidden="true"
22
- className="ml-auto min-w-5 rounded-full bg-primary px-1.5 py-0.5 text-center text-xs font-semibold tabular-nums text-primary-foreground"
22
+ className="ml-auto min-w-5 rounded-full bg-primary px-1.5 py-0.5 text-center text-[0.6875rem] leading-4 font-semibold tabular-nums text-primary-foreground"
23
23
  >
24
24
  {count > 99 ? c('countCap') : count}
25
25
  </span>
@@ -42,7 +42,7 @@ function Item({
42
42
  const body = (
43
43
  <>
44
44
  {item.icon !== null && (
45
- <span className="text-muted-foreground/80">
45
+ <span className="text-muted-foreground/70 transition-colors group-hover/item:text-foreground">
46
46
  <PanelIcon icon={item.icon} />
47
47
  </span>
48
48
  )}
@@ -81,11 +81,11 @@ function Sections({
81
81
  }: Pick<PanelNavModel, 'label' | 'sections'> & { copy: SlotCopy }) {
82
82
  return (
83
83
  <nav aria-label={label} className="text-sm">
84
- <ul className="flex flex-col gap-0.5 pl-2.5">
84
+ <ul className="flex flex-col gap-0.5">
85
85
  {sections.map((section) => (
86
86
  <li
87
87
  key={section.href}
88
- className={cn(section.isOverview && 'mb-1 border-b border-border pb-1')}
88
+ className={cn(section.isOverview && 'mb-1.5 border-b border-border pb-1.5')}
89
89
  >
90
90
  <Item
91
91
  item={section}
@@ -94,12 +94,12 @@ function Sections({
94
94
  />
95
95
 
96
96
  {section.isOpen && section.children.length > 0 && (
97
- <ul className="mt-0.5 ml-4 flex flex-col gap-0.5 border-l border-border pl-2.5">
97
+ <ul className="my-1 ml-5 flex flex-col gap-0.5 border-l-2 border-border pl-2">
98
98
  {section.children.map((child) => (
99
99
  <li key={child.href}>
100
100
  <Item
101
101
  item={child}
102
- className={child.current === 'here' ? HERE : ELSEWHERE}
102
+ className={cn('py-1.5', child.current === 'here' ? HERE : ELSEWHERE)}
103
103
  copy={copy}
104
104
  />
105
105
  </li>
@@ -121,7 +121,9 @@ export function PanelNav({
121
121
  }: PanelNavModel & { copy: SlotCopy }) {
122
122
  return (
123
123
  <>
124
- <div className="sticky top-0 z-20 -mx-4 bg-background/95 px-4 py-2 backdrop-blur-sm sm:-mx-6 sm:px-6 lg:hidden">
124
+ <div
125
+ className={`sticky ${BELOW_HEADER} z-20 -mx-4 bg-background/95 px-4 py-2 backdrop-blur-sm sm:-mx-6 sm:px-6 lg:hidden`}
126
+ >
125
127
  <Disclosure
126
128
  summary={label}
127
129
  contentClassName="p-2"
@@ -131,7 +133,7 @@ export function PanelNav({
131
133
  </Disclosure>
132
134
  </div>
133
135
 
134
- <div className="hidden rounded-lg border border-border bg-card p-2 shadow-elevation lg:block">
136
+ <div className="hidden rounded-xl border border-border bg-card p-2 shadow-elevation lg:block">
135
137
  <Sections label={label} sections={sections} copy={copy} />
136
138
  </div>
137
139
  </>
@@ -2,7 +2,7 @@ import type { PanelPageModel, PanelSectionModel, SlotCopy } from '@meith/theme-k
2
2
  import { fromSlotCopy } from '@meith/theme-kit'
3
3
  import { cn } from '@meith/ui'
4
4
 
5
- import { MUTED_LINK, PAGE, pageAt } from '../shared'
5
+ import { MUTED_LINK, PAGE, PAGE_TITLE, pageAt, SECTION_TITLE } from '../shared'
6
6
 
7
7
  export function PanelPage({
8
8
  title,
@@ -21,25 +21,28 @@ export function PanelPage({
21
21
  id="board-content"
22
22
  tabIndex={-1}
23
23
  className={cn(
24
- 'flex w-full flex-col py-8',
24
+ 'flex w-full flex-col py-6 lg:py-8',
25
25
  frame === 'standalone'
26
26
  ? `flex-1 ${width === 'wide' ? PAGE : pageAt('max-w-4xl')}`
27
- : `px-6 ${width === 'wide' ? 'max-w-none' : 'max-w-4xl'}`,
27
+ : `px-4 sm:px-6 ${width === 'wide' ? 'max-w-none' : 'max-w-4xl'}`,
28
28
  gap === 'loose' ? 'gap-8' : 'gap-6',
29
29
  )}
30
30
  >
31
- <div className="flex flex-wrap items-start justify-between gap-x-6 gap-y-2">
31
+ <div className="flex flex-wrap items-end justify-between gap-x-6 gap-y-3 border-b border-border pb-5">
32
32
  <div className="flex min-w-0 flex-col gap-1">
33
33
  {back !== null && (
34
- <a href={back.href} className={`text-sm font-medium ${MUTED_LINK}`}>
35
- {c('back')} {back.label}
34
+ <a
35
+ href={back.href}
36
+ className={`mb-1 inline-flex w-fit items-center gap-1 text-sm font-medium ${MUTED_LINK}`}
37
+ >
38
+ <span aria-hidden="true">{c('back')}</span> {back.label}
36
39
  </a>
37
40
  )}
38
41
 
39
- <h1 className="font-heading text-2xl font-semibold text-foreground">{title}</h1>
42
+ <h1 className={PAGE_TITLE}>{title}</h1>
40
43
 
41
44
  {regions.lede !== undefined && (
42
- <p className="text-sm text-muted-foreground">{regions.lede}</p>
45
+ <p className="max-w-prose text-sm text-muted-foreground">{regions.lede}</p>
43
46
  )}
44
47
  {regions.meta !== undefined && (
45
48
  <p className="text-xs text-muted-foreground">{regions.meta}</p>
@@ -61,7 +64,7 @@ export function PanelSection({ title, headingId, regions, children }: PanelSecti
61
64
  <section aria-labelledby={headingId} className="flex flex-col gap-3">
62
65
  <div className="flex flex-wrap items-start justify-between gap-x-4 gap-y-1">
63
66
  <div className="flex min-w-0 flex-col gap-0.5">
64
- <h2 id={headingId} className="font-heading text-lg font-semibold text-foreground">
67
+ <h2 id={headingId} className={SECTION_TITLE}>
65
68
  {title}
66
69
  </h2>
67
70
  {regions.description !== undefined && (