create-audora-next 0.1.7 → 2.0.1

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.
Files changed (63) hide show
  1. package/README.md +25 -5
  2. package/assets/audora-blog.png +0 -0
  3. package/assets/audora-next.webp +0 -0
  4. package/index.ts +9 -4
  5. package/package.json +8 -2
  6. package/templates/blog/README.md +164 -0
  7. package/templates/blog/bun.lock +1341 -0
  8. package/templates/blog/env.example.template +5 -0
  9. package/templates/blog/eslint.config.mjs +18 -0
  10. package/templates/blog/gitignore.template +41 -0
  11. package/templates/blog/husky.template/pre-commit +16 -0
  12. package/templates/blog/lint-staged.config.mjs +17 -0
  13. package/templates/blog/next.config.ts +38 -0
  14. package/templates/blog/package.json +59 -0
  15. package/templates/blog/postcss.config.mjs +7 -0
  16. package/templates/blog/public/favicon/apple-touch-icon.png +0 -0
  17. package/templates/blog/public/favicon/favicon-96x96.png +0 -0
  18. package/templates/blog/public/favicon/favicon.ico +0 -0
  19. package/templates/blog/public/favicon/favicon.svg +1 -0
  20. package/templates/blog/public/favicon/site.webmanifest +21 -0
  21. package/templates/blog/public/favicon/web-app-manifest-192x192.png +0 -0
  22. package/templates/blog/public/favicon/web-app-manifest-512x512.png +0 -0
  23. package/templates/blog/public/images/screenshot-desktop-dark.webp +0 -0
  24. package/templates/blog/public/images/screenshot-desktop-light.webp +0 -0
  25. package/templates/blog/public/images/screenshot-mobile-dark.webp +0 -0
  26. package/templates/blog/public/images/screenshot-mobile-light.webp +0 -0
  27. package/templates/blog/src/app/blogs/[slug]/page.tsx +171 -0
  28. package/templates/blog/src/app/blogs/page.tsx +108 -0
  29. package/templates/blog/src/app/layout.tsx +60 -0
  30. package/templates/blog/src/app/llms-full.txt/route.ts +97 -0
  31. package/templates/blog/src/app/llms.txt/route.ts +40 -0
  32. package/templates/blog/src/app/manifest.ts +61 -0
  33. package/templates/blog/src/app/page.tsx +57 -0
  34. package/templates/blog/src/app/robots.ts +16 -0
  35. package/templates/blog/src/app/sitemap.ts +52 -0
  36. package/templates/blog/src/blogs/components/animated-blog-list.tsx +33 -0
  37. package/templates/blog/src/blogs/components/blog-post-card.tsx +46 -0
  38. package/templates/blog/src/blogs/components/blog-section.tsx +34 -0
  39. package/templates/blog/src/blogs/components/blog-table-of-contents.tsx +369 -0
  40. package/templates/blog/src/blogs/components/copy-button.tsx +46 -0
  41. package/templates/blog/src/blogs/components/mdx.tsx +225 -0
  42. package/templates/blog/src/blogs/content/cosketch/cosketch-canvas-engine.mdx +186 -0
  43. package/templates/blog/src/blogs/content/cosketch/cosketch-docker-architecture.mdx +175 -0
  44. package/templates/blog/src/blogs/content/cosketch/cosketch-eraser-and-selection.mdx +207 -0
  45. package/templates/blog/src/blogs/content/hello-world.mdx +66 -0
  46. package/templates/blog/src/blogs/data/mdx.ts +68 -0
  47. package/templates/blog/src/blogs/utils/extract-headings.ts +38 -0
  48. package/templates/blog/src/components/copyable-code.tsx +41 -0
  49. package/templates/blog/src/components/footer.tsx +25 -0
  50. package/templates/blog/src/components/header.tsx +27 -0
  51. package/templates/blog/src/components/icons.tsx +84 -0
  52. package/templates/blog/src/components/section-heading.tsx +11 -0
  53. package/templates/blog/src/components/theme-provider.tsx +11 -0
  54. package/templates/blog/src/components/theme-toggle.tsx +20 -0
  55. package/templates/blog/src/components/view-all-link.tsx +56 -0
  56. package/templates/blog/src/config/site.ts +19 -0
  57. package/templates/blog/src/data/llms.ts +112 -0
  58. package/templates/blog/src/data/site.ts +52 -0
  59. package/templates/blog/src/lib/seo.ts +190 -0
  60. package/templates/blog/src/lib/utils.ts +83 -0
  61. package/templates/blog/src/styles/globals.css +99 -0
  62. package/templates/blog/src/utils/cn.ts +7 -0
  63. package/templates/blog/tsconfig.json +34 -0
