create-ampless 0.2.0-alpha.9 → 1.0.0-alpha.41

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 (90) hide show
  1. package/README.ja.md +77 -0
  2. package/README.md +4 -1
  3. package/dist/index.js +99 -47
  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 +176 -0
  7. package/dist/templates/_shared/README.md +176 -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.ts +2 -0
  12. package/dist/templates/_shared/amplify/data/get-published-post.js +9 -12
  13. package/dist/templates/_shared/amplify/data/list-posts-by-tag.js +6 -9
  14. package/dist/templates/_shared/amplify/data/list-published-posts.js +10 -11
  15. package/dist/templates/_shared/amplify/data/resource.custom.ts +1 -2
  16. package/dist/templates/_shared/amplify/data/resource.ts +22 -5
  17. package/dist/templates/_shared/amplify/events/processor-untrusted/resource.ts +4 -0
  18. package/dist/templates/_shared/amplify/functions/mcp-handler/handler.ts +1 -0
  19. package/dist/templates/_shared/amplify/functions/mcp-handler/resource.ts +11 -0
  20. package/dist/templates/_shared/amplify/functions/user-admin/resource.ts +6 -0
  21. package/dist/templates/_shared/app/(admin)/admin/mcp-tokens/page.tsx +5 -0
  22. package/dist/templates/_shared/app/globals.css +55 -39
  23. package/dist/templates/_shared/app/layout.tsx +24 -11
  24. package/dist/templates/_shared/app/r/[slug]/[[...path]]/route.ts +18 -0
  25. package/dist/templates/_shared/cms.config.ts +1 -23
  26. package/dist/templates/_shared/components/site-chrome/site-sidebar.tsx +2 -3
  27. package/dist/templates/_shared/lib/admin.ts +9 -7
  28. package/dist/templates/_shared/lib/posts-public.ts +1 -1
  29. package/dist/templates/_shared/package.json +11 -12
  30. package/dist/templates/_shared/proxy.ts +22 -8
  31. package/dist/templates/blog/README.ja.md +22 -0
  32. package/dist/templates/blog/README.md +17 -47
  33. package/dist/templates/blog/manifest.ts +1 -1
  34. package/dist/templates/blog/pages/feed.ts +3 -4
  35. package/dist/templates/blog/pages/home.tsx +5 -8
  36. package/dist/templates/blog/pages/post.tsx +7 -7
  37. package/dist/templates/blog/pages/sitemap.ts +3 -4
  38. package/dist/templates/blog/pages/tag.tsx +4 -4
  39. package/dist/templates/blog/tokens.css +26 -40
  40. package/dist/templates/corporate/README.ja.md +18 -0
  41. package/dist/templates/corporate/README.md +12 -14
  42. package/dist/templates/corporate/pages/feed.ts +2 -3
  43. package/dist/templates/corporate/pages/home.tsx +5 -8
  44. package/dist/templates/corporate/pages/post.tsx +7 -7
  45. package/dist/templates/corporate/pages/sitemap.ts +2 -3
  46. package/dist/templates/corporate/pages/tag.tsx +4 -4
  47. package/dist/templates/corporate/tokens.css +17 -39
  48. package/dist/templates/dads/README.ja.md +31 -0
  49. package/dist/templates/dads/README.md +13 -17
  50. package/dist/templates/dads/pages/feed.ts +2 -3
  51. package/dist/templates/dads/pages/home.tsx +5 -8
  52. package/dist/templates/dads/pages/post.tsx +7 -7
  53. package/dist/templates/dads/pages/sitemap.ts +2 -3
  54. package/dist/templates/dads/pages/tag.tsx +4 -4
  55. package/dist/templates/dads/tokens.css +22 -42
  56. package/dist/templates/docs/README.ja.md +24 -0
  57. package/dist/templates/docs/README.md +10 -13
  58. package/dist/templates/docs/pages/feed.ts +2 -3
  59. package/dist/templates/docs/pages/home.tsx +5 -6
  60. package/dist/templates/docs/pages/post.tsx +8 -8
  61. package/dist/templates/docs/pages/sitemap.ts +2 -3
  62. package/dist/templates/docs/pages/tag.tsx +5 -5
  63. package/dist/templates/docs/tokens.css +17 -39
  64. package/dist/templates/landing/README.ja.md +20 -0
  65. package/dist/templates/landing/README.md +14 -19
  66. package/dist/templates/landing/pages/feed.ts +3 -4
  67. package/dist/templates/landing/pages/home.tsx +5 -8
  68. package/dist/templates/landing/pages/post.tsx +7 -7
  69. package/dist/templates/landing/pages/sitemap.ts +2 -3
  70. package/dist/templates/landing/pages/tag.tsx +4 -4
  71. package/dist/templates/landing/tokens.css +17 -39
  72. package/dist/templates/minimal/README.ja.md +14 -0
  73. package/dist/templates/minimal/README.md +9 -47
  74. package/dist/templates/minimal/pages/feed.ts +3 -4
  75. package/dist/templates/minimal/pages/home.tsx +3 -4
  76. package/dist/templates/minimal/pages/post.tsx +6 -6
  77. package/dist/templates/minimal/pages/sitemap.ts +3 -4
  78. package/dist/templates/minimal/pages/tag.tsx +3 -3
  79. package/dist/templates/minimal/tokens.css +17 -39
  80. package/package.json +1 -1
  81. package/dist/templates/_shared/app/(admin)/admin/sites/page.tsx +0 -5
  82. package/dist/templates/_shared/app/site/[siteId]/raw/[slug]/route.ts +0 -5
  83. package/dist/templates/_shared/lib/admin-site-client.ts +0 -10
  84. package/dist/templates/_shared/lib/admin-site.ts +0 -12
  85. /package/dist/templates/_shared/app/{site/[siteId]/[slug] → [slug]}/page.tsx +0 -0
  86. /package/dist/templates/_shared/app/{site/[siteId]/feed.xml → feed.xml}/route.ts +0 -0
  87. /package/dist/templates/_shared/app/{site/[siteId]/og → og}/[slug]/route.ts +0 -0
  88. /package/dist/templates/_shared/app/{site/[siteId]/page.tsx → page.tsx} +0 -0
  89. /package/dist/templates/_shared/app/{site/[siteId]/sitemap.xml → sitemap.xml}/route.ts +0 -0
  90. /package/dist/templates/_shared/app/{site/[siteId]/tag → tag}/[tag]/page.tsx +0 -0
