bsmnt 0.7.0 → 0.9.0

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 (106) hide show
  1. package/dist/application/add-hooks/index.d.ts.map +1 -1
  2. package/dist/application/add-hooks/index.js +1 -0
  3. package/dist/application/add-hooks/index.js.map +1 -1
  4. package/dist/application/add-integration/index.d.ts.map +1 -1
  5. package/dist/application/add-integration/index.js +1 -0
  6. package/dist/application/add-integration/index.js.map +1 -1
  7. package/dist/core/create/execute-plan.d.ts +2 -0
  8. package/dist/core/create/execute-plan.d.ts.map +1 -1
  9. package/dist/core/create/execute-plan.js +3 -0
  10. package/dist/core/create/execute-plan.js.map +1 -1
  11. package/dist/domain/version.d.ts +14 -0
  12. package/dist/domain/version.d.ts.map +1 -0
  13. package/dist/domain/version.js +81 -0
  14. package/dist/domain/version.js.map +1 -0
  15. package/dist/index.js +3 -0
  16. package/dist/index.js.map +1 -1
  17. package/dist/infrastructure/create/executor.d.ts.map +1 -1
  18. package/dist/infrastructure/create/executor.js +2 -0
  19. package/dist/infrastructure/create/executor.js.map +1 -1
  20. package/dist/infrastructure/create/render-generated-docs.d.ts +18 -0
  21. package/dist/infrastructure/create/render-generated-docs.d.ts.map +1 -0
  22. package/dist/infrastructure/create/render-generated-docs.js +72 -0
  23. package/dist/infrastructure/create/render-generated-docs.js.map +1 -0
  24. package/dist/infrastructure/create/setup-dotenvx.d.ts.map +1 -1
  25. package/dist/infrastructure/create/setup-dotenvx.js +10 -4
  26. package/dist/infrastructure/create/setup-dotenvx.js.map +1 -1
  27. package/dist/infrastructure/create/setup-remote.d.ts +6 -1
  28. package/dist/infrastructure/create/setup-remote.d.ts.map +1 -1
  29. package/dist/infrastructure/create/setup-remote.js +7 -2
  30. package/dist/infrastructure/create/setup-remote.js.map +1 -1
  31. package/dist/infrastructure/update/cache.d.ts +14 -0
  32. package/dist/infrastructure/update/cache.d.ts.map +1 -0
  33. package/dist/infrastructure/update/cache.js +41 -0
  34. package/dist/infrastructure/update/cache.js.map +1 -0
  35. package/dist/infrastructure/update/check-version.d.ts +2 -0
  36. package/dist/infrastructure/update/check-version.d.ts.map +1 -0
  37. package/dist/infrastructure/update/check-version.js +33 -0
  38. package/dist/infrastructure/update/check-version.js.map +1 -0
  39. package/dist/infrastructure/update/notifier.d.ts +8 -0
  40. package/dist/infrastructure/update/notifier.d.ts.map +1 -0
  41. package/dist/infrastructure/update/notifier.js +141 -0
  42. package/dist/infrastructure/update/notifier.js.map +1 -0
  43. package/dist/modules/features/cms/sanity-pagebuilder/config.d.ts.map +1 -1
  44. package/dist/modules/features/cms/sanity-pagebuilder/config.js +2 -0
  45. package/dist/modules/features/cms/sanity-pagebuilder/config.js.map +1 -1
  46. package/package.json +2 -1
  47. package/src/agent-skills/manage-env/SKILL.md +5 -3
  48. package/src/modules/features/cms/sanity/files/app/blog/[slug]/page.tsx +10 -9
  49. package/src/modules/features/cms/sanity/files/app/blog/page.tsx +5 -5
  50. package/src/modules/features/cms/sanity/files/lib/scripts/copy-sanity-mcp.ts +6 -6
  51. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/block-actions-item.tsx +81 -0
  52. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/icons.ts +2 -0
  53. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-builder-config.ts +7 -0
  54. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-tree-pane.tsx +4 -6
  55. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/queries.ts +72 -21
  56. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/index.ts +1 -0
  57. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/gated-content.ts +178 -0
  58. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/index.ts +9 -1
  59. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/redirect.ts +5 -4
  60. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/metadata.ts +54 -1
  61. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page-builder.ts +4 -0
  62. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page.ts +11 -0
  63. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/structure.ts +4 -0
  64. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/utils/page-builder-markdown.ts +20 -0
  65. package/src/modules/features/cms/sanity-pagebuilder/files/lib/scripts/sanity-typegen.ts +1 -1
  66. package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/json-ld.tsx +1 -0
  67. package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/metadata.ts +27 -5
  68. package/src/modules/features/env/dotenvx/files/decrypt-env.mjs +12 -12
  69. package/src/modules/features/env/dotenvx/files/push-keys.mjs +3 -3
  70. package/src/modules/features/env/dotenvx/files/save-key.mjs +3 -3
  71. package/src/modules/features/env/dotenvx/files/setup-remote.ts +3 -1
  72. package/src/modules/features/env/dotenvx/files/write-env.mjs +6 -6
  73. package/src/modules/features/forms/hubspot/files/lib/integrations/hubspot/hubspot-form.css +2 -0
  74. package/src/templates/next-default/.env.example +8 -0
  75. package/src/templates/next-default/.github/workflows/ci.yml +7 -0
  76. package/src/templates/next-default/README.md +14 -0
  77. package/src/templates/next-default/biome.json +1 -1
  78. package/src/templates/next-default/components/basement.svg +1 -1
  79. package/src/templates/next-default/knip.json +8 -6
  80. package/src/templates/next-default/lefthook.yml +0 -5
  81. package/src/templates/next-experiments/.env.example +8 -0
  82. package/src/templates/next-experiments/.github/workflows/ci.yml +7 -0
  83. package/src/templates/next-experiments/README.md +14 -0
  84. package/src/templates/next-experiments/biome.json +1 -1
  85. package/src/templates/next-experiments/components/basement.svg +1 -1
  86. package/src/templates/next-experiments/knip.json +8 -6
  87. package/src/templates/next-experiments/lefthook.yml +0 -5
  88. package/src/templates/next-pagebuilder/.env.example +8 -0
  89. package/src/templates/next-pagebuilder/.github/PULL_REQUEST_TEMPLATE.md +13 -0
  90. package/src/templates/next-pagebuilder/.github/workflows/ci.yml +15 -0
  91. package/src/templates/next-pagebuilder/README.md +14 -0
  92. package/src/templates/next-pagebuilder/app/(content)/[[...slug]]/page.tsx +2 -2
  93. package/src/templates/next-pagebuilder/app/actions/unlock-gated-content.ts +39 -0
  94. package/src/templates/next-pagebuilder/biome.json +1 -1
  95. package/src/templates/next-pagebuilder/components/page-builder/components/gated-content/index.tsx +169 -0
  96. package/src/templates/next-pagebuilder/components/page-builder/renderer.tsx +3 -0
  97. package/src/templates/next-pagebuilder/knip.json +8 -6
  98. package/src/templates/next-pagebuilder/lefthook.yml +0 -5
  99. package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.types.ts +81 -0
  100. package/src/templates/next-webgl/.env.example +8 -0
  101. package/src/templates/next-webgl/.github/workflows/ci.yml +7 -0
  102. package/src/templates/next-webgl/README.md +14 -0
  103. package/src/templates/next-webgl/biome.json +1 -1
  104. package/src/templates/next-webgl/components/basement.svg +1 -1
  105. package/src/templates/next-webgl/knip.json +8 -6
  106. package/src/templates/next-webgl/lefthook.yml +0 -5
