create-ampless 0.2.0-alpha.8 → 1.0.0-alpha.39

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 (87) hide show
  1. package/README.ja.md +77 -0
  2. package/README.md +4 -1
  3. package/dist/index.js +670 -88
  4. package/dist/templates/_shared/AGENTS.ja.md +104 -0
  5. package/dist/templates/_shared/AGENTS.md +104 -0
  6. package/dist/templates/_shared/README.ja.md +175 -0
  7. package/dist/templates/_shared/README.md +175 -0
  8. package/dist/templates/_shared/RUNBOOK.ja.md +120 -0
  9. package/dist/templates/_shared/RUNBOOK.md +24 -51
  10. package/dist/templates/_shared/amplify/auth/post-confirmation/resource.ts +6 -0
  11. package/dist/templates/_shared/amplify/backend.custom.ts +41 -0
  12. package/dist/templates/_shared/amplify/backend.ts +12 -2
  13. package/dist/templates/_shared/amplify/data/get-published-post.js +9 -12
  14. package/dist/templates/_shared/amplify/data/list-posts-by-tag.js +6 -9
  15. package/dist/templates/_shared/amplify/data/list-published-posts.js +10 -11
  16. package/dist/templates/_shared/amplify/data/resource.custom.ts +32 -0
  17. package/dist/templates/_shared/amplify/data/resource.ts +26 -13
  18. package/dist/templates/_shared/amplify/events/processor-untrusted/resource.ts +4 -0
  19. package/dist/templates/_shared/amplify/functions/mcp-handler/handler.ts +1 -0
  20. package/dist/templates/_shared/amplify/functions/mcp-handler/resource.ts +11 -0
  21. package/dist/templates/_shared/amplify/functions/user-admin/handler.ts +4 -0
  22. package/dist/templates/_shared/amplify/functions/user-admin/resource.ts +12 -0
  23. package/dist/templates/_shared/app/(admin)/admin/mcp-tokens/page.tsx +5 -0
  24. package/dist/templates/_shared/app/(admin)/admin/users/page.tsx +5 -0
  25. package/dist/templates/_shared/app/globals.css +55 -39
  26. package/dist/templates/_shared/app/layout.tsx +24 -11
  27. package/dist/templates/_shared/app/site/[siteId]/r/[slug]/[[...path]]/route.ts +17 -0
  28. package/dist/templates/_shared/cms.config.ts +1 -23
  29. package/dist/templates/_shared/components/site-chrome/site-sidebar.tsx +2 -3
  30. package/dist/templates/_shared/lib/admin.ts +9 -7
  31. package/dist/templates/_shared/lib/posts-public.ts +1 -1
  32. package/dist/templates/_shared/package.json +11 -9
  33. package/dist/templates/_shared/proxy.ts +3 -3
  34. package/dist/templates/blog/README.ja.md +22 -0
  35. package/dist/templates/blog/README.md +17 -47
  36. package/dist/templates/blog/manifest.ts +1 -1
  37. package/dist/templates/blog/pages/feed.ts +3 -4
  38. package/dist/templates/blog/pages/home.tsx +5 -8
  39. package/dist/templates/blog/pages/post.tsx +7 -7
  40. package/dist/templates/blog/pages/sitemap.ts +3 -4
  41. package/dist/templates/blog/pages/tag.tsx +4 -4
  42. package/dist/templates/blog/tokens.css +26 -40
  43. package/dist/templates/corporate/README.ja.md +18 -0
  44. package/dist/templates/corporate/README.md +12 -14
  45. package/dist/templates/corporate/pages/feed.ts +2 -3
  46. package/dist/templates/corporate/pages/home.tsx +5 -8
  47. package/dist/templates/corporate/pages/post.tsx +7 -7
  48. package/dist/templates/corporate/pages/sitemap.ts +2 -3
  49. package/dist/templates/corporate/pages/tag.tsx +4 -4
  50. package/dist/templates/corporate/tokens.css +17 -39
  51. package/dist/templates/dads/README.ja.md +31 -0
  52. package/dist/templates/dads/README.md +13 -17
  53. package/dist/templates/dads/pages/feed.ts +2 -3
  54. package/dist/templates/dads/pages/home.tsx +5 -8
  55. package/dist/templates/dads/pages/post.tsx +7 -7
  56. package/dist/templates/dads/pages/sitemap.ts +2 -3
  57. package/dist/templates/dads/pages/tag.tsx +4 -4
  58. package/dist/templates/dads/tokens.css +22 -42
  59. package/dist/templates/docs/README.ja.md +24 -0
  60. package/dist/templates/docs/README.md +10 -13
  61. package/dist/templates/docs/pages/feed.ts +2 -3
  62. package/dist/templates/docs/pages/home.tsx +5 -6
  63. package/dist/templates/docs/pages/post.tsx +8 -8
  64. package/dist/templates/docs/pages/sitemap.ts +2 -3
  65. package/dist/templates/docs/pages/tag.tsx +5 -5
  66. package/dist/templates/docs/tokens.css +17 -39
  67. package/dist/templates/landing/README.ja.md +20 -0
  68. package/dist/templates/landing/README.md +14 -19
  69. package/dist/templates/landing/pages/feed.ts +3 -4
  70. package/dist/templates/landing/pages/home.tsx +5 -8
  71. package/dist/templates/landing/pages/post.tsx +7 -7
  72. package/dist/templates/landing/pages/sitemap.ts +2 -3
  73. package/dist/templates/landing/pages/tag.tsx +4 -4
  74. package/dist/templates/landing/tokens.css +17 -39
  75. package/dist/templates/minimal/README.ja.md +14 -0
  76. package/dist/templates/minimal/README.md +9 -47
  77. package/dist/templates/minimal/pages/feed.ts +3 -4
  78. package/dist/templates/minimal/pages/home.tsx +3 -4
  79. package/dist/templates/minimal/pages/post.tsx +6 -6
  80. package/dist/templates/minimal/pages/sitemap.ts +3 -4
  81. package/dist/templates/minimal/pages/tag.tsx +3 -3
  82. package/dist/templates/minimal/tokens.css +17 -39
  83. package/package.json +1 -1
  84. package/dist/templates/_shared/app/(admin)/admin/sites/page.tsx +0 -5
  85. package/dist/templates/_shared/app/site/[siteId]/raw/[slug]/route.ts +0 -5
  86. package/dist/templates/_shared/lib/admin-site-client.ts +0 -10
  87. package/dist/templates/_shared/lib/admin-site.ts +0 -12