@@ -0,0 +1,190 @@
1
+ import type {
2
+ WebSite,
3
+ Organization,
4
+ BreadcrumbList,
5
+ WithContext,
6
+ } from "schema-dts";
7
+ import type { Metadata, Viewport } from "next";
8
+ import { SITE_CONFIG, META_THEME_COLORS } from "@/config/site";
9
+
10
+ // Page-Specific Metadata
11
+ interface PageMetadataProps {
12
+ title: string;
13
+ description?: string;
14
+ image?: string;
15
+ path?: string;
16
+ }
17
+
18
+ export function getPageMetadata({
19
+ title,
20
+ description,
21
+ image,
22
+ path = "",
23
+ }: PageMetadataProps): Metadata {
24
+ const metaTitle = `${title} - ${SITE_CONFIG.name}`;
25
+ const metaDescription = description ?? SITE_CONFIG.description;
26
+ const metaImage = image ?? SITE_CONFIG.ogImage;
27
+ const sitePath = path
28
+ ? `${SITE_CONFIG.url.replace(/\/$/, "")}/${path.replace(/^\//, "")}`
29
+ : SITE_CONFIG.url;
30
+
31
+ return {
32
+ metadataBase: new URL(SITE_CONFIG.url),
33
+ title,
34
+ description: metaDescription,
35
+ alternates: {
36
+ canonical: sitePath,
37
+ },
38
+ openGraph: {
39
+ title: metaTitle,
40
+ description: metaDescription,
41
+ url: sitePath,
42
+ siteName: SITE_CONFIG.name,
43
+ images: [
44
+ {
45
+ url: metaImage,
46
+ width: 1200,
47
+ height: 630,
48
+ alt: title,
49
+ },
50
+ ],
51
+ locale: "en_US",
52
+ type: "website",
53
+ },
54
+ twitter: {
55
+ title: metaTitle,
56
+ description: metaDescription,
57
+ card: "summary_large_image",
58
+ images: [metaImage],
59
+ },
60
+ };
61
+ }
62
+
63
+ // Default Site Metadata
64
+ export function getMetadata(): Metadata {
65
+ return {
66
+ metadataBase: new URL(SITE_CONFIG.url),
67
+ alternates: {
68
+ canonical: "/",
69
+ },
70
+
71
+ title: {
72
+ template: `%s - ${SITE_CONFIG.name}`,
73
+ default: `${SITE_CONFIG.name} - ${SITE_CONFIG.tagline}`,
74
+ },
75
+
76
+ description: SITE_CONFIG.description,
77
+ keywords: SITE_CONFIG.keywords,
78
+ authors: [
79
+ {
80
+ name: SITE_CONFIG.name,
81
+ url: SITE_CONFIG.url,
82
+ },
83
+ ],
84
+
85
+ creator: SITE_CONFIG.name,
86
+ openGraph: {
87
+ siteName: SITE_CONFIG.name,
88
+ url: SITE_CONFIG.url,
89
+ type: "website",
90
+ locale: "en_US",
91
+ title: `${SITE_CONFIG.name} - ${SITE_CONFIG.tagline}`,
92
+ description: SITE_CONFIG.shortDescription,
93
+ images: [
94
+ {
95
+ url: SITE_CONFIG.ogImage,
96
+ width: 1200,
97
+ height: 630,
98
+ alt: SITE_CONFIG.name,
99
+ },
100
+ ],
101
+ },
102
+
103
+ twitter: {
104
+ card: "summary_large_image",
105
+ site: SITE_CONFIG.twitterHandle,
106
+ creator: SITE_CONFIG.twitterHandle,
107
+ title: `${SITE_CONFIG.name} - ${SITE_CONFIG.tagline}`,
108
+ description: SITE_CONFIG.shortDescription,
109
+ images: [SITE_CONFIG.ogImage],
110
+ },
111
+
112
+ icons: {
113
+ icon: [
114
+ { url: "/favicon/favicon.ico", sizes: "any" },
115
+ { url: "/favicon/favicon.svg", type: "image/svg+xml" },
116
+ ],
117
+ apple: {
118
+ url: "/favicon/apple-touch-icon.png",
119
+ type: "image/png",
120
+ sizes: "180x180",
121
+ },
122
+ },
123
+
124
+ robots: {
125
+ index: true,
126
+ follow: true,
127
+ },
128
+ };
129
+ }
130
+
131
+ // Viewport with Theme Colors
132
+ export function getViewport(): Viewport {
133
+ return {
134
+ width: "device-width",
135
+ initialScale: 1,
136
+ maximumScale: 5,
137
+ themeColor: [
138
+ {
139
+ media: "(prefers-color-scheme: light)",
140
+ color: META_THEME_COLORS.light,
141
+ },
142
+ { media: "(prefers-color-scheme: dark)", color: META_THEME_COLORS.dark },
143
+ ],
144
+ };
145
+ }
146
+
147
+ // Structured Data (JSON-LD)
148
+ export function getWebSiteJsonLd(): WithContext<WebSite> {
149
+ return {
150
+ "@context": "https://schema.org",
151
+ "@type": "WebSite",
152
+ name: SITE_CONFIG.name,
153
+ url: SITE_CONFIG.url,
154
+ alternateName: SITE_CONFIG.alternateNames,
155
+ };
156
+ }
157
+
158
+ export function getOrganizationJsonLd(): WithContext<Organization> {
159
+ return {
160
+ "@context": "https://schema.org",
161
+ "@type": "Organization",
162
+ name: SITE_CONFIG.name,
163
+ url: SITE_CONFIG.url,
164
+ logo: `${SITE_CONFIG.url}/favicon/favicon.svg`,
165
+ sameAs: [
166
+ `https://twitter.com/${SITE_CONFIG.twitterHandle.replace("@", "")}`,
167
+ "https://github.com/AudoraLabs",
168
+ ],
169
+ };
170
+ }
171
+
172
+ interface BreadcrumbItem {
173
+ name: string;
174
+ url: string;
175
+ }
176
+
177
+ export function getBreadcrumbJsonLd(
178
+ items: BreadcrumbItem[],
179
+ ): WithContext<BreadcrumbList> {
180
+ return {
181
+ "@context": "https://schema.org",
182
+ "@type": "BreadcrumbList",
183
+ itemListElement: items.map((item, index) => ({
184
+ "@type": "ListItem",
185
+ position: index + 1,
186
+ name: item.name,
187
+ item: item.url,
188
+ })),
189
+ };
190
+ }
@@ -0,0 +1,83 @@
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
7
+
8
+ const MONTH_NAMES = [
9
+ "Jan",
10
+ "Feb",
11
+ "Mar",
12
+ "Apr",
13
+ "May",
14
+ "Jun",
15
+ "Jul",
16
+ "Aug",
17
+ "Sep",
18
+ "Oct",
19
+ "Nov",
20
+ "Dec",
21
+ ] as const;
22
+
23
+ /**
24
+ * Formats employment period date strings for display.
25
+ * - "MM.YYYY" → "Aug 2025"
26
+ * - "YYYY" → "2022"
27
+ * - "Present" → "Present"
28
+ */
29
+ export function formatEmploymentDate(value: string): string {
30
+ if (value === "Present") return value;
31
+ const mmYyyy = /^(\d{1,2})\.(\d{4})$/.exec(value);
32
+ if (mmYyyy) {
33
+ const monthIndex = parseInt(mmYyyy[1], 10) - 1;
34
+ const year = mmYyyy[2];
35
+ if (monthIndex >= 0 && monthIndex <= 11) {
36
+ return `${MONTH_NAMES[monthIndex]} ${year}`;
37
+ }
38
+ }
39
+ if (/^\d{4}$/.test(value)) return value;
40
+ return value;
41
+ }
42
+
43
+ /**
44
+ * Formats an ISO or date-only string for display as "Jan 03, 2026".
45
+ */
46
+ export function formatDateDisplay(date: string): string {
47
+ if (!date.includes("T")) {
48
+ date = `${date}T00:00:00`;
49
+ }
50
+ return new Date(date).toLocaleDateString("en-US", {
51
+ month: "short",
52
+ day: "2-digit",
53
+ year: "numeric",
54
+ });
55
+ }
56
+
57
+ export function formatDate(date: string) {
58
+ const currentDate = new Date().getTime();
59
+ if (!date.includes("T")) {
60
+ date = `${date}T00:00:00`;
61
+ }
62
+ const targetDate = new Date(date).getTime();
63
+ const timeDifference = Math.abs(currentDate - targetDate);
64
+ const daysAgo = Math.floor(timeDifference / (1000 * 60 * 60 * 24));
65
+
66
+ const fullDate = new Date(date).toLocaleString("en-us", {
67
+ month: "long",
68
+ day: "numeric",
69
+ year: "numeric",
70
+ });
71
+
72
+ if (daysAgo < 1) {
73
+ return "Today";
74
+ } else if (daysAgo < 7) {
75
+ return `${fullDate} (${daysAgo}d ago)`;
76
+ } else if (daysAgo < 30) {
77
+ return `${fullDate} (${Math.floor(daysAgo / 7)}w ago)`;
78
+ } else if (daysAgo < 365) {
79
+ return `${fullDate} (${Math.floor(daysAgo / 30)}mo ago)`;
80
+ } else {
81
+ return `${fullDate} (${Math.floor(daysAgo / 365)}y ago)`;
82
+ }
83
+ }
@@ -0,0 +1,99 @@
1
+ @import "tailwindcss";
2
+
3
+ /* Custom dark variant for Tailwind CSS v4 */
4
+ @custom-variant dark (&:where(.dark, .dark *));
5
+
6
+ /* Root color variables using oklch for a muted, premium look */
7
+ :root {
8
+ /* Muted Slate/Zinc Tone */
9
+ --background: oklch(0.985 0.002 260); /* Very softened white */
10
+ --foreground: oklch(0.21 0.015 260); /* Muted slate black */
11
+
12
+ --card: oklch(1 0 0);
13
+ --card-foreground: oklch(0.21 0.015 260);
14
+
15
+ --popover: oklch(1 0 0);
16
+ --popover-foreground: oklch(0.21 0.015 260);
17
+
18
+ --primary: oklch(0.21 0.02 260); /* Deep slate primary */
19
+ --primary-foreground: oklch(0.985 0 0);
20
+
21
+ --secondary: oklch(0.96 0.005 260);
22
+ --secondary-foreground: oklch(0.21 0.015 260);
23
+
24
+ --muted: oklch(0.96 0.005 260);
25
+ --muted-foreground: oklch(0.55 0.015 260); /* Softer muted text */
26
+
27
+ --accent: oklch(0.96 0.005 260);
28
+ --accent-foreground: oklch(0.21 0.015 260);
29
+
30
+ --destructive: oklch(0.65 0.2 23);
31
+ --destructive-foreground: oklch(0.985 0 0);
32
+
33
+ --border: oklch(0.92 0.005 260);
34
+ --input: oklch(0.92 0.005 260);
35
+ --ring: oklch(0.21 0.015 260);
36
+
37
+ --radius: 0.625rem;
38
+
39
+ /** Content max width – change here to update hero, header, and blog layout */
40
+ --content-max-width: 64rem; /* 1024px = 5xl */
41
+ }
42
+
43
+ .dark {
44
+ --background: #0b0d0e; /* Deep dark bg */
45
+ --foreground: oklch(0.92 0.005 260); /* Softened white */
46
+
47
+ --card: oklch(0.12 0.01 260);
48
+ --card-foreground: oklch(0.92 0.005 260);
49
+
50
+ --popover: oklch(0.12 0.01 260);
51
+ --popover-foreground: oklch(0.92 0.005 260);
52
+
53
+ --primary: oklch(0.92 0.005 260);
54
+ --primary-foreground: oklch(0.12 0.01 260);
55
+
56
+ --secondary: oklch(0.2 0.01 260);
57
+ --secondary-foreground: oklch(0.92 0.005 260);
58
+
59
+ --muted: oklch(0.2 0.01 260);
60
+ --muted-foreground: oklch(0.65 0.01 260);
61
+
62
+ --accent: oklch(0.2 0.01 260);
63
+ --accent-foreground: oklch(0.92 0.005 260);
64
+
65
+ --destructive: oklch(0.5 0.2 23);
66
+ --destructive-foreground: oklch(0.92 0.005 260);
67
+
68
+ --border: oklch(0.22 0.01 260);
69
+ --input: oklch(0.22 0.01 260);
70
+ --ring: oklch(0.8 0 0);
71
+ }
72
+
73
+ @theme inline {
74
+ --color-background: var(--background);
75
+ --color-foreground: var(--foreground);
76
+ --color-border: var(--border);
77
+ --color-muted: var(--muted);
78
+ --color-muted-foreground: var(--muted-foreground);
79
+ --color-card: var(--card);
80
+ --color-primary: var(--primary);
81
+ --color-primary-foreground: var(--primary-foreground);
82
+ --color-secondary: var(--secondary);
83
+ --color-secondary-foreground: var(--secondary-foreground);
84
+ --color-accent: var(--accent);
85
+ --color-accent-foreground: var(--accent-foreground);
86
+ --color-destructive: var(--destructive);
87
+ --color-destructive-foreground: var(--destructive-foreground);
88
+ --color-input: var(--input);
89
+ --color-ring: var(--ring);
90
+ --radius-radius: var(--radius);
91
+ --font-sans: var(--font-geist-sans);
92
+ --font-mono: var(--font-geist-mono);
93
+ }
94
+
95
+ body {
96
+ background: var(--background);
97
+ color: var(--foreground);
98
+ font-family: var(--font-geist-sans), Arial, Helvetica, sans-serif;
99
+ }
@@ -0,0 +1,7 @@
1
+ import type { ClassValue } from "clsx";
2
+ import { clsx } from "clsx";
3
+ import { twMerge } from "tailwind-merge";
4
+
5
+ export const cn = (...inputs: ClassValue[]) => {
6
+ return twMerge(clsx(inputs));
7
+ };
@@ -0,0 +1,34 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "react-jsx",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./src/*"]
23
+ }
24
+ },
25
+ "include": [
26
+ "next-env.d.ts",
27
+ "**/*.ts",
28
+ "**/*.tsx",
29
+ ".next/types/**/*.ts",
30
+ ".next/dev/types/**/*.ts",
31
+ "**/*.mts"
32
+ ],
33
+ "exclude": ["node_modules"]
34
+ }