simple-site-generator 0.1.3 → 0.2.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.
- package/dist/cli.js +165 -2
- package/dist/commands/add-page.js +50 -0
- package/dist/commands/build.js +20 -0
- package/dist/commands/check.js +53 -0
- package/dist/commands/init.js +229 -0
- package/dist/commands/verify.js +150 -0
- package/dist/pages/page-schema.js +161 -0
- package/dist/pages/render-page.js +46 -0
- package/dist/paths.js +27 -0
- package/dist/types/cli.d.ts +1 -0
- package/dist/types/commands/add-page.d.ts +26 -0
- package/dist/types/commands/build.d.ts +18 -0
- package/dist/types/commands/check.d.ts +20 -0
- package/dist/types/commands/init.d.ts +40 -0
- package/dist/types/commands/verify.d.ts +47 -0
- package/dist/types/pages/page-schema.d.ts +81 -0
- package/dist/types/pages/render-page.d.ts +26 -0
- package/dist/types/paths.d.ts +16 -0
- package/dist/types/verify/budgets.d.ts +114 -0
- package/dist/types/verify/class-coverage.d.ts +65 -0
- package/dist/types/verify/ignore.d.ts +8 -0
- package/dist/types/verify/lighthouse.d.ts +27 -0
- package/dist/types/verify/seo-surface.d.ts +23 -0
- package/dist/types/verify/serve.d.ts +6 -0
- package/dist/verify/budgets.js +180 -0
- package/dist/verify/class-coverage.js +114 -0
- package/dist/verify/ignore.js +11 -0
- package/dist/verify/lighthouse.js +98 -0
- package/dist/verify/seo-surface.js +97 -0
- package/dist/verify/serve.js +123 -0
- package/package.json +14 -8
- package/templates/app/.github/workflows/deploy-rsync.yaml.hbs +58 -0
- package/templates/app/.oxfmtrc.json +32 -0
- package/templates/app/.oxlintrc.json +10 -0
- package/templates/app/.vscode/extensions.json +3 -0
- package/templates/app/build/Caddyfile.hbs +24 -0
- package/templates/app/build/Caddyfile.preview +34 -0
- package/templates/app/index.html +35 -0
- package/templates/app/logo.svg +143 -0
- package/templates/app/pnpm-workspace.yaml +10 -0
- package/templates/app/public/favicon-128.png +0 -0
- package/templates/app/public/favicon-48.png +0 -0
- package/templates/app/public/favicon-96.png +0 -0
- package/templates/app/public/favicon.ico +0 -0
- package/templates/app/public/images/landing/landing.png +0 -0
- package/templates/app/public/robots.txt +5 -0
- package/templates/app/src/App.vue +22 -0
- package/templates/app/src/pages/Contact.md +25 -0
- package/templates/app/src/pages/Error404Page.vue +31 -0
- package/templates/app/src/pages/GettingStarted/GettingStarted.md +52 -0
- package/templates/app/src/pages/Index.md.hbs +46 -0
- package/templates/app/src/router/index.ts +21 -0
- package/templates/app/src/router/routes.ts +218 -0
- package/templates/app/src/setup.ts +8 -0
- package/templates/app/src/shims-virtual.d.ts +67 -0
- package/templates/app/src/shims-vue.d.ts +6 -0
- package/templates/app/vitrify.config.ts.hbs +249 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { createReadStream } from 'node:fs';
|
|
3
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
4
|
+
import { extname, join, normalize, resolve } from 'node:path';
|
|
5
|
+
import { gzipSync } from 'node:zlib';
|
|
6
|
+
/**
|
|
7
|
+
* Minimal static file server for `ssg verify`.
|
|
8
|
+
*
|
|
9
|
+
* `verify` must measure the *built* site the way a real static host serves it,
|
|
10
|
+
* so it serves `dist/static` on an ephemeral port rather than reusing a dev
|
|
11
|
+
* server (which would report dev transforms, not the deployed bytes).
|
|
12
|
+
*
|
|
13
|
+
* "The way a real static host serves it" includes the two things that move
|
|
14
|
+
* Lighthouse most and that a production host always does:
|
|
15
|
+
*
|
|
16
|
+
* - **compression** — Caddy (and every CDN) serves `encode gzip`; a verifier
|
|
17
|
+
* that ships raw bytes makes every byte budget and every LCP look ~3× worse
|
|
18
|
+
* than production, so the score is not a measurement of the site.
|
|
19
|
+
* - **caching** — fingerprinted assets are immutable, so a browser (and
|
|
20
|
+
* Lighthouse's cache audit) must be told that. Without the header the
|
|
21
|
+
* `cache-insight` audit fails for a site whose host would pass it.
|
|
22
|
+
*/
|
|
23
|
+
const CONTENT_TYPES = {
|
|
24
|
+
'.html': 'text/html; charset=utf-8',
|
|
25
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
26
|
+
'.mjs': 'text/javascript; charset=utf-8',
|
|
27
|
+
'.css': 'text/css; charset=utf-8',
|
|
28
|
+
'.json': 'application/json; charset=utf-8',
|
|
29
|
+
'.svg': 'image/svg+xml',
|
|
30
|
+
'.png': 'image/png',
|
|
31
|
+
'.jpg': 'image/jpeg',
|
|
32
|
+
'.jpeg': 'image/jpeg',
|
|
33
|
+
'.webp': 'image/webp',
|
|
34
|
+
'.avif': 'image/avif',
|
|
35
|
+
'.ico': 'image/x-icon',
|
|
36
|
+
'.txt': 'text/plain; charset=utf-8',
|
|
37
|
+
'.xml': 'application/xml; charset=utf-8',
|
|
38
|
+
'.woff2': 'font/woff2'
|
|
39
|
+
};
|
|
40
|
+
const COMPRESSIBLE = new Set([
|
|
41
|
+
'text/html; charset=utf-8',
|
|
42
|
+
'text/plain; charset=utf-8',
|
|
43
|
+
'text/css; charset=utf-8',
|
|
44
|
+
'text/javascript; charset=utf-8',
|
|
45
|
+
'application/json; charset=utf-8',
|
|
46
|
+
'application/xml; charset=utf-8',
|
|
47
|
+
'image/svg+xml'
|
|
48
|
+
]);
|
|
49
|
+
/** Vite fingerprints assets as `name-<hash>.ext`; those are immutable. */
|
|
50
|
+
const IMMUTABLE = /-[A-Za-z0-9_-]{8,}\.[a-z0-9]+$/;
|
|
51
|
+
function contentType(file) {
|
|
52
|
+
return CONTENT_TYPES[extname(file)] ?? 'application/octet-stream';
|
|
53
|
+
}
|
|
54
|
+
function cacheControl(file, type) {
|
|
55
|
+
if (IMMUTABLE.test(file))
|
|
56
|
+
return 'public, max-age=31536000, immutable';
|
|
57
|
+
if (type.startsWith('text/html'))
|
|
58
|
+
return 'no-cache';
|
|
59
|
+
return 'public, max-age=3600';
|
|
60
|
+
}
|
|
61
|
+
/** Candidate files for a request path, in the order a static host would try. */
|
|
62
|
+
function candidatesFor(root, pathname) {
|
|
63
|
+
const clean = normalize(decodeURIComponent(pathname)).replace(/^(\.\.[/\\])+/, '');
|
|
64
|
+
const base = join(root, clean);
|
|
65
|
+
if (extname(base))
|
|
66
|
+
return [base];
|
|
67
|
+
return [join(base, 'index.html'), `${base}.html`, join(base, 'index.html')];
|
|
68
|
+
}
|
|
69
|
+
export async function startStaticServer(root) {
|
|
70
|
+
const absoluteRoot = resolve(root);
|
|
71
|
+
const server = createServer(async (request, response) => {
|
|
72
|
+
const pathname = new URL(request.url ?? '/', 'http://localhost').pathname;
|
|
73
|
+
const acceptsGzip = /\bgzip\b/.test(request.headers['accept-encoding'] ?? '');
|
|
74
|
+
for (const candidate of candidatesFor(absoluteRoot, pathname)) {
|
|
75
|
+
try {
|
|
76
|
+
const info = await stat(candidate);
|
|
77
|
+
if (!info.isFile())
|
|
78
|
+
continue;
|
|
79
|
+
const type = contentType(candidate);
|
|
80
|
+
const headers = {
|
|
81
|
+
'content-type': type,
|
|
82
|
+
'cache-control': cacheControl(candidate, type),
|
|
83
|
+
vary: 'Accept-Encoding'
|
|
84
|
+
};
|
|
85
|
+
if (acceptsGzip && COMPRESSIBLE.has(type)) {
|
|
86
|
+
const body = gzipSync(await readFile(candidate));
|
|
87
|
+
response.writeHead(200, {
|
|
88
|
+
...headers,
|
|
89
|
+
'content-encoding': 'gzip',
|
|
90
|
+
'content-length': body.length
|
|
91
|
+
});
|
|
92
|
+
response.end(body);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
response.writeHead(200, { ...headers, 'content-length': info.size });
|
|
96
|
+
createReadStream(candidate).pipe(response);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// Try the next candidate.
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
response.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' });
|
|
104
|
+
response.end('Not found');
|
|
105
|
+
});
|
|
106
|
+
await new Promise((done, fail) => {
|
|
107
|
+
server.once('error', fail);
|
|
108
|
+
// Port 0 → the OS picks a free port, so parallel runs cannot collide.
|
|
109
|
+
server.listen(0, '127.0.0.1', () => done());
|
|
110
|
+
});
|
|
111
|
+
const address = server.address();
|
|
112
|
+
if (!address || typeof address === 'string') {
|
|
113
|
+
server.close();
|
|
114
|
+
throw new Error('static server did not bind to a port');
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
url: `http://127.0.0.1:${address.port}`,
|
|
118
|
+
port: address.port,
|
|
119
|
+
close: () => new Promise((done) => {
|
|
120
|
+
server.close(() => done());
|
|
121
|
+
})
|
|
122
|
+
};
|
|
123
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "simple-site-generator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"author": "simsustech",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
|
+
"ssg": "dist/cli.js",
|
|
8
9
|
"simple-site-generator": "dist/cli.js"
|
|
9
10
|
},
|
|
10
11
|
"main": "dist/cli.js",
|
|
@@ -21,19 +22,24 @@
|
|
|
21
22
|
},
|
|
22
23
|
"homepage": "https://github.com/simsustech/simple-site-generator/tree/main/packages/simple-site-generator#readme",
|
|
23
24
|
"dependencies": {
|
|
24
|
-
"@
|
|
25
|
-
"
|
|
26
|
-
"
|
|
25
|
+
"@inquirer/prompts": "^8.7.4",
|
|
26
|
+
"@vitrify/tools": "^0.5.5",
|
|
27
|
+
"chrome-launcher": "^1.2.2",
|
|
28
|
+
"gray-matter": "^4.0.3",
|
|
29
|
+
"handlebars": "^4.7.10",
|
|
30
|
+
"inquirer": "^14.2.4",
|
|
27
31
|
"latest-version": "^9.0.0",
|
|
28
|
-
"
|
|
32
|
+
"lighthouse": "^13.5.0",
|
|
33
|
+
"minimist": "^1.2.8",
|
|
34
|
+
"zod": "^4.6.7"
|
|
29
35
|
},
|
|
30
36
|
"devDependencies": {
|
|
31
37
|
"@types/inquirer": "^9.0.10",
|
|
32
38
|
"@types/minimist": "^1.2.5",
|
|
33
|
-
"@types/node": "^26.5
|
|
39
|
+
"@types/node": "^26.6.5",
|
|
34
40
|
"typescript": "^7.0.2",
|
|
35
|
-
"vite": "^8.3.
|
|
36
|
-
"vue": "^3.5.
|
|
41
|
+
"vite": "^8.3.5",
|
|
42
|
+
"vue": "^3.5.44"
|
|
37
43
|
},
|
|
38
44
|
"files": [
|
|
39
45
|
"dist",
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
name: Build and deploy through rsync
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["production"]
|
|
6
|
+
|
|
7
|
+
env:
|
|
8
|
+
working-directory: ./
|
|
9
|
+
url: {{ url }}
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build:
|
|
13
|
+
name: Build and deploy production
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v7
|
|
17
|
+
|
|
18
|
+
- name: Creating .npmrc
|
|
19
|
+
run: |
|
|
20
|
+
cat > $\{{env.working-directory}}.npmrc << EOF
|
|
21
|
+
@simsustech:registry=https://npm.simsus.tech
|
|
22
|
+
//npm.simsus.tech/:_authToken=$SIMSUSTECH_NPM_TOKEN
|
|
23
|
+
EOF
|
|
24
|
+
env:
|
|
25
|
+
SIMSUSTECH_NPM_TOKEN: $\{{ secrets.SIMSUSTECH_NPM_TOKEN }}
|
|
26
|
+
|
|
27
|
+
- name: Install pnpm
|
|
28
|
+
# generated projects have no packageManager field, so set the version here
|
|
29
|
+
uses: pnpm/action-setup@v6
|
|
30
|
+
with:
|
|
31
|
+
version: 12
|
|
32
|
+
|
|
33
|
+
- name: Use Node.js
|
|
34
|
+
uses: actions/setup-node@v7
|
|
35
|
+
with:
|
|
36
|
+
node-version: 24
|
|
37
|
+
|
|
38
|
+
- name: Install and build
|
|
39
|
+
working-directory: $\{{ env.working-directory }}
|
|
40
|
+
run: |
|
|
41
|
+
pnpm i
|
|
42
|
+
pnpm run build
|
|
43
|
+
|
|
44
|
+
- name: rsync deployments
|
|
45
|
+
uses: burnett01/rsync-deployments@v9
|
|
46
|
+
with:
|
|
47
|
+
switches: -avzr --delete
|
|
48
|
+
path: $\{{env.working-directory}}dist/static/
|
|
49
|
+
remote_path: /srv/$\{{env.url}}
|
|
50
|
+
remote_host: $\{{ secrets.DEPLOY_HOST }}
|
|
51
|
+
remote_user: $\{{ secrets.DEPLOY_USER }}
|
|
52
|
+
remote_key: $\{{ secrets.DEPLOY_KEY }}
|
|
53
|
+
|
|
54
|
+
- name: Remove .npmrc
|
|
55
|
+
# run even if an earlier step failed: this file holds registry tokens
|
|
56
|
+
if: always()
|
|
57
|
+
# -f: the file may legitimately be gone already
|
|
58
|
+
run: rm -f $\{{env.working-directory}}.npmrc
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/oxfmt/configuration_schema.json",
|
|
3
|
+
"semi": false,
|
|
4
|
+
"tabWidth": 2,
|
|
5
|
+
"singleQuote": true,
|
|
6
|
+
"printWidth": 80,
|
|
7
|
+
"trailingComma": "none",
|
|
8
|
+
"sortPackageJson": false,
|
|
9
|
+
"ignorePatterns": [
|
|
10
|
+
"**/dist/**",
|
|
11
|
+
"**/node_modules/**",
|
|
12
|
+
"**/*.hbs",
|
|
13
|
+
".github/**",
|
|
14
|
+
"pnpm-lock.yaml",
|
|
15
|
+
"pnpm-workspace.yaml"
|
|
16
|
+
],
|
|
17
|
+
"overrides": [
|
|
18
|
+
{
|
|
19
|
+
"files": ["*.json5"],
|
|
20
|
+
"options": {
|
|
21
|
+
"singleQuote": false,
|
|
22
|
+
"quoteProps": "preserve"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"files": ["*.yml", "*.yaml"],
|
|
27
|
+
"options": {
|
|
28
|
+
"singleQuote": false
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{{ url }} {
|
|
2
|
+
# The bundler fingerprints every asset as `name-<hash>.ext`; such a file is
|
|
3
|
+
# immutable, so the browser may keep it for a year without revalidating.
|
|
4
|
+
@hashed path_regexp hashed -[A-Za-z0-9_]{8,}\.(js|mjs|css|woff2|png|jpe?g|webp|svg)$
|
|
5
|
+
header @hashed Cache-Control "public, max-age=31536000, immutable"
|
|
6
|
+
|
|
7
|
+
encode zstd gzip
|
|
8
|
+
handle {
|
|
9
|
+
root * /srv/{{ url }}
|
|
10
|
+
try_files {path}.html {path}
|
|
11
|
+
file_server
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
# A missing page has to answer 404. Ending `try_files` in a file — the old
|
|
15
|
+
# `/index.html` fallback, or a `/404.html` one — answers 200, so every
|
|
16
|
+
# mistyped URL is served as a real page and a crawler indexes it. The status
|
|
17
|
+
# a monitor sees would be a lie too.
|
|
18
|
+
handle_errors {
|
|
19
|
+
root * /srv/{{ url }}
|
|
20
|
+
@notFound expression {err.status_code} == 404
|
|
21
|
+
rewrite @notFound /404.html
|
|
22
|
+
file_server
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Local preview of the built site — `pnpm preview`, or directly:
|
|
2
|
+
#
|
|
3
|
+
# caddy run --config build/Caddyfile.preview --adapter caddyfile
|
|
4
|
+
#
|
|
5
|
+
# Why a Caddyfile rather than `caddy file-server`: the routes are extensionless
|
|
6
|
+
# (`/about` serves `about.html`), and `file-server` has no `try_files` — it
|
|
7
|
+
# answers 404 for every page URL. This mirrors the shape of `build/Caddyfile`,
|
|
8
|
+
# which is what production runs, so what you check here is what ships.
|
|
9
|
+
#
|
|
10
|
+
# The port is overridable: PREVIEW_PORT=9000 pnpm preview
|
|
11
|
+
:{$PREVIEW_PORT:8095} {
|
|
12
|
+
# `root` is relative to the directory the command runs in, which is this
|
|
13
|
+
# package — so it needs no absolute path and survives a different checkout.
|
|
14
|
+
root * ./dist/static
|
|
15
|
+
|
|
16
|
+
encode gzip
|
|
17
|
+
|
|
18
|
+
# The bundler fingerprints every asset as `name-<hash>.ext`; such a file is
|
|
19
|
+
# immutable, so the browser may keep it for a year without revalidating.
|
|
20
|
+
@hashed path_regexp hashed -[A-Za-z0-9_]{8,}\.(js|mjs|css|woff2|png|jpe?g|webp|svg)$
|
|
21
|
+
header @hashed Cache-Control "public, max-age=31536000, immutable"
|
|
22
|
+
|
|
23
|
+
try_files {path}.html {path}
|
|
24
|
+
file_server
|
|
25
|
+
|
|
26
|
+
# A missing page answers 404 with the site's own error page, rather than
|
|
27
|
+
# ending `try_files` in a file (which would answer 200 — a soft 404).
|
|
28
|
+
handle_errors {
|
|
29
|
+
root * ./dist/static
|
|
30
|
+
@notFound expression {err.status_code} == 404
|
|
31
|
+
rewrite @notFound /404.html
|
|
32
|
+
file_server
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<!--
|
|
5
|
+
`charset` must stay first: the encoding has to be declared inside the
|
|
6
|
+
first 1 KiB, and the SSR-collected meta tags are injected at the end of the
|
|
7
|
+
head element (see the vitrify injectSsrContext fix) so they cannot push
|
|
8
|
+
this line out of that window.
|
|
9
|
+
-->
|
|
10
|
+
<meta charset="UTF-8" />
|
|
11
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
12
|
+
|
|
13
|
+
<!--
|
|
14
|
+
Fonts are served by the preset's web-fonts provider (bunny); connecting
|
|
15
|
+
early avoids an extra round-trip on the critical path.
|
|
16
|
+
-->
|
|
17
|
+
<link rel="preconnect" href="https://fonts.bunny.net" crossorigin />
|
|
18
|
+
|
|
19
|
+
<!--
|
|
20
|
+
The Meta plugin owns the document title. A static title element written
|
|
21
|
+
here would be a second one in the built HTML, which the verifier rejects —
|
|
22
|
+
and a crawler would then pick arbitrarily between the two.
|
|
23
|
+
|
|
24
|
+
Keep element literals (an angle-bracketed `title`, `head` or `link`) out of
|
|
25
|
+
these comments: vitrify rewrites the head with tag-matching regular
|
|
26
|
+
expressions, and a tag-shaped word inside a comment is a false match that
|
|
27
|
+
mangles the document.
|
|
28
|
+
-->
|
|
29
|
+
</head>
|
|
30
|
+
|
|
31
|
+
<body>
|
|
32
|
+
<!-- A body opening and closing tag is sufficient for Vitrify -->
|
|
33
|
+
<div id="app"></div>
|
|
34
|
+
</body>
|
|
35
|
+
</html>
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
+
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
3
|
+
|
|
4
|
+
<svg
|
|
5
|
+
width="76.023819mm"
|
|
6
|
+
height="66.642418mm"
|
|
7
|
+
viewBox="0 0 76.02382 66.642419"
|
|
8
|
+
version="1.1"
|
|
9
|
+
id="svg130"
|
|
10
|
+
sodipodi:docname="logo text.svg"
|
|
11
|
+
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
|
12
|
+
inkscape:export-filename="/home/stefan/Nextcloud/logo simsustech4.png"
|
|
13
|
+
inkscape:export-xdpi="44"
|
|
14
|
+
inkscape:export-ydpi="44"
|
|
15
|
+
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
16
|
+
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
17
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
18
|
+
xmlns:svg="http://www.w3.org/2000/svg">
|
|
19
|
+
<sodipodi:namedview
|
|
20
|
+
id="namedview132"
|
|
21
|
+
pagecolor="#ffffff"
|
|
22
|
+
bordercolor="#666666"
|
|
23
|
+
borderopacity="1.0"
|
|
24
|
+
inkscape:pageshadow="2"
|
|
25
|
+
inkscape:pageopacity="0.0"
|
|
26
|
+
inkscape:pagecheckerboard="0"
|
|
27
|
+
inkscape:document-units="mm"
|
|
28
|
+
showgrid="false"
|
|
29
|
+
inkscape:zoom="0.5"
|
|
30
|
+
inkscape:cx="281"
|
|
31
|
+
inkscape:cy="-298"
|
|
32
|
+
inkscape:window-width="2560"
|
|
33
|
+
inkscape:window-height="1379"
|
|
34
|
+
inkscape:window-x="0"
|
|
35
|
+
inkscape:window-y="33"
|
|
36
|
+
inkscape:window-maximized="1"
|
|
37
|
+
inkscape:current-layer="layer1"
|
|
38
|
+
fit-margin-top="3"
|
|
39
|
+
fit-margin-left="3"
|
|
40
|
+
fit-margin-right="3"
|
|
41
|
+
fit-margin-bottom="3" />
|
|
42
|
+
<defs
|
|
43
|
+
id="defs127" />
|
|
44
|
+
<g
|
|
45
|
+
inkscape:label="Layer 1"
|
|
46
|
+
inkscape:groupmode="layer"
|
|
47
|
+
id="layer1"
|
|
48
|
+
transform="translate(308.77352,-114.8964)">
|
|
49
|
+
<g
|
|
50
|
+
id="g8406-0"
|
|
51
|
+
transform="matrix(0.03124864,-0.05412422,0.05412422,0.03124864,-375.29155,156.30058)">
|
|
52
|
+
<path
|
|
53
|
+
sodipodi:type="star"
|
|
54
|
+
style="fill:#22c92a;fill-opacity:1;stroke:#111115;stroke-width:64.7013;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
55
|
+
id="path4686-5-0-6"
|
|
56
|
+
inkscape:flatsided="true"
|
|
57
|
+
sodipodi:sides="6"
|
|
58
|
+
sodipodi:cx="-1711.292"
|
|
59
|
+
sodipodi:cy="-2891.6067"
|
|
60
|
+
sodipodi:r1="1941.0391"
|
|
61
|
+
sodipodi:r2="1680.9893"
|
|
62
|
+
sodipodi:arg1="1.0471975"
|
|
63
|
+
sodipodi:arg2="1.5707963"
|
|
64
|
+
inkscape:rounded="0"
|
|
65
|
+
inkscape:randomized="0"
|
|
66
|
+
transform="matrix(0.28316651,0,0,0.28316651,1432.8627,2202.612)"
|
|
67
|
+
inkscape:transform-center-x="1.6003776e-05"
|
|
68
|
+
inkscape:transform-center-y="-1.5044873e-05"
|
|
69
|
+
d="m -740.77237,-1210.6176 -1941.03903,10e-5 -970.5197,-1680.9891 970.5195,-1680.9892 1941.03905,-1e-4 970.51962,1680.9891 z" />
|
|
70
|
+
<path
|
|
71
|
+
style="fill:none;stroke:#000000;stroke-width:0.161583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
72
|
+
d="M 673.46343,907.80596 H 856.6758"
|
|
73
|
+
id="path4451-3-6-2-2" />
|
|
74
|
+
<path
|
|
75
|
+
style="fill:none;stroke:#000000;stroke-width:0.242375px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
76
|
+
d="m 398.74983,1383.7452 137.4093,237.9999"
|
|
77
|
+
id="path5002-2-3-6" />
|
|
78
|
+
<path
|
|
79
|
+
style="fill:none;stroke:#000000;stroke-width:0.242375px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
80
|
+
d="m 673.46343,907.80596 274.81862,10e-5"
|
|
81
|
+
id="path5002-5-9-7-1" />
|
|
82
|
+
<path
|
|
83
|
+
style="fill:none;stroke:#000000;stroke-width:0.242375px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
84
|
+
d="M 1223.1007,1859.8056 1360.51,1621.8057"
|
|
85
|
+
id="path5002-3-1-5-8" />
|
|
86
|
+
<path
|
|
87
|
+
style="fill:none;stroke:#000000;stroke-width:0.48475px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
88
|
+
d="M 536.15913,1621.7451 948.28205,907.80606 1360.51,1621.8057 Z"
|
|
89
|
+
id="path5157-2-9-7" />
|
|
90
|
+
<path
|
|
91
|
+
style="fill:#d67915;fill-opacity:1;stroke:#000000;stroke-width:0.48475px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
92
|
+
d="m 948.28205,907.80606 274.81865,-2e-4 274.8186,475.99984 -137.4093,238 -412.22795,-713.99964"
|
|
93
|
+
id="path5272-7-2-9" />
|
|
94
|
+
<path
|
|
95
|
+
style="fill:#00a4e6;fill-opacity:1;stroke:#000000;stroke-width:0.48475px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
96
|
+
d="m 948.28205,907.80606 -274.81861,-10e-5 -274.81861,475.99984 137.5143,237.9393 412.12292,-713.93904"
|
|
97
|
+
id="path5274-0-2-2" />
|
|
98
|
+
<path
|
|
99
|
+
style="fill:#eece0c;fill-opacity:1;stroke:#000000;stroke-width:0.48475px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
100
|
+
d="m 536.15913,1621.7451 137.30434,238.0605 h 549.63723 l 137.4093,-237.9999 -824.35087,-0.061"
|
|
101
|
+
id="path5276-9-8-0" />
|
|
102
|
+
<path
|
|
103
|
+
style="fill:none;stroke:#000000;stroke-width:18.3212;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
104
|
+
d="m 673.46342,907.80601 549.63728,-8e-5 274.8186,475.99977 -274.8186,475.9999 -549.63723,10e-5"
|
|
105
|
+
id="path7069-9-2" />
|
|
106
|
+
<path
|
|
107
|
+
style="fill:none;stroke:#000000;stroke-width:0.48475px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
108
|
+
d="M 673.46346,1859.8057 398.6448,1383.8058 673.46342,907.80601"
|
|
109
|
+
id="path7071-7-3" />
|
|
110
|
+
<path
|
|
111
|
+
style="fill:none;stroke:#000000;stroke-width:18.3212;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
112
|
+
d="M 673.46342,907.80601 398.64479,1383.8059 673.46344,1859.8058"
|
|
113
|
+
id="path7281-3-7" />
|
|
114
|
+
<path
|
|
115
|
+
style="fill:none;stroke:#000000;stroke-width:18.3208;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
116
|
+
d="m 965.83267,938.18694 c -10.97866,-19.01559 -24.1009,-19.03555 -33.40914,-2.91319"
|
|
117
|
+
id="path33462"
|
|
118
|
+
sodipodi:nodetypes="cc" />
|
|
119
|
+
<path
|
|
120
|
+
style="fill:none;stroke:#000000;stroke-width:18.3208;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
121
|
+
d="m 1342.9801,1591.4734 c 10.9786,19.0155 4.4348,30.3897 -14.1817,30.3897"
|
|
122
|
+
id="path33462-2"
|
|
123
|
+
sodipodi:nodetypes="cc" />
|
|
124
|
+
<path
|
|
125
|
+
style="fill:none;stroke:#000000;stroke-width:18.3208;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
126
|
+
d="m 565.71911,1621.69 c -18.40838,0 -24.03457,-9.498 -15.36275,-24.5181"
|
|
127
|
+
id="path40533"
|
|
128
|
+
sodipodi:nodetypes="cc" />
|
|
129
|
+
<path
|
|
130
|
+
style="fill:none;stroke:#000000;stroke-width:18.3208;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
131
|
+
d="m 565.71916,1621.69 763.07924,0.1731"
|
|
132
|
+
id="path43135" />
|
|
133
|
+
<path
|
|
134
|
+
style="fill:none;stroke:#000000;stroke-width:18.3208;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
135
|
+
d="M 550.35638,1597.1719 932.42356,935.27375"
|
|
136
|
+
id="path43137" />
|
|
137
|
+
<path
|
|
138
|
+
style="fill:none;stroke:#000000;stroke-width:18.3208;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
139
|
+
d="M 965.83269,938.18695 1342.9789,1591.4741"
|
|
140
|
+
id="path43139" />
|
|
141
|
+
</g>
|
|
142
|
+
</g>
|
|
143
|
+
</svg>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# pnpm 12 does not run a dependency's build scripts unless they are allowlisted,
|
|
2
|
+
# and it fails the install when one is skipped. These need theirs:
|
|
3
|
+
# - esbuild / sharp select and link a prebuilt native binary at install time,
|
|
4
|
+
# - @parcel/watcher compiles its native watcher.
|
|
5
|
+
# Vitrify's dev server uses esbuild, so without these the project installs but
|
|
6
|
+
# cannot build.
|
|
7
|
+
allowBuilds:
|
|
8
|
+
'@parcel/watcher': true
|
|
9
|
+
esbuild: true
|
|
10
|
+
sharp: true
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<router-view v-slot="{ Component }">
|
|
3
|
+
<Suspense>
|
|
4
|
+
<template #default>
|
|
5
|
+
<component :is="Component" />
|
|
6
|
+
</template>
|
|
7
|
+
<template #fallback>
|
|
8
|
+
<div>Loading...</div>
|
|
9
|
+
</template>
|
|
10
|
+
</Suspense>
|
|
11
|
+
</router-view>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script lang="ts">
|
|
15
|
+
export default {
|
|
16
|
+
name: 'App'
|
|
17
|
+
}
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<script setup lang="ts"></script>
|
|
21
|
+
|
|
22
|
+
<style lang="sass"></style>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: contact
|
|
3
|
+
title: Contact
|
|
4
|
+
description: How to reach us, including the project repository and support channels.
|
|
5
|
+
label: Contact
|
|
6
|
+
# Prerendered like every other page, so it gets a real <h1> and its own
|
|
7
|
+
# <head> tags. A content page should not be client-only: `noSsr` would make it
|
|
8
|
+
# invisible to a crawler and would fail `ssg verify`.
|
|
9
|
+
tags:
|
|
10
|
+
- contact
|
|
11
|
+
- footer
|
|
12
|
+
lists:
|
|
13
|
+
- header: Links
|
|
14
|
+
items:
|
|
15
|
+
- overline: Source
|
|
16
|
+
label: https://github.com/simsustech/simple-site-generator
|
|
17
|
+
icon:
|
|
18
|
+
name: i-mdi-github
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Get in touch
|
|
22
|
+
|
|
23
|
+
Add your own contact details here — a phone number, an email address and your
|
|
24
|
+
opening hours. Anything you put in the `ssg.extra` block of `vitrify.config.ts`
|
|
25
|
+
is also mirrored into `LocalBusiness` structured data automatically.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<main
|
|
3
|
+
class="ssg-section flex flex-center text-center min-h-[60vh] bg-[var(--q-surface-container-low)] text-[var(--q-on-surface)]"
|
|
4
|
+
>
|
|
5
|
+
<div class="ssg-section__inner">
|
|
6
|
+
<!--
|
|
7
|
+
A real `h1`: the 404 page is a page like any other, and `ssg verify`
|
|
8
|
+
requires exactly one `h1` on every page. Using a styled `div` here would
|
|
9
|
+
make the site fail its own check.
|
|
10
|
+
-->
|
|
11
|
+
<h1 class="text-[clamp(5rem,18vw,10rem)] leading-none m-0">404</h1>
|
|
12
|
+
|
|
13
|
+
<p class="text-step-1 opacity-80 mt-space-2 mb-0">Sorry, nothing here.</p>
|
|
14
|
+
|
|
15
|
+
<!--
|
|
16
|
+
The kit's own action: it carries the primary/on-primary pair, so this
|
|
17
|
+
button cannot drift into a hand-picked colour. An earlier version of this
|
|
18
|
+
page used `color="white" text-color="blue"`, which measured 2.54:1 and put
|
|
19
|
+
a Quasar-blue label on a green page.
|
|
20
|
+
-->
|
|
21
|
+
<CtaButton class="mt-space-4" label="Go home" route="/" />
|
|
22
|
+
</div>
|
|
23
|
+
</main>
|
|
24
|
+
</template>
|
|
25
|
+
|
|
26
|
+
<script setup lang="ts">
|
|
27
|
+
import { CtaButton } from '@simple-site-generator/templates'
|
|
28
|
+
|
|
29
|
+
// Keep the name — `ssg check` and the router both key off it.
|
|
30
|
+
defineOptions({ name: 'Error404Page' })
|
|
31
|
+
</script>
|