@@ -6,6 +6,9 @@ interface GenerateMetadataOptions {
6
6
  title?: string | undefined;
7
7
  description?: string | undefined;
8
8
  keywords?: string[] | undefined;
9
+ ogTitle?: string | undefined;
10
+ ogDescription?: string | undefined;
11
+ canonicalUrl?: string | undefined;
9
12
  image?:
10
13
  | {
11
14
  url?: string | undefined;
@@ -33,6 +36,10 @@ interface SanityMetadataImage {
33
36
  interface SanityPageMetadata {
34
37
  metaTitle?: string | null;
35
38
  metaDescription?: string | null;
39
+ keywords?: string[] | null;
40
+ ogTitle?: string | null;
41
+ ogDescription?: string | null;
42
+ canonicalUrl?: string | null;
36
43
  og?: {
37
44
  image?: SanityMetadataImage | null;
38
45
  } | null;
@@ -81,6 +88,9 @@ const generatePageMetadata = (options: GenerateMetadataOptions): Metadata => {
81
88
  title,
82
89
  description,
83
90
  keywords,
91
+ ogTitle,
92
+ ogDescription,
93
+ canonicalUrl,
84
94
  image,
85
95
  url,
86
96
  siteName = "Basement",
@@ -98,29 +108,33 @@ const generatePageMetadata = (options: GenerateMetadataOptions): Metadata => {
98
108
  const width = image?.width || 1200;
99
109
  const height = image?.height || 630;
100
110
 
111
+ // Social title/description fall back to the SEO meta title/description.
112
+ const socialTitle = ogTitle || title;
113
+ const socialDescription = ogDescription || description;
114
+
101
115
  const metadata: Metadata = {
102
116
  metadataBase: new URL(APP_BASE_URL),
103
117
  title,
104
118
  description,
105
119
  keywords,
106
- alternates: { canonical: url || "/" },
120
+ alternates: { canonical: canonicalUrl || url || "/" },
107
121
  openGraph: {
108
- title,
109
- description,
122
+ description: socialDescription,
110
123
  url: fullUrl,
111
124
  siteName,
112
125
  locale: "en_US",
113
126
  type,
114
127
  images: [{ url: imageUrl, width, height }],
128
+ ...(socialTitle && { title: socialTitle }),
115
129
  ...(publishedTime && { publishedTime }),
116
130
  ...(modifiedTime && { modifiedTime }),
117
131
  ...(authors && { authors }),
118
132
  },
119
133
  twitter: {
120
134
  card: "summary_large_image",
121
- title,
122
- description,
135
+ description: socialDescription,
123
136
  images: [{ url: imageUrl, width, height }],
137
+ ...(socialTitle && { title: socialTitle }),
124
138
  },
125
139
  other: { "fb:app_id": process.env.NEXT_PUBLIC_FACEBOOK_APP_ID || "" },
126
140
  };
@@ -155,9 +169,17 @@ export const generateSanityMetadata = ({
155
169
  pageMetadata?.index ??
156
170
  (pageMetadata?.noIndex != null ? !pageMetadata.noIndex : true);
157
171
 
172
+ const keywords = pageMetadata?.keywords?.length
173
+ ? pageMetadata.keywords
174
+ : undefined;
175
+
158
176
  return generatePageMetadata({
159
177
  title,
160
178
  description,
179
+ keywords,
180
+ ogTitle: pageMetadata?.ogTitle || undefined,
181
+ ogDescription: pageMetadata?.ogDescription || undefined,
182
+ canonicalUrl: pageMetadata?.canonicalUrl || undefined,
161
183
  image: imageUrl ? { url: imageUrl, width: 1200, height: 630 } : undefined,
162
184
  url,
163
185
  siteName,
@@ -17,21 +17,21 @@ if (process.env.CI || process.env.VERCEL) {
17
17
  }
18
18
 
19
19
  const args = process.argv.slice(2);
20
- const file =
21
- args.includes("--prod") || args.includes("--production")
22
- ? ".env.production"
23
- : args.includes("--preview")
24
- ? ".env.preview"
25
- : ".env";
20
+ let file = ".env";
21
+ if (args.includes("--prod") || args.includes("--production")) {
22
+ file = ".env.production";
23
+ } else if (args.includes("--preview")) {
24
+ file = ".env.preview";
25
+ }
26
26
 
27
27
  // Friendlier than dotenvx's MISSING_ENV_FILE error when the file isn't created yet.
28
28
  if (!existsSync(file)) {
29
- const flag =
30
- file === ".env.production"
31
- ? " --prod"
32
- : file === ".env.preview"
33
- ? " --preview"
34
- : "";
29
+ let flag = "";
30
+ if (file === ".env.production") {
31
+ flag = " --prod";
32
+ } else if (file === ".env.preview") {
33
+ flag = " --preview";
34
+ }
35
35
  console.error(
36
36
  `No ${file} yet. Add values with: bun run env:set KEY value${flag}`,
37
37
  );
@@ -96,11 +96,11 @@ async function main() {
96
96
  let ok = true;
97
97
  for (const { name, value } of keys) {
98
98
  if (target === "github") {
99
- console.log(`→ gh secret set ${name}`);
99
+ console.info(`→ gh secret set ${name}`);
100
100
  ok = pipe("gh", ["secret", "set", name], value) && ok;
101
101
  } else {
102
102
  for (const env of vercelEnvsFor(name)) {
103
- console.log(`→ vercel env add ${name} ${env} (sensitive)`);
103
+ console.info(`→ vercel env add ${name} ${env} (sensitive)`);
104
104
  // --sensitive: write-only, never shown in the dashboard.
105
105
  ok = pipe("vercel", ["env", "add", name, env, "--sensitive"], value) && ok;
106
106
  }
@@ -116,7 +116,7 @@ async function main() {
116
116
  process.exit(1);
117
117
  }
118
118
 
119
- console.log(
119
+ console.info(
120
120
  target === "vercel"
121
121
  ? "\nDone. Redeploy for the new env vars to take effect."
122
122
  : "\nDone.",
@@ -131,6 +131,6 @@ try {
131
131
  const ref = `op://${VAULT}/${itemRef}/private-key`;
132
132
  setOpRef(ref);
133
133
 
134
- console.log(`\n✓ Saved to 1Password (${VAULT} / "${title}").`);
135
- console.log(` Set DOTENV_PRIVATE_KEY_OP_REF=${ref} in .env.`);
136
- console.log(" Teammates can now restore with `bun run env:setup`.");
134
+ console.info(`\n✓ Saved to 1Password (${VAULT} / "${title}").`);
135
+ console.info(` Set DOTENV_PRIVATE_KEY_OP_REF=${ref} in .env.`);
136
+ console.info(" Teammates can now restore with `bun run env:setup`.");
@@ -166,8 +166,10 @@ function main(): void {
166
166
  }
167
167
 
168
168
  // Push after connecting so the git integration builds the first deployment.
169
+ // `--no-verify` skips pre-push hooks — this is the initial scaffold push, and
170
+ // a hook failure here would strand it behind an already-created empty remote.
169
171
  console.info("→ git push");
170
- if (!run("git", ["push", "-u", "origin", "HEAD"], "git not found.")) {
172
+ if (!run("git", ["push", "-u", "origin", "HEAD", "--no-verify"], "git not found.")) {
171
173
  console.error(
172
174
  "\nConnected, but the push failed — run `git push` to trigger the first deploy.",
173
175
  );
@@ -12,12 +12,12 @@ const [mode, ...rest] = process.argv.slice(2);
12
12
 
13
13
  // --prod / --preview pick the per-environment file; default is the shared .env.
14
14
  const ENV_FLAGS = new Set(["--prod", "--production", "--preview"]);
15
- const file =
16
- rest.includes("--prod") || rest.includes("--production")
17
- ? ".env.production"
18
- : rest.includes("--preview")
19
- ? ".env.preview"
20
- : ".env";
15
+ let file = ".env";
16
+ if (rest.includes("--prod") || rest.includes("--production")) {
17
+ file = ".env.production";
18
+ } else if (rest.includes("--preview")) {
19
+ file = ".env.preview";
20
+ }
21
21
 
22
22
  // On bulk encrypt, keep browser-public NEXT_PUBLIC_* and the 1Password pointer
23
23
  // plaintext (same exclusions the scaffold uses).
@@ -1,3 +1,5 @@
1
+ /* biome-ignore-all lint/style/noDescendingSpecificity: selector order follows HubSpot's DOM, not specificity. */
2
+
1
3
  /* Styles HubSpot's injected DOM, scoped under `.hubspot-form-wrapper`.
2
4
  *
3
5
  * Tokens map onto the basement starter design system — `--color-primary`
@@ -1,6 +1,7 @@
1
1
  # ============================================
2
2
  # Basement Next Starter - Environment Variables
3
3
  # ============================================
4
+ # bsmnt:if dotenvx
4
5
  # This project uses dotenvx (https://dotenvx.com): the committed `.env` holds
5
6
  # shared values, with secrets encrypted there. The private keys live in
6
7
  # `.env.keys` (gitignored) — restore them with `bun run env:setup`.
@@ -15,6 +16,13 @@
15
16
  #
16
17
  # This file just documents the available variables; real values live in `.env`.
17
18
  # ============================================
19
+ # bsmnt:endif
20
+ # bsmnt:if !dotenvx
21
+ # Copy this file to `.env.local` and fill in the values you need.
22
+ # `.env.local` is gitignored, so real values stay off version control.
23
+ # Only NEXT_PUBLIC_* variables are exposed to the browser.
24
+ # ============================================
25
+ # bsmnt:endif
18
26
 
19
27
 
20
28
  # ============================================
@@ -46,6 +46,7 @@ jobs:
46
46
  - name: Lint
47
47
  run: bun run lint
48
48
 
49
+ # bsmnt:if dotenvx
49
50
  # dotenvx needs the private key to decrypt secrets at build time. It's
50
51
  # pulled from 1Password via an org-wide service-account token + the op-ref
51
52
  # committed in .env. Optional: builds pass without it (only plaintext /
@@ -70,6 +71,12 @@ jobs:
70
71
  fi
71
72
  bun run build
72
73
 
74
+ # bsmnt:endif
75
+ # bsmnt:if !dotenvx
76
+ - name: Build
77
+ run: bun run build
78
+
79
+ # bsmnt:endif
73
80
  # Runs your tests if any exist; a fresh project with none passes cleanly.
74
81
  - name: Test
75
82
  run: |
@@ -22,6 +22,7 @@ bun dev
22
22
  | `bun format` | Format the codebase |
23
23
  | `bun typecheck` | Run TypeScript |
24
24
  | `bun analyze` | Analyze the Next.js bundle |
25
+ <!-- bsmnt:if dotenvx -->
25
26
  | `bun env:set KEY value` | Encrypt one value into `.env` (`--plain`, `--preview`/`--prod`) |
26
27
  | `bun env:encrypt-all` | Bulk-encrypt every plaintext value in a file (`--preview`/`--prod`) |
27
28
  | `bun env:setup` | Restore `.env.keys` from 1Password |
@@ -29,7 +30,9 @@ bun dev
29
30
  | `bun env:setup-remote` | Push private keys to GitHub / Vercel (`github`/`vercel`, or pick) |
30
31
  | `bun setup-remote` | Create the GitHub repo, link a Vercel project, and push the key to Vercel env |
31
32
  | `bun env:save` | Save the private key to 1Password (Development vault) |
33
+ <!-- bsmnt:endif -->
32
34
 
35
+ <!-- bsmnt:if dotenvx -->
33
36
  ## Environment variables
34
37
 
35
38
  Secrets are encrypted at rest with [dotenvx](https://dotenvx.com): the committed `.env` holds encrypted values, the private keys live in `.env.keys` (gitignored), and dev/build run through `dotenvx run` so values decrypt automatically.
@@ -53,6 +56,15 @@ Secrets are encrypted at rest with [dotenvx](https://dotenvx.com): the committed
53
56
 
54
57
  A gitleaks pre-commit hook + CI scan block committed plaintext secrets (install: `brew install gitleaks`).
55
58
 
59
+ <!-- bsmnt:endif -->
60
+ <!-- bsmnt:if !dotenvx -->
61
+ ## Environment variables
62
+
63
+ Copy `.env.example` to `.env.local` and fill in the values you need. `.env.local` is gitignored, so it stays off version control. Only `NEXT_PUBLIC_*` variables are exposed to the browser — everything else is server-only.
64
+
65
+ For deploys, set the same variables in your host's dashboard (e.g. Vercel → Settings → Environment Variables).
66
+
67
+ <!-- bsmnt:endif -->
56
68
  ## Project Structure
57
69
 
58
70
  ```txt
@@ -90,8 +102,10 @@ The workflow takes its runtime versions from your manifests, so there's a single
90
102
 
91
103
  It also caches Bun's install cache between runs, and the test step is a no-op until you add real test files.
92
104
 
105
+ <!-- bsmnt:if dotenvx -->
93
106
  The build decrypts the committed `.env` with the dotenvx private key, pulled from 1Password: set an **org-level** `OP_SERVICE_ACCOUNT_TOKEN` (a read-only [service account](https://developer.1password.com/docs/service-accounts/) scoped to the `Development` vault) — one secret for every repo — and CI reads the key via the op-ref committed in `.env`. A per-repo `DOTENV_PRIVATE_KEY` secret (`bun run env:setup-remote github`) works as a fallback; builds pass without either (only plaintext / `NEXT_PUBLIC_*` is read).
94
107
 
108
+ <!-- bsmnt:endif -->
95
109
  ### Branch protection
96
110
 
97
111
  To block merges when CI fails, enable branch protection on `main`:
@@ -83,7 +83,7 @@
83
83
  "complexity": {
84
84
  "noForEach": "off",
85
85
  "useFlatMap": "warn",
86
- "useSimplifiedLogicExpression": "warn"
86
+ "useSimplifiedLogicExpression": "off"
87
87
  },
88
88
  "correctness": {
89
89
  "noInvalidUseBeforeDeclaration": "error",
@@ -1 +1 @@
1
- <svg width="123" height="18" viewBox="0 0 123 18" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4.43626 6.76458V.252441H.381348V17.1671H4.33011v-3.0447c.40337 2.1989 1.78331 3.2984 4.13983 3.2984 3.96996 0 4.86166-.9515 4.86166-5.6664V9.91493c0-4.7361-.8704-5.66641-4.64937-5.66641-2.54758 0-3.77892.54973-4.24597 2.51606Zm.02123 5.94122V9.42863c.10615-1.14174.84919-1.69146 2.29282-1.69146 1.95315 0 2.31406.42286 2.31406 2.30463v1.628c0 1.8395-.36091 2.2623-2.31406 2.2623-1.4224 0-2.16544-.4228-2.29282-1.2263Zm10.23281.8035c0 3.4252.7643 4.0384 4.9253 3.9115 1.9956-.0211 3.418-.8035 3.97-2.4949v2.2412h4.0337V8.62519c0-3.70008-.8917-4.37667-5.6259-4.37667h-.9766c-4.9678 0-5.9019.65544-5.9019 4.14409v.50744h4.0337c-.0425-1.45889.3184-1.71261 2.314-1.71261 1.7833 0 2.123.25372 2.123 1.71261v.71887l-4.0337.02114c-4.0973 0-4.8616.61314-4.8616 3.86924Zm4.246.0423c0-.7823.2123-.9303 1.3162-.9303l3.3331-.0212v.5497c0 .9938-.6793 1.3744-2.4627 1.4166h-.2122c-1.656 0-1.9744-.1691-1.9744-1.0148Zm10.0417-.3383c0 3.5309.8917 4.2075 5.5622 4.2075h.9766c5.3499 0 6.3265-.592 6.3265-3.6367 0-1.9451-.4883-3.108-2.5051-3.7-1.104-.31718-2.0805-.5709-4.1611-.90919-1.3799-.21143-1.762-.46515-1.762-.99373 0-.78231.4246-.99374 1.9956-.99374 1.6347 0 2.0805.29601 2.0805 1.33203v.38058h4.0337v-.38058c0-3.61551-.8917-4.27095-5.6259-4.27095h-.9766c-4.8192 0-5.7533.57087-5.7533 3.8058 0 1.69146.4458 2.64288 2.1867 3.31948 1.2738.4863 3.2057.7823 3.9487.9303 1.8682.3383 2.3141.6766 2.3141 1.184 0 .7612-.4883.9726-2.3141.9726-1.9744 0-2.5051-.2748-2.5051-1.2474v-.2749H28.978v.2749Zm14.0967-2.3681c0 5.5396.9765 6.5756 6.1779 6.5756h.9765c5.1589 0 6.1142-.6766 6.1142-4.271h-4.2459c0 1.0784-.3609 1.3109-2.3353 1.3109-2.0593 0-2.4415-.3594-2.4415-2.1989v-.0634h9.1289v-1.9875c0-5.03207-.9766-5.96238-6.2204-5.96238h-.9765c-5.2014 0-6.1779 1.01488-6.1779 6.59668Zm4.2459-1.628v-.06343c0-1.64918.3822-1.96633 2.4415-1.96633 1.9956 0 2.4202.31715 2.4414 2.02976h-4.8829Zm10.9122 7.9499h4.0337V9.83035c0-1.48003.7006-2.22004 2.1017-2.22004 1.6135 0 1.932.31715 1.932 1.96632v7.59047h4.0124l-.0212-7.46361c.0424-1.39546.743-2.09318 2.1017-2.09318 1.6135 0 1.8895.31715 1.8895 1.96632v7.59047h4.0337l-.0212-8.20362c0-3.97494-.7219-4.71496-4.6069-4.71496-2.1443 0-3.4393.90916-3.9063 2.91778-.1699-2.41034-.8705-2.91778-4.0974-2.91778-1.8895 0-2.9722.76116-3.418 2.66406V4.50224h-4.0337V17.1671Zm21.6545-6.3219c0 5.5396.9766 6.5756 6.1991 6.5756h.9766c5.1164 0 6.0717-.6766 6.0717-4.271H88.91c0 1.0784-.3609 1.3109-2.3141 1.3109-2.1017 0-2.4839-.3594-2.4839-2.1989v-.0634h9.1289v-1.9875c0-5.03207-.9766-5.96238-6.1779-5.96238h-.9766c-5.2225 0-6.1991 1.01488-6.1991 6.59668Zm4.2247-1.628v-.06343c0-1.64918.3822-1.96633 2.4839-1.96633 1.9744 0 2.399.31715 2.4202 2.02976H84.112Zm10.891 7.9499h4.0337V9.61892c.0212-1.35317.7642-2.00861 2.2293-2.00861 1.868 0 2.208.35943 2.208 2.30462v7.25217h4.033l-.021-8.20362c0-3.97494-.7-4.71496-4.394-4.71496-2.336 0-3.5671.76116-4.0553 2.81206V4.50224H95.003V17.1671Zm13.545-9.30307h1.252v5.05327c0 3.5732.828 4.2498 5.095 4.2498h2.76v-3.5944h-2.335c-1.253 0-1.486-.2114-1.486-1.2686V7.86403h3.63V4.50224h-3.63V1.77476h-3.821v2.72748h-1.465v3.36179Zm10.636 9.30307h3.63v-3.5944h-3.63v3.5944Z" fill="#EFEFEF"/></svg>
1
+ <svg width="123" height="18" viewBox="0 0 123 18" fill="none" xmlns="http://www.w3.org/2000/svg"><title>basement</title><path d="M4.43626 6.76458V.252441H.381348V17.1671H4.33011v-3.0447c.40337 2.1989 1.78331 3.2984 4.13983 3.2984 3.96996 0 4.86166-.9515 4.86166-5.6664V9.91493c0-4.7361-.8704-5.66641-4.64937-5.66641-2.54758 0-3.77892.54973-4.24597 2.51606Zm.02123 5.94122V9.42863c.10615-1.14174.84919-1.69146 2.29282-1.69146 1.95315 0 2.31406.42286 2.31406 2.30463v1.628c0 1.8395-.36091 2.2623-2.31406 2.2623-1.4224 0-2.16544-.4228-2.29282-1.2263Zm10.23281.8035c0 3.4252.7643 4.0384 4.9253 3.9115 1.9956-.0211 3.418-.8035 3.97-2.4949v2.2412h4.0337V8.62519c0-3.70008-.8917-4.37667-5.6259-4.37667h-.9766c-4.9678 0-5.9019.65544-5.9019 4.14409v.50744h4.0337c-.0425-1.45889.3184-1.71261 2.314-1.71261 1.7833 0 2.123.25372 2.123 1.71261v.71887l-4.0337.02114c-4.0973 0-4.8616.61314-4.8616 3.86924Zm4.246.0423c0-.7823.2123-.9303 1.3162-.9303l3.3331-.0212v.5497c0 .9938-.6793 1.3744-2.4627 1.4166h-.2122c-1.656 0-1.9744-.1691-1.9744-1.0148Zm10.0417-.3383c0 3.5309.8917 4.2075 5.5622 4.2075h.9766c5.3499 0 6.3265-.592 6.3265-3.6367 0-1.9451-.4883-3.108-2.5051-3.7-1.104-.31718-2.0805-.5709-4.1611-.90919-1.3799-.21143-1.762-.46515-1.762-.99373 0-.78231.4246-.99374 1.9956-.99374 1.6347 0 2.0805.29601 2.0805 1.33203v.38058h4.0337v-.38058c0-3.61551-.8917-4.27095-5.6259-4.27095h-.9766c-4.8192 0-5.7533.57087-5.7533 3.8058 0 1.69146.4458 2.64288 2.1867 3.31948 1.2738.4863 3.2057.7823 3.9487.9303 1.8682.3383 2.3141.6766 2.3141 1.184 0 .7612-.4883.9726-2.3141.9726-1.9744 0-2.5051-.2748-2.5051-1.2474v-.2749H28.978v.2749Zm14.0967-2.3681c0 5.5396.9765 6.5756 6.1779 6.5756h.9765c5.1589 0 6.1142-.6766 6.1142-4.271h-4.2459c0 1.0784-.3609 1.3109-2.3353 1.3109-2.0593 0-2.4415-.3594-2.4415-2.1989v-.0634h9.1289v-1.9875c0-5.03207-.9766-5.96238-6.2204-5.96238h-.9765c-5.2014 0-6.1779 1.01488-6.1779 6.59668Zm4.2459-1.628v-.06343c0-1.64918.3822-1.96633 2.4415-1.96633 1.9956 0 2.4202.31715 2.4414 2.02976h-4.8829Zm10.9122 7.9499h4.0337V9.83035c0-1.48003.7006-2.22004 2.1017-2.22004 1.6135 0 1.932.31715 1.932 1.96632v7.59047h4.0124l-.0212-7.46361c.0424-1.39546.743-2.09318 2.1017-2.09318 1.6135 0 1.8895.31715 1.8895 1.96632v7.59047h4.0337l-.0212-8.20362c0-3.97494-.7219-4.71496-4.6069-4.71496-2.1443 0-3.4393.90916-3.9063 2.91778-.1699-2.41034-.8705-2.91778-4.0974-2.91778-1.8895 0-2.9722.76116-3.418 2.66406V4.50224h-4.0337V17.1671Zm21.6545-6.3219c0 5.5396.9766 6.5756 6.1991 6.5756h.9766c5.1164 0 6.0717-.6766 6.0717-4.271H88.91c0 1.0784-.3609 1.3109-2.3141 1.3109-2.1017 0-2.4839-.3594-2.4839-2.1989v-.0634h9.1289v-1.9875c0-5.03207-.9766-5.96238-6.1779-5.96238h-.9766c-5.2225 0-6.1991 1.01488-6.1991 6.59668Zm4.2247-1.628v-.06343c0-1.64918.3822-1.96633 2.4839-1.96633 1.9744 0 2.399.31715 2.4202 2.02976H84.112Zm10.891 7.9499h4.0337V9.61892c.0212-1.35317.7642-2.00861 2.2293-2.00861 1.868 0 2.208.35943 2.208 2.30462v7.25217h4.033l-.021-8.20362c0-3.97494-.7-4.71496-4.394-4.71496-2.336 0-3.5671.76116-4.0553 2.81206V4.50224H95.003V17.1671Zm13.545-9.30307h1.252v5.05327c0 3.5732.828 4.2498 5.095 4.2498h2.76v-3.5944h-2.335c-1.253 0-1.486-.2114-1.486-1.2686V7.86403h3.63V4.50224h-3.63V1.77476h-3.821v2.72748h-1.465v3.36179Zm10.636 9.30307h3.63v-3.5944h-3.63v3.5944Z" fill="#EFEFEF"/></svg>
@@ -12,11 +12,13 @@
12
12
  "@svgr/webpack",
13
13
  "gsap",
14
14
  "@gsap/react",
15
- "motion"
15
+ "motion",
16
+ "detect-gpu",
17
+ "lodash-es",
18
+ "@types/lodash-es",
19
+ "react-device-detect",
20
+ "zustand"
16
21
  ],
17
- "ignoreBinaries": [
18
- "op",
19
- "gh",
20
- "vercel"
21
- ]
22
+ "ignoreBinaries": ["op", "gh", "vercel"],
23
+ "ignoreFiles": [".agents/**", ".claude/**", "hooks/**"]
22
24
  }
@@ -14,11 +14,6 @@ pre-commit:
14
14
  run: bunx tsgo --noEmit --incremental false
15
15
  stage_fixed: false
16
16
 
17
- pre-push:
18
- commands:
19
- knip:
20
- run: bun run knip
21
-
22
17
  post-merge:
23
18
  commands:
24
19
  install-deps:
@@ -1,6 +1,7 @@
1
1
  # ============================================
2
2
  # Basement Next Starter - Environment Variables
3
3
  # ============================================
4
+ # bsmnt:if dotenvx
4
5
  # This project uses dotenvx (https://dotenvx.com): the committed `.env` holds
5
6
  # shared values, with secrets encrypted there. The private keys live in
6
7
  # `.env.keys` (gitignored) — restore them with `bun run env:setup`.
@@ -15,6 +16,13 @@
15
16
  #
16
17
  # This file just documents the available variables; real values live in `.env`.
17
18
  # ============================================
19
+ # bsmnt:endif
20
+ # bsmnt:if !dotenvx
21
+ # Copy this file to `.env.local` and fill in the values you need.
22
+ # `.env.local` is gitignored, so real values stay off version control.
23
+ # Only NEXT_PUBLIC_* variables are exposed to the browser.
24
+ # ============================================
25
+ # bsmnt:endif
18
26
 
19
27
 
20
28
  # ============================================
@@ -46,6 +46,7 @@ jobs:
46
46
  - name: Lint
47
47
  run: bun run lint
48
48
 
49
+ # bsmnt:if dotenvx
49
50
  # dotenvx needs the private key to decrypt secrets at build time. It's
50
51
  # pulled from 1Password via an org-wide service-account token + the op-ref
51
52
  # committed in .env. Optional: builds pass without it (only plaintext /
@@ -70,6 +71,12 @@ jobs:
70
71
  fi
71
72
  bun run build
72
73
 
74
+ # bsmnt:endif
75
+ # bsmnt:if !dotenvx
76
+ - name: Build
77
+ run: bun run build
78
+
79
+ # bsmnt:endif
73
80
  # Runs your tests if any exist; a fresh project with none passes cleanly.
74
81
  - name: Test
75
82
  run: |
@@ -22,6 +22,7 @@ bun dev
22
22
  | `bun format` | Format the codebase |
23
23
  | `bun typecheck` | Run TypeScript |
24
24
  | `bun analyze` | Analyze the Next.js bundle |
25
+ <!-- bsmnt:if dotenvx -->
25
26
  | `bun env:set KEY value` | Encrypt one value into `.env` (`--plain`, `--preview`/`--prod`) |
26
27
  | `bun env:encrypt-all` | Bulk-encrypt every plaintext value in a file (`--preview`/`--prod`) |
27
28
  | `bun env:setup` | Restore `.env.keys` from 1Password |
@@ -29,7 +30,9 @@ bun dev
29
30
  | `bun env:setup-remote` | Push private keys to GitHub / Vercel (`github`/`vercel`, or pick) |
30
31
  | `bun setup-remote` | Create the GitHub repo, link a Vercel project, and push the key to Vercel env |
31
32
  | `bun env:save` | Save the private key to 1Password (Development vault) |
33
+ <!-- bsmnt:endif -->
32
34
 
35
+ <!-- bsmnt:if dotenvx -->
33
36
  ## Environment variables
34
37
 
35
38
  Secrets are encrypted at rest with [dotenvx](https://dotenvx.com): the committed `.env` holds encrypted values, the private keys live in `.env.keys` (gitignored), and dev/build run through `dotenvx run` so values decrypt automatically.
@@ -53,6 +56,15 @@ Secrets are encrypted at rest with [dotenvx](https://dotenvx.com): the committed
53
56
 
54
57
  A gitleaks pre-commit hook + CI scan block committed plaintext secrets (install: `brew install gitleaks`).
55
58
 
59
+ <!-- bsmnt:endif -->
60
+ <!-- bsmnt:if !dotenvx -->
61
+ ## Environment variables
62
+
63
+ Copy `.env.example` to `.env.local` and fill in the values you need. `.env.local` is gitignored, so it stays off version control. Only `NEXT_PUBLIC_*` variables are exposed to the browser — everything else is server-only.
64
+
65
+ For deploys, set the same variables in your host's dashboard (e.g. Vercel → Settings → Environment Variables).
66
+
67
+ <!-- bsmnt:endif -->
56
68
  ## Project Structure
57
69
 
58
70
  ```txt
@@ -90,8 +102,10 @@ The workflow takes its runtime versions from your manifests, so there's a single
90
102
 
91
103
  It also caches Bun's install cache between runs, and the test step is a no-op until you add real test files.
92
104
 
105
+ <!-- bsmnt:if dotenvx -->
93
106
  The build decrypts the committed `.env` with the dotenvx private key, pulled from 1Password: set an **org-level** `OP_SERVICE_ACCOUNT_TOKEN` (a read-only [service account](https://developer.1password.com/docs/service-accounts/) scoped to the `Development` vault) — one secret for every repo — and CI reads the key via the op-ref committed in `.env`. A per-repo `DOTENV_PRIVATE_KEY` secret (`bun run env:setup-remote github`) works as a fallback; builds pass without either (only plaintext / `NEXT_PUBLIC_*` is read).
94
107
 
108
+ <!-- bsmnt:endif -->
95
109
  ### Branch protection
96
110
 
97
111
  To block merges when CI fails, enable branch protection on `main`:
@@ -83,7 +83,7 @@
83
83
  "complexity": {
84
84
  "noForEach": "off",
85
85
  "useFlatMap": "warn",
86
- "useSimplifiedLogicExpression": "warn"
86
+ "useSimplifiedLogicExpression": "off"
87
87
  },
88
88
  "correctness": {
89
89
  "noInvalidUseBeforeDeclaration": "error",
@@ -1 +1 @@
1
- <svg width="123" height="18" viewBox="0 0 123 18" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4.43626 6.76458V.252441H.381348V17.1671H4.33011v-3.0447c.40337 2.1989 1.78331 3.2984 4.13983 3.2984 3.96996 0 4.86166-.9515 4.86166-5.6664V9.91493c0-4.7361-.8704-5.66641-4.64937-5.66641-2.54758 0-3.77892.54973-4.24597 2.51606Zm.02123 5.94122V9.42863c.10615-1.14174.84919-1.69146 2.29282-1.69146 1.95315 0 2.31406.42286 2.31406 2.30463v1.628c0 1.8395-.36091 2.2623-2.31406 2.2623-1.4224 0-2.16544-.4228-2.29282-1.2263Zm10.23281.8035c0 3.4252.7643 4.0384 4.9253 3.9115 1.9956-.0211 3.418-.8035 3.97-2.4949v2.2412h4.0337V8.62519c0-3.70008-.8917-4.37667-5.6259-4.37667h-.9766c-4.9678 0-5.9019.65544-5.9019 4.14409v.50744h4.0337c-.0425-1.45889.3184-1.71261 2.314-1.71261 1.7833 0 2.123.25372 2.123 1.71261v.71887l-4.0337.02114c-4.0973 0-4.8616.61314-4.8616 3.86924Zm4.246.0423c0-.7823.2123-.9303 1.3162-.9303l3.3331-.0212v.5497c0 .9938-.6793 1.3744-2.4627 1.4166h-.2122c-1.656 0-1.9744-.1691-1.9744-1.0148Zm10.0417-.3383c0 3.5309.8917 4.2075 5.5622 4.2075h.9766c5.3499 0 6.3265-.592 6.3265-3.6367 0-1.9451-.4883-3.108-2.5051-3.7-1.104-.31718-2.0805-.5709-4.1611-.90919-1.3799-.21143-1.762-.46515-1.762-.99373 0-.78231.4246-.99374 1.9956-.99374 1.6347 0 2.0805.29601 2.0805 1.33203v.38058h4.0337v-.38058c0-3.61551-.8917-4.27095-5.6259-4.27095h-.9766c-4.8192 0-5.7533.57087-5.7533 3.8058 0 1.69146.4458 2.64288 2.1867 3.31948 1.2738.4863 3.2057.7823 3.9487.9303 1.8682.3383 2.3141.6766 2.3141 1.184 0 .7612-.4883.9726-2.3141.9726-1.9744 0-2.5051-.2748-2.5051-1.2474v-.2749H28.978v.2749Zm14.0967-2.3681c0 5.5396.9765 6.5756 6.1779 6.5756h.9765c5.1589 0 6.1142-.6766 6.1142-4.271h-4.2459c0 1.0784-.3609 1.3109-2.3353 1.3109-2.0593 0-2.4415-.3594-2.4415-2.1989v-.0634h9.1289v-1.9875c0-5.03207-.9766-5.96238-6.2204-5.96238h-.9765c-5.2014 0-6.1779 1.01488-6.1779 6.59668Zm4.2459-1.628v-.06343c0-1.64918.3822-1.96633 2.4415-1.96633 1.9956 0 2.4202.31715 2.4414 2.02976h-4.8829Zm10.9122 7.9499h4.0337V9.83035c0-1.48003.7006-2.22004 2.1017-2.22004 1.6135 0 1.932.31715 1.932 1.96632v7.59047h4.0124l-.0212-7.46361c.0424-1.39546.743-2.09318 2.1017-2.09318 1.6135 0 1.8895.31715 1.8895 1.96632v7.59047h4.0337l-.0212-8.20362c0-3.97494-.7219-4.71496-4.6069-4.71496-2.1443 0-3.4393.90916-3.9063 2.91778-.1699-2.41034-.8705-2.91778-4.0974-2.91778-1.8895 0-2.9722.76116-3.418 2.66406V4.50224h-4.0337V17.1671Zm21.6545-6.3219c0 5.5396.9766 6.5756 6.1991 6.5756h.9766c5.1164 0 6.0717-.6766 6.0717-4.271H88.91c0 1.0784-.3609 1.3109-2.3141 1.3109-2.1017 0-2.4839-.3594-2.4839-2.1989v-.0634h9.1289v-1.9875c0-5.03207-.9766-5.96238-6.1779-5.96238h-.9766c-5.2225 0-6.1991 1.01488-6.1991 6.59668Zm4.2247-1.628v-.06343c0-1.64918.3822-1.96633 2.4839-1.96633 1.9744 0 2.399.31715 2.4202 2.02976H84.112Zm10.891 7.9499h4.0337V9.61892c.0212-1.35317.7642-2.00861 2.2293-2.00861 1.868 0 2.208.35943 2.208 2.30462v7.25217h4.033l-.021-8.20362c0-3.97494-.7-4.71496-4.394-4.71496-2.336 0-3.5671.76116-4.0553 2.81206V4.50224H95.003V17.1671Zm13.545-9.30307h1.252v5.05327c0 3.5732.828 4.2498 5.095 4.2498h2.76v-3.5944h-2.335c-1.253 0-1.486-.2114-1.486-1.2686V7.86403h3.63V4.50224h-3.63V1.77476h-3.821v2.72748h-1.465v3.36179Zm10.636 9.30307h3.63v-3.5944h-3.63v3.5944Z" fill="#EFEFEF"/></svg>
1
+ <svg width="123" height="18" viewBox="0 0 123 18" fill="none" xmlns="http://www.w3.org/2000/svg"><title>basement</title><path d="M4.43626 6.76458V.252441H.381348V17.1671H4.33011v-3.0447c.40337 2.1989 1.78331 3.2984 4.13983 3.2984 3.96996 0 4.86166-.9515 4.86166-5.6664V9.91493c0-4.7361-.8704-5.66641-4.64937-5.66641-2.54758 0-3.77892.54973-4.24597 2.51606Zm.02123 5.94122V9.42863c.10615-1.14174.84919-1.69146 2.29282-1.69146 1.95315 0 2.31406.42286 2.31406 2.30463v1.628c0 1.8395-.36091 2.2623-2.31406 2.2623-1.4224 0-2.16544-.4228-2.29282-1.2263Zm10.23281.8035c0 3.4252.7643 4.0384 4.9253 3.9115 1.9956-.0211 3.418-.8035 3.97-2.4949v2.2412h4.0337V8.62519c0-3.70008-.8917-4.37667-5.6259-4.37667h-.9766c-4.9678 0-5.9019.65544-5.9019 4.14409v.50744h4.0337c-.0425-1.45889.3184-1.71261 2.314-1.71261 1.7833 0 2.123.25372 2.123 1.71261v.71887l-4.0337.02114c-4.0973 0-4.8616.61314-4.8616 3.86924Zm4.246.0423c0-.7823.2123-.9303 1.3162-.9303l3.3331-.0212v.5497c0 .9938-.6793 1.3744-2.4627 1.4166h-.2122c-1.656 0-1.9744-.1691-1.9744-1.0148Zm10.0417-.3383c0 3.5309.8917 4.2075 5.5622 4.2075h.9766c5.3499 0 6.3265-.592 6.3265-3.6367 0-1.9451-.4883-3.108-2.5051-3.7-1.104-.31718-2.0805-.5709-4.1611-.90919-1.3799-.21143-1.762-.46515-1.762-.99373 0-.78231.4246-.99374 1.9956-.99374 1.6347 0 2.0805.29601 2.0805 1.33203v.38058h4.0337v-.38058c0-3.61551-.8917-4.27095-5.6259-4.27095h-.9766c-4.8192 0-5.7533.57087-5.7533 3.8058 0 1.69146.4458 2.64288 2.1867 3.31948 1.2738.4863 3.2057.7823 3.9487.9303 1.8682.3383 2.3141.6766 2.3141 1.184 0 .7612-.4883.9726-2.3141.9726-1.9744 0-2.5051-.2748-2.5051-1.2474v-.2749H28.978v.2749Zm14.0967-2.3681c0 5.5396.9765 6.5756 6.1779 6.5756h.9765c5.1589 0 6.1142-.6766 6.1142-4.271h-4.2459c0 1.0784-.3609 1.3109-2.3353 1.3109-2.0593 0-2.4415-.3594-2.4415-2.1989v-.0634h9.1289v-1.9875c0-5.03207-.9766-5.96238-6.2204-5.96238h-.9765c-5.2014 0-6.1779 1.01488-6.1779 6.59668Zm4.2459-1.628v-.06343c0-1.64918.3822-1.96633 2.4415-1.96633 1.9956 0 2.4202.31715 2.4414 2.02976h-4.8829Zm10.9122 7.9499h4.0337V9.83035c0-1.48003.7006-2.22004 2.1017-2.22004 1.6135 0 1.932.31715 1.932 1.96632v7.59047h4.0124l-.0212-7.46361c.0424-1.39546.743-2.09318 2.1017-2.09318 1.6135 0 1.8895.31715 1.8895 1.96632v7.59047h4.0337l-.0212-8.20362c0-3.97494-.7219-4.71496-4.6069-4.71496-2.1443 0-3.4393.90916-3.9063 2.91778-.1699-2.41034-.8705-2.91778-4.0974-2.91778-1.8895 0-2.9722.76116-3.418 2.66406V4.50224h-4.0337V17.1671Zm21.6545-6.3219c0 5.5396.9766 6.5756 6.1991 6.5756h.9766c5.1164 0 6.0717-.6766 6.0717-4.271H88.91c0 1.0784-.3609 1.3109-2.3141 1.3109-2.1017 0-2.4839-.3594-2.4839-2.1989v-.0634h9.1289v-1.9875c0-5.03207-.9766-5.96238-6.1779-5.96238h-.9766c-5.2225 0-6.1991 1.01488-6.1991 6.59668Zm4.2247-1.628v-.06343c0-1.64918.3822-1.96633 2.4839-1.96633 1.9744 0 2.399.31715 2.4202 2.02976H84.112Zm10.891 7.9499h4.0337V9.61892c.0212-1.35317.7642-2.00861 2.2293-2.00861 1.868 0 2.208.35943 2.208 2.30462v7.25217h4.033l-.021-8.20362c0-3.97494-.7-4.71496-4.394-4.71496-2.336 0-3.5671.76116-4.0553 2.81206V4.50224H95.003V17.1671Zm13.545-9.30307h1.252v5.05327c0 3.5732.828 4.2498 5.095 4.2498h2.76v-3.5944h-2.335c-1.253 0-1.486-.2114-1.486-1.2686V7.86403h3.63V4.50224h-3.63V1.77476h-3.821v2.72748h-1.465v3.36179Zm10.636 9.30307h3.63v-3.5944h-3.63v3.5944Z" fill="#EFEFEF"/></svg>
@@ -12,11 +12,13 @@
12
12
  "@svgr/webpack",
13
13
  "gsap",
14
14
  "@gsap/react",
15
- "motion"
15
+ "motion",
16
+ "detect-gpu",
17
+ "lodash-es",
18
+ "@types/lodash-es",
19
+ "react-device-detect",
20
+ "zustand"
16
21
  ],
17
- "ignoreBinaries": [
18
- "op",
19
- "gh",
20
- "vercel"
21
- ]
22
+ "ignoreBinaries": ["op", "gh", "vercel"],
23
+ "ignoreFiles": [".agents/**", ".claude/**", "hooks/**"]
22
24
  }
@@ -14,11 +14,6 @@ pre-commit:
14
14
  run: bunx tsgo --noEmit --incremental false
15
15
  stage_fixed: false
16
16
 
17
- pre-push:
18
- commands:
19
- knip:
20
- run: bun run knip
21
-
22
17
  post-merge:
23
18
  commands:
24
19
  install-deps:
@@ -1,6 +1,7 @@
1
1
  # ============================================
2
2
  # Basement Next Starter - Environment Variables
3
3
  # ============================================
4
+ # bsmnt:if dotenvx
4
5
  # This project uses dotenvx (https://dotenvx.com): the committed `.env` holds
5
6
  # shared values, with secrets encrypted there. The private keys live in
6
7
  # `.env.keys` (gitignored) — restore them with `bun run env:setup`.
@@ -15,6 +16,13 @@
15
16
  #
16
17
  # This file just documents the available variables; real values live in `.env`.
17
18
  # ============================================
19
+ # bsmnt:endif
20
+ # bsmnt:if !dotenvx
21
+ # Copy this file to `.env.local` and fill in the values you need.
22
+ # `.env.local` is gitignored, so real values stay off version control.
23
+ # Only NEXT_PUBLIC_* variables are exposed to the browser.
24
+ # ============================================
25
+ # bsmnt:endif
18
26
 
19
27
  # Base URL
20
28
  NEXT_PUBLIC_BASE_URL=
@@ -0,0 +1,13 @@
1
+ ## Summary
2
+
3
+ <!-- Brief description of what this PR does -->
4
+
5
+ ## Changes
6
+
7
+ -
8
+
9
+ ## Checklist
10
+
11
+ - [ ] `bun lint` passes
12
+ - [ ] Tested locally in dev mode
13
+ - [ ] No breaking changes (or documented in summary)
@@ -46,6 +46,7 @@ jobs:
46
46
  - name: Lint
47
47
  run: bun run lint
48
48
 
49
+ # bsmnt:if dotenvx
49
50
  # dotenvx needs the private key to decrypt secrets at build time. It's
50
51
  # pulled from 1Password via an org-wide service-account token + the op-ref
51
52
  # committed in .env. Optional: builds pass without it (only plaintext /
@@ -77,6 +78,20 @@ jobs:
77
78
  fi
78
79
  bun run build
79
80
 
81
+ # bsmnt:endif
82
+ # bsmnt:if !dotenvx
83
+ - name: Build
84
+ env:
85
+ # Sanity project ID and dataset are public (they ship in the client
86
+ # bundle), so they are stored as repository variables. The Sanity read
87
+ # token is secret — add it under Settings → Secrets and variables →
88
+ # Actions. All three are empty (and harmless) until Sanity is set up.
89
+ NEXT_PUBLIC_SANITY_PROJECT_ID: ${{ vars.NEXT_PUBLIC_SANITY_PROJECT_ID }}
90
+ NEXT_PUBLIC_SANITY_DATASET: ${{ vars.NEXT_PUBLIC_SANITY_DATASET }}
91
+ SANITY_API_READ_TOKEN: ${{ secrets.SANITY_API_READ_TOKEN }}
92
+ run: bun run build
93
+
94
+ # bsmnt:endif
80
95
  # Runs your tests if any exist; a fresh project with none passes cleanly.
81
96
  - name: Test
82
97
  run: |
@@ -21,6 +21,7 @@ bun dev
21
21
  - `bun build` - Production build
22
22
  - `bun lint` - Run linter
23
23
  - `bun sanity:typegen` - Generate Sanity types
24
+ <!-- bsmnt:if dotenvx -->
24
25
  - `bun env:set KEY value` - Encrypt one value into `.env` (`--plain`, `--preview`/`--prod`)
25
26
  - `bun env:encrypt-all` - Bulk-encrypt every plaintext value in a file (`--preview`/`--prod`)
26
27
  - `bun env:setup` - Restore `.env.keys` from 1Password
@@ -28,7 +29,9 @@ bun dev
28
29
  - `bun env:setup-remote` - Push private keys to GitHub / Vercel (`github`/`vercel`, or pick)
29
30
  - `bun setup-remote` - Create the GitHub repo, link a Vercel project, and push the key to Vercel env
30
31
  - `bun env:save` - Save the private key to 1Password (Development vault)
32
+ <!-- bsmnt:endif -->
31
33
 
34
+ <!-- bsmnt:if dotenvx -->
32
35
  ## Environment variables
33
36
 
34
37
  Secrets are encrypted at rest with [dotenvx](https://dotenvx.com): the committed `.env` holds encrypted values, the private keys live in `.env.keys` (gitignored), and dev/build run through `dotenvx run` so values decrypt automatically.
@@ -52,6 +55,15 @@ Secrets are encrypted at rest with [dotenvx](https://dotenvx.com): the committed
52
55
 
53
56
  A gitleaks pre-commit hook + CI scan block committed plaintext secrets (install: `brew install gitleaks`).
54
57
 
58
+ <!-- bsmnt:endif -->
59
+ <!-- bsmnt:if !dotenvx -->
60
+ ## Environment variables
61
+
62
+ Copy `.env.example` to `.env.local` and fill in the values you need. `.env.local` is gitignored, so it stays off version control. Only `NEXT_PUBLIC_*` variables are exposed to the browser — everything else is server-only.
63
+
64
+ For deploys, set the same variables in your host's dashboard (e.g. Vercel → Settings → Environment Variables).
65
+
66
+ <!-- bsmnt:endif -->
55
67
  ## Continuous Integration
56
68
 
57
69
  A GitHub Actions workflow (`.github/workflows/ci.yml`) runs on every pull request to `main` and on pushes to `main`. It runs type checking, linting, the production build, tests, and `knip`.
@@ -63,8 +75,10 @@ The workflow takes its runtime versions from your manifests, so there's a single
63
75
 
64
76
  It also caches Bun's install cache between runs, and the test step is a no-op until you add real test files.
65
77
 
78
+ <!-- bsmnt:if dotenvx -->
66
79
  The build decrypts the committed `.env` with the dotenvx private key, pulled from 1Password: set an **org-level** `OP_SERVICE_ACCOUNT_TOKEN` (a read-only [service account](https://developer.1password.com/docs/service-accounts/) scoped to the `Development` vault) — one secret for every repo — and CI reads the key via the op-ref committed in `.env`. A per-repo `DOTENV_PRIVATE_KEY` secret (`bun run env:setup-remote github`) works as a fallback; builds pass without either (only plaintext / `NEXT_PUBLIC_*` is read).
67
80
 
81
+ <!-- bsmnt:endif -->
68
82
  ### Sanity environment
69
83
 
70
84
  The build reads Sanity config from GitHub Actions variables/secrets (empty and harmless until you configure Sanity). Once your repo is on GitHub, the quickest way to set them is:
@@ -14,7 +14,7 @@ import { checkRedirect } from "@/lib/utils/check-redirect"
14
14
  import { generateSanityMetadata } from "@/lib/utils/metadata"
15
15
  import { getSlugTag } from "@/lib/utils/slug-tag"
16
16
 
17
- const getPageDocument = async (slug: string | null, stega = true) =>
17
+ const getPageDocument = async (slug: string | null, stega?: boolean) =>
18
18
  fetchSanity<PAGE_QUERY_RESULT>({
19
19
  query: PAGE_QUERY,
20
20
  params: { slug },
@@ -27,7 +27,7 @@ const toPath = (segments?: string[]) =>
27
27
 
28
28
  const getResolvedPage = async (
29
29
  path: string | null,
30
- stega = true
30
+ stega?: boolean
31
31
  ): Promise<NonNullable<PAGE_QUERY_RESULT> | null> => {
32
32
  const { data: page } = await getPageDocument(path, stega)
33
33
  return (page as NonNullable<PAGE_QUERY_RESULT> | null) ?? null