glashjs 0.14.0 → 0.14.2
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/README.md +2 -1
- package/bin/glash.mjs +6 -0
- package/package.json +1 -1
- package/src/assets/animated-favicon.mjs +6 -7
- package/src/build.mjs +3 -3
- package/src/config.mjs +10 -2
- package/src/create.mjs +2 -1
- package/src/server/html.mjs +12 -2
- package/src/server/server.mjs +8 -2
- package/templates/glash_favicon.svg +79 -0
package/README.md
CHANGED
|
@@ -179,7 +179,8 @@ export default defineConfig({
|
|
|
179
179
|
outDir: '.glash/out',
|
|
180
180
|
stylesheets: ['/app.css'],
|
|
181
181
|
offline: true,
|
|
182
|
-
|
|
182
|
+
favicon: 'node_modules/glashjs/templates/glash_favicon.svg',
|
|
183
|
+
animatedFavicon: false,
|
|
183
184
|
dataPrefixes: ['/api/', '/auth/', '/rest/', '/live', '/stream'],
|
|
184
185
|
});
|
|
185
186
|
```
|
package/bin/glash.mjs
CHANGED
|
@@ -56,6 +56,11 @@ function glashMark() {
|
|
|
56
56
|
];
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
function faviconLabel(cfg) {
|
|
60
|
+
const source = String(cfg.favicon || 'glash_favicon.svg');
|
|
61
|
+
return source.split(/[\\/]/).pop() || 'glash_favicon.svg';
|
|
62
|
+
}
|
|
63
|
+
|
|
59
64
|
// LAN IPv4 addresses, so the dev server prints a Network URL you can open from
|
|
60
65
|
// your phone or another device on the same network.
|
|
61
66
|
function lanAddresses() {
|
|
@@ -79,6 +84,7 @@ async function serve(dev) {
|
|
|
79
84
|
console.log('');
|
|
80
85
|
for (const line of glashMark()) console.log(line);
|
|
81
86
|
console.log(` glashjs ${dev ? 'dev' : 'serve'} — "${cfg.name}"`);
|
|
87
|
+
console.log(` favicon ${faviconLabel(cfg)} -> /favicon.svg`);
|
|
82
88
|
if (port !== preferredPort) console.log(` Port ${preferredPort} is in use; using ${port} instead.`);
|
|
83
89
|
console.log(` ${pages} page route(s), ${apis} api route(s)`);
|
|
84
90
|
routes.forEach((r) => console.log(` ${r.isApi ? 'api ' : 'page'} ${r.pattern}`));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "glashjs",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.2",
|
|
4
4
|
"description": "glashjs — The Postgres-native full-stack framework for builders who want to ship without DevOps. Framework, hosting, database, auth, and deploy in one GlashDB-native runtime.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
// glashjs animated favicon
|
|
2
2
|
// ---------------------------------------------------------------------------
|
|
3
|
-
// Ships an animated
|
|
4
|
-
// -
|
|
5
|
-
// tab icon natively (the default glash mark).
|
|
3
|
+
// Ships an optional animated/favicon runtime with every build. Two reliable modes:
|
|
4
|
+
// - SVG -> set once; the default is the official glash mark.
|
|
6
5
|
// - frame cycling -> a tiny runtime swaps the <link rel=icon> href
|
|
7
6
|
// across frames on an interval. Works everywhere,
|
|
8
7
|
// and pauses while the tab is hidden (no wasted CPU).
|
|
@@ -47,7 +46,7 @@ if (CONFIG.auto) startGlashFavicon();
|
|
|
47
46
|
/**
|
|
48
47
|
* Resolve + emit the animated favicon for a build.
|
|
49
48
|
* cfg.animatedFavicon may be:
|
|
50
|
-
* true -> the bundled default glash
|
|
49
|
+
* true -> the bundled default glash mark
|
|
51
50
|
* "<path>" -> a project animated SVG/GIF
|
|
52
51
|
* { frames:[...], fps, auto } -> frame-cycling animation
|
|
53
52
|
*/
|
|
@@ -59,14 +58,14 @@ export async function generateAnimatedFavicon(outDir, cfg, root, log = () => {})
|
|
|
59
58
|
|
|
60
59
|
if (setting === true || typeof setting === 'string') {
|
|
61
60
|
const src = setting === true
|
|
62
|
-
? fileURLToPath(new URL('../../templates/
|
|
61
|
+
? fileURLToPath(new URL('../../templates/glash_favicon.svg', import.meta.url))
|
|
63
62
|
: path.resolve(root, setting);
|
|
64
63
|
const out = 'favicon-animated' + path.extname(src);
|
|
65
64
|
try {
|
|
66
65
|
await fs.copyFile(src, path.join(outDir, out));
|
|
67
66
|
} catch {
|
|
68
|
-
log(` ! animated favicon source not found (${setting}) — using bundled
|
|
69
|
-
await fs.copyFile(fileURLToPath(new URL('../../templates/
|
|
67
|
+
log(` ! animated favicon source not found (${setting}) — using bundled glash_favicon.svg`);
|
|
68
|
+
await fs.copyFile(fileURLToPath(new URL('../../templates/glash_favicon.svg', import.meta.url)), path.join(outDir, 'favicon-animated.svg'));
|
|
70
69
|
baked.animated = '/favicon-animated.svg';
|
|
71
70
|
}
|
|
72
71
|
baked.animated = baked.animated || '/' + out;
|
package/src/build.mjs
CHANGED
|
@@ -115,7 +115,7 @@ export async function build({ root = process.cwd(), log = console.log } = {}) {
|
|
|
115
115
|
log(` saved ${t.savedPercent}% (${kb(t.originalBytes - t.optimizedBytes)})`);
|
|
116
116
|
log(` routes ${routesBuilt.compiled} JSX route(s) precompiled (no runtime esbuild in prod)`);
|
|
117
117
|
log(` offline ${cfg.offline ? `${offline.precached} files precached (works offline after first visit)` : 'disabled'}`);
|
|
118
|
-
log(` favicon
|
|
118
|
+
log(` favicon glash_favicon.svg${animated.enabled ? ' + animated (glash-favicon.mjs)' : ''}`);
|
|
119
119
|
log(` version ${version}`);
|
|
120
120
|
log(` security strict CSP + ${Object.keys(securityHeaders(cfg.security)).length} headers\n`);
|
|
121
121
|
|
|
@@ -126,7 +126,7 @@ export async function build({ root = process.cwd(), log = console.log } = {}) {
|
|
|
126
126
|
// framework. Try the configured path first, then fall back to that bundled logo
|
|
127
127
|
// so the glashdb favicon shows out-of-the-box even before any override.
|
|
128
128
|
async function copyFavicon(cfg, root, outDir, log) {
|
|
129
|
-
const bundled = path.resolve(fileURLToPath(new URL('../templates/
|
|
129
|
+
const bundled = path.resolve(fileURLToPath(new URL('../templates/glash_favicon.svg', import.meta.url)));
|
|
130
130
|
const candidates = [path.resolve(root, cfg.favicon), bundled];
|
|
131
131
|
for (const src of candidates) {
|
|
132
132
|
try {
|
|
@@ -134,7 +134,7 @@ async function copyFavicon(cfg, root, outDir, log) {
|
|
|
134
134
|
return;
|
|
135
135
|
} catch { /* try next */ }
|
|
136
136
|
}
|
|
137
|
-
log(` ! favicon not found (looked in ${cfg.favicon}, bundled
|
|
137
|
+
log(` ! favicon not found (looked in ${cfg.favicon}, bundled glash_favicon.svg) — preview favicon will be missing`);
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
function kb(bytes) {
|
package/src/config.mjs
CHANGED
|
@@ -11,11 +11,11 @@ export const DEFAULT_CONFIG = {
|
|
|
11
11
|
shortName: undefined,
|
|
12
12
|
// The preview favicon defaults to the official glashdb logo shipped with the
|
|
13
13
|
// framework; override with your own path relative to the project root.
|
|
14
|
-
favicon: 'node_modules/glashjs/templates/
|
|
14
|
+
favicon: 'node_modules/glashjs/templates/glash_favicon.svg',
|
|
15
15
|
// Animated favicon: true = bundled animated glash mark, a path to your own
|
|
16
16
|
// animated SVG/GIF, or { frames: ['/f0.svg', ...], fps: 8 } to cycle frames.
|
|
17
17
|
// false disables it. Emits `glash-favicon.mjs` — call startGlashFavicon() once.
|
|
18
|
-
animatedFavicon:
|
|
18
|
+
animatedFavicon: false,
|
|
19
19
|
publicDir: 'public',
|
|
20
20
|
// File-based routes (pages + api/) served by `glash dev` / `glash serve`.
|
|
21
21
|
routesDir: 'routes',
|
|
@@ -25,6 +25,14 @@ export const DEFAULT_CONFIG = {
|
|
|
25
25
|
outDir: '.glash/out',
|
|
26
26
|
themeColor: '#0b0d12',
|
|
27
27
|
offline: true,
|
|
28
|
+
// Built-in IP auto-translation. true = detect the visitor's country (from the
|
|
29
|
+
// edge geo header, falling back to a public IP service) and, if their native
|
|
30
|
+
// language differs from the page, offer a one-tap in-place translation. The
|
|
31
|
+
// framework serves the runtime (/_glash/i18n.js) and a geo endpoint
|
|
32
|
+
// (/api/_glash/geo) automatically — no route or layout wiring per app.
|
|
33
|
+
// true | false | { auto?: boolean, geoEndpoint?: string }
|
|
34
|
+
// auto: translate immediately instead of prompting.
|
|
35
|
+
i18n: true,
|
|
28
36
|
// Requests under these prefixes are treated as live/updated data: network-first
|
|
29
37
|
// in the Service Worker, so offline mode degrades exactly there (no stale live data).
|
|
30
38
|
dataPrefixes: ['/api/', '/rest/', '/auth/', '/realtime', '/live', '/stream'],
|
package/src/create.mjs
CHANGED
|
@@ -136,7 +136,8 @@ export default defineConfig({
|
|
|
136
136
|
publicDir: 'public',
|
|
137
137
|
outDir: '.glash/out',
|
|
138
138
|
offline: true,
|
|
139
|
-
|
|
139
|
+
favicon: 'node_modules/glashjs/templates/glash_favicon.svg',
|
|
140
|
+
animatedFavicon: false,
|
|
140
141
|
stylesheets: ${hasCss ? "['/app.css']" : '[]'},
|
|
141
142
|
dataPrefixes: ['/api/', '/auth/', '/rest/', '/live', '/stream'],
|
|
142
143
|
security: {
|
package/src/server/html.mjs
CHANGED
|
@@ -49,7 +49,7 @@ ${headHtml}
|
|
|
49
49
|
`;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
function shellTail({ offline = true, animatedFavicon =
|
|
52
|
+
function shellTail({ offline = true, animatedFavicon = false, dev = false, nonce = '', i18n = false }) {
|
|
53
53
|
const n = nonce ? ` nonce="${escapeHtml(nonce)}"` : '';
|
|
54
54
|
const fav = animatedFavicon
|
|
55
55
|
? `<script type="module"${n}>try{const m=await import("/glash-favicon.mjs");m.startGlashFavicon&&m.startGlashFavicon();}catch{}</script>`
|
|
@@ -57,6 +57,16 @@ function shellTail({ offline = true, animatedFavicon = true, dev = false, nonce
|
|
|
57
57
|
const off = offline
|
|
58
58
|
? `<script type="module"${n}>try{const m=await import("/glash-offline.mjs");m.registerGlashOffline&&m.registerGlashOffline();}catch{}</script>`
|
|
59
59
|
: '';
|
|
60
|
+
// Built-in IP auto-translation: the framework serves /_glash/i18n.js (the
|
|
61
|
+
// dependency-free runtime). It detects the visitor's country, maps it to their
|
|
62
|
+
// native language, and offers a one-tap in-place translation. CSP-safe: a
|
|
63
|
+
// nonce'd module import of a same-origin URL, no inline logic.
|
|
64
|
+
const i18nOpts = i18n && typeof i18n === 'object'
|
|
65
|
+
? { auto: !!i18n.auto, ...(i18n.geoEndpoint ? { geoEndpoint: i18n.geoEndpoint } : {}) }
|
|
66
|
+
: {};
|
|
67
|
+
const intl = i18n
|
|
68
|
+
? `<script type="module"${n}>try{const m=await import("/_glash/i18n.js");m.glashAutoTranslate&&m.glashAutoTranslate(${JSON.stringify(i18nOpts)});}catch{}</script>`
|
|
69
|
+
: '';
|
|
60
70
|
// Dev HMR: a nonce'd inline script (CSP-safe). On a change event it does an
|
|
61
71
|
// in-place soft refresh (no full reload, keeps scroll/focus/inputs), falling
|
|
62
72
|
// back to a full reload if the nav runtime hasn't loaded yet.
|
|
@@ -65,7 +75,7 @@ function shellTail({ offline = true, animatedFavicon = true, dev = false, nonce
|
|
|
65
75
|
: '';
|
|
66
76
|
// Client-side navigation runtime (external 'self' module, CSP-safe).
|
|
67
77
|
const nav = '<script type="module" src="/_glash/nav.js"></script>';
|
|
68
|
-
return `\n${fav}${off}${hmr}${nav}\n</body>\n</html>`;
|
|
78
|
+
return `\n${fav}${off}${intl}${hmr}${nav}\n</body>\n</html>`;
|
|
69
79
|
}
|
|
70
80
|
|
|
71
81
|
/**
|
package/src/server/server.mjs
CHANGED
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
// service worker, favicons) with Brotli negotiation.
|
|
7
7
|
// Every response carries the secure-by-default headers.
|
|
8
8
|
import http from 'node:http';
|
|
9
|
-
import { promises as fs, existsSync, statSync, watch, createReadStream } from 'node:fs';
|
|
9
|
+
import { promises as fs, existsSync, statSync, watch, createReadStream, readFileSync } from 'node:fs';
|
|
10
10
|
import { Transform } from 'node:stream';
|
|
11
11
|
import { randomBytes } from 'node:crypto';
|
|
12
12
|
import path from 'node:path';
|
|
13
|
-
import { pathToFileURL } from 'node:url';
|
|
13
|
+
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
14
|
+
import { geoRouteHandler } from '../i18n.mjs';
|
|
14
15
|
import { discoverRoutes, matchRoute, findMiddleware } from './router.mjs';
|
|
15
16
|
import { renderDocument, documentParts, renderMeta, escapeHtml } from './html.mjs';
|
|
16
17
|
import { NAV_CLIENT } from './nav-client.mjs';
|
|
@@ -28,6 +29,11 @@ const MIME = {
|
|
|
28
29
|
};
|
|
29
30
|
const mime = (file) => MIME[path.extname(file).toLowerCase()] || 'application/octet-stream';
|
|
30
31
|
|
|
32
|
+
// The built-in i18n runtime served at /_glash/i18n.js. i18n.mjs is dependency-free
|
|
33
|
+
// and valid browser ESM (its server-only exports are harmless in the browser), so
|
|
34
|
+
// we serve it raw — single source of truth, works identically in dev and prod.
|
|
35
|
+
const I18N_CLIENT = readFileSync(fileURLToPath(new URL('../i18n.mjs', import.meta.url)), 'utf8');
|
|
36
|
+
|
|
31
37
|
/** Build the glashjs server. Returns { server, listen, cfg }. */
|
|
32
38
|
export async function createGlashServer({ root = process.cwd(), dev = false } = {}) {
|
|
33
39
|
loadEnvFiles(root);
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<svg width="508" height="508" viewBox="0 0 508 508" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<g filter="url(#filter0_ii_12189_2)">
|
|
3
|
+
<rect x="43.9863" y="34.4604" width="420.999" height="420.999" rx="102.477" fill="url(#paint0_linear_12189_2)"/>
|
|
4
|
+
</g>
|
|
5
|
+
<rect x="45.1251" y="35.5992" width="418.722" height="418.722" rx="101.338" stroke="#D4D4D4" stroke-width="2.27759"/>
|
|
6
|
+
<g opacity="0.6" filter="url(#filter1_f_12189_2)" style="mix-blend-mode:overlay">
|
|
7
|
+
<circle cx="253.556" cy="253.556" r="119.602" fill="url(#paint1_linear_12189_2)"/>
|
|
8
|
+
<circle cx="253.556" cy="253.556" r="118.463" stroke="#D4D4D4" stroke-width="2.27759"/>
|
|
9
|
+
</g>
|
|
10
|
+
<mask id="mask0_12189_2" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="43" y="34" width="422" height="422">
|
|
11
|
+
<rect x="45.1241" y="35.5997" width="418.722" height="418.722" rx="101.338" fill="url(#paint2_linear_12189_2)" stroke="#D4D4D4" stroke-width="2.27759"/>
|
|
12
|
+
</mask>
|
|
13
|
+
<g mask="url(#mask0_12189_2)">
|
|
14
|
+
<g filter="url(#filter2_f_12189_2)">
|
|
15
|
+
<ellipse cx="259.269" cy="23.0167" rx="215.284" ry="122.507" fill="url(#paint3_linear_12189_2)"/>
|
|
16
|
+
<path d="M259.269 -98.3521C318.557 -98.3521 372.177 -84.6747 410.935 -62.6196C449.725 -40.5457 473.414 -10.2249 473.414 23.0171C473.414 56.2589 449.725 86.579 410.935 108.653C372.177 130.708 318.557 144.385 259.269 144.385C199.98 144.385 146.361 130.708 107.604 108.653C68.813 86.579 45.1242 56.2589 45.124 23.0171C45.124 -10.2249 68.8128 -40.5457 107.604 -62.6196C146.361 -84.6747 199.98 -98.352 259.269 -98.3521Z" stroke="#D4D4D4" stroke-width="2.27759"/>
|
|
17
|
+
</g>
|
|
18
|
+
<g filter="url(#filter3_f_12189_2)" style="mix-blend-mode:plus-lighter">
|
|
19
|
+
<ellipse cx="254.486" cy="29.677" rx="47.8408" ry="81.3294" fill="url(#paint4_linear_12189_2)"/>
|
|
20
|
+
<path d="M254.486 -50.5137C267.129 -50.5137 278.78 -41.7943 287.333 -27.2539C295.871 -12.7399 301.188 7.3854 301.188 29.6768C301.188 51.9682 295.871 72.0943 287.333 86.6084C278.78 101.149 267.129 109.867 254.486 109.867C241.844 109.867 230.193 101.149 221.64 86.6084C213.102 72.0943 207.784 51.9682 207.784 29.6768C207.784 7.3854 213.102 -12.7399 221.64 -27.2539C230.193 -41.7943 241.844 -50.5137 254.486 -50.5137Z" stroke="#D4D4D4" stroke-width="2.27759"/>
|
|
21
|
+
</g>
|
|
22
|
+
</g>
|
|
23
|
+
<path d="M336.671 312.331L239.265 374.956L164.548 342.247L261.906 279.621L336.671 312.331Z" fill="#FEFFFF"/>
|
|
24
|
+
<path d="M264.17 166.655C286.185 166.655 304.057 184.575 304.057 206.591C304.057 213.479 302.371 219.983 299.047 225.956C292.014 238.626 278.67 246.478 264.17 246.478C258.148 246.478 252.367 245.178 246.972 242.624C233.146 236.025 224.234 221.91 224.234 206.591C224.234 184.575 242.155 166.655 264.17 166.655ZM264.17 128.116C220.814 128.116 185.695 163.235 185.695 206.591C185.695 237.807 203.953 264.784 230.352 277.406C240.565 282.319 252.03 285.017 264.122 285.017C293.604 285.017 319.328 268.734 332.672 244.648C338.983 233.375 342.548 220.416 342.548 206.591C342.548 163.283 307.43 128.116 264.122 128.116H264.17Z" fill="#FEFFFF"/>
|
|
25
|
+
<defs>
|
|
26
|
+
<filter id="filter0_ii_12189_2" x="43.9863" y="34.4604" width="420.999" height="449.704" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
27
|
+
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
28
|
+
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
29
|
+
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
|
30
|
+
<feOffset/>
|
|
31
|
+
<feGaussianBlur stdDeviation="19.1363"/>
|
|
32
|
+
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
|
33
|
+
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.24 0"/>
|
|
34
|
+
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_12189_2"/>
|
|
35
|
+
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
|
36
|
+
<feOffset dy="28.7045"/>
|
|
37
|
+
<feGaussianBlur stdDeviation="19.1363"/>
|
|
38
|
+
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
|
39
|
+
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.3 0"/>
|
|
40
|
+
<feBlend mode="normal" in2="effect1_innerShadow_12189_2" result="effect2_innerShadow_12189_2"/>
|
|
41
|
+
</filter>
|
|
42
|
+
<filter id="filter1_f_12189_2" x="-0.000152588" y="-0.000152588" width="507.113" height="507.113" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
43
|
+
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
44
|
+
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
45
|
+
<feGaussianBlur stdDeviation="66.9771" result="effect1_foregroundBlur_12189_2"/>
|
|
46
|
+
</filter>
|
|
47
|
+
<filter id="filter2_f_12189_2" x="-109.105" y="-252.581" width="736.749" height="551.196" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
48
|
+
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
49
|
+
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
50
|
+
<feGaussianBlur stdDeviation="76.5453" result="effect1_foregroundBlur_12189_2"/>
|
|
51
|
+
</filter>
|
|
52
|
+
<filter id="filter3_f_12189_2" x="149.237" y="-109.061" width="210.5" height="277.477" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
53
|
+
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
54
|
+
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
55
|
+
<feGaussianBlur stdDeviation="28.7045" result="effect1_foregroundBlur_12189_2"/>
|
|
56
|
+
</filter>
|
|
57
|
+
<linearGradient id="paint0_linear_12189_2" x1="254.486" y1="34.4604" x2="254.486" y2="455.46" gradientUnits="userSpaceOnUse">
|
|
58
|
+
<stop stop-opacity="0.3"/>
|
|
59
|
+
<stop offset="1" stop-opacity="0.6"/>
|
|
60
|
+
</linearGradient>
|
|
61
|
+
<linearGradient id="paint1_linear_12189_2" x1="253.556" y1="133.954" x2="253.556" y2="373.158" gradientUnits="userSpaceOnUse">
|
|
62
|
+
<stop stop-opacity="0"/>
|
|
63
|
+
<stop offset="1" stop-opacity="0.6"/>
|
|
64
|
+
</linearGradient>
|
|
65
|
+
<linearGradient id="paint2_linear_12189_2" x1="254.485" y1="34.4609" x2="254.485" y2="455.46" gradientUnits="userSpaceOnUse">
|
|
66
|
+
<stop stop-color="#4B4B4B"/>
|
|
67
|
+
<stop offset="1" stop-color="#AFAFAF"/>
|
|
68
|
+
</linearGradient>
|
|
69
|
+
<linearGradient id="paint3_linear_12189_2" x1="272.501" y1="142.813" x2="267.703" y2="-48.6569" gradientUnits="userSpaceOnUse">
|
|
70
|
+
<stop stop-color="#D8D8D8" stop-opacity="0.05"/>
|
|
71
|
+
<stop offset="0.61" stop-color="#B2B2B2" stop-opacity="0.05"/>
|
|
72
|
+
<stop offset="0.98" stop-color="#404040"/>
|
|
73
|
+
</linearGradient>
|
|
74
|
+
<linearGradient id="paint4_linear_12189_2" x1="254.486" y1="-51.6523" x2="254.486" y2="111.006" gradientUnits="userSpaceOnUse">
|
|
75
|
+
<stop stop-opacity="0"/>
|
|
76
|
+
<stop offset="1" stop-opacity="0.6"/>
|
|
77
|
+
</linearGradient>
|
|
78
|
+
</defs>
|
|
79
|
+
</svg>
|