diff --git a/app/globals.css b/app/globals.css index 875c01e..a5a2a84 100644 --- a/app/globals.css +++ b/app/globals.css @@ -2,32 +2,37 @@ @tailwind components; @tailwind utilities; -:root { - --foreground-rgb: 0, 0, 0; - --background-start-rgb: 214, 219, 220; - --background-end-rgb: 255, 255, 255; -} - -@media (prefers-color-scheme: dark) { - :root { - --foreground-rgb: 255, 255, 255; - --background-start-rgb: 0, 0, 0; - --background-end-rgb: 0, 0, 0; +@layer base { + * { + @apply text-white + } + body { + @apply bg-mixed-100 + } + input { + @apply py-2 px-4 rounded-full bg-mixed-200 placeholder:text-dark-500 + } + h1,h2,h3,h4,h5,h6 { + @apply font-bold + } + p { + @apply py-1 } } -body { - color: rgb(var(--foreground-rgb)); - background: linear-gradient( - to bottom, - transparent, - rgb(var(--background-end-rgb)) - ) - rgb(var(--background-start-rgb)); -} +@layer components { + .strapline { + @apply text-primary-500 uppercase font-bold mb-2 text-lg + } -@layer utilities { - .text-balance { - text-wrap: balance; + .card { + @apply bg-mixed-200 rounded-3xl p-6 shadow-2xl + } + + .btn-primary { + @apply bg-primary-500 py-4 px-6 text-mixed-100 rounded-full font-bold text-lg + } + .btn-secondary { + @apply text-primary-500 py-4 px-6 font-bold text-lg } } diff --git a/app/layout.tsx b/app/layout.tsx index 3314e47..cb9e8bd 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,6 +1,12 @@ import type { Metadata } from "next"; import { Inter } from "next/font/google"; import "./globals.css"; +import { + BookOpenIcon, + CircleStackIcon, + Cog8ToothIcon, + PuzzlePieceIcon, +} from "@heroicons/react/24/solid"; const inter = Inter({ subsets: ["latin"] }); @@ -16,7 +22,30 @@ export default function RootLayout({ }>) { return ( -
{children} + + +
- Get started by editing
- app/page.tsx
-
+ Tabletop Commander (TC) is a rules-and-tools app for tabletop games + - board, card, war, role-playing, you name it! It is the spiritual + successor of Chapter Master by Emmaline Autumn, a Warhammer 40,000 + 9th Edition rules reference and game helper. +
++ Emma decided to move on from Chapter Master as her interest in 40k + was supplanted by the greater wargaming hobby after the release of + 10th edition made clear that Chapter Master was too inflexible and + tedious to work on. +
++ See, Emma had a vision that anyone could contribute to making rules + corrections so that anyone could have all of the rules as they + currently exist. This ballooned into the idea that you could have + all the rules as they existed at any time +
++ The basis of TC is called a Game System Package. This package + includes everything needed for a game system, including schemas, + publications, and tools. Players can follow a Game System to get + consistently updated content publications, or fork it to + maintain it themselves. +
++ The neat part is that no one does! You can contribute to any + Game System with updates to publications and schemas through a + community review system. Those with the high enough scores + contribute more towards a total approval score which is used to + determine whether the Game System. The more your contributions + are approved, the higher your score becomes, the more weight + your approval and contributions carry. +
++ If your score is high enough, and a contribution request has + enough approvals, you can even be the one to merge it in! +
++ Those who have studied English or databases, you would know that + a schema is a structural pattern. TC aims to provide a simple, + user-edited and maintained schema system for any game. +
++ If that flew over your head, don't worry. Others can share + the schemas they've made with everyone, which come as part + of a Game System package that you can fork or follow to get both + content and schemas ready to use. +
++ The schema system makes use of a powerful custom query language + (tcQuery) I designed. By writing queries directly into the + schema, we can reduce the amount of re-written content, while + maintaining the presence of data anywhere we need it. +
++ Publications are the actual content of the rules. They + don't just contain the content, but also the style in which + the content is shown. +
++ Content can include text, images, and even video (through + YouTube links or external embeds). Content can link to other + parts of the publication through context based pop-overs. +
++ Publications use an enhanced markdown syntax (tcMD) that + implements tcQuery, and adds a bit of custom syntax for things + like pop-overs and styling hints for rendering. +
++ The styling aspect is similar to a very trimmed down CSS, but + can accomplish quite a lot. For example, this page is actually + built using tcMD!* +
+- Find in-depth information about Next.js features and API. -
- - - -- Learn about Next.js in an interactive course with quizzes! -
- - - -- Explore starter templates for Next.js. -
- - - -- Instantly deploy your Next.js site to a shareable URL with Vercel. -
- -{JSON.stringify(elements,null,2)}+ > + ); +}; + +const createElements = (body: string) => { + const tokens = tokenize(body); + + return tokens; +}; + +type InlineToken = { + type: "text" | "bold"; + content: string; +}; + +type Line = string | InlineToken[]; + +type MultilineToken = { + type: "code"; + lines: Token[]; +}; + +type Token = { + type: "h1" | "h2" | "h3" | "p"; + line: Line; +}; + +const tokenize = (md: string) => { + const tokens: (Token | MultilineToken)[] = []; + md = md.replace(/(?<=[a-z])\n(?=[a-z])/g, " "); + const lines = md.split("\n"); + const multilineFlags = { + heading: 0, + }; + + const tokenMatches = [ + { + rx: /^\s*#\s/, + create: (line: Line) => tokens.push({ type: "h1", line }), + }, + { + rx: /^\s*##\s/, + create: (line: Line) => tokens.push({ type: "h2", line }), + }, + { + rx: /^\s*###\s/, + create: (line: Line) => tokens.push({ type: "h3", line }), + }, + ]; + + for (let line of lines) { + let foundLine = false; + token: + for (const token of tokenMatches) { + if (!token.rx.test(line)) continue token; + foundLine = true; + line = line.replace(token.rx, "").trim(); + + const lineContent = tokenizeInline(line); + token.create(lineContent); + } + + if (foundLine) continue; + + tokens.push({ + type: "p", + line: tokenizeInline(line), + }); + } + + console.log(tokens); + return tokens.filter((t) => (t as Token).line || (t as MultilineToken).lines); +}; + +const tokenizeInline = (line: string) => { + line = line.trim(); + const originalLine = line; + const insertMarker = "{^}"; + const tokens: InlineToken[] = []; + + const tokenMatches = [ + { + rx: /\*\*(.*?)\*\*/g, + create: (content: string) => + tokens.push({ + content, + type: "bold", + }), + }, + ]; + for (const token of tokenMatches) { + let match; + let last = 0; + while ((match = token.rx.exec(line)) !== null) { + const tokenStart = match.index; + const tokenEnd = match.index + match[0].length; + console.log(tokenEnd, token.rx.lastIndex); + token.create(line.substring(tokenStart, tokenEnd)); + line = line.slice(last, tokenStart) + "{^}" + + line.slice(tokenEnd, line.length); + last = tokenEnd; + } + } + + if (tokens.length) { + return zipArrays( + line.split(insertMarker).map((t): InlineToken => ({ + content: t, + type: "text", + })), + tokens, + ).filter((t) => t.content); + } + return originalLine; +}; diff --git a/lib/zip.ts b/lib/zip.ts new file mode 100644 index 0000000..3e09e27 --- /dev/null +++ b/lib/zip.ts @@ -0,0 +1,17 @@ +export function zipArrays