@@ -1,4 +1,17 @@
1
1
  @import 'tailwindcss';
2
+
3
+ /*
4
+ * Tailwind v4 doesn't scan `node_modules/` by default. The admin UI
5
+ * (`@ampless/admin`) and runtime UI bits (`@ampless/runtime`) ship
6
+ * compiled JS that uses Tailwind utility classes; without these
7
+ * explicit sources, every utility used only inside those packages
8
+ * (e.g. the admin sidebar's `flex`, `fixed`, `md:sticky`, ...) gets
9
+ * tree-shaken out of the final CSS, and the admin layout falls back
10
+ * to browser defaults — sidebar overlaps main content, etc.
11
+ */
12
+ @source '../node_modules/@ampless/admin/dist';
13
+ @source '../node_modules/@ampless/runtime/dist';
14
+
2
15
  @plugin '@tailwindcss/typography';
3
16
  /* DADS theme plugin from Digital Agency. Loaded globally so the
4
17
  * `templates/dads/` theme can use DADS-conformant color variables
@@ -36,6 +49,31 @@
36
49
  --radius-sm: calc(var(--radius) - 4px);
37
50
  }
38
51
 
52
+ /*
53
+ * Color scheme:
54
+ *
55
+ * - `:root { color-scheme: light dark }` opts the page into both modes
56
+ * and asks the browser to pick based on the visitor's
57
+ * `prefers-color-scheme` setting.
58
+ * - `html[data-color-scheme='light' | 'dark']` lets the site admin pin
59
+ * one mode regardless of system preference. The attribute is written
60
+ * by `app/layout.tsx` from the per-site setting
61
+ * (`theme.colorScheme`, default `'auto'` = no attribute).
62
+ * - Token values below (and in each theme's `tokens.css`) use the CSS
63
+ * `light-dark(L, D)` function so a single declaration covers both
64
+ * modes — the active `color-scheme` selects between L and D at
65
+ * paint time.
66
+ */
67
+ :root {
68
+ color-scheme: light dark;
69
+ }
70
+ html[data-color-scheme='light'] {
71
+ color-scheme: light;
72
+ }
73
+ html[data-color-scheme='dark'] {
74
+ color-scheme: dark;
75
+ }
76
+
39
77
  /*
40
78
  * Default tokens — used as fallback when no theme's `[data-theme=...]`
41
79
  * selector matches (e.g. body has no data-theme attribute, or admin
@@ -45,49 +83,27 @@
45
83
  * `<body data-theme="...">` attribute makes the scoped selector match.
46
84
  */
