diff --git a/package.json b/package.json
index ff83f3f..b593388 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,9 @@
"build": "next build",
"start": "next start",
"lint": "next lint",
- "format": "prettier --write \"./src/**/*.{js,jsx,ts,tsx}\""
+ "format": "prettier --write \"./src/**/*.{js,jsx,ts,tsx}\"",
+ "park-ui": "npx @park-ui/cli",
+ "components:add": "npx @park-ui/cli components add"
},
"dependencies": {
"@ark-ui/react": "^3.9.0",
diff --git a/src/app/(frontend)/Moderustic-VariableFont_wght.ttf b/src/app/(frontend)/Moderustic-VariableFont_wght.ttf
new file mode 100644
index 0000000..a8d8a31
Binary files /dev/null and b/src/app/(frontend)/Moderustic-VariableFont_wght.ttf differ
diff --git a/src/app/(frontend)/[locale]/about/page.tsx b/src/app/(frontend)/[locale]/about/page.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/(frontend)/[locale]/layout.tsx b/src/app/(frontend)/[locale]/layout.tsx
index 10334fd..39d1268 100644
--- a/src/app/(frontend)/[locale]/layout.tsx
+++ b/src/app/(frontend)/[locale]/layout.tsx
@@ -1,19 +1,16 @@
import "../globals.css";
+import Footer from "@/components/layout/footer";
import { I18nProviderClient } from "@/i18n/client";
import type { Metadata } from "next";
+import Navbar from "@/components/layout/navbar";
import { Params } from "./params";
import localFont from "next/font/local";
+import { styled } from "@styled-system/jsx";
-const geistSans = localFont({
- src: "../fonts/GeistVF.woff",
- variable: "--font-geist-sans",
- weight: "100 900",
-});
-const geistMono = localFont({
- src: "../fonts/GeistMonoVF.woff",
- variable: "--font-geist-mono",
- weight: "100 900",
+const moderustic = localFont({
+ src: "../Moderustic-VariableFont_wght.ttf",
+ display: "swap",
});
export const metadata: Metadata = {
@@ -30,9 +27,13 @@ export default function RootLayout({
}>) {
return (
-
- {children}
-
+
+
+
+ {children}
+
+
+
);
}
diff --git a/src/app/(frontend)/[locale]/page.module.css b/src/app/(frontend)/[locale]/page.module.css
deleted file mode 100644
index ee9b8e6..0000000
--- a/src/app/(frontend)/[locale]/page.module.css
+++ /dev/null
@@ -1,168 +0,0 @@
-.page {
- --gray-rgb: 0, 0, 0;
- --gray-alpha-200: rgba(var(--gray-rgb), 0.08);
- --gray-alpha-100: rgba(var(--gray-rgb), 0.05);
-
- --button-primary-hover: #383838;
- --button-secondary-hover: #f2f2f2;
-
- display: grid;
- grid-template-rows: 20px 1fr 20px;
- align-items: center;
- justify-items: center;
- min-height: 100svh;
- padding: 80px;
- gap: 64px;
- font-family: var(--font-geist-sans);
-}
-
-@media (prefers-color-scheme: dark) {
- .page {
- --gray-rgb: 255, 255, 255;
- --gray-alpha-200: rgba(var(--gray-rgb), 0.145);
- --gray-alpha-100: rgba(var(--gray-rgb), 0.06);
-
- --button-primary-hover: #ccc;
- --button-secondary-hover: #1a1a1a;
- }
-}
-
-.main {
- display: flex;
- flex-direction: column;
- gap: 32px;
- grid-row-start: 2;
-}
-
-.main ol {
- font-family: var(--font-geist-mono);
- padding-left: 0;
- margin: 0;
- font-size: 14px;
- line-height: 24px;
- letter-spacing: -0.01em;
- list-style-position: inside;
-}
-
-.main li:not(:last-of-type) {
- margin-bottom: 8px;
-}
-
-.main code {
- font-family: inherit;
- background: var(--gray-alpha-100);
- padding: 2px 4px;
- border-radius: 4px;
- font-weight: 600;
-}
-
-.ctas {
- display: flex;
- gap: 16px;
-}
-
-.ctas a {
- appearance: none;
- border-radius: 128px;
- height: 48px;
- padding: 0 20px;
- border: none;
- border: 1px solid transparent;
- transition:
- background 0.2s,
- color 0.2s,
- border-color 0.2s;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 16px;
- line-height: 20px;
- font-weight: 500;
-}
-
-a.primary {
- background: var(--foreground);
- color: var(--background);
- gap: 8px;
-}
-
-a.secondary {
- border-color: var(--gray-alpha-200);
- min-width: 180px;
-}
-
-.footer {
- grid-row-start: 3;
- display: flex;
- gap: 24px;
-}
-
-.footer a {
- display: flex;
- align-items: center;
- gap: 8px;
-}
-
-.footer img {
- flex-shrink: 0;
-}
-
-/* Enable hover only on non-touch devices */
-@media (hover: hover) and (pointer: fine) {
- a.primary:hover {
- background: var(--button-primary-hover);
- border-color: transparent;
- }
-
- a.secondary:hover {
- background: var(--button-secondary-hover);
- border-color: transparent;
- }
-
- .footer a:hover {
- text-decoration: underline;
- text-underline-offset: 4px;
- }
-}
-
-@media (max-width: 600px) {
- .page {
- padding: 32px;
- padding-bottom: 80px;
- }
-
- .main {
- align-items: center;
- }
-
- .main ol {
- text-align: center;
- }
-
- .ctas {
- flex-direction: column;
- }
-
- .ctas a {
- font-size: 14px;
- height: 40px;
- padding: 0 16px;
- }
-
- a.secondary {
- min-width: auto;
- }
-
- .footer {
- flex-wrap: wrap;
- align-items: center;
- justify-content: center;
- }
-}
-
-@media (prefers-color-scheme: dark) {
- .logo {
- filter: invert();
- }
-}
diff --git a/src/app/(frontend)/[locale]/page.tsx b/src/app/(frontend)/[locale]/page.tsx
index eb2f0f7..571cf93 100644
--- a/src/app/(frontend)/[locale]/page.tsx
+++ b/src/app/(frontend)/[locale]/page.tsx
@@ -1,98 +1,8 @@
-import Image from "next/image";
+import { Box } from "styled-system/jsx";
import { getI18n } from "@/i18n/server";
-import styles from "./page.module.css";
export default async function Home() {
const t = await getI18n();
- return (
-
-
-
-
- -
- {t("general.getStarted")}
src/app/page.tsx
.
-
- - Save and see your changes instantly.
-
-
-
-
-
-
- );
+ return ;
}
diff --git a/src/app/(frontend)/globals.css b/src/app/(frontend)/globals.css
index 2284dc8..e27a23b 100644
--- a/src/app/(frontend)/globals.css
+++ b/src/app/(frontend)/globals.css
@@ -1,44 +1 @@
@layer reset, base, tokens, recipes, utilities;
-
-:root {
- --background: #ffffff;
- --foreground: #171717;
-}
-
-@media (prefers-color-scheme: dark) {
- :root {
- --background: #0a0a0a;
- --foreground: #ededed;
- }
-}
-
-html,
-body {
- max-width: 100vw;
- overflow-x: hidden;
-}
-
-body {
- color: var(--foreground);
- background: var(--background);
- font-family: Arial, Helvetica, sans-serif;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-
-* {
- box-sizing: border-box;
- padding: 0;
- margin: 0;
-}
-
-a {
- color: inherit;
- text-decoration: none;
-}
-
-@media (prefers-color-scheme: dark) {
- html {
- color-scheme: dark;
- }
-}
diff --git a/src/components/layout/footer.tsx b/src/components/layout/footer.tsx
new file mode 100644
index 0000000..c1e7672
--- /dev/null
+++ b/src/components/layout/footer.tsx
@@ -0,0 +1,15 @@
+import { Text } from "@/components/ui/text";
+import { stack } from "@styled-system/patterns";
+import { styled } from "@styled-system/jsx";
+
+export default async function Footer() {
+ return (
+
+ Powered by Jenyus
+
+ );
+}
diff --git a/src/components/layout/language-picker.tsx b/src/components/layout/language-picker.tsx
new file mode 100644
index 0000000..23723c4
--- /dev/null
+++ b/src/components/layout/language-picker.tsx
@@ -0,0 +1,41 @@
+"use client";
+
+import { defaultLocale, locales } from "@/i18n/locales";
+import { useChangeLocale, useCurrentLocale, useI18n } from "@/i18n/client";
+
+import { Button } from "@/components/ui/button";
+import { Menu } from "@/components/ui/menu";
+
+export default function LanguagePicker(props: Menu.RootProps) {
+ const changeLocale = useChangeLocale();
+ const locale = useCurrentLocale();
+ const t = useI18n();
+
+ return (
+
+
+
+
+
+
+
+ {locales.map((locale) => (
+ changeLocale(locale.code)}
+ >
+
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/src/components/layout/mobile-nav.tsx b/src/components/layout/mobile-nav.tsx
new file mode 100644
index 0000000..23e2ee0
--- /dev/null
+++ b/src/components/layout/mobile-nav.tsx
@@ -0,0 +1,68 @@
+"use client";
+
+import { Menu, X } from "lucide-react";
+
+import { IconButton } from "@/components/ui/icon-button";
+import Image from "next/image";
+import Link from "next/link";
+import { Media } from "@/payload-types";
+import NavLink from "@/components/layout/nav-link";
+import { css } from "@styled-system/css";
+import { styled } from "@styled-system/jsx";
+import { useI18n } from "@/i18n/client";
+import { useState } from "react";
+
+export default function MobileNav() {
+ const [show, setShow] = useState(false);
+ const t = useI18n();
+
+ return (
+
+ setShow((show) => !show)}>
+
+
+ setShow(false)}
+ >
+
+ setShow(false)}>
+
+
+
+
+
+ Localbites
+
+
+ {t("general.home")}
+ {t("general.about")}
+
+
+ {t("general.contact")}
+
+
+
+ );
+}
diff --git a/src/components/layout/nav-link.tsx b/src/components/layout/nav-link.tsx
new file mode 100644
index 0000000..f535826
--- /dev/null
+++ b/src/components/layout/nav-link.tsx
@@ -0,0 +1,36 @@
+import Link, { LinkProps } from "next/link";
+import { RecipeVariantProps, cva, cx } from "@styled-system/css";
+
+import { AnchorHTMLAttributes } from "react";
+
+const variants = cva({
+ base: { _hover: { color: "gray.800" } },
+ variants: {
+ type: {
+ button: {
+ background: "accent.9",
+ color: "white",
+ p: 2,
+ borderRadius: "md",
+ _hover: {
+ background: "accent.11",
+ },
+ },
+ },
+ },
+});
+
+export default function NavLink({
+ className,
+ children,
+ type,
+ ...props
+}: RecipeVariantProps &
+ Omit, keyof LinkProps> &
+ LinkProps) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/components/layout/navbar.tsx b/src/components/layout/navbar.tsx
new file mode 100644
index 0000000..46470d1
--- /dev/null
+++ b/src/components/layout/navbar.tsx
@@ -0,0 +1,57 @@
+import { Box, styled } from "@styled-system/jsx";
+
+import LanguagePicker from "./language-picker";
+import Link from "next/link";
+import MobileNav from "./mobile-nav";
+import NavLink from "./nav-link";
+import { css } from "@styled-system/css";
+import { flex } from "@styled-system/patterns";
+import { getI18n } from "@/i18n/server";
+
+export default async function Navbar() {
+ const t = await getI18n();
+
+ return (
+ <>
+
+
+ Localbites
+
+
+
+
+ {t("general.about")}
+
+
+
+ {t("general.contact")}
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/components/ui/heading.tsx b/src/components/ui/heading.tsx
new file mode 100644
index 0000000..6c736e3
--- /dev/null
+++ b/src/components/ui/heading.tsx
@@ -0,0 +1 @@
+export { Heading, type HeadingProps } from './styled/heading'
diff --git a/src/components/ui/icon-button.tsx b/src/components/ui/icon-button.tsx
new file mode 100644
index 0000000..394bce2
--- /dev/null
+++ b/src/components/ui/icon-button.tsx
@@ -0,0 +1 @@
+export { IconButton, type IconButtonProps } from './styled/icon-button'
diff --git a/src/components/ui/menu.tsx b/src/components/ui/menu.tsx
new file mode 100644
index 0000000..8884a71
--- /dev/null
+++ b/src/components/ui/menu.tsx
@@ -0,0 +1 @@
+export * as Menu from './styled/menu'
diff --git a/src/components/ui/spinner.tsx b/src/components/ui/spinner.tsx
new file mode 100644
index 0000000..cdc9d6e
--- /dev/null
+++ b/src/components/ui/spinner.tsx
@@ -0,0 +1,29 @@
+import { forwardRef } from 'react'
+import { styled } from 'styled-system/jsx'
+import { Spinner as StyledSpinner, type SpinnerProps as StyledSpinnerProps } from './styled/spinner'
+
+export interface SpinnerProps extends StyledSpinnerProps {
+ /**
+ * For accessibility, it is important to add a fallback loading text.
+ * This text will be visible to screen readers.
+ * @default "Loading..."
+ */
+ label?: string
+}
+
+export const Spinner = forwardRef((props, ref) => {
+ const { label = 'Loading...', ...rest } = props
+
+ return (
+
+ {label && {label}}
+
+ )
+})
+
+Spinner.displayName = 'Spinner'
diff --git a/src/components/ui/styled/heading.tsx b/src/components/ui/styled/heading.tsx
new file mode 100644
index 0000000..c901b1b
--- /dev/null
+++ b/src/components/ui/styled/heading.tsx
@@ -0,0 +1,10 @@
+import { styled } from 'styled-system/jsx'
+import { type TextVariantProps, text } from 'styled-system/recipes'
+import type { ComponentProps, StyledComponent } from 'styled-system/types'
+
+type TextProps = TextVariantProps & { as?: React.ElementType }
+
+export type HeadingProps = ComponentProps
+export const Heading = styled('h2', text, {
+ defaultProps: { variant: 'heading' },
+}) as StyledComponent<'h2', TextProps>
diff --git a/src/components/ui/styled/icon-button.tsx b/src/components/ui/styled/icon-button.tsx
new file mode 100644
index 0000000..69f6dc3
--- /dev/null
+++ b/src/components/ui/styled/icon-button.tsx
@@ -0,0 +1,9 @@
+import { ark } from '@ark-ui/react/factory'
+import { styled } from 'styled-system/jsx'
+import { type ButtonVariantProps, button } from 'styled-system/recipes'
+import type { ComponentProps } from 'styled-system/types'
+
+export type IconButtonProps = ComponentProps
+export const IconButton = styled(ark.button, button, {
+ defaultProps: { px: '0' } as ButtonVariantProps,
+})
diff --git a/src/components/ui/styled/menu.tsx b/src/components/ui/styled/menu.tsx
new file mode 100644
index 0000000..974398a
--- /dev/null
+++ b/src/components/ui/styled/menu.tsx
@@ -0,0 +1,103 @@
+'use client'
+import type { Assign } from '@ark-ui/react'
+import { Menu } from '@ark-ui/react/menu'
+import { type MenuVariantProps, menu } from 'styled-system/recipes'
+import type { ComponentProps, HTMLStyledProps } from 'styled-system/types'
+import { createStyleContext } from './utils/create-style-context'
+
+const { withRootProvider, withContext } = createStyleContext(menu)
+
+export type RootProviderProps = ComponentProps
+export const RootProvider = withRootProvider>(
+ Menu.RootProvider,
+)
+
+export type RootProps = ComponentProps
+export const Root = withRootProvider>(Menu.Root)
+
+export const Arrow = withContext<
+ HTMLDivElement,
+ Assign, Menu.ArrowBaseProps>
+>(Menu.Arrow, 'arrow')
+
+export const ArrowTip = withContext<
+ HTMLDivElement,
+ Assign, Menu.ArrowTipBaseProps>
+>(Menu.ArrowTip, 'arrowTip')
+
+export const CheckboxItem = withContext<
+ HTMLDivElement,
+ Assign, Menu.CheckboxItemBaseProps>
+>(Menu.CheckboxItem, 'item')
+
+export const Content = withContext<
+ HTMLDivElement,
+ Assign, Menu.ContentBaseProps>
+>(Menu.Content, 'content')
+
+export const ContextTrigger = withContext<
+ HTMLButtonElement,
+ Assign, Menu.ContextTriggerBaseProps>
+>(Menu.ContextTrigger, 'contextTrigger')
+
+export const Indicator = withContext<
+ HTMLDivElement,
+ Assign, Menu.IndicatorBaseProps>
+>(Menu.Indicator, 'indicator')
+
+export const ItemGroupLabel = withContext<
+ HTMLDivElement,
+ Assign, Menu.ItemGroupLabelBaseProps>
+>(Menu.ItemGroupLabel, 'itemGroupLabel')
+
+export const ItemGroup = withContext<
+ HTMLDivElement,
+ Assign, Menu.ItemGroupBaseProps>
+>(Menu.ItemGroup, 'itemGroup')
+
+export const ItemIndicator = withContext<
+ HTMLDivElement,
+ Assign, Menu.ItemIndicatorBaseProps>
+>(Menu.ItemIndicator, 'itemIndicator')
+
+export const Item = withContext, Menu.ItemBaseProps>>(
+ Menu.Item,
+ 'item',
+)
+
+export const ItemText = withContext<
+ HTMLDivElement,
+ Assign, Menu.ItemTextBaseProps>
+>(Menu.ItemText, 'itemText')
+
+export const Positioner = withContext<
+ HTMLDivElement,
+ Assign, Menu.PositionerBaseProps>
+>(Menu.Positioner, 'positioner')
+
+export const RadioItemGroup = withContext<
+ HTMLDivElement,
+ Assign, Menu.RadioItemGroupBaseProps>
+>(Menu.RadioItemGroup, 'itemGroup')
+
+export const RadioItem = withContext<
+ HTMLDivElement,
+ Assign, Menu.RadioItemBaseProps>
+>(Menu.RadioItem, 'item')
+
+export const Separator = withContext<
+ HTMLHRElement,
+ Assign, Menu.SeparatorBaseProps>
+>(Menu.Separator, 'separator')
+
+export const TriggerItem = withContext<
+ HTMLDivElement,
+ Assign, Menu.TriggerItemBaseProps>
+>(Menu.TriggerItem, 'triggerItem')
+
+export const Trigger = withContext<
+ HTMLButtonElement,
+ Assign, Menu.TriggerBaseProps>
+>(Menu.Trigger, 'trigger')
+
+export { MenuContext as Context } from '@ark-ui/react/menu'
diff --git a/src/components/ui/styled/spinner.tsx b/src/components/ui/styled/spinner.tsx
new file mode 100644
index 0000000..c818624
--- /dev/null
+++ b/src/components/ui/styled/spinner.tsx
@@ -0,0 +1,7 @@
+import { ark } from '@ark-ui/react/factory'
+import { styled } from 'styled-system/jsx'
+import { spinner } from 'styled-system/recipes'
+import type { ComponentProps } from 'styled-system/types'
+
+export type SpinnerProps = ComponentProps
+export const Spinner = styled(ark.div, spinner)
diff --git a/src/components/ui/styled/text.tsx b/src/components/ui/styled/text.tsx
new file mode 100644
index 0000000..6af2baf
--- /dev/null
+++ b/src/components/ui/styled/text.tsx
@@ -0,0 +1,8 @@
+import { styled } from 'styled-system/jsx'
+import { type TextVariantProps, text } from 'styled-system/recipes'
+import type { ComponentProps, StyledComponent } from 'styled-system/types'
+
+type ParagraphProps = TextVariantProps & { as?: React.ElementType }
+
+export type TextProps = ComponentProps
+export const Text = styled('p', text) as StyledComponent<'p', ParagraphProps>
diff --git a/src/components/ui/text.tsx b/src/components/ui/text.tsx
new file mode 100644
index 0000000..8cba639
--- /dev/null
+++ b/src/components/ui/text.tsx
@@ -0,0 +1 @@
+export { Text, type TextProps } from './styled/text'
diff --git a/tsconfig.json b/tsconfig.json
index 674ced2..f77399a 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -26,6 +26,9 @@
"@/*": [
"./src/*"
],
+ "@styled-system/*": [
+ "./styled-system/*"
+ ],
"styled-system/*": [
"./styled-system/*"
],