create-rush-cms 0.0.2 → 0.2.0
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.
- package/LICENSE +21 -0
- package/README.md +58 -2
- package/dist/cli.js +209 -0
- package/package.json +14 -5
- package/template/.env.example +5 -0
- package/template/README.md +148 -0
- package/template/_gitignore +6 -0
- package/template/astro.config.ts +28 -0
- package/template/package.json +28 -0
- package/template/public/favicon.svg +4 -0
- package/template/src/astro-modules.d.ts +5 -0
- package/template/src/components/language-switcher.astro +39 -0
- package/template/src/components/list-page.astro +95 -0
- package/template/src/components/nav-links.astro +33 -0
- package/template/src/components/site-footer.astro +29 -0
- package/template/src/components/site-header.astro +36 -0
- package/template/src/layouts/base.astro +49 -0
- package/template/src/lib/locale.ts +12 -0
- package/template/src/lib/route-page.ts +15 -0
- package/template/src/pages/404.astro +17 -0
- package/template/src/pages/[...locale]/contact.astro +59 -0
- package/template/src/pages/[...path].astro +34 -0
- package/template/src/pages/robots.txt.ts +10 -0
- package/template/src/pages/sitemap.xml.ts +4 -0
- package/template/src/styles/global.css +278 -0
- package/template/src/views/collection.astro +21 -0
- package/template/src/views/entry.astro +65 -0
- package/template/src/views/home.astro +160 -0
- package/template/src/views/index.ts +45 -0
- package/template/src/views/not-found.astro +21 -0
- package/template/src/views/taxonomy.astro +21 -0
- package/template/tsconfig.json +9 -0
- package/index.js +0 -3
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getRush, getUrls } from '@rushcms/astro'
|
|
3
|
+
import Hreflang from '@rushcms/astro/components/hreflang.astro'
|
|
4
|
+
import RushImage from '@rushcms/astro/components/rush-image.astro'
|
|
5
|
+
import type { Alternate, Entry, LocaleCode } from '@rushcms/client'
|
|
6
|
+
import Base from '../layouts/base.astro'
|
|
7
|
+
import { formatDate } from '../lib/locale'
|
|
8
|
+
|
|
9
|
+
type PageUrl = (page: number) => string | undefined
|
|
10
|
+
|
|
11
|
+
type Props = {
|
|
12
|
+
locale: LocaleCode
|
|
13
|
+
title: string
|
|
14
|
+
page: number
|
|
15
|
+
lastPage: number
|
|
16
|
+
entries: Entry[]
|
|
17
|
+
pageUrl: PageUrl
|
|
18
|
+
translations?: Alternate[]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { locale, title, page, lastPage, entries, pageUrl, translations = [] } = Astro.props
|
|
22
|
+
const urls = getUrls(Astro)
|
|
23
|
+
const site = await getRush(Astro).site.get()
|
|
24
|
+
const previous = page > 1 ? pageUrl(page - 1) : undefined
|
|
25
|
+
const next = page < lastPage ? pageUrl(page + 1) : undefined
|
|
26
|
+
const current = pageUrl(page)
|
|
27
|
+
const canonical = Astro.site && current !== undefined ? new URL(current, Astro.site) : undefined
|
|
28
|
+
const heading = page > 1 ? `${title} (${page}/${lastPage})` : title
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
<Base locale={locale} title={`${heading} | ${site.name}`} translations={translations}>
|
|
32
|
+
{canonical && <link slot="head" rel="canonical" href={canonical.href} />}
|
|
33
|
+
{page === 1 && <Hreflang slot="head" alternates={translations} />}
|
|
34
|
+
<div class="mx-auto max-w-6xl px-6 py-16">
|
|
35
|
+
<header class="max-w-3xl">
|
|
36
|
+
<h1 class="text-4xl font-semibold tracking-tight">{title}</h1>
|
|
37
|
+
{
|
|
38
|
+
lastPage > 1 && (
|
|
39
|
+
<p class="mt-2 text-muted">
|
|
40
|
+
Page {page} of {lastPage}
|
|
41
|
+
</p>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
</header>
|
|
45
|
+
{
|
|
46
|
+
entries.length > 0 ? (
|
|
47
|
+
<ul class="mt-12 grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
48
|
+
{entries.map((entry) => (
|
|
49
|
+
<li>
|
|
50
|
+
<article class="card group relative flex h-full flex-col">
|
|
51
|
+
{entry.featured_image?.url && (
|
|
52
|
+
<RushImage
|
|
53
|
+
media={entry.featured_image}
|
|
54
|
+
class="mb-4 aspect-video w-full rounded-lg object-cover"
|
|
55
|
+
/>
|
|
56
|
+
)}
|
|
57
|
+
<h2 class="text-lg font-medium group-hover:text-accent">
|
|
58
|
+
<a href={urls.servedPath(entry, locale)} class="after:absolute after:inset-0">
|
|
59
|
+
{entry.title}
|
|
60
|
+
</a>
|
|
61
|
+
</h2>
|
|
62
|
+
{entry.excerpt && <p class="mt-2 line-clamp-3 text-muted">{entry.excerpt}</p>}
|
|
63
|
+
<p class="mt-auto pt-4 text-sm text-muted">
|
|
64
|
+
<time datetime={entry.published_at}>
|
|
65
|
+
{formatDate(entry.published_at, locale)}
|
|
66
|
+
</time>
|
|
67
|
+
</p>
|
|
68
|
+
</article>
|
|
69
|
+
</li>
|
|
70
|
+
))}
|
|
71
|
+
</ul>
|
|
72
|
+
) : (
|
|
73
|
+
<p class="mt-12 text-muted">Nothing has been published here yet.</p>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
{
|
|
77
|
+
lastPage > 1 && (
|
|
78
|
+
<nav aria-label="Pagination" class="mt-12 flex items-center justify-between gap-4">
|
|
79
|
+
{previous ? (
|
|
80
|
+
<a href={previous} rel="prev" class="button button-secondary">
|
|
81
|
+
Previous page
|
|
82
|
+
</a>
|
|
83
|
+
) : (
|
|
84
|
+
<span />
|
|
85
|
+
)}
|
|
86
|
+
{next && (
|
|
87
|
+
<a href={next} rel="next" class="button button-secondary">
|
|
88
|
+
Next page
|
|
89
|
+
</a>
|
|
90
|
+
)}
|
|
91
|
+
</nav>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
</div>
|
|
95
|
+
</Base>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { NavigationLink } from '@rushcms/client'
|
|
3
|
+
|
|
4
|
+
type Props = {
|
|
5
|
+
links: NavigationLink[]
|
|
6
|
+
nested?: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { links, nested = false } = Astro.props
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<ul class:list={['nav-links', nested ? 'nav-links-nested' : 'nav-links-top']}>
|
|
13
|
+
{
|
|
14
|
+
links.map((link) => (
|
|
15
|
+
<li class="nav-item">
|
|
16
|
+
{link.href === undefined ? (
|
|
17
|
+
<span class="nav-label">{link.label}</span>
|
|
18
|
+
) : (
|
|
19
|
+
<a
|
|
20
|
+
{...link.attributes}
|
|
21
|
+
href={link.href}
|
|
22
|
+
target={link.target}
|
|
23
|
+
rel={link.rel}
|
|
24
|
+
class:list={['nav-link', link.className]}
|
|
25
|
+
>
|
|
26
|
+
{link.label}
|
|
27
|
+
</a>
|
|
28
|
+
)}
|
|
29
|
+
{link.children.length > 0 && <Astro.self links={link.children} nested />}
|
|
30
|
+
</li>
|
|
31
|
+
))
|
|
32
|
+
}
|
|
33
|
+
</ul>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { NavigationLink } from '@rushcms/client'
|
|
3
|
+
import NavLinks from './nav-links.astro'
|
|
4
|
+
|
|
5
|
+
type Props = {
|
|
6
|
+
name: string
|
|
7
|
+
links: NavigationLink[]
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { name, links } = Astro.props
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<footer class="border-t border-line">
|
|
14
|
+
<div
|
|
15
|
+
class="mx-auto flex max-w-6xl flex-col gap-6 px-6 py-10 text-sm text-muted md:flex-row md:items-start md:justify-between"
|
|
16
|
+
>
|
|
17
|
+
<p>
|
|
18
|
+
© {`${new Date().getFullYear()} ${name}`}. Powered by{' '}
|
|
19
|
+
<span class="font-medium text-ink">Rush CMS</span>.
|
|
20
|
+
</p>
|
|
21
|
+
{
|
|
22
|
+
links.length > 0 && (
|
|
23
|
+
<nav aria-label="Footer" class="footer-nav">
|
|
24
|
+
<NavLinks links={links} />
|
|
25
|
+
</nav>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
</div>
|
|
29
|
+
</footer>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { Alternate, LocaleCode, NavigationLink } from '@rushcms/client'
|
|
3
|
+
import LanguageSwitcher from './language-switcher.astro'
|
|
4
|
+
import NavLinks from './nav-links.astro'
|
|
5
|
+
|
|
6
|
+
type Props = {
|
|
7
|
+
name: string
|
|
8
|
+
home: string
|
|
9
|
+
links: NavigationLink[]
|
|
10
|
+
locale: LocaleCode
|
|
11
|
+
translations: Alternate[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { name, home, links, locale, translations } = Astro.props
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<header class="relative z-30 border-b border-line bg-surface/80 backdrop-blur">
|
|
18
|
+
<div
|
|
19
|
+
class="mx-auto flex max-w-6xl flex-wrap items-center justify-between gap-x-8 gap-y-3 px-6 py-4"
|
|
20
|
+
>
|
|
21
|
+
<a href={home} class="flex items-center gap-2 text-lg font-semibold tracking-tight">
|
|
22
|
+
<img src="/favicon.svg" alt="" width="28" height="28" />
|
|
23
|
+
<span>{name}</span>
|
|
24
|
+
</a>
|
|
25
|
+
<div class="flex flex-wrap items-center gap-x-6 gap-y-2">
|
|
26
|
+
{
|
|
27
|
+
links.length > 0 && (
|
|
28
|
+
<nav aria-label="Main" class="site-nav">
|
|
29
|
+
<NavLinks links={links} />
|
|
30
|
+
</nav>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
<LanguageSwitcher locale={locale} translations={translations} />
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</header>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getNavigation, getRush, getUrls } from '@rushcms/astro'
|
|
3
|
+
import PreviewBanner from '@rushcms/astro/components/preview-banner.astro'
|
|
4
|
+
import { optional, toBcp47 } from '@rushcms/client'
|
|
5
|
+
import type { Alternate, LocaleCode } from '@rushcms/client'
|
|
6
|
+
import SiteFooter from '../components/site-footer.astro'
|
|
7
|
+
import SiteHeader from '../components/site-header.astro'
|
|
8
|
+
import '../styles/global.css'
|
|
9
|
+
|
|
10
|
+
type Props = {
|
|
11
|
+
locale: LocaleCode
|
|
12
|
+
title?: string | undefined
|
|
13
|
+
translations?: Alternate[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const { locale, title, translations = [] } = Astro.props
|
|
17
|
+
const site = await getRush(Astro).site.get()
|
|
18
|
+
const home = getUrls(Astro).homepage(locale)
|
|
19
|
+
// TODO: create a navigation with the key "main" in Rush CMS, or change the key here.
|
|
20
|
+
const main = (await optional(getNavigation(Astro, 'main'))) ?? []
|
|
21
|
+
// TODO: create a navigation with the key "footer" in Rush CMS, or change the key here.
|
|
22
|
+
const footer = (await optional(getNavigation(Astro, 'footer'))) ?? []
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
<!doctype html>
|
|
26
|
+
<html lang={toBcp47(locale)}>
|
|
27
|
+
<head>
|
|
28
|
+
<meta charset="utf-8" />
|
|
29
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
30
|
+
<meta name="color-scheme" content="light dark" />
|
|
31
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
32
|
+
{title !== undefined && <title>{title}</title>}
|
|
33
|
+
<slot name="head" />
|
|
34
|
+
</head>
|
|
35
|
+
<body class="flex min-h-screen flex-col bg-canvas font-sans text-ink antialiased">
|
|
36
|
+
<a
|
|
37
|
+
href="#content"
|
|
38
|
+
class="sr-only rounded-md bg-accent px-4 py-2 font-medium text-accent-ink focus:not-sr-only focus:fixed focus:top-4 focus:left-4 focus:z-50"
|
|
39
|
+
>
|
|
40
|
+
Skip to content
|
|
41
|
+
</a>
|
|
42
|
+
<PreviewBanner />
|
|
43
|
+
<SiteHeader name={site.name} home={home} links={main} locale={locale} translations={translations} />
|
|
44
|
+
<main id="content" tabindex="-1" class="flex-1 focus:outline-none">
|
|
45
|
+
<slot />
|
|
46
|
+
</main>
|
|
47
|
+
<SiteFooter name={site.name} links={footer} />
|
|
48
|
+
</body>
|
|
49
|
+
</html>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { toBcp47 } from '@rushcms/client'
|
|
2
|
+
import type { LocaleCode } from '@rushcms/client'
|
|
3
|
+
|
|
4
|
+
export function languageName(locale: LocaleCode): string {
|
|
5
|
+
const tag = toBcp47(locale)
|
|
6
|
+
const name = new Intl.DisplayNames([tag], { type: 'language' }).of(tag) ?? tag
|
|
7
|
+
return name.charAt(0).toLocaleUpperCase(tag) + name.slice(1)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function formatDate(value: string, locale: LocaleCode): string {
|
|
11
|
+
return new Intl.DateTimeFormat(toBcp47(locale), { dateStyle: 'long' }).format(new Date(value))
|
|
12
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { resolvePage } from '@rushcms/astro'
|
|
2
|
+
import type { EntryMissing, SitePage, SitePageContext } from '@rushcms/astro'
|
|
3
|
+
import type { APIContext } from 'astro'
|
|
4
|
+
|
|
5
|
+
export type RouteContext = SitePageContext & Pick<APIContext, 'redirect'>
|
|
6
|
+
|
|
7
|
+
export type RoutedPage = SitePage | EntryMissing
|
|
8
|
+
|
|
9
|
+
export async function routePage(context: RouteContext): Promise<RoutedPage | Response> {
|
|
10
|
+
const page = await resolvePage(context)
|
|
11
|
+
if (page.kind === 'moved') {
|
|
12
|
+
return context.redirect(page.location, page.permanent ? 301 : 302)
|
|
13
|
+
}
|
|
14
|
+
return page
|
|
15
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { SitePageProps } from '@rushcms/astro'
|
|
3
|
+
import { routePage } from '../lib/route-page'
|
|
4
|
+
import NotFound from '../views/not-found.astro'
|
|
5
|
+
|
|
6
|
+
export const prerender = false
|
|
7
|
+
|
|
8
|
+
type Props = SitePageProps
|
|
9
|
+
|
|
10
|
+
const page = await routePage(Astro)
|
|
11
|
+
if (page instanceof Response) {
|
|
12
|
+
return page
|
|
13
|
+
}
|
|
14
|
+
Astro.response.status = 404
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<NotFound />
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getRush, getUrls } from '@rushcms/astro'
|
|
3
|
+
import Form from '@rushcms/astro/components/form.astro'
|
|
4
|
+
import { optional } from '@rushcms/client'
|
|
5
|
+
import Base from '../../layouts/base.astro'
|
|
6
|
+
import NotFound from '../../views/not-found.astro'
|
|
7
|
+
|
|
8
|
+
export const prerender = false
|
|
9
|
+
|
|
10
|
+
const locale = getUrls(Astro).localeOf(Astro.params.locale)
|
|
11
|
+
// TODO: create a form with the key "contact" in Rush CMS, or change the key here.
|
|
12
|
+
const form = locale === undefined ? undefined : await optional(getRush(Astro).forms.get('contact'))
|
|
13
|
+
const notice = locale !== undefined && form === undefined && import.meta.env.DEV
|
|
14
|
+
if (form === undefined && !notice) {
|
|
15
|
+
Astro.response.status = 404
|
|
16
|
+
}
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
{
|
|
20
|
+
locale !== undefined && (form !== undefined || notice) ? (
|
|
21
|
+
<Base locale={locale} title={form?.name ?? 'Contact'}>
|
|
22
|
+
<div class="mx-auto max-w-2xl px-6 py-16">
|
|
23
|
+
<h1 class="text-4xl font-semibold tracking-tight">{form?.name ?? 'Contact'}</h1>
|
|
24
|
+
{form ? (
|
|
25
|
+
<div class="contact-form mt-10">
|
|
26
|
+
<Form
|
|
27
|
+
form={form}
|
|
28
|
+
submitLabel="Send"
|
|
29
|
+
messages={{
|
|
30
|
+
sent: 'Thank you, your message was sent.',
|
|
31
|
+
error: 'Your message could not be sent. Please try again.',
|
|
32
|
+
required: 'This field is required.',
|
|
33
|
+
invalid_option: 'Choose one of the options.',
|
|
34
|
+
invalid_email: 'Enter a valid email address.',
|
|
35
|
+
invalid_url: 'Enter a valid URL.',
|
|
36
|
+
invalid_number: 'Enter a number.',
|
|
37
|
+
invalid_format: 'Check the format of this field.',
|
|
38
|
+
too_small: 'Enter {min} or more.',
|
|
39
|
+
too_large: 'Enter {max} or less.',
|
|
40
|
+
too_short: 'Use at least {min} characters.',
|
|
41
|
+
too_long: 'Use at most {max} characters.',
|
|
42
|
+
rate_limited: 'Too many attempts. Try again in a minute.',
|
|
43
|
+
captcha_failed: 'The captcha check failed. Please try again.'
|
|
44
|
+
}}
|
|
45
|
+
/>
|
|
46
|
+
</div>
|
|
47
|
+
) : (
|
|
48
|
+
<p lang="en" class="mt-6 rounded-lg border border-dashed border-line px-5 py-4 text-muted">
|
|
49
|
+
This page shows the Rush CMS form with the key <code class="font-mono">contact</code>.
|
|
50
|
+
Create that form in Rush CMS and reload. This notice appears only in{' '}
|
|
51
|
+
<code class="font-mono">astro dev</code>; production builds answer 404 here.
|
|
52
|
+
</p>
|
|
53
|
+
)}
|
|
54
|
+
</div>
|
|
55
|
+
</Base>
|
|
56
|
+
) : (
|
|
57
|
+
<NotFound />
|
|
58
|
+
)
|
|
59
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getUrls } from '@rushcms/astro'
|
|
3
|
+
import type { SitePageProps } from '@rushcms/astro'
|
|
4
|
+
import { routePage } from '../lib/route-page'
|
|
5
|
+
import Home from '../views/home.astro'
|
|
6
|
+
import { viewsFor } from '../views/index'
|
|
7
|
+
import NotFound from '../views/not-found.astro'
|
|
8
|
+
import Taxonomy from '../views/taxonomy.astro'
|
|
9
|
+
|
|
10
|
+
export * from '@rushcms/astro/site-paths'
|
|
11
|
+
|
|
12
|
+
type Props = SitePageProps
|
|
13
|
+
|
|
14
|
+
const page = await routePage(Astro)
|
|
15
|
+
if (page instanceof Response) {
|
|
16
|
+
return page
|
|
17
|
+
}
|
|
18
|
+
if (page.kind === 'missing') {
|
|
19
|
+
Astro.response.status = 404
|
|
20
|
+
}
|
|
21
|
+
const collection =
|
|
22
|
+
page.kind === 'collection'
|
|
23
|
+
? page.collection
|
|
24
|
+
: page.kind === 'found'
|
|
25
|
+
? page.entry.collection
|
|
26
|
+
: undefined
|
|
27
|
+
const { list: List, entry: Entry } = viewsFor(collection, getUrls(Astro))
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
{page.kind === 'homepage' && <Home page={page} />}
|
|
31
|
+
{page.kind === 'collection' && <List page={page} />}
|
|
32
|
+
{page.kind === 'taxonomy' && <Taxonomy page={page} />}
|
|
33
|
+
{page.kind === 'found' && <Entry entry={page.entry} locale={page.locale} />}
|
|
34
|
+
{page.kind === 'missing' && <NotFound />}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { APIRoute } from 'astro'
|
|
2
|
+
import { RUSH_PREVIEW } from 'astro:env/server'
|
|
3
|
+
|
|
4
|
+
export const GET: APIRoute = ({ site }) => {
|
|
5
|
+
const rules = RUSH_PREVIEW ? 'Disallow: /' : 'Allow: /'
|
|
6
|
+
const sitemap = site === undefined ? '' : `\nSitemap: ${new URL('/sitemap.xml', site).href}\n`
|
|
7
|
+
return new Response(`User-agent: *\n${rules}\n${sitemap}`, {
|
|
8
|
+
headers: { 'Content-Type': 'text/plain; charset=utf-8' }
|
|
9
|
+
})
|
|
10
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
@import 'tailwindcss';
|
|
2
|
+
@plugin '@tailwindcss/typography';
|
|
3
|
+
|
|
4
|
+
@theme {
|
|
5
|
+
--font-sans:
|
|
6
|
+
ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
|
|
7
|
+
sans-serif;
|
|
8
|
+
--font-mono: ui-monospace, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
|
|
9
|
+
--color-canvas: oklch(0.985 0.004 265);
|
|
10
|
+
--color-surface: oklch(1 0 0);
|
|
11
|
+
--color-ink: oklch(0.23 0.025 265);
|
|
12
|
+
--color-muted: oklch(0.5 0.025 265);
|
|
13
|
+
--color-line: oklch(0.91 0.012 265);
|
|
14
|
+
--color-accent: oklch(0.52 0.21 277);
|
|
15
|
+
--color-accent-ink: oklch(0.99 0.005 277);
|
|
16
|
+
--color-accent-soft: oklch(0.93 0.045 277);
|
|
17
|
+
--color-danger: oklch(0.55 0.2 25);
|
|
18
|
+
--color-success: oklch(0.55 0.14 155);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@media (prefers-color-scheme: dark) {
|
|
22
|
+
:root {
|
|
23
|
+
--color-canvas: oklch(0.17 0.018 265);
|
|
24
|
+
--color-surface: oklch(0.21 0.02 265);
|
|
25
|
+
--color-ink: oklch(0.95 0.008 265);
|
|
26
|
+
--color-muted: oklch(0.74 0.02 265);
|
|
27
|
+
--color-line: oklch(0.31 0.022 265);
|
|
28
|
+
--color-accent: oklch(0.76 0.14 277);
|
|
29
|
+
--color-accent-ink: oklch(0.18 0.03 277);
|
|
30
|
+
--color-accent-soft: oklch(0.3 0.07 277);
|
|
31
|
+
--color-danger: oklch(0.72 0.17 25);
|
|
32
|
+
--color-success: oklch(0.75 0.14 155);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@layer base {
|
|
37
|
+
:root {
|
|
38
|
+
color-scheme: light dark;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
:focus-visible {
|
|
42
|
+
outline: 2px solid var(--color-accent);
|
|
43
|
+
outline-offset: 3px;
|
|
44
|
+
border-radius: 4px;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
::selection {
|
|
48
|
+
background-color: var(--color-accent-soft);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@layer components {
|
|
53
|
+
.button,
|
|
54
|
+
.contact-form button[type='submit'] {
|
|
55
|
+
display: inline-flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
justify-content: center;
|
|
58
|
+
gap: 0.5rem;
|
|
59
|
+
border-radius: 9999px;
|
|
60
|
+
padding: 0.625rem 1.25rem;
|
|
61
|
+
font-weight: 500;
|
|
62
|
+
transition:
|
|
63
|
+
background-color 150ms,
|
|
64
|
+
border-color 150ms,
|
|
65
|
+
color 150ms;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.button-primary,
|
|
69
|
+
.contact-form button[type='submit'] {
|
|
70
|
+
background-color: var(--color-accent);
|
|
71
|
+
color: var(--color-accent-ink);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.button-primary:hover,
|
|
75
|
+
.contact-form button[type='submit']:hover {
|
|
76
|
+
background-color: color-mix(in oklch, var(--color-accent) 88%, var(--color-ink));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.button-secondary {
|
|
80
|
+
border: 1px solid var(--color-line);
|
|
81
|
+
background-color: var(--color-surface);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.button-secondary:hover {
|
|
85
|
+
border-color: var(--color-accent);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.card {
|
|
89
|
+
border: 1px solid var(--color-line);
|
|
90
|
+
border-radius: 1rem;
|
|
91
|
+
background-color: var(--color-surface);
|
|
92
|
+
padding: 1.5rem;
|
|
93
|
+
transition:
|
|
94
|
+
border-color 150ms,
|
|
95
|
+
box-shadow 150ms;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.card:hover,
|
|
99
|
+
.card:focus-within {
|
|
100
|
+
border-color: var(--color-accent);
|
|
101
|
+
box-shadow: 0 8px 30px -12px color-mix(in oklch, var(--color-accent) 40%, transparent);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.nav-links-top {
|
|
105
|
+
display: flex;
|
|
106
|
+
flex-wrap: wrap;
|
|
107
|
+
align-items: center;
|
|
108
|
+
gap: 0.25rem 1.25rem;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.nav-item {
|
|
112
|
+
position: relative;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.nav-link,
|
|
116
|
+
.nav-label {
|
|
117
|
+
display: inline-block;
|
|
118
|
+
padding-block: 0.25rem;
|
|
119
|
+
color: var(--color-muted);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.nav-link:hover,
|
|
123
|
+
.nav-link[aria-current] {
|
|
124
|
+
color: var(--color-ink);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.site-nav .nav-links-nested {
|
|
128
|
+
position: absolute;
|
|
129
|
+
top: 100%;
|
|
130
|
+
left: -1rem;
|
|
131
|
+
z-index: 20;
|
|
132
|
+
display: none;
|
|
133
|
+
min-width: 12rem;
|
|
134
|
+
border: 1px solid var(--color-line);
|
|
135
|
+
border-radius: 0.75rem;
|
|
136
|
+
background-color: var(--color-surface);
|
|
137
|
+
padding: 0.5rem 1rem;
|
|
138
|
+
box-shadow: 0 12px 30px -12px rgb(0 0 0 / 0.25);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.site-nav .nav-item:hover > .nav-links-nested,
|
|
142
|
+
.site-nav .nav-item:focus-within > .nav-links-nested {
|
|
143
|
+
display: block;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.footer-nav .nav-links-top {
|
|
147
|
+
align-items: flex-start;
|
|
148
|
+
gap: 1rem 2.5rem;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.footer-nav .nav-links-nested {
|
|
152
|
+
margin-top: 0.25rem;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
[data-rush-preview] {
|
|
156
|
+
position: sticky;
|
|
157
|
+
top: 0;
|
|
158
|
+
z-index: 40;
|
|
159
|
+
display: flex;
|
|
160
|
+
flex-wrap: wrap;
|
|
161
|
+
align-items: center;
|
|
162
|
+
justify-content: center;
|
|
163
|
+
gap: 0.5rem 1rem;
|
|
164
|
+
background-color: var(--color-ink);
|
|
165
|
+
padding: 0.5rem 1rem;
|
|
166
|
+
color: var(--color-canvas);
|
|
167
|
+
font-size: 0.875rem;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
[data-rush-preview] a {
|
|
171
|
+
font-weight: 600;
|
|
172
|
+
text-decoration: underline;
|
|
173
|
+
text-underline-offset: 3px;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.contact-form form {
|
|
177
|
+
display: grid;
|
|
178
|
+
gap: 1.5rem;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.contact-form label,
|
|
182
|
+
.contact-form legend {
|
|
183
|
+
display: block;
|
|
184
|
+
margin-bottom: 0.375rem;
|
|
185
|
+
font-weight: 500;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.contact-form fieldset label {
|
|
189
|
+
display: flex;
|
|
190
|
+
align-items: center;
|
|
191
|
+
gap: 0.5rem;
|
|
192
|
+
font-weight: 400;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.contact-form input:not([type='checkbox'], [type='radio']),
|
|
196
|
+
.contact-form textarea,
|
|
197
|
+
.contact-form select {
|
|
198
|
+
width: 100%;
|
|
199
|
+
border: 1px solid var(--color-line);
|
|
200
|
+
border-radius: 0.5rem;
|
|
201
|
+
background-color: var(--color-surface);
|
|
202
|
+
padding: 0.625rem 0.75rem;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.contact-form textarea {
|
|
206
|
+
min-height: 9rem;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.contact-form input:focus-visible,
|
|
210
|
+
.contact-form textarea:focus-visible,
|
|
211
|
+
.contact-form select:focus-visible {
|
|
212
|
+
outline-offset: 0;
|
|
213
|
+
border-color: var(--color-accent);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.contact-form input[type='checkbox'],
|
|
217
|
+
.contact-form input[type='radio'] {
|
|
218
|
+
accent-color: var(--color-accent);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.contact-form [id$='-help'] {
|
|
222
|
+
margin-top: 0.375rem;
|
|
223
|
+
color: var(--color-muted);
|
|
224
|
+
font-size: 0.875rem;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.contact-form [data-rush-error-for] {
|
|
228
|
+
margin-top: 0.375rem;
|
|
229
|
+
color: var(--color-danger);
|
|
230
|
+
font-size: 0.875rem;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.contact-form [data-rush-status] {
|
|
234
|
+
border: 1px solid var(--color-line);
|
|
235
|
+
border-radius: 0.5rem;
|
|
236
|
+
padding: 0.75rem 1rem;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.contact-form [data-rush-status][data-state='sent'] {
|
|
240
|
+
border-color: var(--color-success);
|
|
241
|
+
color: var(--color-success);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.contact-form [data-rush-status][data-state='error'] {
|
|
245
|
+
border-color: var(--color-danger);
|
|
246
|
+
color: var(--color-danger);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.contact-form button[type='submit'] {
|
|
250
|
+
justify-self: start;
|
|
251
|
+
cursor: pointer;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.contact-form button[type='submit']:disabled {
|
|
255
|
+
cursor: progress;
|
|
256
|
+
opacity: 0.7;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.prose {
|
|
261
|
+
--tw-prose-body: var(--color-ink);
|
|
262
|
+
--tw-prose-headings: var(--color-ink);
|
|
263
|
+
--tw-prose-lead: var(--color-muted);
|
|
264
|
+
--tw-prose-links: var(--color-accent);
|
|
265
|
+
--tw-prose-bold: var(--color-ink);
|
|
266
|
+
--tw-prose-counters: var(--color-muted);
|
|
267
|
+
--tw-prose-bullets: var(--color-line);
|
|
268
|
+
--tw-prose-hr: var(--color-line);
|
|
269
|
+
--tw-prose-quotes: var(--color-ink);
|
|
270
|
+
--tw-prose-quote-borders: var(--color-accent);
|
|
271
|
+
--tw-prose-captions: var(--color-muted);
|
|
272
|
+
--tw-prose-kbd: var(--color-ink);
|
|
273
|
+
--tw-prose-code: var(--color-ink);
|
|
274
|
+
--tw-prose-pre-code: var(--color-ink);
|
|
275
|
+
--tw-prose-pre-bg: var(--color-surface);
|
|
276
|
+
--tw-prose-th-borders: var(--color-line);
|
|
277
|
+
--tw-prose-td-borders: var(--color-line);
|
|
278
|
+
}
|