47
85
  :root {
48
- --background: oklch(1 0 0);
49
- --foreground: oklch(0.145 0 0);
50
- --card: oklch(1 0 0);
51
- --card-foreground: oklch(0.145 0 0);
52
- --primary: oklch(0.205 0 0);
53
- --primary-foreground: oklch(0.985 0 0);
54
- --secondary: oklch(0.97 0 0);
55
- --secondary-foreground: oklch(0.205 0 0);
56
- --muted: oklch(0.97 0 0);
57
- --muted-foreground: oklch(0.556 0 0);
58
- --accent: oklch(0.97 0 0);
59
- --accent-foreground: oklch(0.205 0 0);
60
- --destructive: oklch(0.577 0.245 27.325);
61
- --destructive-foreground: oklch(0.985 0 0);
62
- --border: oklch(0.922 0 0);
63
- --input: oklch(0.922 0 0);
64
- --ring: oklch(0.708 0 0);
86
+ --background: light-dark(oklch(1 0 0), oklch(0.145 0 0));
87
+ --foreground: light-dark(oklch(0.145 0 0), oklch(0.985 0 0));
88
+ --card: light-dark(oklch(1 0 0), oklch(0.145 0 0));
89
+ --card-foreground: light-dark(oklch(0.145 0 0), oklch(0.985 0 0));
90
+ --primary: light-dark(oklch(0.205 0 0), oklch(0.985 0 0));
91
+ --primary-foreground: light-dark(oklch(0.985 0 0), oklch(0.205 0 0));
92
+ --secondary: light-dark(oklch(0.97 0 0), oklch(0.269 0 0));
93
+ --secondary-foreground: light-dark(oklch(0.205 0 0), oklch(0.985 0 0));
94
+ --muted: light-dark(oklch(0.97 0 0), oklch(0.269 0 0));
95
+ --muted-foreground: light-dark(oklch(0.556 0 0), oklch(0.708 0 0));
96
+ --accent: light-dark(oklch(0.97 0 0), oklch(0.269 0 0));
97
+ --accent-foreground: light-dark(oklch(0.205 0 0), oklch(0.985 0 0));
98
+ --destructive: light-dark(oklch(0.577 0.245 27.325), oklch(0.396 0.141 25.723));
99
+ --destructive-foreground: light-dark(oklch(0.985 0 0), oklch(0.985 0 0));
100
+ --border: light-dark(oklch(0.922 0 0), oklch(0.269 0 0));
101
+ --input: light-dark(oklch(0.922 0 0), oklch(0.269 0 0));
102
+ --ring: light-dark(oklch(0.708 0 0), oklch(0.439 0 0));
65
103
  --radius: 0.5rem;
66
104
  --ampless-body-font: system-ui, -apple-system, sans-serif;
67
105
  }
68
106
 