@@ -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
+ - **フッター注記** — 住所 / 会社情報 / 追加の注意書き
@@ -1,20 +1,18 @@
1
- # {{siteName}}
1
+ > 日本語版: [README.ja.md](./README.ja.md)
2
+ >
3
+ # Corporate theme
2
4
 
3
- Corporate / company-site theme: navy on near-white, hero + news layout, header + footer navigation.
5
+ Conservative business / company-site layout with hero and news section. Navy on near-white, header + footer navigation.
4
6
 
5
7
  ## Customizing
6
8
 
7
9
  In `/admin/sites/<siteId>/theme`:
8
10
 
9
- - Tagline (small line above the site name in the hero)
10
- - Header navigation (label + URL pairs)
11
- - Footer links + legend (address / company info / extra small print)
12
- - Primary color, corner radius
13
-
14
- ## Getting started
15
-
16
- ```bash
17
- npm install
18
- npx ampx sandbox
19
- npm run dev
20
- ```
11
+ - **Tagline** short phrase shown above the site name in the hero
12
+ - **Primary color**
13
+ - **Corner radius**
14
+ - **Top story slug** — feature one published post between the hero and the news section
15
+ - **Logo image URL**
16
+ - **Header navigation** — label + URL pairs
17
+ - **Footer links** — label + URL pairs
18
+ - **Footer legend** — address / company info / extra small print
@@ -1,12 +1,11 @@
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
- export async function corporateFeedHandler({ siteId }: Ctx): Promise<Response> {
9
- const url = publicAssetUrl(`public/plugins/rss/${siteId}/feed.xml`)
7
+ export async function corporateFeedHandler(_ctx: Ctx): Promise<Response> {
8
+ const url = publicAssetUrl('public/plugins/rss/feed.xml')
10
9
  const upstream = await fetch(url, { cache: 'no-store' })
11
10
  if (!upstream.ok) {
12
11
  return new Response(
@@ -7,20 +7,17 @@ import { renderBody } from '@/lib/posts'
7
7
  import { SiteHeader } from '@/components/site-chrome/site-header'
8
8
  import { SiteFooter } from '@/components/site-chrome/site-footer'
9
9
 
10
- export default async function CorporateHome({ params }: ThemeRouteContext) {
11
- const { siteId } = await params
10
+ export default async function CorporateHome(_: ThemeRouteContext) {
12
11
  const [settings, theme, postsResult] = await Promise.all([
13
- loadSiteSettings(siteId),
14
- loadThemeConfig(siteId),
15
- listPublishedPosts({ siteId, limit: 8 }),
12
+ loadSiteSettings(),
13
+ loadThemeConfig(),
14
+ listPublishedPosts({ limit: 8 }),
16
15
  ])
17
16
 
18
17
  // Top-story embed between hero and news. Filtered out of news to
19
18
  // avoid duplication. Skipped silently if missing / unpublished.
20
19
  const featuredSlug = theme.values.featuredSlug?.trim()
21
- const featured = featuredSlug
22
- ? await getPublishedPost(featuredSlug, { siteId })
23
- : null
20
+ const featured = featuredSlug ? await getPublishedPost(featuredSlug) : null
24
21
  const posts = featured
25
22
  ? postsResult.items.filter((p) => p.slug !== featured.slug)
26
23
  : 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 CorporatePost({ 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,12 +1,11 @@
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
- export async function corporateSitemapHandler({ siteId }: Ctx): Promise<Response> {
9
- const url = publicAssetUrl(`public/plugins/seo/${siteId}/sitemap.xml`)
7
+ export async function corporateSitemapHandler(_ctx: Ctx): Promise<Response> {
8
+ const url = publicAssetUrl('public/plugins/seo/sitemap.xml')
10
9
  const upstream = await fetch(url, { cache: 'no-store' })
11
10
  if (!upstream.ok) {
12
11
  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 CorporateTag({ 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
  if (posts.length === 0) notFound()
20
20
 
@@ -3,45 +3,23 @@
3
3
  * selector pattern as the other themes. */
4
4
 
5
5
  [data-theme='corporate'] {
6
- --background: oklch(0.99 0 0);
7
- --foreground: oklch(0.18 0.02 250);
8
- --card: oklch(1 0 0);
9
- --card-foreground: oklch(0.18 0.02 250);
10
- --primary: oklch(0.4 0.13 250);
11
- --primary-foreground: oklch(0.99 0 0);
12
- --secondary: oklch(0.95 0.01 250);
13
- --secondary-foreground: oklch(0.25 0.05 250);
14
- --muted: oklch(0.96 0.005 250);
15
- --muted-foreground: oklch(0.5 0.02 250);
16
- --accent: oklch(0.95 0.02 250);
17
- --accent-foreground: oklch(0.3 0.08 250);
18
- --destructive: oklch(0.55 0.22 25);
19
- --destructive-foreground: oklch(0.99 0 0);
20
- --border: oklch(0.9 0.005 250);
21
- --input: oklch(0.9 0.005 250);
22
- --ring: oklch(0.4 0.13 250);
6
+ --background: light-dark(oklch(0.99 0 0), oklch(0.16 0.02 250));
7
+ --foreground: light-dark(oklch(0.18 0.02 250), oklch(0.96 0.005 250));
8
+ --card: light-dark(oklch(1 0 0), oklch(0.22 0.02 250));
9
+ --card-foreground: light-dark(oklch(0.18 0.02 250), oklch(0.96 0.005 250));
10
+ --primary: light-dark(oklch(0.4 0.13 250), oklch(0.7 0.14 250));
11
+ --primary-foreground: light-dark(oklch(0.99 0 0), oklch(0.16 0.02 250));
12
+ --secondary: light-dark(oklch(0.95 0.01 250), oklch(0.28 0.03 250));
13
+ --secondary-foreground: light-dark(oklch(0.25 0.05 250), oklch(0.96 0.005 250));
14
+ --muted: light-dark(oklch(0.96 0.005 250), oklch(0.26 0.02 250));
15
+ --muted-foreground: light-dark(oklch(0.5 0.02 250), oklch(0.7 0.02 250));
16
+ --accent: light-dark(oklch(0.95 0.02 250), oklch(0.32 0.05 250));
17
+ --accent-foreground: light-dark(oklch(0.3 0.08 250), oklch(0.96 0.005 250));
18
+ --destructive: light-dark(oklch(0.55 0.22 25), oklch(0.55 0.22 25));
19
+ --destructive-foreground: light-dark(oklch(0.99 0 0), oklch(0.99 0 0));
20
+ --border: light-dark(oklch(0.9 0.005 250), oklch(0.32 0.02 250));
21
+ --input: light-dark(oklch(0.9 0.005 250), oklch(0.32 0.02 250));
22
+ --ring: light-dark(oklch(0.4 0.13 250), oklch(0.7 0.14 250));
23
23
  --radius: 0.25rem;
24
24
  --ampless-body-font: system-ui, -apple-system, sans-serif;
25
25
  }
26
-
27
- @media (prefers-color-scheme: dark) {
28
- [data-theme='corporate'] {
29
- --background: oklch(0.16 0.02 250);
30
- --foreground: oklch(0.96 0.005 250);
31
- --card: oklch(0.22 0.02 250);
32
- --card-foreground: oklch(0.96 0.005 250);
33
- --primary: oklch(0.7 0.14 250);
34
- --primary-foreground: oklch(0.16 0.02 250);
35
- --secondary: oklch(0.28 0.03 250);
36
- --secondary-foreground: oklch(0.96 0.005 250);
37
- --muted: oklch(0.26 0.02 250);
38
- --muted-foreground: oklch(0.7 0.02 250);
39
- --accent: oklch(0.32 0.05 250);
40
- --accent-foreground: oklch(0.96 0.005 250);
41
- --destructive: oklch(0.55 0.22 25);
42
- --destructive-foreground: oklch(0.99 0 0);
43
- --border: oklch(0.32 0.02 250);
44
- --input: oklch(0.32 0.02 250);
45
- --ring: oklch(0.7 0.14 250);
46
- }
47
- }
@@ -0,0 +1,31 @@
1
+ > English: [README.md](./README.md)
2
+ >
3
+ # DADS テーマ
4
+
5
+ デジタル庁デザインシステム準拠のレイアウト。政府・自治体・公共系サイト向け。高コントラスト、アクセシビリティ重視、装飾控えめ。公式の **[デジタル庁デザインシステム Tailwind プラグイン](https://github.com/digital-go-jp/tailwind-theme-plugin)**(`@digital-go-jp/tailwind-theme-plugin`、MIT)をベースに構築されています。
6
+
7
+ ## プラグインが提供する機能
8
+
9
+ - **カラーパレット** — プライマリカラーに `solidBlue`(`#0017c1`)を使用し、DADS の全スケール(ライトブルー、シアン、グリーン、ライム、イエロー、オレンジ、レッド、マゼンタ)を Tailwind クラス(`bg-blue-900`、`text-blue-50` など)で利用可能
10
+ - **タイポグラフィ** — プラグイン経由で `fontFamily.sans` に Noto Sans JP を設定。`--ampless-body-font` として参照可能
11
+ - **ボーダー半径** — `rounded-4` / `rounded-6` が利用可能
12
+
13
+ `tokens.css` がプラグインの CSS 変数(`--color-blue-900` など)を ampless 標準のテーマ変数(`--primary`、`--background` など)にバインドするため、共有 chrome(SiteHeader、SiteFooter、shadcn ボタンなど)が自動的に DADS カラーで描画されます。
14
+
15
+ DADS が新しいパレットバージョンを公開した場合、`@digital-go-jp/tailwind-theme-plugin` をバージョンアップするだけでテーマに反映されます。
16
+
17
+ ## カスタマイズ
18
+
19
+ `/admin/sites/<siteId>/theme` で設定:
20
+
21
+ - **ロゴ画像 URL** — 組織マーク
22
+ - **プライマリカラー** — デフォルトは DADS の `solidBlue`。DADS 以外の色に変更するとサイトは DADS 準拠ではなくなります。
23
+ - **トップストーリーのスラッグ** — ヒーローとお知らせ一覧の間に公開済み投稿を 1 件フィーチャー
24
+ - **ヘッダーナビ** — ラベル + URL のペア
25
+ - **フッターリンク** — ラベル + URL のペア
26
+ - **フッター注記** — 住所 / 機関情報 / 追加の注意書き
27
+
28
+ ## 注意事項
29
+
30
+ - ダークモードは反転色による近似実装です。プラグイン 0.3.4 時点では DADS の公式ダークパレットは存在しません。公式ダークパレットが公開された際は、`tokens.css` のダーク変数バインディングを更新してください。
31
+ - このプラグインはデザイントークンのみを提供します。フル DADS コンポーネント(ボタン、アラート、タブなど)については [design-system-example-components](https://github.com/digital-go-jp/design-system-example-components) を参照し、必要に応じて取り込んでください。
@@ -1,6 +1,8 @@
1
- # {{siteName}}
1
+ > 日本語版: [README.ja.md](./README.ja.md)
2
+ >
3
+ # DADS theme
2
4
 
3
- DADS theme built on the official **[Digital Agency Design System Tailwind plugin](https://github.com/digital-go-jp/tailwind-theme-plugin)** (`@digital-go-jp/tailwind-theme-plugin`, MIT). Color palette and typography conform to DADS specifications, suitable for Japanese government / public-sector / institutional sites.
5
+ Government / public-sector layout following the Digital Agency Design System aesthetic. High contrast, accessibility-first, minimal decoration. Built on the official **[Digital Agency Design System Tailwind plugin](https://github.com/digital-go-jp/tailwind-theme-plugin)** (`@digital-go-jp/tailwind-theme-plugin`, MIT).
4
6
 
5
7
  ## What's plugin-backed
6
8
 
@@ -8,28 +10,22 @@ DADS theme — built on the official **[Digital Agency Design System Tailwind pl
8
10
  - **Typography** — `fontFamily.sans` set to Noto Sans JP via the plugin; surfaced through `--ampless-body-font`
9
11
  - **Border radii** — `rounded-4` / `rounded-6` available
10
12
 
11
- `templates/dads/tokens.css` binds the plugin's CSS variables (`--color-blue-900` etc.) to ampless's standard theme variables (`--primary`, `--background`, ...) so all shared chrome (SiteHeader, SiteFooter, shadcn buttons, etc.) automatically renders in DADS colors.
13
+ `tokens.css` binds the plugin's CSS variables (`--color-blue-900` etc.) to the standard ampless theme variables (`--primary`, `--background`, ...) so all shared chrome (SiteHeader, SiteFooter, shadcn buttons, etc.) automatically renders in DADS colors.
12
14
 
13
- When DADS publishes a new palette version, bumping `@digital-go-jp/tailwind-theme-plugin` in `package.json` is enough — the theme picks it up.
15
+ When DADS publishes a new palette version, bumping `@digital-go-jp/tailwind-theme-plugin` is enough — the theme picks it up.
14
16
 
15
17
  ## Customizing
16
18
 
17
19
  In `/admin/sites/<siteId>/theme`:
18
20
 
19
- - **Logo image URL** — branding (organization mark)
20
- - **Primary color** — defaults to DADS solidBlue. Changing to a non-DADS color makes the site no longer DADS-conformant.
21
- - **Top story slug** — feature one published post between hero and news
22
- - **Header navigation / Footer links / Footer legend**
21
+ - **Logo image URL** — organization mark
22
+ - **Primary color** — defaults to DADS `solidBlue`. Changing to a non-DADS color makes the site no longer DADS-conformant.
23
+ - **Top story slug** — feature one published post between the hero and the news section
24
+ - **Header navigation** label + URL pairs
25
+ - **Footer links** — label + URL pairs
26
+ - **Footer legend** — address / agency info / extra small print
23
27
 
24
28
  ## Notes
25
29
 
26
- - Dark mode uses an inverted approximation. DADS doesn't ship an official dark palette as of plugin 0.3.4; when one lands, update `templates/dads/tokens.css` to bind the dark variables.
30
+ - Dark mode uses an inverted approximation. DADS doesn't ship an official dark palette as of plugin 0.3.4; when one lands, update `tokens.css` to bind the dark variables.
27
31
  - The plugin only provides design tokens. For full DADS components (buttons, alerts, tabs, etc.), see [design-system-example-components](https://github.com/digital-go-jp/design-system-example-components) and adapt as needed.
28
-
29
- ## Getting started
30
-
31
- ```bash
32
- npm install
33
- npx ampx sandbox
34
- npm run dev
35
- ```
@@ -1,12 +1,11 @@
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
- export async function dadsFeedHandler({ siteId }: Ctx): Promise<Response> {
9
- const url = publicAssetUrl(`public/plugins/rss/${siteId}/feed.xml`)
7
+ export async function dadsFeedHandler(_ctx: Ctx): Promise<Response> {
8
+ const url = publicAssetUrl('public/plugins/rss/feed.xml')
10
9
  const upstream = await fetch(url, { cache: 'no-store' })
11
10
  if (!upstream.ok) {
12
11
  return new Response(
@@ -11,18 +11,15 @@ import { SiteFooter } from '@/components/site-chrome/site-footer'
11
11
  // formal news list with prominent dates. Layout deliberately
12
12
  // understated — DADS leans on hierarchy and whitespace, not
13
13
  // decoration.
14
- export default async function DadsHome({ params }: ThemeRouteContext) {
15
- const { siteId } = await params
14
+ export default async function DadsHome(_: ThemeRouteContext) {
16
15
  const [settings, theme, postsResult] = await Promise.all([
17
- loadSiteSettings(siteId),
18
- loadThemeConfig(siteId),
19
- listPublishedPosts({ siteId, limit: 10 }),
16
+ loadSiteSettings(),
17
+ loadThemeConfig(),
18
+ listPublishedPosts({ limit: 10 }),
20
19
  ])
21
20
 
22
21
  const featuredSlug = theme.values.featuredSlug?.trim()
23
- const featured = featuredSlug
24
- ? await getPublishedPost(featuredSlug, { siteId })
25
- : null
22
+ const featured = featuredSlug ? await getPublishedPost(featuredSlug) : null
26
23
  const posts = featured
27
24
  ? postsResult.items.filter((p) => p.slug !== featured.slug)
28
25
  : 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 DadsPost({ 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