@zorgo/next 1.3.0 → 2.0.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.d.mts +9 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +44 -66
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +43 -65
- package/dist/cli.mjs.map +1 -1
- package/dist/components.d.mts +735 -5
- package/dist/components.d.ts +735 -5
- package/dist/components.js +3692 -525
- package/dist/components.js.map +1 -1
- package/dist/components.mjs +3663 -540
- package/dist/components.mjs.map +1 -1
- package/dist/images.d.mts +25 -13
- package/dist/images.d.ts +25 -13
- package/dist/images.js +18 -8
- package/dist/images.js.map +1 -1
- package/dist/images.mjs +15 -8
- package/dist/images.mjs.map +1 -1
- package/dist/index.d.mts +315 -9
- package/dist/index.d.ts +315 -9
- package/dist/index.js +58 -52
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +58 -52
- package/dist/index.mjs.map +1 -1
- package/dist/item-page.d.mts +6 -0
- package/dist/item-page.d.ts +6 -0
- package/dist/item-page.js +4 -2
- package/dist/item-page.js.map +1 -1
- package/dist/item-page.mjs +4 -2
- package/dist/item-page.mjs.map +1 -1
- package/dist/legal.d.mts +31 -0
- package/dist/legal.d.ts +31 -0
- package/dist/legal.js +322 -0
- package/dist/legal.js.map +1 -0
- package/dist/legal.mjs +293 -0
- package/dist/legal.mjs.map +1 -0
- package/dist/seo.d.mts +56 -1
- package/dist/seo.d.ts +56 -1
- package/dist/seo.js +111 -2
- package/dist/seo.js.map +1 -1
- package/dist/seo.mjs +106 -1
- package/dist/seo.mjs.map +1 -1
- package/dist/server.d.mts +27 -1
- package/dist/server.d.ts +27 -1
- package/dist/server.js +322 -102
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +322 -104
- package/dist/server.mjs.map +1 -1
- package/dist/slugs.d.mts +2 -1
- package/dist/slugs.d.ts +2 -1
- package/dist/slugs.js +2 -0
- package/dist/slugs.js.map +1 -1
- package/dist/slugs.mjs +1 -0
- package/dist/tree/README.md +2 -2
- package/dist/tree/app/error.tsx +12 -3
- package/dist/tree/app/global-error.tsx +21 -4
- package/dist/tree/app/globals.css +41 -0
- package/dist/tree/app/not-found.tsx +17 -3
- package/dist/tree/app/privacy/page.tsx.gen.ts +28 -9
- package/dist/tree/app/terms/page.tsx.gen.ts +26 -10
- package/dist/tree/gitignore +6 -1
- package/dist/tree/next.config.ts +6 -5
- package/dist/tree/open-next.config.ts +7 -0
- package/dist/tree/package.json.gen.ts +8 -2
- package/package.json +7 -2
- package/dist/tree/app/globals.css.gen.ts +0 -124
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/* Hand-owned from the moment this site is scaffolded — prebuild never
|
|
2
|
+
overwrites this file (see ZOR-391). Add custom vars, @font-face, and brand
|
|
3
|
+
classes here; they survive every `pnpm dev` / `pnpm build`.
|
|
4
|
+
|
|
5
|
+
./generated/branding.css is written by prebuild from franchise_branding and
|
|
6
|
+
carries the Google Fonts @import (or nothing, if this site ships its own
|
|
7
|
+
fonts in public/fonts/) plus the derived color tokens. It must stay the
|
|
8
|
+
first @import below so its font @import — if present — is still the first
|
|
9
|
+
statement in the bundled stylesheet; Tailwind resolves @import itself, so a
|
|
10
|
+
later position would land it after non-import content and browsers would
|
|
11
|
+
silently drop it. */
|
|
12
|
+
@import "./generated/branding.css";
|
|
13
|
+
@import "tailwindcss";
|
|
14
|
+
/* Tailwind v4 skips node_modules; @zorgo/next ships un-obfuscated components so
|
|
15
|
+
their class names are scannable here. */
|
|
16
|
+
@source "../node_modules/@zorgo/next/dist/components.mjs";
|
|
17
|
+
@source "../node_modules/@zorgo/next/dist/item-page.mjs";
|
|
18
|
+
@source "../node_modules/@zorgo/next/dist/legal.mjs";
|
|
19
|
+
|
|
20
|
+
@layer base {
|
|
21
|
+
button,
|
|
22
|
+
[type="button"],
|
|
23
|
+
[type="submit"],
|
|
24
|
+
[type="reset"] {
|
|
25
|
+
cursor: pointer;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
button:disabled,
|
|
29
|
+
[type="button"]:disabled,
|
|
30
|
+
[type="submit"]:disabled,
|
|
31
|
+
[type="reset"]:disabled {
|
|
32
|
+
cursor: not-allowed;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* TY-02: justified body copy produces visible rivers and wild word spacing.
|
|
36
|
+
Left-aligned is the default with no opt-in — this makes the utility a
|
|
37
|
+
no-op rather than just avoiding it by convention. */
|
|
38
|
+
.text-justify {
|
|
39
|
+
text-align: left;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -1,8 +1,22 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
|
|
3
|
+
// min-h-full, not a viewport height: this renders inside the layout’s
|
|
4
|
+
// <main>, between a real header and footer, so centring on the viewport would
|
|
5
|
+
// push it below the fold.
|
|
1
6
|
export default function NotFound() {
|
|
2
7
|
return (
|
|
3
|
-
<div>
|
|
4
|
-
<
|
|
5
|
-
<
|
|
8
|
+
<div className="mx-auto flex min-h-full max-w-md flex-col items-center justify-center gap-4 px-4 py-24 text-center">
|
|
9
|
+
<p className="text-6xl font-black text-primary">404</p>
|
|
10
|
+
<h1 className="text-3xl font-bold">We can't find that page</h1>
|
|
11
|
+
<p className="opacity-70">
|
|
12
|
+
It may have moved, or the link that brought you here is out of date.
|
|
13
|
+
</p>
|
|
14
|
+
<Link
|
|
15
|
+
href="/"
|
|
16
|
+
className="mt-2 inline-flex min-h-11 items-center justify-center rounded-xl bg-primary px-6 py-3 font-semibold text-primary-foreground transition-colors duration-300 hover:bg-primary/80"
|
|
17
|
+
>
|
|
18
|
+
Back to the home page
|
|
19
|
+
</Link>
|
|
6
20
|
</div>
|
|
7
21
|
);
|
|
8
22
|
}
|
|
@@ -1,15 +1,34 @@
|
|
|
1
1
|
import type { ScaffoldOptions } from "../../../../types";
|
|
2
2
|
|
|
3
|
+
// The policy text itself lives in @zorgo/next/components (src/components/client/legal.ts),
|
|
4
|
+
// so updating the package updates every scaffolded site. Only the brand name and
|
|
5
|
+
// the "last updated" date are baked in here.
|
|
3
6
|
export default function generatePrivacyPage(opts: ScaffoldOptions) {
|
|
7
|
+
if (!opts.includePrivacyPage) return;
|
|
4
8
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
</div>
|
|
11
|
-
)
|
|
12
|
-
}`;
|
|
13
|
-
}
|
|
9
|
+
const today = new Date().toLocaleDateString("en-US", {
|
|
10
|
+
year: "numeric",
|
|
11
|
+
month: "long",
|
|
12
|
+
day: "numeric",
|
|
13
|
+
});
|
|
14
14
|
|
|
15
|
+
return `import { LegalPage, privacyPolicyHtml } from "@zorgo/next/legal";
|
|
16
|
+
|
|
17
|
+
const BRAND = ${JSON.stringify(opts.franchiseName)};
|
|
18
|
+
|
|
19
|
+
export const metadata = {
|
|
20
|
+
title: "Privacy Policy",
|
|
21
|
+
description: \`How \${BRAND} handles your information.\`,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default function PrivacyPage() {
|
|
25
|
+
return (
|
|
26
|
+
<LegalPage
|
|
27
|
+
title="Privacy Policy"
|
|
28
|
+
lastUpdated="${today}"
|
|
29
|
+
html={privacyPolicyHtml({ brand: BRAND, lastUpdated: "${today}" })}
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
`;
|
|
15
34
|
}
|
|
@@ -1,16 +1,32 @@
|
|
|
1
1
|
import type { ScaffoldOptions } from "../../../../types";
|
|
2
2
|
|
|
3
|
-
//
|
|
3
|
+
// See privacy/page.tsx.gen.ts — the terms text is shipped by the package.
|
|
4
4
|
export default function generateTermsPage(opts: ScaffoldOptions) {
|
|
5
|
+
if (!opts.includeTermsPage) return;
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
</div>
|
|
12
|
-
)
|
|
13
|
-
}`;
|
|
14
|
-
}
|
|
7
|
+
const today = new Date().toLocaleDateString("en-US", {
|
|
8
|
+
year: "numeric",
|
|
9
|
+
month: "long",
|
|
10
|
+
day: "numeric",
|
|
11
|
+
});
|
|
15
12
|
|
|
13
|
+
return `import { LegalPage, termsOfUseHtml } from "@zorgo/next/legal";
|
|
14
|
+
|
|
15
|
+
const BRAND = ${JSON.stringify(opts.franchiseName)};
|
|
16
|
+
|
|
17
|
+
export const metadata = {
|
|
18
|
+
title: "Terms of Service",
|
|
19
|
+
description: \`Terms for ordering online from \${BRAND}.\`,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default function TermsPage() {
|
|
23
|
+
return (
|
|
24
|
+
<LegalPage
|
|
25
|
+
title="Online Ordering Terms of Use"
|
|
26
|
+
lastUpdated="${today}"
|
|
27
|
+
html={termsOfUseHtml({ brand: BRAND, lastUpdated: "${today}" })}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
`;
|
|
16
32
|
}
|
package/dist/tree/gitignore
CHANGED
|
@@ -39,7 +39,12 @@ next-env.d.ts
|
|
|
39
39
|
|
|
40
40
|
/.wrangler/
|
|
41
41
|
|
|
42
|
-
# generated from .env by scripts/prebuild.ts
|
|
42
|
+
# generated from .env / franchise_branding by scripts/prebuild.ts
|
|
43
43
|
/wrangler.toml
|
|
44
44
|
/lib/generated/
|
|
45
|
+
/app/generated/
|
|
46
|
+
/public/styles/
|
|
45
47
|
/public/images/items/
|
|
48
|
+
/public/images/logo.*
|
|
49
|
+
# AVIF ladders encoded from assets/images/ — the originals are what's committed
|
|
50
|
+
/public/images/**/*.avif
|
package/dist/tree/next.config.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { NextConfig } from "next";
|
|
2
2
|
import path from "path";
|
|
3
|
-
import {
|
|
3
|
+
import { IMAGE_WIDTHS } from "@zorgo/next/images";
|
|
4
4
|
|
|
5
5
|
const monorepoRoot = path.resolve(__dirname, "../../..");
|
|
6
6
|
|
|
@@ -10,15 +10,16 @@ const nextConfig: NextConfig = {
|
|
|
10
10
|
root: monorepoRoot,
|
|
11
11
|
},
|
|
12
12
|
transpilePackages: ["@zorgo", "@zorgo-website-core"],
|
|
13
|
-
//
|
|
14
|
-
// a
|
|
15
|
-
//
|
|
13
|
+
// Menu item images and the site's own photos in assets/images/ are both
|
|
14
|
+
// resized to a fixed AVIF ladder at prebuild, so nothing needs a runtime
|
|
15
|
+
// optimizer (and no Supabase host in remotePatterns — a custom loader
|
|
16
|
+
// bypasses that check entirely).
|
|
16
17
|
images: {
|
|
17
18
|
loader: "custom",
|
|
18
19
|
loaderFile: "./image-loader.ts",
|
|
19
20
|
// Must mirror the rungs prebuild emits, or Next advertises srcset widths no
|
|
20
21
|
// variant has and the browser picks a candidate narrower than it asked for.
|
|
21
|
-
deviceSizes: [...
|
|
22
|
+
deviceSizes: [...IMAGE_WIDTHS],
|
|
22
23
|
imageSizes: [],
|
|
23
24
|
},
|
|
24
25
|
};
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { defineCloudflareConfig } from "@opennextjs/cloudflare";
|
|
2
|
+
import staticAssetsIncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/static-assets-incremental-cache";
|
|
2
3
|
|
|
3
4
|
export default defineCloudflareConfig({
|
|
5
|
+
// Without this the cache defaults to "dummy": every prerendered page is a
|
|
6
|
+
// miss, and `dynamicParams = false` turns that miss into a 404. The menu is
|
|
7
|
+
// rebuilt from the prebuilt data on every deploy, so a read-only cache
|
|
8
|
+
// served from Workers assets is enough — swap to r2IncrementalCache if a
|
|
9
|
+
// site ever needs ISR.
|
|
10
|
+
incrementalCache: staticAssetsIncrementalCache,
|
|
4
11
|
});
|
|
@@ -12,8 +12,14 @@ export default function generatePackageJson(opts: ScaffoldOptions): string {
|
|
|
12
12
|
"dev:no-prebuild": "next dev",
|
|
13
13
|
"prebuild": "tsx scripts/prebuild.ts",
|
|
14
14
|
"build": "tsx scripts/prebuild.ts && next build",
|
|
15
|
-
"cloudflare:deploy":
|
|
16
|
-
|
|
15
|
+
"cloudflare:deploy":
|
|
16
|
+
"tsx scripts/prebuild.ts && opennextjs-cloudflare build && wrangler deploy",
|
|
17
|
+
"cloudflare:deploy:staging":
|
|
18
|
+
"tsx scripts/prebuild.ts && opennextjs-cloudflare build && wrangler deploy --env staging",
|
|
19
|
+
"preview":
|
|
20
|
+
"tsx scripts/prebuild.ts && opennextjs-cloudflare build && wrangler dev",
|
|
21
|
+
"preview:staging":
|
|
22
|
+
"tsx scripts/prebuild.ts && opennextjs-cloudflare build && wrangler dev --env staging",
|
|
17
23
|
},
|
|
18
24
|
dependencies: PINNED_VERSIONS,
|
|
19
25
|
devDependencies: DEV_DEPENDENCIES,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zorgo/next",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"bin": {
|
|
@@ -59,6 +59,11 @@
|
|
|
59
59
|
"require": "./dist/images.js",
|
|
60
60
|
"types": "./dist/images.d.ts"
|
|
61
61
|
},
|
|
62
|
+
"./legal": {
|
|
63
|
+
"import": "./dist/legal.mjs",
|
|
64
|
+
"require": "./dist/legal.js",
|
|
65
|
+
"types": "./dist/legal.d.ts"
|
|
66
|
+
},
|
|
62
67
|
"./item-page": {
|
|
63
68
|
"import": "./dist/item-page.mjs",
|
|
64
69
|
"require": "./dist/item-page.js",
|
|
@@ -87,7 +92,7 @@
|
|
|
87
92
|
"zustand": "^4.x.x"
|
|
88
93
|
},
|
|
89
94
|
"scripts": {
|
|
90
|
-
"build": "tsup && tsx scripts/obfuscate.ts",
|
|
95
|
+
"build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsup && tsx scripts/obfuscate.ts",
|
|
91
96
|
"release": "node scripts/publish.mjs",
|
|
92
97
|
"ship": "changeset && changeset version && node scripts/publish.mjs",
|
|
93
98
|
"dev": "echo \"No build step. Consumers transpile TS sources.\"",
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import type { ScaffoldOptions } from "../../../types";
|
|
2
|
-
|
|
3
|
-
/** Keep in sync with scripts/prebuild.ts HARDCODED_BRANDING until DB branding lands. */
|
|
4
|
-
const HARDCODED_BRANDING = {
|
|
5
|
-
backgroundBase: "#F5F5F5",
|
|
6
|
-
primary: "#3264FF",
|
|
7
|
-
accent: "#9664FF",
|
|
8
|
-
fontFamily: "Inter",
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
const FONT_ON_LIGHT = "#0a0a0a";
|
|
12
|
-
const FONT_ON_DARK = "#f5f5f5";
|
|
13
|
-
/** Channel delta toward white (elevated) / black (recessed). */
|
|
14
|
-
const BACKGROUND_SURFACE_DELTA = 15;
|
|
15
|
-
|
|
16
|
-
function parseHexRgb(
|
|
17
|
-
background: string,
|
|
18
|
-
): { r: number; g: number; b: number } | null {
|
|
19
|
-
const hex = background.trim().replace(/^#/, "");
|
|
20
|
-
const full =
|
|
21
|
-
hex.length === 3
|
|
22
|
-
? hex
|
|
23
|
-
.split("")
|
|
24
|
-
.map((c) => c + c)
|
|
25
|
-
.join("")
|
|
26
|
-
: hex;
|
|
27
|
-
if (full.length !== 6 || Number.isNaN(Number.parseInt(full, 16))) {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
return {
|
|
31
|
-
r: Number.parseInt(full.slice(0, 2), 16),
|
|
32
|
-
g: Number.parseInt(full.slice(2, 4), 16),
|
|
33
|
-
b: Number.parseInt(full.slice(4, 6), 16),
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function toHex({ r, g, b }: { r: number; g: number; b: number }): string {
|
|
38
|
-
return `#${[r, g, b].map((c) => c.toString(16).padStart(2, "0")).join("")}`;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function deriveFontColor(background: string): string {
|
|
42
|
-
const rgb = parseHexRgb(background);
|
|
43
|
-
if (!rgb) return FONT_ON_LIGHT;
|
|
44
|
-
const luminance = (0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b) / 255;
|
|
45
|
-
return luminance > 0.5 ? FONT_ON_LIGHT : FONT_ON_DARK;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function deriveElevatedBackground(background: string): string {
|
|
49
|
-
const rgb = parseHexRgb(background);
|
|
50
|
-
if (!rgb) return background;
|
|
51
|
-
return toHex({
|
|
52
|
-
r: Math.min(255, rgb.r + BACKGROUND_SURFACE_DELTA),
|
|
53
|
-
g: Math.min(255, rgb.g + BACKGROUND_SURFACE_DELTA),
|
|
54
|
-
b: Math.min(255, rgb.b + BACKGROUND_SURFACE_DELTA),
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function deriveRecessedBackground(background: string): string {
|
|
59
|
-
const rgb = parseHexRgb(background);
|
|
60
|
-
if (!rgb) return background;
|
|
61
|
-
return toHex({
|
|
62
|
-
r: Math.max(0, rgb.r - BACKGROUND_SURFACE_DELTA),
|
|
63
|
-
g: Math.max(0, rgb.g - BACKGROUND_SURFACE_DELTA),
|
|
64
|
-
b: Math.max(0, rgb.b - BACKGROUND_SURFACE_DELTA),
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export default function generateGlobalsCss(_opts: ScaffoldOptions): string {
|
|
69
|
-
const branding = HARDCODED_BRANDING;
|
|
70
|
-
const fontFamilyParam = branding.fontFamily.replace(/ /g, "+");
|
|
71
|
-
const foreground = deriveFontColor(branding.backgroundBase);
|
|
72
|
-
const primaryForeground = deriveFontColor(branding.primary);
|
|
73
|
-
const accentForeground = deriveFontColor(branding.accent);
|
|
74
|
-
const backgroundElevated = deriveElevatedBackground(branding.backgroundBase);
|
|
75
|
-
const backgroundRecessed = deriveRecessedBackground(branding.backgroundBase);
|
|
76
|
-
|
|
77
|
-
return `@import url("https://fonts.googleapis.com/css2?family=${fontFamilyParam}:wght@400;700&display=swap");
|
|
78
|
-
@import "tailwindcss";
|
|
79
|
-
/* Tailwind v4 skips node_modules; @zorgo/next ships un-obfuscated components so
|
|
80
|
-
their class names are scannable here. */
|
|
81
|
-
@source "../node_modules/@zorgo/next/dist/components.mjs";
|
|
82
|
-
@source "../node_modules/@zorgo/next/dist/item-page.mjs";
|
|
83
|
-
|
|
84
|
-
:root {
|
|
85
|
-
--background: ${branding.backgroundBase};
|
|
86
|
-
--background-elevated: ${backgroundElevated};
|
|
87
|
-
--background-recessed: ${backgroundRecessed};
|
|
88
|
-
--foreground: ${foreground};
|
|
89
|
-
--primary: ${branding.primary};
|
|
90
|
-
--primary-foreground: ${primaryForeground};
|
|
91
|
-
--accent: ${branding.accent};
|
|
92
|
-
--accent-foreground: ${accentForeground};
|
|
93
|
-
--font-sans: "${branding.fontFamily}", sans-serif;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
@theme inline {
|
|
97
|
-
--color-background: var(--background);
|
|
98
|
-
--color-background-elevated: var(--background-elevated);
|
|
99
|
-
--color-background-recessed: var(--background-recessed);
|
|
100
|
-
--color-foreground: var(--foreground);
|
|
101
|
-
--color-primary: var(--primary);
|
|
102
|
-
--color-primary-foreground: var(--primary-foreground);
|
|
103
|
-
--color-accent: var(--accent);
|
|
104
|
-
--color-accent-foreground: var(--accent-foreground);
|
|
105
|
-
--font-sans: var(--font-sans);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
@layer base {
|
|
109
|
-
button,
|
|
110
|
-
[type="button"],
|
|
111
|
-
[type="submit"],
|
|
112
|
-
[type="reset"] {
|
|
113
|
-
cursor: pointer;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
button:disabled,
|
|
117
|
-
[type="button"]:disabled,
|
|
118
|
-
[type="submit"]:disabled,
|
|
119
|
-
[type="reset"]:disabled {
|
|
120
|
-
cursor: not-allowed;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
`;
|
|
124
|
-
}
|