69
- @media (prefers-color-scheme: dark) {
70
- :root {
71
- --background: oklch(0.145 0 0);
72
- --foreground: oklch(0.985 0 0);
73
- --card: oklch(0.145 0 0);
74
- --card-foreground: oklch(0.985 0 0);
75
- --primary: oklch(0.985 0 0);
76
- --primary-foreground: oklch(0.205 0 0);
77
- --secondary: oklch(0.269 0 0);
78
- --secondary-foreground: oklch(0.985 0 0);
79
- --muted: oklch(0.269 0 0);
80
- --muted-foreground: oklch(0.708 0 0);
81
- --accent: oklch(0.269 0 0);
82
- --accent-foreground: oklch(0.985 0 0);
83
- --destructive: oklch(0.396 0.141 25.723);
84
- --destructive-foreground: oklch(0.985 0 0);
85
- --border: oklch(0.269 0 0);
86
- --input: oklch(0.269 0 0);
87
- --ring: oklch(0.439 0 0);
88
- }
89
- }
90
-
91
107
  body {
92
108
  font-family: var(--ampless-body-font, system-ui, -apple-system, sans-serif);
93
109
  background: var(--background);
@@ -1,6 +1,6 @@
1
1
  import type { Metadata } from 'next'
2
2
  import { headers } from 'next/headers'
3
- import { DEFAULT_SITE_ID } from 'ampless'
3
+ import { validateColorScheme } from '@ampless/runtime'
4
4
  import { Providers } from './providers'
5
5
  import { siteMetadata } from '@/lib/seo'
6
6
  import { loadThemeConfig, renderThemeCss } from '@/lib/theme-config'
@@ -8,25 +8,38 @@ import { getLocale, getDictionary } from '@/lib/i18n'
8
8
  import { I18nProvider } from '@/components/i18n-provider'
9
9
  import './globals.css'
10
10
 
11
- // Resolve metadata per site at request time. The middleware sets
12
- // `x-site-id` so we can pick the right merged settings; falls back to
13
- // DEFAULT_SITE_ID for admin / API routes that don't go through the
14
- // public middleware path.
15
11
  export async function generateMetadata(): Promise<Metadata> {
16
- const h = await headers()
17
- const siteId = h.get('x-site-id') ?? DEFAULT_SITE_ID
18
- return siteMetadata(siteId)
12
+ return siteMetadata()
19
13
  }
20
14
 
21
15
  export default async function RootLayout({ children }: { children: React.ReactNode }) {
22
16
  const h = await headers()
23
- const siteId = h.get('x-site-id') ?? DEFAULT_SITE_ID
24
- const theme = await loadThemeConfig(siteId)
17
+ const theme = await loadThemeConfig()
25
18
  const themeCss = renderThemeCss(theme.cssVars)
26
19
  const locale = getLocale()
27
20
  const dict = getDictionary(locale)
21
+ // `data-color-scheme` pins the visitor to light or dark regardless
22
+ // of their system `prefers-color-scheme`. `'auto'` (the default)
23
+ // means we don't emit the attribute at all, so `globals.css`'s
24
+ // `:root { color-scheme: light dark }` lets the browser follow the
25
+ // system setting. Themes use `light-dark()` in their tokens.css so a
26
+ // single declaration covers both modes; the active `color-scheme`
27
+ // selects which value is rendered.
28
+ //
29
+ // The admin's theme-settings iframe preview passes the unsaved
30
+ // selection via `?previewColorScheme=<mode>`, which middleware
31
+ // forwards as `x-preview-color-scheme`. When that header is present
32
+ // it overrides the stored site setting so the preview updates live.
33
+ const previewHeader = h.get('x-preview-color-scheme')
34
+ const effectiveColorScheme = previewHeader
35
+ ? validateColorScheme(previewHeader)
36
+ : theme.colorScheme
37
+ const htmlProps: { lang: string; 'data-color-scheme'?: 'light' | 'dark' } = { lang: locale }
38
+ if (effectiveColorScheme !== 'auto') {
39
+ htmlProps['data-color-scheme'] = effectiveColorScheme
40
+ }
28
41
  return (
29
- <html lang={locale}>
42
+ <html {...htmlProps}>
30
43
  <head>
31
44
  {/* Inline `:root` overrides come AFTER globals.css so they win
32
45
  against the static defaults. Validated values only — see
@@ -0,0 +1,17 @@
1
+ import { ampless } from '@/lib/ampless'
2
+ import { createUnderscoreRouteHandler } from '@ampless/runtime/routes'
3
+
4
+ // Unified handler for the public `/_/<slug>(/...)` URL family.
5
+ // Covers two cases that both bypass the theme's post page:
6
+ //
7
+ // - `format: 'html'` posts with `metadata.no_layout === true`
8
+ // → bare HTML response, no Next.js root layout, no theme chrome
9
+ // - `format: 'static'` posts
10
+ // → S3 presigned URL redirect for the entrypoint and every bundle file
11
+ //
12
+ // File location uses the literal folder name `r/` (not `_/`) because
13
+ // Next.js's App Router excludes any path part starting with `_` from
14
+ // route discovery. The middleware rewrites the public `/_/` prefix
15
+ // to `/r/` internally; the browser URL stays `/_/<slug>(/...)`.
16
+ export const dynamic = 'force-dynamic'
17
+ export const GET = createUnderscoreRouteHandler(ampless)
@@ -27,29 +27,7 @@ export default defineConfig({
27
27
  losslessForPng: true,
28
28
  },
29
29
  },
30
- // Multi-site config. Leave undefined for single-site operation; the
31
- // runtime then serves every request as siteId='default'. To run
32
- // multiple sites in one Amplify environment, declare two or more
33
- // entries here:
34
- //
35
- // sites: {
36
- // blog: {
37
- // domains: ['blog.example.com', 'www.example.com'],
38
- // name: 'My Blog',
39
- // url: 'https://blog.example.com',
40
- // },
41
- // docs: {
42
- // domains: ['docs.example.com'],
43
- // name: 'Docs',
44
- // url: 'https://docs.example.com',
45
- // },
46
- // },
47
- //
48
- // In multi-site mode the middleware forces `Cache-Control: private,
49
- // no-store` on public responses (Amplify Hosting's CloudFront cache
50
- // key cannot disambiguate by Host). Single-site mode leaves caching
51
- // to the default page directives.
52
-
30
+ // Date display format.
53
31
  // 'iso' — YYYY-MM-DD (default; SSR-safe, locale-neutral)
54
32
  // 'long' — "April 27, 2026" (en-US)
55
33
  // 'locale' — browser/server locale
@@ -4,7 +4,6 @@ import { listPostsByTag } from '@/lib/posts-public'
4
4
 
5
5
  interface Props {
6
6
  links: string | undefined
7
- siteId: string
8
7
  className?: string
9
8
  }
10
9
 
@@ -19,7 +18,7 @@ interface Props {
19
18
  * paired with `force-dynamic` on the page so fresh content shows up
20
19
  * after publish events.
21
20
  */
22
- export async function SiteSidebar({ links, siteId, className }: Props) {
21
+ export async function SiteSidebar({ links, className }: Props) {
23
22
  const items = parseLinkList(links)
24
23
  if (items.length === 0) return null
25
24
 
@@ -28,7 +27,7 @@ export async function SiteSidebar({ links, siteId, className }: Props) {
28
27
  items.map(async (item) => {
29
28
  const tagRef = isTagListUrl(item.url)
30
29
  if (!tagRef) return { type: 'link' as const, label: item.label, url: item.url }
31
- const { items: posts } = await listPostsByTag(tagRef.tag, { siteId, limit: 50 })
30
+ const { items: posts } = await listPostsByTag(tagRef.tag, { limit: 50 })
32
31
  return {
33
32
  type: 'tagSection' as const,
34
33
  label: item.label,
@@ -7,13 +7,14 @@
7
7
  // `amplify_outputs.json` and `cms.config` into a single `Admin`
8
8
  // instance.
9
9
  //
10
- // NOTE: We intentionally do NOT pass an `ampless` instance to
11
- // createAdmin. That would import `lib/ampless.ts`, which imports
12
- // `themes-registry`, which (via theme pages `lib/i18n.ts`) imports
13
- // back to this file a circular chain that crashes with a TDZ
14
- // ReferenceError on `ampless` at module init. `createAdmin` builds
15
- // its own internal Ampless when omitted, which is functionally
16
- // equivalent for admin's needs.
10
+ // NOTE: we pass `ampless` as a thunk (not the resolved instance) so we
11
+ // don't have a static `import './ampless'` at the top of this file.
12
+ // A static import would form the cycle
13
+ // `lib/admin.ts lib/ampless.ts themes-registry themes lib/i18n.ts lib/admin.ts`
14
+ // and crash with a TDZ ReferenceError on `ampless` at module init.
15
+ // The thunk uses dynamic `import()` so `lib/ampless.ts` only loads on
16
+ // the first `loadSiteSettings` / `loadThemeConfig` call (request
17
+ // time), by which point every module has finished initialising.
17
18
 
18
19
  import outputs from '../amplify_outputs.json'
19
20
  import cmsConfig from '@/cms.config'
@@ -22,6 +23,7 @@ import { createAdmin } from '@ampless/admin'
22
23
  export const admin = createAdmin({
23
24
  outputs,
24
25
  cmsConfig,
26
+ ampless: async () => (await import('./ampless')).ampless,
25
27
  locale: (cmsConfig as { locale?: string }).locale ?? 'en',
26
28
  })
27
29
 
@@ -4,7 +4,7 @@
4
4
  //
5
5
  // New code should import from `@/lib/ampless` directly:
6
6
  // import { ampless } from '@/lib/ampless'
7
- // const posts = await ampless.listPublishedPosts({ siteId })
7
+ // const posts = await ampless.listPublishedPosts()
8
8
 
9
9
  import { ampless } from './ampless'
10
10
 
@@ -8,7 +8,9 @@
8
8
  "build": "next build",
9
9
  "start": "next start",
10
10
  "lint": "next lint",
11
- "sandbox": "ampx sandbox"
11
+ "sandbox": "ampx sandbox --once && next dev",
12
+ "update-ampless": "npx create-ampless@latest upgrade",
13
+ "copy-theme": "npx create-ampless@latest copy-theme"
12
14
  },
13
15
  "dependencies": {
14
16
  "@aws-amplify/adapter-nextjs": "^1.6.0",
@@ -20,15 +22,15 @@
20
22
  "@tiptap/pm": "^3.23.4",
21
23
  "@tiptap/react": "^3.23.4",
22
24
  "@tiptap/starter-kit": "^3.23.4",
23
- "@ampless/plugin-og-image": "^0.2.0-alpha.1",
24
- "@ampless/plugin-rss": "^0.2.0-alpha.1",
25
- "@ampless/plugin-seo": "^0.2.0-alpha.1",
26
- "@ampless/plugin-webhook": "^0.2.0-alpha.1",
27
- "@ampless/admin": "^0.2.0-alpha.6",
28
- "@ampless/backend": "^0.2.0-alpha.2",
29
- "@ampless/runtime": "^0.2.0-alpha.3",
25
+ "@ampless/plugin-og-image": "^0.2.0-alpha.9",
26
+ "@ampless/plugin-rss": "^0.2.0-alpha.9",
27
+ "@ampless/plugin-seo": "^0.2.0-alpha.9",
28
+ "@ampless/plugin-webhook": "^0.2.0-alpha.9",
29
+ "@ampless/admin": "^1.0.0-alpha.26",
30
+ "@ampless/backend": "^1.0.0-alpha.19",
31
+ "@ampless/runtime": "^1.0.0-alpha.13",
30
32
  "@digital-go-jp/tailwind-theme-plugin": "^0.3.4",
31
- "ampless": "^0.2.0-alpha.1",
33
+ "ampless": "^1.0.0-alpha.9",
32
34
  "aws-amplify": "^6.10.0",
33
35
  "class-variance-authority": "^0.7.1",
34
36
  "clsx": "^2.1.1",
@@ -5,9 +5,9 @@
5
5
  // for API stability; only the user-side file convention (proxy.ts +
6
6
  // `export const proxy`) follows Next 16's rename.
7
7
  //
8
- // See `@ampless/runtime/middleware` for behaviour details: multi-site
9
- // host rewrite, `<slug>.html` → raw route, `?previewTheme=` header
10
- // forwarding, multi-site Cache-Control override.
8
+ // See `@ampless/runtime/middleware` for behaviour details:
9
+ // `/path` → `/site/default/path` rewrite, `?previewTheme=` header
10
+ // forwarding.
11
11
 
12
12
  import cmsConfig from './cms.config'
13
13
  import { createAmplessMiddleware } from '@ampless/runtime/middleware'
@@ -0,0 +1,22 @@
1
+ > English: [README.md](./README.md)
2
+ >
3
+ # Blog テーマ
4
+
5
+ shadcn/ui のデフォルトに準じたニュートラル系モノクロ。時系列の投稿フィード、個別投稿ページ、タグ別一覧を提供します。
6
+
7
+ ## カスタマイズ
8
+
9
+ `/admin/sites/<siteId>/theme` で設定:
10
+
11
+ - **プライマリカラー** — ボタン、リンク、強調表示の背景色
12
+ - **アクセントカラー**
13
+ - **フォーカスリング**
14
+ - **破壊的操作** — 削除等の操作色
15
+ - **角丸**
16
+ - **本文フォント** — システムサンセリフ / セリフ (Georgia) / セリフ (Iowan) / 等幅
17
+ - **トップに固定する記事のスラッグ** — 公開済み投稿を 1 件ホームの先頭に固定
18
+ - **ロゴ画像 URL**
19
+ - **ヘッダーナビ** — ラベル + URL のペア
20
+ - **フッターリンク** — ラベル + URL のペア
21
+
22
+ サイトごとのライト/ダークカラースキームは ampless の標準テーマトークン経由で反映されます。
@@ -1,52 +1,22 @@
1
- # {{siteName}}
1
+ > 日本語版: [README.ja.md](./README.ja.md)
2
+ >
3
+ # Blog theme
2
4
 
3
- A blog site powered by [ampless](https://github.com/heavymoons/ampless).
5
+ Neutral monochrome with shadcn/ui defaults. Chronological post feed, single-post page, and tag listing.
4
6
 
5
- ## Getting Started
7
+ ## Customizing
6
8
 
7
- This project uses Amplify Gen 2 for the backend (Cognito, DynamoDB, S3) and Next.js for the frontend.
9
+ In `/admin/sites/<siteId>/theme`:
8
10
 
9
- ```bash
10
- # 1. Install dependencies
11
- npm install
11
+ - **Primary color** — buttons, links, accent fills
12
+ - **Accent color**
13
+ - **Focus ring**
14
+ - **Destructive** — color used for destructive actions
15
+ - **Corner radius**
16
+ - **Body font** — System sans / Serif (Georgia) / Serif (Iowan) / Monospace
17
+ - **Featured post slug** — pin one published post to the top of the home page
18
+ - **Logo image URL**
19
+ - **Header navigation** — label + URL pairs
20
+ - **Footer links** — label + URL pairs
12
21
 
13
- # 2. Start a personal AWS sandbox (terminal 1)
14
- # Requires AWS credentials configured (`aws configure`).
15
- # First run takes ~5–10 min to provision resources.
16
- # Generates amplify_outputs.json when ready.
17
- npx ampx sandbox
18
-
19
- # 3. Start the Next.js dev server (terminal 2)
20
- npm run dev
21
- ```
22
-
23
- Then open [http://localhost:3000](http://localhost:3000).
24
-
25
- ## First admin user
26
-
27
- Open [http://localhost:3000/login](http://localhost:3000/login) and click **Create admin account**. The first user to register is automatically added to the `ampless-admin` Cognito group.
28
-
29
- After that, manage content from `/admin`:
30
-
31
- - `/admin` — dashboard
32
- - `/admin/posts` — list / create / edit posts (tiptap editor)
33
- - `/admin/media` — upload images to S3
34
-
35
- ## Production deploy
36
-
37
- ```bash
38
- git init && git add . && git commit -m "init"
39
- git remote add origin <your-repo>
40
- git push
41
- # Then connect the repo to AWS Amplify Hosting in the AWS console.
42
- ```
43
-
44
- ## Customize
45
-
46
- - `cms.config.ts` — site name, media delivery mode, plugins
47
- - `app/` — Next.js App Router pages (`(public)/` for the blog, `(admin)/` for the CMS)
48
- - `amplify/` — Amplify Gen 2 backend definitions (auth / data / storage)
49
-
50
- ## Plugins
51
-
52
- Enabled: {{plugins}}
22
+ Per-site light/dark color scheme is honored through the standard ampless theme tokens.
@@ -1,7 +1,7 @@
1
1
  import { defineTheme } from 'ampless'
2
2
 
3
3
  // Customizable fields for the Blog theme. Edit values in
4
- // `/admin/sites/<siteId>/theme` — they're stored in KvStore and applied
4
+ // `/admin/sites/default/theme` — they're stored in KvStore and applied
5
5
  // at render time as CSS variables on every public page.
6
6
  //
7
7
  // Labels / descriptions / groups accept either a plain string (works in
@@ -1,14 +1,13 @@
1
1
  import { publicAssetUrl } from '@/lib/storage'
2
2
 
3
3
  interface Ctx {
4
- siteId: string
5
4
  request: Request
6
5
  }
7
6
 
8
7
  // /feed.xml proxy — plugin-rss regenerates the feed on content events
9
- // and writes it to `public/plugins/rss/{siteId}/feed.xml`.
10
- export async function blogFeedHandler({ siteId }: Ctx): Promise<Response> {
11
- const url = publicAssetUrl(`public/plugins/rss/${siteId}/feed.xml`)
8
+ // and writes it to `public/plugins/rss/feed.xml`.
9
+ export async function blogFeedHandler(_ctx: Ctx): Promise<Response> {
10
+ const url = publicAssetUrl('public/plugins/rss/feed.xml')
12
11
  const upstream = await fetch(url, { cache: 'no-store' })
13
12
  if (!upstream.ok) {
14
13
  return new Response(
@@ -9,21 +9,18 @@ import { SiteHeader } from '@/components/site-chrome/site-header'
9
9
  import { SiteFooter } from '@/components/site-chrome/site-footer'
10
10
  import { t } from '@/lib/i18n'
11
11
 
12
- export default async function BlogHome({ params }: ThemeRouteContext) {
13
- const { siteId } = await params
12
+ export default async function BlogHome(_: ThemeRouteContext) {
14
13
  const [settings, theme, postsResult] = await Promise.all([
15
- loadSiteSettings(siteId),
16
- loadThemeConfig(siteId),
17
- listPublishedPosts({ siteId }),
14
+ loadSiteSettings(),
15
+ loadThemeConfig(),
16
+ listPublishedPosts(),
18
17
  ])
19
18
 
20
19
  // Featured (pinned) post: render the body inline above the list,
21
20
  // then drop the same slug from the feed so it doesn't show twice.
22
21
  // Missing / unpublished slugs return null and the section is skipped.
23
22
  const featuredSlug = theme.values.featuredSlug?.trim()
24
- const featured = featuredSlug
25
- ? await getPublishedPost(featuredSlug, { siteId })
26
- : null
23
+ const featured = featuredSlug ? await getPublishedPost(featuredSlug) : null
27
24
  const posts = featured
28
25
  ? postsResult.items.filter((p) => p.slug !== featured.slug)
29
26
  : postsResult.items
@@ -16,18 +16,18 @@ import { t } from '@/lib/i18n'
16
16
  type PostCtx = ThemeRouteContext<{ slug: string }>
17
17
 
18
18
  export async function generatePostMetadata({ params }: PostCtx): Promise<Metadata> {
19
- const { siteId, slug } = await params
20
- const post = await getPublishedPost(slug, { siteId })
19
+ const { slug } = await params
20
+ const post = await getPublishedPost(slug)
21
21
  if (!post) return {}
22
- return postMetadata(post, siteId)
22
+ return postMetadata(post)
23
23
  }
24
24
 
25
25
  export default async function BlogPost({ params }: PostCtx) {
26
- const { siteId, slug } = await params
26
+ const { slug } = await params
27
27
  const [post, settings, theme] = await Promise.all([
28
- getPublishedPost(slug, { siteId }),
29
- loadSiteSettings(siteId),
30
- loadThemeConfig(siteId),
28
+ getPublishedPost(slug),
29
+ loadSiteSettings(),
30
+ loadThemeConfig(),
31
31
  ])
32
32
  if (!post) notFound()
33
33
 
@@ -1,14 +1,13 @@
1
1
  import { publicAssetUrl } from '@/lib/storage'
2
2
 
3
3
  interface Ctx {
4
- siteId: string
5
4
  request: Request
6
5
  }
7
6
 
8
7
  // /sitemap.xml proxy — plugin-seo regenerates the sitemap on every
9
- // content event and writes it to `public/plugins/seo/{siteId}/sitemap.xml`.
10
- export async function blogSitemapHandler({ siteId }: Ctx): Promise<Response> {
11
- const url = publicAssetUrl(`public/plugins/seo/${siteId}/sitemap.xml`)
8
+ // content event and writes it to `public/plugins/seo/sitemap.xml`.
9
+ export async function blogSitemapHandler(_ctx: Ctx): Promise<Response> {
10
+ const url = publicAssetUrl('public/plugins/seo/sitemap.xml')
12
11
  const upstream = await fetch(url, { cache: 'no-store' })
13
12
  if (!upstream.ok) {
14
13
  return new Response(
@@ -9,12 +9,12 @@ import { SiteFooter } from '@/components/site-chrome/site-footer'
9
9
  import { t } from '@/lib/i18n'
10
10
 
11
11
  export default async function BlogTag({ params }: ThemeRouteContext<{ tag: string }>) {
12
- const { siteId, tag } = await params
12
+ const { tag } = await params
13
13
  const decodedTag = decodeURIComponent(tag)
14
14
  const [{ items: posts }, settings, theme] = await Promise.all([
15
- listPostsByTag(decodedTag, { siteId, limit: 50 }),
16
- loadSiteSettings(siteId),
17
- loadThemeConfig(siteId),
15
+ listPostsByTag(decodedTag, { limit: 50 }),
16
+ loadSiteSettings(),
17
+ loadThemeConfig(),
18
18
  ])
19
19
 
20
20
  if (posts.length === 0) notFound()
@@ -7,48 +7,34 @@
7
7
  * loads, so `:root` selectors trump scoped attribute selectors of
8
8
  * equal specificity. The dispatcher emits `--primary` etc. only for
9
9
  * fields that have an override, leaving everything else to fall back
10
- * to these defaults. */
10
+ * to these defaults.
11
+ *
12
+ * Dark mode: tokens are declared with the CSS `light-dark(L, D)`
13
+ * function. The active `color-scheme` (set on `:root` and optionally
14
+ * pinned per-site via `html[data-color-scheme]`) selects whether L or
15
+ * D is rendered. `globals.css` opts the document into `color-scheme:
16
+ * light dark` by default — visitors with `prefers-color-scheme: dark`
17
+ * see the dark palette automatically.
18
+ */
11
19
 
12
20
  [data-theme='blog'] {
13
- --background: oklch(1 0 0);
14
- --foreground: oklch(0.145 0 0);
15
- --card: oklch(1 0 0);
16
- --card-foreground: oklch(0.145 0 0);
17
- --primary: oklch(0.205 0 0);
18
- --primary-foreground: oklch(0.985 0 0);
19
- --secondary: oklch(0.97 0 0);
20
- --secondary-foreground: oklch(0.205 0 0);
21
- --muted: oklch(0.97 0 0);
22
- --muted-foreground: oklch(0.556 0 0);
23
- --accent: oklch(0.97 0 0);
24
- --accent-foreground: oklch(0.205 0 0);
25
- --destructive: oklch(0.577 0.245 27.325);
26
- --destructive-foreground: oklch(0.985 0 0);
27
- --border: oklch(0.922 0 0);
28
- --input: oklch(0.922 0 0);
29
- --ring: oklch(0.708 0 0);
21
+ --background: light-dark(oklch(1 0 0), oklch(0.145 0 0));
22
+ --foreground: light-dark(oklch(0.145 0 0), oklch(0.985 0 0));
23
+ --card: light-dark(oklch(1 0 0), oklch(0.145 0 0));
24
+ --card-foreground: light-dark(oklch(0.145 0 0), oklch(0.985 0 0));
25
+ --primary: light-dark(oklch(0.205 0 0), oklch(0.985 0 0));
26
+ --primary-foreground: light-dark(oklch(0.985 0 0), oklch(0.205 0 0));
27
+ --secondary: light-dark(oklch(0.97 0 0), oklch(0.269 0 0));
28
+ --secondary-foreground: light-dark(oklch(0.205 0 0), oklch(0.985 0 0));
29
+ --muted: light-dark(oklch(0.97 0 0), oklch(0.269 0 0));
30
+ --muted-foreground: light-dark(oklch(0.556 0 0), oklch(0.708 0 0));
31
+ --accent: light-dark(oklch(0.97 0 0), oklch(0.269 0 0));
32
+ --accent-foreground: light-dark(oklch(0.205 0 0), oklch(0.985 0 0));
33
+ --destructive: light-dark(oklch(0.577 0.245 27.325), oklch(0.396 0.141 25.723));
34
+ --destructive-foreground: light-dark(oklch(0.985 0 0), oklch(0.985 0 0));
35
+ --border: light-dark(oklch(0.922 0 0), oklch(0.269 0 0));
36
+ --input: light-dark(oklch(0.922 0 0), oklch(0.269 0 0));
37
+ --ring: light-dark(oklch(0.708 0 0), oklch(0.439 0 0));
30
38
  --radius: 0.5rem;
31
39
  --ampless-body-font: system-ui, -apple-system, sans-serif;
32
40
  }
33
-
34
- @media (prefers-color-scheme: dark) {
35
- [data-theme='blog'] {
36
- --background: oklch(0.145 0 0);
37
- --foreground: oklch(0.985 0 0);
38
- --card: oklch(0.145 0 0);
39
- --card-foreground: oklch(0.985 0 0);
40
- --primary: oklch(0.985 0 0);
41
- --primary-foreground: oklch(0.205 0 0);
42
- --secondary: oklch(0.269 0 0);
43
- --secondary-foreground: oklch(0.985 0 0);
44
- --muted: oklch(0.269 0 0);
45
- --muted-foreground: oklch(0.708 0 0);
46
- --accent: oklch(0.269 0 0);
47
- --accent-foreground: oklch(0.985 0 0);
48
- --destructive: oklch(0.396 0.141 25.723);
49
- --destructive-foreground: oklch(0.985 0 0);
50
- --border: oklch(0.269 0 0);
51
- --input: oklch(0.269 0 0);
52
- --ring: oklch(0.439 0 0);
53
- }
54
- }
@@ -0,0 +1,18 @@
1
+ > English: [README.md](./README.md)
2
+ >
3
+ # Corporate テーマ
4
+
5
+ 企業サイト向けの落ち着いたレイアウト。明るい背景にネイビーの配色、ヒーロー + お知らせ一覧構成、ヘッダー / フッターナビ付き。
6
+
7
+ ## カスタマイズ
8
+
9
+ `/admin/sites/<siteId>/theme` で設定:
10
+
11
+ - **タグライン** — ヒーロー内、サイト名の上に表示される短いフレーズ
12
+ - **プライマリカラー**
13
+ - **角丸**
14
+ - **トップストーリーのスラッグ** — ヒーローとお知らせ一覧の間に公開済み投稿を 1 件フィーチャー
15
+ - **ロゴ画像 URL**
16
+ - **ヘッダーナビ** — ラベル + URL のペア
17
+ - **フッターリンク** — ラベル + URL のペア
18
+ - **フッター注記** — 住所 / 会社情報 / 追加の注意書き