first commit
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": ["next/core-web-vitals", "next", "prettier"],
|
||||||
|
"plugins": ["@next/eslint-plugin-next", "prettier"],
|
||||||
|
"rules": {
|
||||||
|
"prettier/prettier": "warn",
|
||||||
|
"react/react-in-jsx-scope": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/dist
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
#custom
|
||||||
|
temp
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Yarn
|
||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/sdks
|
||||||
|
!.yarn/versions
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"arcanis.vscode-zipfs",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"esbenp.prettier-vscode"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"search.exclude": {
|
||||||
|
"**/.yarn": true,
|
||||||
|
"**/.pnp.*": true
|
||||||
|
},
|
||||||
|
"typescript.tsdk": ".yarn/sdks/typescript/lib",
|
||||||
|
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||||
|
"eslint.nodePath": ".yarn/sdks",
|
||||||
|
"prettier.prettierPath": ".yarn/sdks/prettier/index.cjs"
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const {existsSync} = require(`fs`);
|
||||||
|
const {createRequire} = require(`module`);
|
||||||
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
|
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||||
|
|
||||||
|
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||||
|
const absRequire = createRequire(absPnpApiPath);
|
||||||
|
|
||||||
|
if (existsSync(absPnpApiPath)) {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
// Setup the environment to be able to require eslint/bin/eslint.js
|
||||||
|
require(absPnpApiPath).setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer to the real eslint/bin/eslint.js your application uses
|
||||||
|
module.exports = absRequire(`eslint/bin/eslint.js`);
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const {existsSync} = require(`fs`);
|
||||||
|
const {createRequire} = require(`module`);
|
||||||
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
|
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||||
|
|
||||||
|
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||||
|
const absRequire = createRequire(absPnpApiPath);
|
||||||
|
|
||||||
|
if (existsSync(absPnpApiPath)) {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
// Setup the environment to be able to require eslint
|
||||||
|
require(absPnpApiPath).setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer to the real eslint your application uses
|
||||||
|
module.exports = absRequire(`eslint`);
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const {existsSync} = require(`fs`);
|
||||||
|
const {createRequire} = require(`module`);
|
||||||
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
|
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||||
|
|
||||||
|
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||||
|
const absRequire = createRequire(absPnpApiPath);
|
||||||
|
|
||||||
|
if (existsSync(absPnpApiPath)) {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
// Setup the environment to be able to require eslint/use-at-your-own-risk
|
||||||
|
require(absPnpApiPath).setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer to the real eslint/use-at-your-own-risk your application uses
|
||||||
|
module.exports = absRequire(`eslint/use-at-your-own-risk`);
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "eslint",
|
||||||
|
"version": "8.56.0-sdk",
|
||||||
|
"main": "./lib/api.js",
|
||||||
|
"type": "commonjs",
|
||||||
|
"bin": {
|
||||||
|
"eslint": "./bin/eslint.js"
|
||||||
|
},
|
||||||
|
"exports": {
|
||||||
|
"./package.json": "./package.json",
|
||||||
|
".": "./lib/api.js",
|
||||||
|
"./use-at-your-own-risk": "./lib/unsupported-api.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
# This file is automatically generated by @yarnpkg/sdks.
|
||||||
|
# Manual changes might be lost!
|
||||||
|
|
||||||
|
integrations:
|
||||||
|
- vscode
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const {existsSync} = require(`fs`);
|
||||||
|
const {createRequire} = require(`module`);
|
||||||
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
|
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||||
|
|
||||||
|
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||||
|
const absRequire = createRequire(absPnpApiPath);
|
||||||
|
|
||||||
|
if (existsSync(absPnpApiPath)) {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
// Setup the environment to be able to require prettier/bin/prettier.cjs
|
||||||
|
require(absPnpApiPath).setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer to the real prettier/bin/prettier.cjs your application uses
|
||||||
|
module.exports = absRequire(`prettier/bin/prettier.cjs`);
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const {existsSync} = require(`fs`);
|
||||||
|
const {createRequire} = require(`module`);
|
||||||
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
|
const relPnpApiPath = "../../../.pnp.cjs";
|
||||||
|
|
||||||
|
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||||
|
const absRequire = createRequire(absPnpApiPath);
|
||||||
|
|
||||||
|
if (existsSync(absPnpApiPath)) {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
// Setup the environment to be able to require prettier
|
||||||
|
require(absPnpApiPath).setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer to the real prettier your application uses
|
||||||
|
module.exports = absRequire(`prettier`);
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "prettier",
|
||||||
|
"version": "3.2.5-sdk",
|
||||||
|
"main": "./index.cjs",
|
||||||
|
"type": "commonjs",
|
||||||
|
"bin": "./bin/prettier.cjs"
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const {existsSync} = require(`fs`);
|
||||||
|
const {createRequire} = require(`module`);
|
||||||
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
|
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||||
|
|
||||||
|
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||||
|
const absRequire = createRequire(absPnpApiPath);
|
||||||
|
|
||||||
|
if (existsSync(absPnpApiPath)) {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
// Setup the environment to be able to require typescript/bin/tsc
|
||||||
|
require(absPnpApiPath).setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer to the real typescript/bin/tsc your application uses
|
||||||
|
module.exports = absRequire(`typescript/bin/tsc`);
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const {existsSync} = require(`fs`);
|
||||||
|
const {createRequire} = require(`module`);
|
||||||
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
|
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||||
|
|
||||||
|
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||||
|
const absRequire = createRequire(absPnpApiPath);
|
||||||
|
|
||||||
|
if (existsSync(absPnpApiPath)) {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
// Setup the environment to be able to require typescript/bin/tsserver
|
||||||
|
require(absPnpApiPath).setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer to the real typescript/bin/tsserver your application uses
|
||||||
|
module.exports = absRequire(`typescript/bin/tsserver`);
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const {existsSync} = require(`fs`);
|
||||||
|
const {createRequire} = require(`module`);
|
||||||
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
|
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||||
|
|
||||||
|
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||||
|
const absRequire = createRequire(absPnpApiPath);
|
||||||
|
|
||||||
|
if (existsSync(absPnpApiPath)) {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
// Setup the environment to be able to require typescript/lib/tsc.js
|
||||||
|
require(absPnpApiPath).setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer to the real typescript/lib/tsc.js your application uses
|
||||||
|
module.exports = absRequire(`typescript/lib/tsc.js`);
|
||||||
@ -0,0 +1,225 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const {existsSync} = require(`fs`);
|
||||||
|
const {createRequire} = require(`module`);
|
||||||
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
|
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||||
|
|
||||||
|
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||||
|
const absRequire = createRequire(absPnpApiPath);
|
||||||
|
|
||||||
|
const moduleWrapper = tsserver => {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
return tsserver;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {isAbsolute} = require(`path`);
|
||||||
|
const pnpApi = require(`pnpapi`);
|
||||||
|
|
||||||
|
const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//);
|
||||||
|
const isPortal = str => str.startsWith("portal:/");
|
||||||
|
const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`);
|
||||||
|
|
||||||
|
const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => {
|
||||||
|
return `${locator.name}@${locator.reference}`;
|
||||||
|
}));
|
||||||
|
|
||||||
|
// VSCode sends the zip paths to TS using the "zip://" prefix, that TS
|
||||||
|
// doesn't understand. This layer makes sure to remove the protocol
|
||||||
|
// before forwarding it to TS, and to add it back on all returned paths.
|
||||||
|
|
||||||
|
function toEditorPath(str) {
|
||||||
|
// We add the `zip:` prefix to both `.zip/` paths and virtual paths
|
||||||
|
if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) {
|
||||||
|
// We also take the opportunity to turn virtual paths into physical ones;
|
||||||
|
// this makes it much easier to work with workspaces that list peer
|
||||||
|
// dependencies, since otherwise Ctrl+Click would bring us to the virtual
|
||||||
|
// file instances instead of the real ones.
|
||||||
|
//
|
||||||
|
// We only do this to modules owned by the the dependency tree roots.
|
||||||
|
// This avoids breaking the resolution when jumping inside a vendor
|
||||||
|
// with peer dep (otherwise jumping into react-dom would show resolution
|
||||||
|
// errors on react).
|
||||||
|
//
|
||||||
|
const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str;
|
||||||
|
if (resolved) {
|
||||||
|
const locator = pnpApi.findPackageLocator(resolved);
|
||||||
|
if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) {
|
||||||
|
str = resolved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
str = normalize(str);
|
||||||
|
|
||||||
|
if (str.match(/\.zip\//)) {
|
||||||
|
switch (hostInfo) {
|
||||||
|
// Absolute VSCode `Uri.fsPath`s need to start with a slash.
|
||||||
|
// VSCode only adds it automatically for supported schemes,
|
||||||
|
// so we have to do it manually for the `zip` scheme.
|
||||||
|
// The path needs to start with a caret otherwise VSCode doesn't handle the protocol
|
||||||
|
//
|
||||||
|
// Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910
|
||||||
|
//
|
||||||
|
// 2021-10-08: VSCode changed the format in 1.61.
|
||||||
|
// Before | ^zip:/c:/foo/bar.zip/package.json
|
||||||
|
// After | ^/zip//c:/foo/bar.zip/package.json
|
||||||
|
//
|
||||||
|
// 2022-04-06: VSCode changed the format in 1.66.
|
||||||
|
// Before | ^/zip//c:/foo/bar.zip/package.json
|
||||||
|
// After | ^/zip/c:/foo/bar.zip/package.json
|
||||||
|
//
|
||||||
|
// 2022-05-06: VSCode changed the format in 1.68
|
||||||
|
// Before | ^/zip/c:/foo/bar.zip/package.json
|
||||||
|
// After | ^/zip//c:/foo/bar.zip/package.json
|
||||||
|
//
|
||||||
|
case `vscode <1.61`: {
|
||||||
|
str = `^zip:${str}`;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case `vscode <1.66`: {
|
||||||
|
str = `^/zip/${str}`;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case `vscode <1.68`: {
|
||||||
|
str = `^/zip${str}`;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case `vscode`: {
|
||||||
|
str = `^/zip/${str}`;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
// To make "go to definition" work,
|
||||||
|
// We have to resolve the actual file system path from virtual path
|
||||||
|
// and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip)
|
||||||
|
case `coc-nvim`: {
|
||||||
|
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
||||||
|
str = resolve(`zipfile:${str}`);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
// Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server)
|
||||||
|
// We have to resolve the actual file system path from virtual path,
|
||||||
|
// everything else is up to neovim
|
||||||
|
case `neovim`: {
|
||||||
|
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
||||||
|
str = `zipfile://${str}`;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default: {
|
||||||
|
str = `zip:${str}`;
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromEditorPath(str) {
|
||||||
|
switch (hostInfo) {
|
||||||
|
case `coc-nvim`: {
|
||||||
|
str = str.replace(/\.zip::/, `.zip/`);
|
||||||
|
// The path for coc-nvim is in format of /<pwd>/zipfile:/<pwd>/.yarn/...
|
||||||
|
// So in order to convert it back, we use .* to match all the thing
|
||||||
|
// before `zipfile:`
|
||||||
|
return process.platform === `win32`
|
||||||
|
? str.replace(/^.*zipfile:\//, ``)
|
||||||
|
: str.replace(/^.*zipfile:/, ``);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case `neovim`: {
|
||||||
|
str = str.replace(/\.zip::/, `.zip/`);
|
||||||
|
// The path for neovim is in format of zipfile:///<pwd>/.yarn/...
|
||||||
|
return str.replace(/^zipfile:\/\//, ``);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case `vscode`:
|
||||||
|
default: {
|
||||||
|
return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`)
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force enable 'allowLocalPluginLoads'
|
||||||
|
// TypeScript tries to resolve plugins using a path relative to itself
|
||||||
|
// which doesn't work when using the global cache
|
||||||
|
// https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238
|
||||||
|
// VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but
|
||||||
|
// TypeScript already does local loads and if this code is running the user trusts the workspace
|
||||||
|
// https://github.com/microsoft/vscode/issues/45856
|
||||||
|
const ConfiguredProject = tsserver.server.ConfiguredProject;
|
||||||
|
const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype;
|
||||||
|
ConfiguredProject.prototype.enablePluginsWithOptions = function() {
|
||||||
|
this.projectService.allowLocalPluginLoads = true;
|
||||||
|
return originalEnablePluginsWithOptions.apply(this, arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
// And here is the point where we hijack the VSCode <-> TS communications
|
||||||
|
// by adding ourselves in the middle. We locate everything that looks
|
||||||
|
// like an absolute path of ours and normalize it.
|
||||||
|
|
||||||
|
const Session = tsserver.server.Session;
|
||||||
|
const {onMessage: originalOnMessage, send: originalSend} = Session.prototype;
|
||||||
|
let hostInfo = `unknown`;
|
||||||
|
|
||||||
|
Object.assign(Session.prototype, {
|
||||||
|
onMessage(/** @type {string | object} */ message) {
|
||||||
|
const isStringMessage = typeof message === 'string';
|
||||||
|
const parsedMessage = isStringMessage ? JSON.parse(message) : message;
|
||||||
|
|
||||||
|
if (
|
||||||
|
parsedMessage != null &&
|
||||||
|
typeof parsedMessage === `object` &&
|
||||||
|
parsedMessage.arguments &&
|
||||||
|
typeof parsedMessage.arguments.hostInfo === `string`
|
||||||
|
) {
|
||||||
|
hostInfo = parsedMessage.arguments.hostInfo;
|
||||||
|
if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) {
|
||||||
|
const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match(
|
||||||
|
// The RegExp from https://semver.org/ but without the caret at the start
|
||||||
|
/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
|
||||||
|
) ?? []).map(Number)
|
||||||
|
|
||||||
|
if (major === 1) {
|
||||||
|
if (minor < 61) {
|
||||||
|
hostInfo += ` <1.61`;
|
||||||
|
} else if (minor < 66) {
|
||||||
|
hostInfo += ` <1.66`;
|
||||||
|
} else if (minor < 68) {
|
||||||
|
hostInfo += ` <1.68`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => {
|
||||||
|
return typeof value === 'string' ? fromEditorPath(value) : value;
|
||||||
|
});
|
||||||
|
|
||||||
|
return originalOnMessage.call(
|
||||||
|
this,
|
||||||
|
isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
send(/** @type {any} */ msg) {
|
||||||
|
return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => {
|
||||||
|
return typeof value === `string` ? toEditorPath(value) : value;
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return tsserver;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (existsSync(absPnpApiPath)) {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
// Setup the environment to be able to require typescript/lib/tsserver.js
|
||||||
|
require(absPnpApiPath).setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer to the real typescript/lib/tsserver.js your application uses
|
||||||
|
module.exports = moduleWrapper(absRequire(`typescript/lib/tsserver.js`));
|
||||||
@ -0,0 +1,225 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const {existsSync} = require(`fs`);
|
||||||
|
const {createRequire} = require(`module`);
|
||||||
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
|
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||||
|
|
||||||
|
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||||
|
const absRequire = createRequire(absPnpApiPath);
|
||||||
|
|
||||||
|
const moduleWrapper = tsserver => {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
return tsserver;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {isAbsolute} = require(`path`);
|
||||||
|
const pnpApi = require(`pnpapi`);
|
||||||
|
|
||||||
|
const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//);
|
||||||
|
const isPortal = str => str.startsWith("portal:/");
|
||||||
|
const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`);
|
||||||
|
|
||||||
|
const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => {
|
||||||
|
return `${locator.name}@${locator.reference}`;
|
||||||
|
}));
|
||||||
|
|
||||||
|
// VSCode sends the zip paths to TS using the "zip://" prefix, that TS
|
||||||
|
// doesn't understand. This layer makes sure to remove the protocol
|
||||||
|
// before forwarding it to TS, and to add it back on all returned paths.
|
||||||
|
|
||||||
|
function toEditorPath(str) {
|
||||||
|
// We add the `zip:` prefix to both `.zip/` paths and virtual paths
|
||||||
|
if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) {
|
||||||
|
// We also take the opportunity to turn virtual paths into physical ones;
|
||||||
|
// this makes it much easier to work with workspaces that list peer
|
||||||
|
// dependencies, since otherwise Ctrl+Click would bring us to the virtual
|
||||||
|
// file instances instead of the real ones.
|
||||||
|
//
|
||||||
|
// We only do this to modules owned by the the dependency tree roots.
|
||||||
|
// This avoids breaking the resolution when jumping inside a vendor
|
||||||
|
// with peer dep (otherwise jumping into react-dom would show resolution
|
||||||
|
// errors on react).
|
||||||
|
//
|
||||||
|
const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str;
|
||||||
|
if (resolved) {
|
||||||
|
const locator = pnpApi.findPackageLocator(resolved);
|
||||||
|
if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) {
|
||||||
|
str = resolved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
str = normalize(str);
|
||||||
|
|
||||||
|
if (str.match(/\.zip\//)) {
|
||||||
|
switch (hostInfo) {
|
||||||
|
// Absolute VSCode `Uri.fsPath`s need to start with a slash.
|
||||||
|
// VSCode only adds it automatically for supported schemes,
|
||||||
|
// so we have to do it manually for the `zip` scheme.
|
||||||
|
// The path needs to start with a caret otherwise VSCode doesn't handle the protocol
|
||||||
|
//
|
||||||
|
// Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910
|
||||||
|
//
|
||||||
|
// 2021-10-08: VSCode changed the format in 1.61.
|
||||||
|
// Before | ^zip:/c:/foo/bar.zip/package.json
|
||||||
|
// After | ^/zip//c:/foo/bar.zip/package.json
|
||||||
|
//
|
||||||
|
// 2022-04-06: VSCode changed the format in 1.66.
|
||||||
|
// Before | ^/zip//c:/foo/bar.zip/package.json
|
||||||
|
// After | ^/zip/c:/foo/bar.zip/package.json
|
||||||
|
//
|
||||||
|
// 2022-05-06: VSCode changed the format in 1.68
|
||||||
|
// Before | ^/zip/c:/foo/bar.zip/package.json
|
||||||
|
// After | ^/zip//c:/foo/bar.zip/package.json
|
||||||
|
//
|
||||||
|
case `vscode <1.61`: {
|
||||||
|
str = `^zip:${str}`;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case `vscode <1.66`: {
|
||||||
|
str = `^/zip/${str}`;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case `vscode <1.68`: {
|
||||||
|
str = `^/zip${str}`;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case `vscode`: {
|
||||||
|
str = `^/zip/${str}`;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
// To make "go to definition" work,
|
||||||
|
// We have to resolve the actual file system path from virtual path
|
||||||
|
// and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip)
|
||||||
|
case `coc-nvim`: {
|
||||||
|
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
||||||
|
str = resolve(`zipfile:${str}`);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
// Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server)
|
||||||
|
// We have to resolve the actual file system path from virtual path,
|
||||||
|
// everything else is up to neovim
|
||||||
|
case `neovim`: {
|
||||||
|
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
||||||
|
str = `zipfile://${str}`;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default: {
|
||||||
|
str = `zip:${str}`;
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromEditorPath(str) {
|
||||||
|
switch (hostInfo) {
|
||||||
|
case `coc-nvim`: {
|
||||||
|
str = str.replace(/\.zip::/, `.zip/`);
|
||||||
|
// The path for coc-nvim is in format of /<pwd>/zipfile:/<pwd>/.yarn/...
|
||||||
|
// So in order to convert it back, we use .* to match all the thing
|
||||||
|
// before `zipfile:`
|
||||||
|
return process.platform === `win32`
|
||||||
|
? str.replace(/^.*zipfile:\//, ``)
|
||||||
|
: str.replace(/^.*zipfile:/, ``);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case `neovim`: {
|
||||||
|
str = str.replace(/\.zip::/, `.zip/`);
|
||||||
|
// The path for neovim is in format of zipfile:///<pwd>/.yarn/...
|
||||||
|
return str.replace(/^zipfile:\/\//, ``);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case `vscode`:
|
||||||
|
default: {
|
||||||
|
return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`)
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force enable 'allowLocalPluginLoads'
|
||||||
|
// TypeScript tries to resolve plugins using a path relative to itself
|
||||||
|
// which doesn't work when using the global cache
|
||||||
|
// https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238
|
||||||
|
// VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but
|
||||||
|
// TypeScript already does local loads and if this code is running the user trusts the workspace
|
||||||
|
// https://github.com/microsoft/vscode/issues/45856
|
||||||
|
const ConfiguredProject = tsserver.server.ConfiguredProject;
|
||||||
|
const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype;
|
||||||
|
ConfiguredProject.prototype.enablePluginsWithOptions = function() {
|
||||||
|
this.projectService.allowLocalPluginLoads = true;
|
||||||
|
return originalEnablePluginsWithOptions.apply(this, arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
// And here is the point where we hijack the VSCode <-> TS communications
|
||||||
|
// by adding ourselves in the middle. We locate everything that looks
|
||||||
|
// like an absolute path of ours and normalize it.
|
||||||
|
|
||||||
|
const Session = tsserver.server.Session;
|
||||||
|
const {onMessage: originalOnMessage, send: originalSend} = Session.prototype;
|
||||||
|
let hostInfo = `unknown`;
|
||||||
|
|
||||||
|
Object.assign(Session.prototype, {
|
||||||
|
onMessage(/** @type {string | object} */ message) {
|
||||||
|
const isStringMessage = typeof message === 'string';
|
||||||
|
const parsedMessage = isStringMessage ? JSON.parse(message) : message;
|
||||||
|
|
||||||
|
if (
|
||||||
|
parsedMessage != null &&
|
||||||
|
typeof parsedMessage === `object` &&
|
||||||
|
parsedMessage.arguments &&
|
||||||
|
typeof parsedMessage.arguments.hostInfo === `string`
|
||||||
|
) {
|
||||||
|
hostInfo = parsedMessage.arguments.hostInfo;
|
||||||
|
if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) {
|
||||||
|
const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match(
|
||||||
|
// The RegExp from https://semver.org/ but without the caret at the start
|
||||||
|
/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
|
||||||
|
) ?? []).map(Number)
|
||||||
|
|
||||||
|
if (major === 1) {
|
||||||
|
if (minor < 61) {
|
||||||
|
hostInfo += ` <1.61`;
|
||||||
|
} else if (minor < 66) {
|
||||||
|
hostInfo += ` <1.66`;
|
||||||
|
} else if (minor < 68) {
|
||||||
|
hostInfo += ` <1.68`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => {
|
||||||
|
return typeof value === 'string' ? fromEditorPath(value) : value;
|
||||||
|
});
|
||||||
|
|
||||||
|
return originalOnMessage.call(
|
||||||
|
this,
|
||||||
|
isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
send(/** @type {any} */ msg) {
|
||||||
|
return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => {
|
||||||
|
return typeof value === `string` ? toEditorPath(value) : value;
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return tsserver;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (existsSync(absPnpApiPath)) {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
// Setup the environment to be able to require typescript/lib/tsserverlibrary.js
|
||||||
|
require(absPnpApiPath).setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer to the real typescript/lib/tsserverlibrary.js your application uses
|
||||||
|
module.exports = moduleWrapper(absRequire(`typescript/lib/tsserverlibrary.js`));
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const {existsSync} = require(`fs`);
|
||||||
|
const {createRequire} = require(`module`);
|
||||||
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
|
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||||
|
|
||||||
|
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||||
|
const absRequire = createRequire(absPnpApiPath);
|
||||||
|
|
||||||
|
if (existsSync(absPnpApiPath)) {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
// Setup the environment to be able to require typescript
|
||||||
|
require(absPnpApiPath).setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer to the real typescript your application uses
|
||||||
|
module.exports = absRequire(`typescript`);
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"name": "typescript",
|
||||||
|
"version": "5.3.3-sdk",
|
||||||
|
"main": "./lib/typescript.js",
|
||||||
|
"type": "commonjs",
|
||||||
|
"bin": {
|
||||||
|
"tsc": "./bin/tsc",
|
||||||
|
"tsserver": "./bin/tsserver"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
compressionLevel: mixed
|
||||||
|
|
||||||
|
enableGlobalCache: false
|
||||||
|
|
||||||
|
nodeLinker: pnp
|
||||||
|
|
||||||
|
yarnPath: .yarn/releases/yarn-4.1.0.cjs
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
# stage 1 as builder
|
||||||
|
FROM node:latest as builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy in the package file as well as other yarn
|
||||||
|
# dependencies in the local directory, assuming the
|
||||||
|
# yarn berry release module is inside .yarn/releases
|
||||||
|
# already
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Install the dependencies and make the folder
|
||||||
|
RUN yarn install
|
||||||
|
|
||||||
|
# Build the project and copy the files
|
||||||
|
RUN yarn build
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
ENTRYPOINT ["yarn", "start"]
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none">
|
||||||
|
<g clip-path="url(#a)">
|
||||||
|
<path fill="currentColor" fill-rule="evenodd" d="M12.05 2C6.54 2 2.08 6.58 2.08 12.25c0 4.54 2.86 8.37 6.82 9.73.5.1.68-.22.68-.5l-.02-1.9c-2.77.62-3.35-1.22-3.35-1.22-.45-1.18-1.1-1.49-1.1-1.49-.91-.63.06-.63.06-.63 1 .07 1.54 1.06 1.54 1.06.89 1.56 2.32 1.12 2.9.84a2.2 2.2 0 0 1 .63-1.37c-2.21-.24-4.54-1.12-4.54-5.06 0-1.12.4-2.04 1.02-2.75a3.8 3.8 0 0 1 .1-2.72s.84-.27 2.74 1.06a9.4 9.4 0 0 1 4.99 0c1.9-1.33 2.74-1.06 2.74-1.06.54 1.41.2 2.47.1 2.72a3.98 3.98 0 0 1 1.02 2.75c0 3.94-2.33 4.8-4.56 5.06.37.32.68.93.68 1.9l-.02 2.82c0 .27.18.6.68.5a10.23 10.23 0 0 0 6.82-9.74A10.1 10.1 0 0 0 12.05 2Z" clip-rule="evenodd"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="a">
|
||||||
|
<path fill="#fff" d="M0 0h24v24H0z"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 850 B |
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="currentColor"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 15c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1s1 .45 1 1v4c0 .55-.45 1-1 1zm1-8h-2V7h2v2z"/></svg>
|
||||||
|
After Width: | Height: | Size: 313 B |
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="currentColor"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm6.93 6h-2.95c-.32-1.25-.78-2.45-1.38-3.56 1.84.63 3.37 1.91 4.33 3.56zM12 4.04c.83 1.2 1.48 2.53 1.91 3.96h-3.82c.43-1.43 1.08-2.76 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2s.06 1.34.14 2H4.26zm.82 2h2.95c.32 1.25.78 2.45 1.38 3.56-1.84-.63-3.37-1.9-4.33-3.56zm2.95-8H5.08c.96-1.66 2.49-2.93 4.33-3.56C8.81 5.55 8.35 6.75 8.03 8zM12 19.96c-.83-1.2-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.76-1.91 3.96zM14.34 14H9.66c-.09-.66-.16-1.32-.16-2s.07-1.35.16-2h4.68c.09.65.16 1.32.16 2s-.07 1.34-.16 2zm.25 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95c-.96 1.65-2.49 2.93-4.33 3.56zM16.36 14c.08-.66.14-1.32.14-2s-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2h-3.38z"/></svg>
|
||||||
|
After Width: | Height: | Size: 953 B |
@ -0,0 +1,10 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_638_197)">
|
||||||
|
<path d="M6.34338 6.34315V8.33719L14.2418 8.34426L5.63627 16.9497L7.05048 18.364L15.656 9.75847L15.663 17.6569H17.6571V6.34315H6.34338Z" fill="currentColor"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_638_197">
|
||||||
|
<rect width="24" height="24" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 403 B |
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path d="M12 21.4 10.6 20C5.4 15.4 2 12.3 2 8.5 2 5.5 4.4 3 7.5 3A6 6 0 0 1 12 5a6 6 0 0 1 4.5-2c3 0 5.5 2.4 5.5 5.5 0 3.8-3.4 6.9-8.6 11.5L12 21.4Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 266 B |
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path d="M9.4 16.6 4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4Zm5.2 0 4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 217 B |
|
After Width: | Height: | Size: 35 KiB |
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="706" height="689" fill="none" viewBox="0 0 706 689">
|
||||||
|
<path fill="#D9D9D9" d="M229.7 31.7a78 78 0 0 1 98.3-22l14.4 7.6a78 78 0 0 0 63.7 4l15.5-5.9a78 78 0 0 1 95.6 34.9l6 10.8a78 78 0 0 0 52.9 38.4l12.6 2.4a78 78 0 0 1 62.6 82.6l-.7 9.2a78 78 0 0 0 23.8 62.3l6.7 6.5a78 78 0 0 1 8 103.8l-6.6 8.5a78 78 0 0 0-14.2 64.7l2.2 10a78 78 0 0 1-49 90.4l-14 5.2a78 78 0 0 0-45.7 45.4l-5.2 13.6a78 78 0 0 1-88.4 48.7l-16.8-3.4a78 78 0 0 0-62 13.8l-13.7 10.2a78 78 0 0 1-100.7-6.6l-10.5-10.1a78 78 0 0 0-60.7-21.7l-15 1.3a78 78 0 0 1-82.6-61l-2.2-10.1a78 78 0 0 0-40.4-52.5l-9.5-5a78 78 0 0 1-36.9-97.3l3.4-8.8a78 78 0 0 0-5-66.5l-4.5-8a78 78 0 0 1 21.7-101.3l10.3-7.6A78 78 0 0 0 74.7 161l.9-12.4a78 78 0 0 1 71.7-72.1l16.5-1.3a78 78 0 0 0 56-30.5l10-13Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 803 B |
|
After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 11 KiB |
@ -0,0 +1,48 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 294 94" style="enable-background:new 0 0 294 94;" xml:space="preserve">
|
||||||
|
<path fill="#1D508B" d="M46.6,0h200.6c25.7,0,46.6,20.9,46.6,46.6l0,0c0,25.7-20.9,46.6-46.6,46.6H46.6C20.9,93.2,0,72.4,0,46.6l0,0
|
||||||
|
C0,20.9,20.9,0,46.6,0z"/>
|
||||||
|
<g>
|
||||||
|
<path fill="#F2F2F2" d="M66.6,53V34.3h3.6l8.1,12.5V34.3h3.6V53h-3.6l-8.1-12.5V53H66.6z"/>
|
||||||
|
<path fill="#F2F2F2" d="M91.9,53.4c-1.4,0-2.7-0.3-3.7-0.9c-1.1-0.6-1.9-1.5-2.5-2.6c-0.6-1.1-0.9-2.4-0.9-3.8c0-1.5,0.3-2.8,0.9-3.9
|
||||||
|
c0.6-1.1,1.4-2,2.5-2.6s2.3-0.9,3.7-0.9c1.4,0,2.7,0.3,3.7,0.9s1.9,1.5,2.5,2.6c0.6,1.1,0.9,2.4,0.9,3.8c0,1.5-0.3,2.7-0.9,3.9
|
||||||
|
c-0.6,1.1-1.4,2-2.5,2.6C94.5,53.1,93.3,53.4,91.9,53.4z M91.9,50.1c1.1,0,2-0.4,2.5-1.2c0.6-0.8,0.8-1.8,0.8-3
|
||||||
|
c0-1.2-0.3-2.2-0.9-3c-0.6-0.7-1.4-1.1-2.5-1.1c-0.8,0-1.4,0.2-1.9,0.5s-0.9,0.8-1.1,1.4c-0.2,0.6-0.4,1.3-0.4,2.1
|
||||||
|
c0,1.3,0.3,2.3,0.9,3C90,49.8,90.8,50.1,91.9,50.1z"/>
|
||||||
|
<path fill="#F2F2F2" d="M105.2,53l-5.1-14h3.5l3.3,9.7l3.3-9.7h3.5l-5.1,14H105.2z"/>
|
||||||
|
<path fill="#F2F2F2" d="M121.9,53.4c-1.4,0-2.7-0.3-3.7-0.9c-1.1-0.6-1.9-1.5-2.5-2.6c-0.6-1.1-0.9-2.4-0.9-3.8c0-1.5,0.3-2.8,0.9-3.9
|
||||||
|
c0.6-1.1,1.4-2,2.5-2.6s2.3-0.9,3.7-0.9c1.4,0,2.7,0.3,3.7,0.9s1.9,1.5,2.5,2.6c0.6,1.1,0.9,2.4,0.9,3.8c0,1.5-0.3,2.7-0.9,3.9
|
||||||
|
c-0.6,1.1-1.4,2-2.5,2.6C124.6,53.1,123.3,53.4,121.9,53.4z M121.9,50.1c1.1,0,2-0.4,2.5-1.2c0.6-0.8,0.8-1.8,0.8-3
|
||||||
|
c0-1.2-0.3-2.2-0.9-3c-0.6-0.7-1.4-1.1-2.5-1.1c-0.8,0-1.4,0.2-1.9,0.5s-0.9,0.8-1.1,1.4c-0.2,0.6-0.4,1.3-0.4,2.1
|
||||||
|
c0,1.3,0.3,2.3,0.9,3C120,49.8,120.8,50.1,121.9,50.1z"/>
|
||||||
|
<path fill="#F2F2F2" d="M131.9,59.3V39h3.1v9.6h0.4v10.7H131.9z M138.8,53.4c-1.4,0-2.6-0.3-3.5-1c-0.9-0.6-1.6-1.5-2.1-2.7
|
||||||
|
c-0.5-1.1-0.7-2.4-0.7-3.8s0.2-2.7,0.7-3.8c0.5-1.1,1.2-2,2.1-2.7s2-1,3.4-1c1.3,0,2.5,0.3,3.5,1c1,0.6,1.8,1.5,2.3,2.6
|
||||||
|
s0.8,2.4,0.8,3.8c0,1.4-0.3,2.7-0.8,3.8c-0.5,1.1-1.3,2-2.3,2.7S140.1,53.4,138.8,53.4z M138.2,50.3c0.8,0,1.4-0.2,1.9-0.6
|
||||||
|
c0.5-0.4,0.8-0.9,1.1-1.5c0.2-0.6,0.4-1.4,0.4-2.2c0-0.8-0.1-1.5-0.4-2.1c-0.2-0.6-0.6-1.2-1.1-1.5c-0.5-0.4-1.2-0.6-1.9-0.6
|
||||||
|
c-0.7,0-1.3,0.2-1.8,0.5s-0.8,0.9-1,1.5c-0.2,0.6-0.3,1.4-0.3,2.2c0,0.8,0.1,1.6,0.3,2.2c0.2,0.6,0.5,1.1,1,1.5
|
||||||
|
C136.8,50.1,137.4,50.3,138.2,50.3z"/>
|
||||||
|
<path fill="#F2F2F2" d="M154.4,53.4c-1.4,0-2.7-0.3-3.7-0.9c-1.1-0.6-1.9-1.5-2.5-2.6c-0.6-1.1-0.9-2.4-0.9-3.8c0-1.5,0.3-2.8,0.9-3.9
|
||||||
|
c0.6-1.1,1.4-2,2.5-2.6s2.3-0.9,3.7-0.9c1.4,0,2.7,0.3,3.7,0.9s1.9,1.5,2.5,2.6c0.6,1.1,0.9,2.4,0.9,3.8c0,1.5-0.3,2.7-0.9,3.9
|
||||||
|
c-0.6,1.1-1.4,2-2.5,2.6C157.1,53.1,155.9,53.4,154.4,53.4z M154.4,50.1c1.1,0,2-0.4,2.5-1.2c0.6-0.8,0.8-1.8,0.8-3
|
||||||
|
c0-1.2-0.3-2.2-0.9-3c-0.6-0.7-1.4-1.1-2.5-1.1c-0.8,0-1.4,0.2-1.9,0.5s-0.9,0.8-1.1,1.4c-0.2,0.6-0.4,1.3-0.4,2.1
|
||||||
|
c0,1.3,0.3,2.3,0.9,3C152.5,49.8,153.3,50.1,154.4,50.1z"/>
|
||||||
|
<path fill="#F2F2F2" d="M164.7,53V33.9h3.5V53H164.7z"/>
|
||||||
|
<path fill="#F2F2F2" d="M178.4,53.4c-1.4,0-2.7-0.3-3.7-0.9c-1.1-0.6-1.9-1.5-2.5-2.6c-0.6-1.1-0.9-2.4-0.9-3.8c0-1.5,0.3-2.8,0.9-3.9
|
||||||
|
c0.6-1.1,1.4-2,2.5-2.6s2.3-0.9,3.7-0.9c1.4,0,2.7,0.3,3.7,0.9s1.9,1.5,2.5,2.6c0.6,1.1,0.9,2.4,0.9,3.8c0,1.5-0.3,2.7-0.9,3.9
|
||||||
|
c-0.6,1.1-1.4,2-2.5,2.6C181.1,53.1,179.9,53.4,178.4,53.4z M178.4,50.1c1.1,0,2-0.4,2.5-1.2c0.6-0.8,0.8-1.8,0.8-3
|
||||||
|
c0-1.2-0.3-2.2-0.9-3c-0.6-0.7-1.4-1.1-2.5-1.1c-0.8,0-1.4,0.2-1.9,0.5s-0.9,0.8-1.1,1.4c-0.2,0.6-0.4,1.3-0.4,2.1
|
||||||
|
c0,1.3,0.3,2.3,0.9,3C176.5,49.8,177.3,50.1,178.4,50.1z"/>
|
||||||
|
<path fill="#F2F2F2" d="M186.9,41.7V39h10v2.7H186.9z M196.9,53c-1,0.2-1.9,0.3-2.9,0.2c-0.9,0-1.8-0.2-2.5-0.5s-1.3-0.8-1.7-1.5
|
||||||
|
c-0.3-0.6-0.5-1.3-0.5-2c0-0.7,0-1.4,0-2.3v-12h3.5v11.8c0,0.5,0,1,0,1.5c0,0.4,0.1,0.8,0.3,1c0.3,0.5,0.8,0.8,1.5,0.8
|
||||||
|
s1.4,0,2.3-0.1V53z"/>
|
||||||
|
<path fill="#F2F2F2" d="M205.5,53.4c-1.8,0-3.2-0.4-4.3-1.2s-1.7-1.9-2-3.4l3.6-0.5c0.1,0.7,0.5,1.2,1,1.5s1.2,0.6,1.9,0.6
|
||||||
|
c0.6,0,1.1-0.1,1.5-0.4s0.5-0.6,0.5-1c0-0.3-0.1-0.5-0.2-0.7c-0.1-0.2-0.4-0.3-0.9-0.5c-0.5-0.2-1.2-0.4-2.2-0.6
|
||||||
|
c-1.1-0.3-2.1-0.6-2.7-1c-0.7-0.4-1.2-0.8-1.5-1.3c-0.3-0.5-0.5-1.1-0.5-1.8c0-0.9,0.2-1.7,0.7-2.3c0.5-0.7,1.1-1.2,1.9-1.5
|
||||||
|
c0.8-0.4,1.8-0.5,3-0.5c1.1,0,2.1,0.2,2.9,0.5c0.8,0.3,1.5,0.8,2.1,1.4c0.5,0.6,0.8,1.4,1,2.2l-3.6,0.6c-0.1-0.5-0.3-0.9-0.7-1.2
|
||||||
|
c-0.4-0.3-0.9-0.5-1.6-0.5c-0.6,0-1.2,0-1.6,0.3s-0.6,0.5-0.6,0.9c0,0.2,0.1,0.4,0.3,0.6s0.5,0.3,1.1,0.5c0.5,0.2,1.3,0.4,2.4,0.7
|
||||||
|
c1.1,0.3,1.9,0.6,2.6,1c0.6,0.4,1.1,0.8,1.4,1.3c0.3,0.5,0.4,1.1,0.4,1.9c0,1.4-0.5,2.6-1.6,3.4C208.7,53,207.3,53.4,205.5,53.4z"
|
||||||
|
/>
|
||||||
|
<path fill="#F2F2F2" d="M214.2,53l0-18.7h3.6v11.4l4.8-6.8h4.4l-5.1,7l5.4,7h-4.6l-4.9-6.8V53H214.2z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.4 KiB |
@ -0,0 +1,41 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 293.9 93.2" style="enable-background:new 0 0 293.9 93.2;" xml:space="preserve">
|
||||||
|
<path fill="#1D508B" d="M46.6,0h200.6c25.7,0,46.6,20.9,46.6,46.6l0,0c0,25.7-20.9,46.6-46.6,46.6c0,0,0,0,0,0H46.6
|
||||||
|
C20.9,93.2,0,72.4,0,46.6c0,0,0,0,0,0l0,0C0,20.9,20.9,0,46.6,0z"/>
|
||||||
|
<path fill="#F2F2F2" d="M66.3,54.8V36.1h3.5v7.7h8.4v-7.7h3.5v18.7h-3.5v-7.7h-8.4v7.7H66.3z"/>
|
||||||
|
<path fill="#F2F2F2" d="M91.7,55.2c-1.3,0-2.6-0.3-3.7-1c-1.1-0.6-1.9-1.5-2.5-2.6c-1.2-2.4-1.2-5.3,0-7.7c0.6-1.1,1.5-2,2.5-2.6
|
||||||
|
c1.1-0.6,2.4-1,3.7-0.9c1.3,0,2.6,0.3,3.7,1c1.1,0.6,1.9,1.5,2.5,2.6c0.6,1.2,0.9,2.5,0.9,3.8c0,1.3-0.3,2.7-0.9,3.8
|
||||||
|
c-0.6,1.1-1.4,2-2.5,2.6C94.3,54.9,93,55.2,91.7,55.2z M91.7,51.9c1,0.1,1.9-0.4,2.5-1.2c0.6-0.9,0.9-1.9,0.8-3
|
||||||
|
c0.1-1.1-0.2-2.1-0.8-3c-0.6-0.8-1.6-1.2-2.5-1.1c-0.7,0-1.3,0.2-1.9,0.5c-0.5,0.4-0.9,0.9-1.1,1.5c-0.2,0.7-0.4,1.4-0.3,2.1
|
||||||
|
c-0.1,1.1,0.2,2.1,0.8,3C89.8,51.5,90.7,51.9,91.7,51.9z"/>
|
||||||
|
<path fill="#F2F2F2" d="M101.6,54.8v-14h6.6c0.5,0,0.9,0,1.4,0.1c0.3,0,0.7,0.1,1,0.2c0.7,0.2,1.3,0.6,1.8,1.1c0.6,0.7,0.9,1.5,0.8,2.4
|
||||||
|
c0,0.5-0.1,1-0.3,1.5c-0.2,0.4-0.5,0.7-0.8,1c-0.1,0.1-0.3,0.2-0.5,0.3c-0.1,0.1-0.3,0.1-0.5,0.2c0.3,0,0.7,0.1,1,0.3
|
||||||
|
c0.5,0.2,0.9,0.6,1.2,1c0.4,0.6,0.6,1.3,0.5,2c0,0.8-0.2,1.7-0.7,2.4c-0.5,0.7-1.2,1.2-2,1.4c-0.4,0.1-0.7,0.2-1.1,0.2
|
||||||
|
c-0.4,0-0.9,0.1-1.3,0.1L101.6,54.8z M105.2,46.4h2.2c0.3,0,0.5,0,0.8,0c0.2,0,0.4-0.1,0.6-0.2c0.2-0.1,0.4-0.3,0.5-0.5
|
||||||
|
c0.1-0.2,0.2-0.5,0.2-0.8c0-0.3-0.1-0.6-0.2-0.8c-0.1-0.2-0.4-0.4-0.6-0.5c-0.2-0.1-0.5-0.1-0.7-0.1h-2.7V46.4z M105.2,52.1h3.2
|
||||||
|
c0.2,0,0.4,0,0.6-0.1c0.2,0,0.4-0.1,0.5-0.2c0.2-0.1,0.4-0.3,0.5-0.5c0.1-0.2,0.2-0.5,0.2-0.8c0-0.3-0.1-0.6-0.2-0.8
|
||||||
|
c-0.1-0.2-0.3-0.3-0.5-0.5c-0.2-0.1-0.4-0.1-0.5-0.1c-0.2,0-0.3,0-0.5,0h-3.2V52.1z"/>
|
||||||
|
<path fill="#F2F2F2" d="M122.7,55.2c-1.3,0-2.6-0.3-3.7-1c-1.1-0.6-1.9-1.5-2.5-2.6c-0.6-1.2-0.9-2.5-0.9-3.8c0-1.3,0.3-2.7,0.9-3.9
|
||||||
|
c0.6-1.1,1.5-2,2.5-2.6c1.1-0.6,2.4-1,3.7-0.9c1.3,0,2.6,0.3,3.7,1c1.1,0.6,1.9,1.5,2.5,2.6c0.6,1.2,0.9,2.5,0.9,3.8
|
||||||
|
c0,1.3-0.3,2.7-0.9,3.8c-0.6,1.1-1.4,2-2.5,2.6C125.3,54.9,124,55.2,122.7,55.2z M122.7,51.9c1,0.1,1.9-0.4,2.5-1.2
|
||||||
|
c0.6-0.9,0.9-1.9,0.8-3c0.1-1.1-0.2-2.1-0.8-3c-0.6-0.8-1.6-1.2-2.5-1.1c-0.7,0-1.3,0.2-1.9,0.5c-0.5,0.4-0.9,0.9-1.1,1.5
|
||||||
|
c-0.3,0.7-0.4,1.4-0.4,2.1c-0.1,1.1,0.2,2.1,0.8,3C120.8,51.5,121.7,51.9,122.7,51.9z"/>
|
||||||
|
<path fill="#F2F2F2" d="M132.7,54.8v-14h12.5v14h-3.5V44.1h-5.5v10.7H132.7z"/>
|
||||||
|
<path fill="#F2F2F2" d="M155.1,55.2c-1.3,0-2.6-0.3-3.7-1c-1.1-0.6-1.9-1.5-2.5-2.6c-1.2-2.4-1.2-5.3,0-7.7c0.6-1.1,1.5-2,2.5-2.6
|
||||||
|
c1.1-0.6,2.4-1,3.7-0.9c1.3,0,2.6,0.3,3.7,1c1.1,0.6,1.9,1.5,2.5,2.6c1.2,2.4,1.2,5.3,0,7.7c-0.6,1.1-1.4,2-2.5,2.6
|
||||||
|
C157.7,54.9,156.4,55.2,155.1,55.2z M155.1,51.9c1,0.1,1.9-0.4,2.5-1.2c0.6-0.9,0.9-1.9,0.8-3c0.1-1.1-0.2-2.1-0.9-3
|
||||||
|
c-0.6-0.8-1.6-1.2-2.5-1.1c-0.7,0-1.3,0.2-1.9,0.5c-0.5,0.4-0.9,0.9-1.1,1.5c-0.2,0.7-0.4,1.4-0.4,2.1c-0.1,1.1,0.2,2.1,0.9,3
|
||||||
|
C153.2,51.5,154.2,51.9,155.1,51.9z"/>
|
||||||
|
<path fill="#F2F2F2" d="M163.8,54.8v-2.9c0.4,0.1,0.8,0.1,1.2,0c0.3-0.1,0.5-0.4,0.6-0.7c0.2-0.4,0.3-0.9,0.4-1.3
|
||||||
|
c0.1-0.7,0.3-1.6,0.4-2.6s0.2-2,0.3-3.2s0.1-2.2,0.2-3.3h10.6v14H174V44.1h-4c0,0.5-0.1,1-0.1,1.6s-0.1,1.3-0.2,2s-0.1,1.3-0.2,1.9
|
||||||
|
s-0.2,1.1-0.3,1.6c-0.1,0.9-0.5,1.8-0.9,2.6c-0.4,0.6-1,1-1.7,1.2C165.7,55.1,164.7,55.1,163.8,54.8z"/>
|
||||||
|
<path fill="#F2F2F2" d="M187.4,55.2c-1.3,0-2.6-0.3-3.7-1c-1.1-0.6-1.9-1.5-2.5-2.6c-1.2-2.4-1.2-5.3,0-7.7c0.6-1.1,1.4-2,2.5-2.6
|
||||||
|
c1.1-0.6,2.4-1,3.7-0.9c1.3,0,2.6,0.3,3.7,1c1.1,0.6,1.9,1.5,2.5,2.6c0.6,1.2,0.9,2.5,0.9,3.8c0,1.3-0.3,2.7-0.9,3.8
|
||||||
|
c-0.6,1.1-1.4,2-2.5,2.6C190,54.9,188.7,55.2,187.4,55.2z M187.4,51.9c1,0.1,2-0.4,2.6-1.1c0.6-0.9,0.9-1.9,0.8-3
|
||||||
|
c0.1-1.1-0.2-2.1-0.9-3c-0.6-0.8-1.6-1.2-2.5-1.1c-0.7,0-1.3,0.2-1.9,0.5c-0.5,0.4-0.9,0.9-1.1,1.5c-0.3,0.7-0.4,1.4-0.4,2.1
|
||||||
|
c-0.1,1.1,0.2,2.1,0.9,3C185.6,51.5,186.5,51.9,187.4,51.9z"/>
|
||||||
|
<path fill="#F2F2F2" d="M209.1,57.9v-3.1h-11.7v-14h3.5v10.7h5.5V40.8h3.5v10.9h2.7v6.2H209.1z"/>
|
||||||
|
<path fill="#F2F2F2" d="M214.7,54.8v-14h3.6v6.8l4.8-6.8h4.4l-5.1,7l5.4,7h-4.6l-4.8-6.8v6.8H214.7z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.1 KiB |
@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 293.9 93.2" style="enable-background:new 0 0 293.9 93.2;" xml:space="preserve">
|
||||||
|
<path fill="#0065B3" d="M46.6,0h200.6c25.7,0,46.6,20.9,46.6,46.6l0,0c0,25.7-20.9,46.6-46.6,46.6c0,0,0,0,0,0H46.6
|
||||||
|
C20.9,93.2,0,72.4,0,46.6c0,0,0,0,0,0l0,0C0,20.9,20.9,0,46.6,0z"/>
|
||||||
|
<path fill="#FFFFFF" d="M99,56.4V37.6h7.9h0.7c0.3,0,0.6,0,0.8,0.1c1,0.1,1.9,0.5,2.8,1.1c0.7,0.6,1.2,1.3,1.6,2.2
|
||||||
|
c0.3,0.9,0.5,1.9,0.5,2.9c0,1-0.2,1.9-0.5,2.8c-0.3,0.8-0.9,1.6-1.6,2.1c-0.8,0.6-1.7,1-2.7,1.1c-0.3,0-0.5,0.1-0.8,0.1h-0.7h-4.4
|
||||||
|
v6.3H99z M102.5,46.7h4.2c0.2,0,0.4,0,0.6,0c0.2,0,0.4-0.1,0.6-0.1c0.4-0.1,0.8-0.3,1.1-0.7c0.3-0.3,0.4-0.6,0.5-1
|
||||||
|
c0.1-0.3,0.1-0.7,0.2-1.1c0-0.4-0.1-0.7-0.2-1.1c-0.1-0.4-0.3-0.7-0.5-1c-0.3-0.3-0.7-0.6-1.1-0.7c-0.2,0-0.4-0.1-0.6-0.1h-0.6h-4.2
|
||||||
|
V46.7z"/>
|
||||||
|
<path fill="#FFFFFF" d="M122.2,56.8c-1.3,0-2.6-0.3-3.7-1c-1.1-0.6-1.9-1.5-2.5-2.6c-0.6-1.2-0.9-2.5-0.9-3.8c0-1.3,0.3-2.7,0.9-3.9
|
||||||
|
c0.6-1.1,1.4-2,2.5-2.6c1.1-0.6,2.4-1,3.7-0.9c1.3,0,2.6,0.3,3.7,1c1.1,0.6,1.9,1.5,2.5,2.6c0.6,1.2,0.9,2.5,0.9,3.8
|
||||||
|
c0,1.3-0.3,2.7-0.9,3.8c-0.6,1.1-1.4,2-2.5,2.6C124.8,56.5,123.5,56.8,122.2,56.8z M122.2,53.5c1,0.1,1.9-0.4,2.5-1.2
|
||||||
|
c0.6-0.9,0.9-1.9,0.8-3c0.1-1.1-0.2-2.1-0.8-3c-0.6-0.8-1.6-1.2-2.5-1.1c-0.7,0-1.3,0.2-1.9,0.5c-0.5,0.4-0.9,0.9-1.1,1.4
|
||||||
|
c-0.2,0.7-0.4,1.4-0.3,2.2c-0.1,1.1,0.2,2.1,0.8,3C120.3,53.1,121.2,53.5,122.2,53.5z"/>
|
||||||
|
<path fill="#FFFFFF" d="M132.4,56.4V37.3h3.6v19.1H132.4z"/>
|
||||||
|
<path fill="#FFFFFF" d="M146.2,56.8c-1.3,0-2.6-0.3-3.7-1c-1.1-0.6-1.9-1.5-2.5-2.6c-0.6-1.2-0.9-2.5-0.9-3.8c0-1.3,0.3-2.7,0.9-3.9
|
||||||
|
c0.6-1.1,1.4-2,2.5-2.6c1.1-0.6,2.4-1,3.7-0.9c1.3,0,2.6,0.3,3.7,1c1.1,0.6,1.9,1.5,2.5,2.6c1.2,2.4,1.2,5.3,0,7.7
|
||||||
|
c-0.6,1.1-1.4,2-2.5,2.6C148.8,56.5,147.5,56.8,146.2,56.8z M146.2,53.5c1,0.1,1.9-0.4,2.5-1.2c1.1-1.8,1.1-4.1,0-6
|
||||||
|
c-0.6-0.8-1.6-1.2-2.5-1.1c-0.7,0-1.3,0.2-1.9,0.5c-0.5,0.4-0.9,0.9-1.1,1.4c-0.3,0.7-0.4,1.4-0.4,2.2c-0.1,1.1,0.2,2.1,0.9,3
|
||||||
|
C144.3,53.1,145.2,53.5,146.2,53.5z"/>
|
||||||
|
<path fill="#FFFFFF" d="M154.6,45v-2.7h10V45H154.6z M164.6,56.4c-0.9,0.2-1.9,0.3-2.9,0.2c-0.9,0-1.7-0.2-2.5-0.5
|
||||||
|
c-0.7-0.3-1.3-0.8-1.7-1.5c-0.3-0.6-0.5-1.3-0.5-2c0-0.7,0-1.4,0-2.3v-12h3.5v11.8c0,0.5,0,1,0,1.5c0,0.3,0.1,0.7,0.3,1
|
||||||
|
c0.3,0.5,0.9,0.8,1.5,0.8c0.8,0,1.5,0,2.3-0.1L164.6,56.4z"/>
|
||||||
|
<path fill="#FFFFFF" d="M173.2,56.8c-1.5,0.1-3-0.3-4.3-1.2c-1.1-0.8-1.8-2-2-3.4l3.6-0.5c0.1,0.6,0.5,1.2,1,1.5
|
||||||
|
c0.6,0.4,1.2,0.6,1.9,0.6c0.5,0,1-0.1,1.5-0.4c0.3-0.2,0.5-0.6,0.5-1c0-0.2-0.1-0.5-0.2-0.7c-0.3-0.2-0.6-0.4-0.9-0.5
|
||||||
|
c-0.5-0.2-1.2-0.4-2.3-0.7c-0.9-0.2-1.9-0.6-2.7-1c-0.6-0.3-1.1-0.7-1.5-1.3c-0.3-0.6-0.5-1.2-0.5-1.8c0-0.8,0.2-1.7,0.7-2.3
|
||||||
|
c0.5-0.7,1.2-1.2,1.9-1.5c0.9-0.4,1.9-0.6,2.9-0.5c1,0,2,0.2,2.9,0.5c0.8,0.3,1.5,0.8,2.1,1.4c0.5,0.6,0.9,1.4,1,2.2l-3.6,0.7
|
||||||
|
c0-0.5-0.3-0.9-0.7-1.2c-0.5-0.3-1-0.5-1.6-0.5c-0.5-0.1-1.1,0-1.6,0.3c-0.4,0.2-0.6,0.5-0.6,0.9c0,0.2,0.1,0.5,0.3,0.6
|
||||||
|
c0.3,0.2,0.7,0.4,1.1,0.5c0.5,0.2,1.3,0.4,2.4,0.7c0.9,0.2,1.8,0.6,2.6,1c0.6,0.3,1.1,0.8,1.4,1.3c0.3,0.6,0.5,1.2,0.4,1.9
|
||||||
|
c0.1,1.3-0.5,2.6-1.6,3.4C176.3,56.4,174.8,56.9,173.2,56.8z"/>
|
||||||
|
<path fill="#FFFFFF" d="M181.9,56.4V37.6h3.6v11.4l4.8-6.8h4.4l-5.1,7l5.4,7h-4.6l-4.9-6.8v6.8L181.9,56.4z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
@ -0,0 +1,13 @@
|
|||||||
|
<svg width="294" height="94" viewBox="0 0 294 94" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="293.869" height="93.2251" rx="46.6126" fill="#F4F4F4"/>
|
||||||
|
<rect width="293.869" height="93.2251" rx="46.6126" fill="#DCFF77"/>
|
||||||
|
<path d="M99.9096 54.9995V41.9919H94.9303V39.2061H107.872V41.9919H102.893V54.9995H99.9096ZM114.355 55.3285C113.178 55.3285 112.194 55.0544 111.405 54.506C110.615 53.9576 110.019 53.2118 109.617 52.2686C109.222 51.3253 109.025 50.2615 109.025 49.077C109.025 47.8925 109.222 46.8286 109.617 45.8854C110.012 44.9422 110.593 44.1964 111.361 43.648C112.129 43.0996 113.075 42.8254 114.201 42.8254C115.335 42.8254 116.322 43.0959 117.163 43.637C118.004 44.1781 118.654 44.9202 119.115 45.8635C119.583 46.7994 119.817 47.8705 119.817 49.077C119.817 50.2615 119.587 51.3253 119.126 52.2686C118.673 53.2118 118.037 53.9576 117.218 54.506C116.399 55.0544 115.444 55.3285 114.355 55.3285ZM108.52 60.264V43.1544H111.152V51.2705H111.525V60.264H108.52ZM113.872 52.6744C114.516 52.6744 115.042 52.5135 115.452 52.1918C115.861 51.8701 116.165 51.4387 116.362 50.8976C116.56 50.3492 116.658 49.7423 116.658 49.077C116.658 48.4189 116.556 47.8193 116.351 47.2783C116.146 46.7299 115.828 46.2948 115.397 45.9731C114.973 45.6441 114.428 45.4796 113.763 45.4796C113.141 45.4796 112.637 45.6295 112.249 45.9293C111.869 46.229 111.591 46.6495 111.416 47.1905C111.24 47.7316 111.152 48.3604 111.152 49.077C111.152 49.7935 111.24 50.4223 111.416 50.9634C111.591 51.5045 111.876 51.9249 112.271 52.2247C112.673 52.5245 113.207 52.6744 113.872 52.6744ZM125.521 55.3285C124.672 55.3285 123.952 55.1677 123.36 54.846C122.775 54.5169 122.329 54.0819 122.022 53.5408C121.722 52.9924 121.572 52.3892 121.572 51.7311C121.572 51.1828 121.656 50.6819 121.825 50.2286C121.993 49.7752 122.263 49.3768 122.636 49.0331C123.016 48.6821 123.525 48.3897 124.161 48.1557C124.599 47.9948 125.122 47.8523 125.729 47.728C126.336 47.6037 127.023 47.4867 127.791 47.377C128.559 47.26 129.403 47.132 130.324 46.9931L129.25 47.5854C129.25 46.8834 129.081 46.368 128.745 46.0389C128.409 45.7099 127.846 45.5454 127.056 45.5454C126.617 45.5454 126.16 45.6514 125.685 45.8635C125.21 46.0755 124.877 46.452 124.687 46.9931L121.989 46.1376C122.289 45.1579 122.852 44.3609 123.678 43.7467C124.504 43.1325 125.63 42.8254 127.056 42.8254C128.102 42.8254 129.03 42.9863 129.842 43.308C130.654 43.6297 131.268 44.1854 131.684 44.9751C131.918 45.4138 132.057 45.8525 132.101 46.2912C132.145 46.7299 132.167 47.2198 132.167 47.7609V54.9995H129.557V52.5647L129.93 53.0692C129.352 53.8662 128.727 54.4438 128.054 54.8021C127.389 55.1531 126.544 55.3285 125.521 55.3285ZM126.157 52.9815C126.705 52.9815 127.166 52.8864 127.539 52.6963C127.919 52.4989 128.219 52.2759 128.438 52.0273C128.665 51.7787 128.818 51.5703 128.899 51.4021C129.052 51.0804 129.14 50.7075 129.162 50.2834C129.191 49.852 129.206 49.4937 129.206 49.2086L130.083 49.4279C129.198 49.5742 128.482 49.6985 127.934 49.8008C127.385 49.8959 126.943 49.9836 126.606 50.0641C126.27 50.1445 125.974 50.2322 125.718 50.3273C125.426 50.4443 125.188 50.5722 125.005 50.7112C124.83 50.8428 124.698 50.989 124.61 51.1499C124.53 51.3107 124.49 51.4899 124.49 51.6873C124.49 51.9578 124.555 52.1918 124.687 52.3892C124.826 52.5793 125.02 52.7256 125.268 52.8279C125.517 52.9303 125.813 52.9815 126.157 52.9815ZM137.914 54.9995V45.7867H134.032V43.1544H144.78V45.7867H140.897V54.9995H137.914ZM149.919 55.3285C149.071 55.3285 148.351 55.1677 147.759 54.846C147.174 54.5169 146.728 54.0819 146.421 53.5408C146.121 52.9924 145.971 52.3892 145.971 51.7311C145.971 51.1828 146.055 50.6819 146.223 50.2286C146.391 49.7752 146.662 49.3768 147.035 49.0331C147.415 48.6821 147.923 48.3897 148.559 48.1557C148.998 47.9948 149.521 47.8523 150.128 47.728C150.735 47.6037 151.422 47.4867 152.19 47.377C152.957 47.26 153.802 47.132 154.723 46.9931L153.648 47.5854C153.648 46.8834 153.48 46.368 153.144 46.0389C152.808 45.7099 152.245 45.5454 151.455 45.5454C151.016 45.5454 150.559 45.6514 150.084 45.8635C149.609 46.0755 149.276 46.452 149.086 46.9931L146.388 46.1376C146.688 45.1579 147.251 44.3609 148.077 43.7467C148.903 43.1325 150.029 42.8254 151.455 42.8254C152.5 42.8254 153.429 42.9863 154.241 43.308C155.052 43.6297 155.666 44.1854 156.083 44.9751C156.317 45.4138 156.456 45.8525 156.5 46.2912C156.544 46.7299 156.566 47.2198 156.566 47.7609V54.9995H153.955V52.5647L154.328 53.0692C153.751 53.8662 153.126 54.4438 152.453 54.8021C151.788 55.1531 150.943 55.3285 149.919 55.3285ZM150.556 52.9815C151.104 52.9815 151.565 52.8864 151.937 52.6963C152.318 52.4989 152.617 52.2759 152.837 52.0273C153.063 51.7787 153.217 51.5703 153.297 51.4021C153.451 51.0804 153.539 50.7075 153.561 50.2834C153.59 49.852 153.605 49.4937 153.605 49.2086L154.482 49.4279C153.597 49.5742 152.881 49.6985 152.332 49.8008C151.784 49.8959 151.342 49.9836 151.005 50.0641C150.669 50.1445 150.373 50.2322 150.117 50.3273C149.824 50.4443 149.587 50.5722 149.404 50.7112C149.228 50.8428 149.097 50.989 149.009 51.1499C148.929 51.3107 148.888 51.4899 148.888 51.6873C148.888 51.9578 148.954 52.1918 149.086 52.3892C149.225 52.5793 149.419 52.7256 149.667 52.8279C149.916 52.9303 150.212 52.9815 150.556 52.9815Z" fill="#3C4B0E"/>
|
||||||
|
<g clip-path="url(#clip0_1631_399)">
|
||||||
|
<path d="M198.741 48.6451H188.87V58.516H185.58V48.6451H175.709V45.3548H185.58V35.4839H188.87V45.3548H198.741V48.6451Z" fill="#3C4B0E"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_1631_399">
|
||||||
|
<rect width="39.4836" height="39.4836" fill="white" transform="translate(167.484 27.2578)"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 5.4 KiB |
@ -0,0 +1,4 @@
|
|||||||
|
<svg width="233" height="211" viewBox="0 0 233 211" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M156.264 13.7064C165.5 14.5002 221 -22 207 30.5001C191 85 238.5 76 229.5 53.5C220.5 30.5 160.5 54 133 65.5C100.5 80 52.5 112.5 21 202" stroke="black" stroke-width="4" stroke-linecap="round"/>
|
||||||
|
<path d="M3.88246 185.501C12.3245 191.353 16.7631 199.342 20.0999 206.688C25.6455 199.693 34.0274 192.569 40.1507 190.963" stroke="black" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 511 B |
@ -0,0 +1,4 @@
|
|||||||
|
<svg width="324" height="204" viewBox="0 0 324 204" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M2.26367 9.70678C25.0192 9.21211 78.0294 8.51072 144.258 28.6477C210.486 50.5746 239.332 17.1765 215.017 4.85245C189.321 -7.71015 178.314 33.9598 224.153 60.5272C288.393 106.405 300.133 157.475 306.578 197.63" stroke="black" stroke-width="4" stroke-linecap="round"/>
|
||||||
|
<path d="M285.131 187.052C295.057 189.698 301.94 195.711 307.568 201.492C310.415 193.031 315.888 183.489 321.105 179.904" stroke="black" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 585 B |
|
After Width: | Height: | Size: 18 KiB |
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="189" height="189" fill="none" viewBox="0 0 189 189">
|
||||||
|
<path stroke="currentColor" stroke-width="25" d="M94.5 94.5V0m0 94.5V189m0-94.5L47.2 12.7m47.3 81.8 47.3 81.8M94.5 94.5 12.7 47.2m81.8 47.3 81.8 47.3M94.5 94.5H0m94.5 0H189m-94.5 0-81.8 47.3m81.8-47.3 81.8-47.3M94.5 94.5l-47.3 81.8m47.3-81.8 47.3-81.8"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 363 B |
|
After Width: | Height: | Size: 1.6 MiB |
@ -0,0 +1,4 @@
|
|||||||
|
<svg width="52" height="96" viewBox="0 0 52 96" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M4.24662 87.561C1.44277 85.86 4.55733 83.8994 5.99919 85.3994C7.80151 87.2743 6.05268 90.1094 2.1198 88.8105" stroke="#575F1B" stroke-width="3" stroke-linecap="square" stroke-linejoin="round"/>
|
||||||
|
<path d="M7.41982 80.1161C18.4051 25.7442 44.1194 -1.80065 49.9763 7.20733C53.5349 12.6805 29.0992 15.1552 7.41982 80.1161Z" stroke="#575F1B" stroke-width="3" stroke-linecap="square" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 515 B |
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="341" height="126" viewBox="0 0 341 126" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M324.349 9.15834C156.59 -19.1278 8.10148 44.3659 1.76304 89.4665C-7.36238 154.397 309.24 118.744 337.788 53.4907C354.063 16.2906 226.707 11.3983 209.545 18.0272" stroke="#861118" stroke-width="3"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 314 B |
|
After Width: | Height: | Size: 270 KiB |
|
After Width: | Height: | Size: 278 KiB |
|
After Width: | Height: | Size: 787 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 354 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 687 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 82 KiB |
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="290" height="290" fill="none" viewBox="0 0 290 290">
|
||||||
|
<path fill="currentColor" d="M110 43.6a46 46 0 0 1 70.7 0l16.7 20a46 46 0 0 0 19.4 13.8l22.8 8.4a46 46 0 0 1 22.2 68.9L249.4 173a46 46 0 0 0-7.8 23.3l-1.2 23.4a46 46 0 0 1-57.6 42L157 255a46 46 0 0 0-23.3 0l-25.8 6.8a46 46 0 0 1-57.6-42.1L49 196.4a46 46 0 0 0-7.8-23.3l-12.4-18.4A46 46 0 0 1 51 85.8l22.8-8.4a46 46 0 0 0 19.4-13.7l16.7-20Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 451 B |
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="201" height="210" fill="none" viewBox="0 0 201 210">
|
||||||
|
<path fill="currentColor" d="M65 16.3a48.5 48.5 0 0 1 71.3 0A47.4 47.4 0 0 0 162 30.8l1.8.3A45.6 45.6 0 0 1 198 90.5c-3.3 9.6-3.3 20 0 29.5 9 26-7.1 54-34.2 59.4l-1.8.4c-9.9 2-18.9 7-25.7 14.5a48.5 48.5 0 0 1-71.3 0 47.4 47.4 0 0 0-25.6-14.5l-2-.4c-27-5.4-43-33.3-34-59.4a46.5 46.5 0 0 0 0-29.5c-9-26 7-54 34-59.4l2-.3c9.8-2 18.8-7.1 25.6-14.5Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 456 B |
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="294" height="294" fill="none" viewBox="0 0 294 294">
|
||||||
|
<path stroke="currentColor" stroke-width="17" d="M147 147V0m0 147v147m0-147L73.5 19.7M147 147l73.5 127.3M147 147 19.7 73.5M147 147l127.3 73.5M147 147H0m147 0h147m-147 0L19.7 220.5M147 147l127.3-73.5M147 147 73.5 274.3M147 147l73.5-127.3"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 348 B |
@ -0,0 +1,131 @@
|
|||||||
|
.card {
|
||||||
|
border-radius: 36px;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 36px;
|
||||||
|
background-color: var(--background);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textWrapper {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: var(--on-background);
|
||||||
|
font-size: 1.8rem;
|
||||||
|
max-width: 500px;
|
||||||
|
font-weight: 800;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
color: var(--on-background-secondary);
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: 600;
|
||||||
|
max-width: 500px;
|
||||||
|
margin-top: 6px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.backdropOnText {
|
||||||
|
background-color: var(--background);
|
||||||
|
padding: 0.5rem 8px;
|
||||||
|
box-decoration-break: clone;
|
||||||
|
-webkit-box-decoration-break: clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
.backdropOnText {
|
||||||
|
background-color: var(--background);
|
||||||
|
padding: 0 8px;
|
||||||
|
box-decoration-break: clone;
|
||||||
|
-webkit-box-decoration-break: clone;
|
||||||
|
z-index: 1 !important;
|
||||||
|
position: relative;
|
||||||
|
box-shadow: 0px 5px 0px var(--background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.backdropTextHelper {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
z-index: 0 !important;
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.backdropTextHelper .backdropOnText {
|
||||||
|
box-shadow: 00px -5px 0px var(--background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
margin-top: 48px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardLinkWrapper {
|
||||||
|
margin-top: 16px;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
position: relative;
|
||||||
|
max-width: 360px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardLink {
|
||||||
|
border-radius: 16px;
|
||||||
|
background-color: var(--background);
|
||||||
|
|
||||||
|
a {
|
||||||
|
cursor: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardLink:first-child {
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardLinkContent {
|
||||||
|
padding: 16px;
|
||||||
|
padding-right: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 16px;
|
||||||
|
right: 16px;
|
||||||
|
color: var(--on-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardLink .title {
|
||||||
|
color: var(--on-background);
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardLink .subtitle {
|
||||||
|
color: var(--on-background-secondary);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 800px) {
|
||||||
|
.title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media screen and (max-width: 620px) {
|
||||||
|
.cardLink {
|
||||||
|
width: 100%;
|
||||||
|
max-width: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,84 @@
|
|||||||
|
import { clsx } from 'clsx';
|
||||||
|
import styles from './card.module.scss';
|
||||||
|
import { manrope } from '@/styles/fonts';
|
||||||
|
import React, { forwardRef } from 'react';
|
||||||
|
|
||||||
|
type CardProps = Omit<
|
||||||
|
React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>,
|
||||||
|
'title'
|
||||||
|
> & {
|
||||||
|
title?: React.ReactNode;
|
||||||
|
subtitle?: React.ReactNode;
|
||||||
|
backdropOnText?: boolean;
|
||||||
|
classes?: { [key: string]: string };
|
||||||
|
};
|
||||||
|
|
||||||
|
const Card = forwardRef<HTMLDivElement, CardProps>(function Card(
|
||||||
|
props: CardProps,
|
||||||
|
ref
|
||||||
|
) {
|
||||||
|
const {
|
||||||
|
title,
|
||||||
|
subtitle,
|
||||||
|
backdropOnText,
|
||||||
|
children,
|
||||||
|
classes = {},
|
||||||
|
className: restClassName,
|
||||||
|
...restProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={ref} className={clsx(styles.card, restClassName)} {...restProps}>
|
||||||
|
<div className={clsx(styles.textWrapper, classes.textWrapper)}>
|
||||||
|
<h3
|
||||||
|
className={clsx(manrope.className, styles.title, classes.title)}
|
||||||
|
>
|
||||||
|
<span className={clsx(backdropOnText && styles.backdropOnText)}>
|
||||||
|
{title}
|
||||||
|
</span>
|
||||||
|
</h3>
|
||||||
|
{backdropOnText && (
|
||||||
|
<h3
|
||||||
|
className={clsx(
|
||||||
|
manrope.className,
|
||||||
|
styles.title,
|
||||||
|
classes.title,
|
||||||
|
styles.backdropTextHelper
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className={clsx(backdropOnText && styles.backdropOnText)}>
|
||||||
|
{title}
|
||||||
|
</span>
|
||||||
|
</h3>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{subtitle && (
|
||||||
|
<div className={clsx(styles.textWrapper, classes.textWrapper)}>
|
||||||
|
<p className={clsx(styles.subtitle, classes.subtitle)}>
|
||||||
|
<span className={clsx(backdropOnText && styles.backdropOnText)}>
|
||||||
|
{subtitle}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
{backdropOnText && (
|
||||||
|
<p
|
||||||
|
className={clsx(
|
||||||
|
styles.subtitle,
|
||||||
|
classes.subtitle,
|
||||||
|
styles.backdropTextHelper
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className={clsx(backdropOnText && styles.backdropOnText)}>
|
||||||
|
{subtitle}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{children && (
|
||||||
|
<div className={clsx(styles.content, classes.content)}>{children}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export { Card };
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
import { clsx } from 'clsx';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import styles from './card.module.scss';
|
||||||
|
import LinkIcon from '@/assets/icons/link.svg';
|
||||||
|
import { animated, useSpring } from '@react-spring/web';
|
||||||
|
import { CursorEffect } from '@/components/cursor';
|
||||||
|
|
||||||
|
type CardProps = React.DetailedHTMLProps<
|
||||||
|
React.HTMLAttributes<HTMLDivElement>,
|
||||||
|
HTMLDivElement
|
||||||
|
> & {
|
||||||
|
title?: string;
|
||||||
|
hrefTitle?: string;
|
||||||
|
href: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function CardLink(props: CardProps) {
|
||||||
|
const {
|
||||||
|
title,
|
||||||
|
hrefTitle,
|
||||||
|
href,
|
||||||
|
className: restClassName,
|
||||||
|
...restProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const [springs, api] = useSpring(() => ({
|
||||||
|
scale: 1,
|
||||||
|
config: { mass: 1, tension: 150, friction: 10 },
|
||||||
|
}));
|
||||||
|
|
||||||
|
const handleEnter = () => {
|
||||||
|
api.start({
|
||||||
|
scale: 1.01,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const handleLeave = () => {
|
||||||
|
api.start({
|
||||||
|
scale: 1,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CursorEffect
|
||||||
|
className={clsx(styles.cardLinkWrapper, restClassName)}
|
||||||
|
cursorBorderRadius={16}
|
||||||
|
cursorPadding={6}
|
||||||
|
>
|
||||||
|
<animated.div
|
||||||
|
className={clsx(styles.cardLink)}
|
||||||
|
style={{ ...springs }}
|
||||||
|
onMouseEnter={handleEnter}
|
||||||
|
onMouseLeave={handleLeave}
|
||||||
|
>
|
||||||
|
<Link href={href} target="_blank">
|
||||||
|
<div className={styles.cardLinkContent}>
|
||||||
|
<h3 className={styles.title}>{title}</h3>
|
||||||
|
<p className={styles.subtitle}>{hrefTitle}</p>
|
||||||
|
</div>
|
||||||
|
<LinkIcon className={styles.icon} />
|
||||||
|
</Link>
|
||||||
|
</animated.div>
|
||||||
|
</CursorEffect>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from './card';
|
||||||
|
export * from './cardLink';
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
.root {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1000000;
|
||||||
|
pointer-events: none;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hovered {
|
||||||
|
backdrop-filter: invert(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.idle {
|
||||||
|
background-color: #000;
|
||||||
|
|
||||||
|
.spot {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spot {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-image: radial-gradient(#ffffffe6 0%, #ffffffbd 30%, #ffffff6e 50%, #ffffff00 70%);
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
@ -0,0 +1,293 @@
|
|||||||
|
import useFrame from '@/utils/useFrame';
|
||||||
|
import { Fragment, useEffect, useRef } from 'react';
|
||||||
|
import styles from './cursor.module.css';
|
||||||
|
import { smooth } from '@/utils/smooth';
|
||||||
|
|
||||||
|
type CursorProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Cursor(props: CursorProps) {
|
||||||
|
const { children } = props;
|
||||||
|
const mousePosition = useRef<{
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
cursorPadding: number;
|
||||||
|
cursorBorderRadius: number;
|
||||||
|
targetRect: DOMRect | null;
|
||||||
|
target: { link: boolean; button: boolean; cursorEffector: boolean };
|
||||||
|
pressed: boolean;
|
||||||
|
active: boolean;
|
||||||
|
}>({
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
cursorPadding: 0,
|
||||||
|
cursorBorderRadius: 0,
|
||||||
|
targetRect: null,
|
||||||
|
target: { link: false, button: false, cursorEffector: false },
|
||||||
|
pressed: false,
|
||||||
|
active: false,
|
||||||
|
});
|
||||||
|
const cursorRef = useRef<HTMLDivElement>(null);
|
||||||
|
const cursorSpotRef = useRef<HTMLDivElement>(null);
|
||||||
|
const prevCursorState = useRef<{
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
borderRadius: number;
|
||||||
|
}>({
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
borderRadius: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let isReadyForHide = false;
|
||||||
|
let isTouch = false;
|
||||||
|
|
||||||
|
const updateTargetFromPath = (path: any[]) => {
|
||||||
|
const isLink = path.find((el) => el?.tagName === 'A');
|
||||||
|
const isButton = path.find((el) => el?.tagName === 'BUTTON');
|
||||||
|
const isCursorEffector = path.find((el) =>
|
||||||
|
el?.getAttribute?.('data-cursor-effect')
|
||||||
|
);
|
||||||
|
const cursorPadding = +(
|
||||||
|
path
|
||||||
|
.find((el) => el?.getAttribute?.('data-cursor-padding') !== null)
|
||||||
|
?.getAttribute?.('data-cursor-padding') || 16
|
||||||
|
);
|
||||||
|
|
||||||
|
const cursorBorderRadius = +(
|
||||||
|
path
|
||||||
|
.find(
|
||||||
|
(el) => el?.getAttribute?.('data-cursor-border-radius') !== null
|
||||||
|
)
|
||||||
|
?.getAttribute?.('data-cursor-border-radius') || 0
|
||||||
|
);
|
||||||
|
|
||||||
|
let targetRect = null;
|
||||||
|
|
||||||
|
if (isLink) {
|
||||||
|
targetRect = isLink.getBoundingClientRect();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isButton) {
|
||||||
|
targetRect = isButton.getBoundingClientRect();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCursorEffector) {
|
||||||
|
targetRect = isCursorEffector.getBoundingClientRect();
|
||||||
|
}
|
||||||
|
|
||||||
|
mousePosition.current = {
|
||||||
|
...mousePosition.current,
|
||||||
|
cursorPadding,
|
||||||
|
cursorBorderRadius,
|
||||||
|
targetRect,
|
||||||
|
target: {
|
||||||
|
link: Boolean(isLink),
|
||||||
|
button: Boolean(isButton),
|
||||||
|
cursorEffector: Boolean(isCursorEffector),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseMove = (event: MouseEvent) => {
|
||||||
|
if (isTouch) return;
|
||||||
|
updateTargetFromPath(event.composedPath());
|
||||||
|
|
||||||
|
if (!mousePosition.current.active) {
|
||||||
|
prevCursorState.current = {
|
||||||
|
...prevCursorState.current,
|
||||||
|
x: event.clientX,
|
||||||
|
y: event.clientY,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
isReadyForHide = false;
|
||||||
|
|
||||||
|
mousePosition.current = {
|
||||||
|
...mousePosition.current,
|
||||||
|
x: event.clientX,
|
||||||
|
y: event.clientY,
|
||||||
|
active: true,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseLeave = () => {
|
||||||
|
isReadyForHide = true;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!isReadyForHide) return;
|
||||||
|
|
||||||
|
console.log('mouse leave');
|
||||||
|
|
||||||
|
mousePosition.current = {
|
||||||
|
...mousePosition.current,
|
||||||
|
active: false,
|
||||||
|
};
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleScroll = () => {
|
||||||
|
if (isTouch) return;
|
||||||
|
updateTargetFromPath(
|
||||||
|
document.elementsFromPoint(
|
||||||
|
mousePosition.current.x,
|
||||||
|
mousePosition.current.y
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseDown = (event: MouseEvent) => {
|
||||||
|
if (isTouch) return;
|
||||||
|
updateTargetFromPath(event.composedPath());
|
||||||
|
mousePosition.current = {
|
||||||
|
...mousePosition.current,
|
||||||
|
pressed: true,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseUp = (event: MouseEvent) => {
|
||||||
|
if (isTouch) return;
|
||||||
|
updateTargetFromPath(event.composedPath());
|
||||||
|
mousePosition.current = {
|
||||||
|
...mousePosition.current,
|
||||||
|
pressed: false,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePointerDown = (event: PointerEvent) => {
|
||||||
|
isTouch = event.pointerType === 'touch';
|
||||||
|
|
||||||
|
if (isTouch) handleMouseLeave();
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('pointerdown', handlePointerDown);
|
||||||
|
window.addEventListener('mousemove', handleMouseMove);
|
||||||
|
window.document.body.addEventListener('mouseleave', handleMouseLeave);
|
||||||
|
window.addEventListener('scroll', handleScroll);
|
||||||
|
window.addEventListener('mousedown', handleMouseDown);
|
||||||
|
window.addEventListener('mouseup', handleMouseUp);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('pointerdown', handlePointerDown);
|
||||||
|
window.removeEventListener('mousemove', handleMouseMove);
|
||||||
|
window.document.body.removeEventListener('mouseleave', handleMouseLeave);
|
||||||
|
window.removeEventListener('scroll', handleScroll);
|
||||||
|
window.removeEventListener('click', handleMouseDown);
|
||||||
|
window.removeEventListener('mouseup', handleMouseUp);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useFrame((delta) => {
|
||||||
|
let x = mousePosition.current.x;
|
||||||
|
let y = mousePosition.current.y;
|
||||||
|
let width = 30;
|
||||||
|
let height = 30;
|
||||||
|
let borderRadius = 0;
|
||||||
|
|
||||||
|
let gapHover = mousePosition.current.cursorPadding;
|
||||||
|
const targetRect = mousePosition.current.targetRect;
|
||||||
|
|
||||||
|
const isHovered =
|
||||||
|
(mousePosition.current.target.link ||
|
||||||
|
mousePosition.current.target.button ||
|
||||||
|
mousePosition.current.target.cursorEffector) &&
|
||||||
|
targetRect !== null;
|
||||||
|
|
||||||
|
if (!mousePosition.current.active) {
|
||||||
|
width = 0;
|
||||||
|
height = 0;
|
||||||
|
borderRadius = 15;
|
||||||
|
} else if (isHovered) {
|
||||||
|
const targetCenterX = targetRect.left + targetRect.width / 2;
|
||||||
|
const targetCenterY = targetRect.top + targetRect.height / 2;
|
||||||
|
|
||||||
|
width = targetRect.width + gapHover * 2;
|
||||||
|
height = targetRect.height + gapHover * 2;
|
||||||
|
|
||||||
|
x = x * 0.05 + targetCenterX * 0.95 - width / 2;
|
||||||
|
y = y * 0.05 + targetCenterY * 0.95 - height / 2;
|
||||||
|
} else {
|
||||||
|
width = 30;
|
||||||
|
height = 30;
|
||||||
|
x = x - width / 2;
|
||||||
|
y = y - height / 2;
|
||||||
|
borderRadius = 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mousePosition.current.pressed) {
|
||||||
|
width -= 10;
|
||||||
|
height -= 10;
|
||||||
|
x += 5;
|
||||||
|
y += 5;
|
||||||
|
gapHover -= 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mousePosition.current.cursorBorderRadius) {
|
||||||
|
borderRadius = mousePosition.current.cursorBorderRadius + gapHover;
|
||||||
|
} else {
|
||||||
|
borderRadius = Math.min(width / 2, height / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
x = smooth(prevCursorState.current.x, x, 0.02 * delta);
|
||||||
|
y = smooth(prevCursorState.current.y, y, 0.02 * delta);
|
||||||
|
width = smooth(prevCursorState.current.width, width, 0.02 * delta);
|
||||||
|
height = smooth(prevCursorState.current.height, height, 0.02 * delta);
|
||||||
|
borderRadius = smooth(
|
||||||
|
prevCursorState.current.borderRadius,
|
||||||
|
borderRadius,
|
||||||
|
0.02 * delta
|
||||||
|
);
|
||||||
|
|
||||||
|
prevCursorState.current = {
|
||||||
|
...prevCursorState.current,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
borderRadius,
|
||||||
|
};
|
||||||
|
|
||||||
|
const cursorNode = cursorRef.current;
|
||||||
|
const cursorSpotNode = cursorSpotRef.current;
|
||||||
|
|
||||||
|
if (cursorNode !== null && cursorSpotNode !== null) {
|
||||||
|
cursorNode.style.transform = `translate3D(${x}px, ${y}px, 0)`;
|
||||||
|
cursorNode.style.width = `${width}px`;
|
||||||
|
cursorNode.style.height = `${height}px`;
|
||||||
|
cursorNode.style.borderRadius = `${Math.min(borderRadius, 36 + gapHover)}px`;
|
||||||
|
|
||||||
|
const size = Math.max((width + height) / 2, 48);
|
||||||
|
|
||||||
|
cursorSpotNode.style.transform = `translate(${mousePosition.current.x - x - size / 2}px, ${mousePosition.current.y - y - size / 2}px)`;
|
||||||
|
cursorSpotNode.style.width = `${size}px`;
|
||||||
|
cursorSpotNode.style.height = `${size}px`;
|
||||||
|
|
||||||
|
if (isHovered) {
|
||||||
|
if (!cursorNode.classList.contains(styles.hovered)) {
|
||||||
|
cursorNode.classList.add(styles.hovered);
|
||||||
|
cursorNode.classList.remove(styles.idle);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!cursorNode.classList.contains(styles.idle)) {
|
||||||
|
cursorNode.classList.add(styles.idle);
|
||||||
|
cursorNode.classList.remove(styles.hovered);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<div ref={cursorRef} className={styles.root}>
|
||||||
|
<div ref={cursorSpotRef} className={styles.spot} />
|
||||||
|
</div>
|
||||||
|
{children}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,196 @@
|
|||||||
|
import { smooth } from '@/utils/smooth';
|
||||||
|
import useFrame from '@/utils/useFrame';
|
||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
type CursorEffectProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
effectDistance?: number;
|
||||||
|
effectForce?: number;
|
||||||
|
cursorPadding?: number;
|
||||||
|
cursorBorderRadius?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const clamp = (value: number, min: number, max: number) => {
|
||||||
|
const minOffset = Math.max(value - min, 0);
|
||||||
|
|
||||||
|
return Math.min(minOffset / (max - min), 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const inRect = (x: number, y: number, rect: DOMRect, gap: number = 0) => {
|
||||||
|
return (
|
||||||
|
rect.left - gap < x &&
|
||||||
|
x < rect.right + gap &&
|
||||||
|
rect.top - gap < y &&
|
||||||
|
y < rect.bottom + gap
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function CursorEffect(props: CursorEffectProps) {
|
||||||
|
const {
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
effectDistance = 128,
|
||||||
|
effectForce = 6,
|
||||||
|
cursorPadding = 16,
|
||||||
|
cursorBorderRadius = 0,
|
||||||
|
} = props;
|
||||||
|
const cursorAffectorRef = useRef<HTMLDivElement>(null);
|
||||||
|
const cursorAffectoContainerrRef = useRef<HTMLDivElement>(null);
|
||||||
|
const mousePosition = useRef<{
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
targetRect: DOMRect | null;
|
||||||
|
}>({
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
targetRect: null,
|
||||||
|
});
|
||||||
|
const prevCursorState = useRef<{
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}>({
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let isTouch = false;
|
||||||
|
|
||||||
|
const handleMouseMove = (event: MouseEvent) => {
|
||||||
|
if (isTouch) return;
|
||||||
|
|
||||||
|
let targetRect =
|
||||||
|
cursorAffectoContainerrRef.current?.getBoundingClientRect() || null;
|
||||||
|
|
||||||
|
mousePosition.current = {
|
||||||
|
x: event.clientX,
|
||||||
|
y: event.clientY,
|
||||||
|
targetRect,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleScroll = () => {
|
||||||
|
if (isTouch) return;
|
||||||
|
|
||||||
|
let targetRect =
|
||||||
|
cursorAffectoContainerrRef.current?.getBoundingClientRect() || null;
|
||||||
|
|
||||||
|
mousePosition.current = {
|
||||||
|
...mousePosition.current,
|
||||||
|
targetRect,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePointerDown = (event: PointerEvent) => {
|
||||||
|
isTouch = event.pointerType === 'touch';
|
||||||
|
|
||||||
|
let targetRect =
|
||||||
|
cursorAffectoContainerrRef.current?.getBoundingClientRect() || null;
|
||||||
|
|
||||||
|
mousePosition.current = {
|
||||||
|
...mousePosition.current,
|
||||||
|
targetRect,
|
||||||
|
x: -9999999,
|
||||||
|
y: -9999999,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('pointerdown', handlePointerDown);
|
||||||
|
window.addEventListener('mousemove', handleMouseMove);
|
||||||
|
window.addEventListener('scroll', handleScroll);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('pointerdown', handlePointerDown);
|
||||||
|
window.removeEventListener('mousemove', handleMouseMove);
|
||||||
|
window.removeEventListener('scroll', handleScroll);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useFrame((delta) => {
|
||||||
|
let x = 0;
|
||||||
|
let y = 0;
|
||||||
|
|
||||||
|
const gap = effectDistance;
|
||||||
|
const offsetMax = effectForce;
|
||||||
|
|
||||||
|
const mousePos = mousePosition.current;
|
||||||
|
const rect = mousePosition.current.targetRect;
|
||||||
|
|
||||||
|
const isActive = rect && inRect(mousePos.x, mousePos.y, rect, gap);
|
||||||
|
|
||||||
|
if (isActive) {
|
||||||
|
const halfWidth = rect.width / 2;
|
||||||
|
const targetCenterX = rect.left + halfWidth;
|
||||||
|
const leftStart = rect.left - gap;
|
||||||
|
const leftMiddle = rect.left - offsetMax;
|
||||||
|
const rightMiddle = rect.right + offsetMax;
|
||||||
|
const rightEnd = rect.right + gap;
|
||||||
|
|
||||||
|
const affectX =
|
||||||
|
clamp(mousePos.y, rect.top - gap, rect.top) *
|
||||||
|
(1 - clamp(mousePos.y, rect.bottom, rect.bottom + gap));
|
||||||
|
|
||||||
|
if (mousePos.x < leftMiddle) {
|
||||||
|
x = -clamp(mousePos.x, leftStart, leftMiddle);
|
||||||
|
} else if (mousePos.x < targetCenterX) {
|
||||||
|
x = -(1 - clamp(mousePos.x, leftMiddle, targetCenterX));
|
||||||
|
} else if (mousePos.x < rightMiddle) {
|
||||||
|
x = clamp(mousePos.x, targetCenterX, rightMiddle);
|
||||||
|
} else if (mousePos.x < rightEnd) {
|
||||||
|
x = 1 - clamp(mousePos.x, rightMiddle, rightEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
x = x * affectX;
|
||||||
|
|
||||||
|
const halfHeight = rect.height / 2;
|
||||||
|
const targetCenterY = rect.top + halfHeight;
|
||||||
|
const topStart = rect.top - gap;
|
||||||
|
const topMiddle = rect.top - offsetMax;
|
||||||
|
const bottomMiddle = rect.bottom + offsetMax;
|
||||||
|
const bottomEnd = rect.bottom + gap;
|
||||||
|
|
||||||
|
const affectY =
|
||||||
|
clamp(mousePos.x, rect.left - gap, rect.left) *
|
||||||
|
(1 - clamp(mousePos.x, rect.right, rect.right + gap));
|
||||||
|
|
||||||
|
if (mousePos.y < topMiddle) {
|
||||||
|
y = -clamp(mousePos.y, topStart, topMiddle);
|
||||||
|
} else if (mousePos.y < targetCenterY) {
|
||||||
|
y = -(1 - clamp(mousePos.y, topMiddle, targetCenterY));
|
||||||
|
} else if (mousePos.y < bottomMiddle) {
|
||||||
|
y = clamp(mousePos.y, targetCenterY, bottomMiddle);
|
||||||
|
} else if (mousePos.y < bottomEnd) {
|
||||||
|
y = 1 - clamp(mousePos.y, bottomMiddle, bottomEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
y = y * affectY;
|
||||||
|
|
||||||
|
x = x * offsetMax;
|
||||||
|
y = y * offsetMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
x = smooth(prevCursorState.current.x, x, 0.02 * delta);
|
||||||
|
y = smooth(prevCursorState.current.y, y, 0.02 * delta);
|
||||||
|
|
||||||
|
prevCursorState.current = { x, y };
|
||||||
|
|
||||||
|
if (cursorAffectorRef.current) {
|
||||||
|
cursorAffectorRef.current.style.transform = `translate3D(${x}px, ${y}px, 0)`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={cursorAffectoContainerrRef} className={className}>
|
||||||
|
<div
|
||||||
|
ref={cursorAffectorRef}
|
||||||
|
style={{ width: '100%', height: '100%' }}
|
||||||
|
data-cursor-effect
|
||||||
|
data-cursor-padding={cursorPadding}
|
||||||
|
data-cursor-border-radius={cursorBorderRadius}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from './cursor';
|
||||||
|
export * from './cursorEffect';
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
.footer {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0px 48px 48px;
|
||||||
|
max-width: 1500px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fatLittleStar {
|
||||||
|
position: relative;
|
||||||
|
margin-right: auto;
|
||||||
|
transform: translate(30px, 10px);
|
||||||
|
color: #0059ff;
|
||||||
|
z-index: -1;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.author {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xtimms {
|
||||||
|
color: #0059ff;
|
||||||
|
cursor: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1240px) {
|
||||||
|
.footer {
|
||||||
|
padding: 0px 36px 36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 800px) {
|
||||||
|
.footer {
|
||||||
|
padding: 0px 24px 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 500px) {
|
||||||
|
.footer {
|
||||||
|
padding: 0px 16px 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
import useLocale, { LocalesMap } from '@/utils/useLocale';
|
||||||
|
import { clsx } from 'clsx';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import styles from './footer.module.scss';
|
||||||
|
import FatLittleStar from '@/assets/images/fat-little-star.svg';
|
||||||
|
import { useRef } from 'react';
|
||||||
|
import useScroll from '@/utils/useScroll';
|
||||||
|
|
||||||
|
type CardProps = React.DetailedHTMLProps<
|
||||||
|
React.HTMLAttributes<HTMLDivElement>,
|
||||||
|
HTMLDivElement
|
||||||
|
>;
|
||||||
|
|
||||||
|
const localesMap: LocalesMap = {
|
||||||
|
ru: {
|
||||||
|
madeBy: 'сделано',
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
madeBy: 'made by',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Footer(props: CardProps) {
|
||||||
|
const { className: restClassName, ...restProps } = props;
|
||||||
|
const t = useLocale(localesMap);
|
||||||
|
const fatLittleStarRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useScroll((scrollY) => {
|
||||||
|
if (
|
||||||
|
typeof window === 'undefined' ||
|
||||||
|
window.document.scrollingElement === null
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const scrollOffset =
|
||||||
|
window.document.scrollingElement.scrollHeight -
|
||||||
|
window.innerHeight -
|
||||||
|
scrollY;
|
||||||
|
|
||||||
|
if (fatLittleStarRef.current) {
|
||||||
|
fatLittleStarRef.current.style.transform = `translate3D(30px, ${scrollOffset / -2 + 10}px, 0) rotate(${scrollOffset / 10}deg)`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<footer className={clsx(restClassName, styles.footer)} {...restProps}>
|
||||||
|
<div ref={fatLittleStarRef} className={styles.fatLittleStar}>
|
||||||
|
<FatLittleStar />
|
||||||
|
</div>
|
||||||
|
<p className={styles.author}>
|
||||||
|
{`${t('madeBy')} `}
|
||||||
|
<Link
|
||||||
|
className={styles.xtimms}
|
||||||
|
href="https://xtimms.t.me"
|
||||||
|
target="_blank"
|
||||||
|
prefetch={false}
|
||||||
|
>
|
||||||
|
@xtimms
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from './footer';
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
.card {
|
||||||
|
border-radius: 36px;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: #a4a4a41c;
|
||||||
|
backdrop-filter: blur(18px);
|
||||||
|
|
||||||
|
a {
|
||||||
|
cursor: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 14px;
|
||||||
|
padding-left: 28px;
|
||||||
|
padding-right: 22px;
|
||||||
|
transition: 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkIcon {
|
||||||
|
width: 42px;
|
||||||
|
height: 42px;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.storeNameText {
|
||||||
|
color: #242424;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 620px) {
|
||||||
|
.content {
|
||||||
|
padding: 36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
import styles from './githubCardButton.module.scss';
|
||||||
|
import Link, { LinkProps } from 'next/link';
|
||||||
|
import LinkIcon from '@/assets/icons/link.svg';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { CursorEffect } from '@/components/cursor';
|
||||||
|
|
||||||
|
type GithubCardButtonProps = Omit<LinkProps, 'href'> & { className?: string };
|
||||||
|
|
||||||
|
export function GithubCardButton(props: GithubCardButtonProps) {
|
||||||
|
const { className: restCalssName, ...restProps } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CursorEffect cursorBorderRadius={36} cursorPadding={12}>
|
||||||
|
<div className={clsx(styles.card, restCalssName)}>
|
||||||
|
<Link
|
||||||
|
{...restProps}
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/ztimms73/ridebus"
|
||||||
|
>
|
||||||
|
<div className={styles.content}>
|
||||||
|
<span className={styles.storeNameText}>GitHub</span>
|
||||||
|
<LinkIcon className={styles.linkIcon} />
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</CursorEffect>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from './githubCardButton';
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
.linkWrapper {
|
||||||
|
display: inline-flex;
|
||||||
|
height: min-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
display: inline-flex;
|
||||||
|
padding: 18px;
|
||||||
|
cursor: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkIcon {
|
||||||
|
color: var(--on-background);
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
import styles from './githubLink.module.scss';
|
||||||
|
import Link, { LinkProps } from 'next/link';
|
||||||
|
import GithubIcon from '@/assets/icons/github.svg';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { CursorEffect } from '@/components/cursor';
|
||||||
|
|
||||||
|
type GithubLinkProps = Omit<LinkProps, 'href'> & { className?: string };
|
||||||
|
|
||||||
|
export function GithubLink(props: GithubLinkProps) {
|
||||||
|
const { className: restClassName, ...restProps } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CursorEffect
|
||||||
|
effectDistance={48}
|
||||||
|
effectForce={4}
|
||||||
|
cursorPadding={0}
|
||||||
|
className={clsx(styles.linkWrapper)}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
{...restProps}
|
||||||
|
className={clsx(styles.link, restClassName)}
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/ztimms73/ridebus"
|
||||||
|
prefetch={false}
|
||||||
|
>
|
||||||
|
<GithubIcon className={styles.linkIcon} />
|
||||||
|
</Link>
|
||||||
|
</CursorEffect>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from './githubLink';
|
||||||
@ -0,0 +1,89 @@
|
|||||||
|
.card {
|
||||||
|
border-radius: 36px;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: #a4a4a41c;
|
||||||
|
transition: 0.3s ease;
|
||||||
|
backdrop-filter: blur(18px);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #3d3d3d1c;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
cursor: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.disableGlass {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
backdrop-filter: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #eaeaea;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qrCode {
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
margin-right: 20px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkIcon {
|
||||||
|
width: 42px;
|
||||||
|
height: 42px;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.getText {
|
||||||
|
color: #838383;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: -6px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.storeNameText {
|
||||||
|
color: #242424;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1180px) {
|
||||||
|
.qrCode {
|
||||||
|
width: 180px;
|
||||||
|
height: 180px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 980px) {
|
||||||
|
.qrCode {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding-left: 32px;
|
||||||
|
padding-bottom: 26px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media screen and (max-width: 620px) {
|
||||||
|
.content {
|
||||||
|
padding: 36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
import styles from './googlePlayButton.module.scss';
|
||||||
|
import Link, { LinkProps } from 'next/link';
|
||||||
|
import QRCodeSvg from '@/assets/images/qr-code.svg';
|
||||||
|
import LinkIcon from '@/assets/icons/link.svg';
|
||||||
|
import useLocale, { LocalesMap } from '@/utils/useLocale';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { CursorEffect } from '@/components/cursor';
|
||||||
|
|
||||||
|
const locales: LocalesMap = {
|
||||||
|
ru: {
|
||||||
|
getText: 'Доступно в',
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
getText: 'Get it on',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
type GooglePlayButtonProps = Omit<LinkProps, 'href'> & {
|
||||||
|
className?: string;
|
||||||
|
disableGlass?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function GooglePlayButton(props: GooglePlayButtonProps) {
|
||||||
|
const {
|
||||||
|
className: restCalssName,
|
||||||
|
disableGlass = false,
|
||||||
|
...restProps
|
||||||
|
} = props;
|
||||||
|
const t = useLocale(locales);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CursorEffect cursorBorderRadius={36} cursorPadding={12}>
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
styles.card,
|
||||||
|
disableGlass && styles.disableGlass,
|
||||||
|
restCalssName
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
{...restProps}
|
||||||
|
target="_blank"
|
||||||
|
href="https://play.google.com/store/apps/details?id=org.xtimms.ridebus"
|
||||||
|
>
|
||||||
|
<div className={styles.content}>
|
||||||
|
<QRCodeSvg className={styles.qrCode} />
|
||||||
|
<div className={styles.textContainer}>
|
||||||
|
<LinkIcon className={styles.linkIcon} />
|
||||||
|
<span className={styles.getText}>{t('getText')}</span>
|
||||||
|
<span className={styles.storeNameText}>Google Play</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</CursorEffect>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from './googlePlayButton';
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
.rootWrapper {
|
||||||
|
}
|
||||||
|
|
||||||
|
.root {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--on-background);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 18px;
|
||||||
|
cursor: none;
|
||||||
|
}
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
import styles from './googlePlayLink.module.scss';
|
||||||
|
import Link, { LinkProps } from 'next/link';
|
||||||
|
import LinkIcon from '@/assets/icons/link.svg';
|
||||||
|
import useLocale, { LocalesMap } from '@/utils/useLocale';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { CursorEffect } from '@/components/cursor';
|
||||||
|
|
||||||
|
const locales: LocalesMap = {
|
||||||
|
ru: {
|
||||||
|
getText: 'Доступно в Google Play',
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
getText: 'Get it on Google Play',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
type GooglePlayLinkProps = Omit<LinkProps, 'href'> & { className?: string };
|
||||||
|
|
||||||
|
export function GooglePlayLink(props: GooglePlayLinkProps) {
|
||||||
|
const { className: restCalssName, ...restProps } = props;
|
||||||
|
const t = useLocale(locales);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CursorEffect
|
||||||
|
effectDistance={48}
|
||||||
|
effectForce={4}
|
||||||
|
cursorPadding={0}
|
||||||
|
className={clsx(styles.rootWrapper, restCalssName)}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
{...restProps}
|
||||||
|
className={clsx(styles.root)}
|
||||||
|
target="_blank"
|
||||||
|
href="https://play.google.com/store/apps/details?id=org.xtimms.ridebus"
|
||||||
|
>
|
||||||
|
{t('getText')} <LinkIcon />
|
||||||
|
</Link>
|
||||||
|
</CursorEffect>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from './googlePlayLink';
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
#ifdef GL_ES
|
||||||
|
precision mediump float;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const vec3 background = vec3(1.0);
|
||||||
|
const vec3 primary = vec3(0., 0.345, 0.761);
|
||||||
|
const vec3 tertiary = vec3(0.675, 0.514, 0.682);
|
||||||
|
|
||||||
|
varying float vHeight;
|
||||||
|
varying float vColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
float height = vHeight * 2.0 + 0.9;
|
||||||
|
|
||||||
|
vec3 color = mix(primary, tertiary, vColor * 3.0 + 0.5);
|
||||||
|
|
||||||
|
gl_FragColor = vec4(mix(color, background, height), 1.0);
|
||||||
|
}
|
||||||
@ -0,0 +1,116 @@
|
|||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
import styles from './gradients.module.css';
|
||||||
|
import { initGrainFilter } from './grainPostProcessing';
|
||||||
|
import { InitThreeOptions, initThree } from './initThree';
|
||||||
|
import { initScene } from './initScene';
|
||||||
|
import { initGradient } from './initGradients';
|
||||||
|
import Stats from 'stats.js';
|
||||||
|
|
||||||
|
const threeCanvas = (
|
||||||
|
canvas: HTMLCanvasElement,
|
||||||
|
viewportSize: InitThreeOptions,
|
||||||
|
initTimeOffset: number = 0
|
||||||
|
) => {
|
||||||
|
const { renderer, clock } = initThree(canvas, viewportSize);
|
||||||
|
const { scene, camera, updateSize: updateCameraSize } = initScene(renderer);
|
||||||
|
const {
|
||||||
|
render: renderGradient,
|
||||||
|
updateSize: updateGradientSize,
|
||||||
|
unload: unloadGradinet,
|
||||||
|
} = initGradient(scene, renderer);
|
||||||
|
const { render: renderFrame, resize: resizeFrame } = initGrainFilter(
|
||||||
|
renderer,
|
||||||
|
scene,
|
||||||
|
camera
|
||||||
|
);
|
||||||
|
//const stats = new Stats();
|
||||||
|
// the number will decide which information will be displayed
|
||||||
|
// 0 => FPS Frames rendered in the last second. The higher the number the better.
|
||||||
|
// 1 => MS Milliseconds needed to render a frame. The lower the number the better.
|
||||||
|
// 2 => MB MBytes of allocated memory. (Run Chrome with --enable-precise-memory-info)
|
||||||
|
// 3 => CUSTOM User-defined panel support.
|
||||||
|
//stats.showPanel(0);
|
||||||
|
|
||||||
|
//document.body.appendChild(stats.dom);
|
||||||
|
|
||||||
|
let elapsedTime = 0;
|
||||||
|
let delta = 0;
|
||||||
|
let isActive = true;
|
||||||
|
|
||||||
|
const animate = () => {
|
||||||
|
if (!isActive) return;
|
||||||
|
//stats.begin();
|
||||||
|
elapsedTime = clock.elapsedTime + initTimeOffset;
|
||||||
|
delta = clock.getDelta();
|
||||||
|
|
||||||
|
renderGradient(elapsedTime, delta);
|
||||||
|
renderFrame(elapsedTime, delta);
|
||||||
|
|
||||||
|
//stats.end();
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
};
|
||||||
|
|
||||||
|
const resizeCanvas = (width: number, height: number) => {
|
||||||
|
renderer.setSize(width, height);
|
||||||
|
updateCameraSize();
|
||||||
|
updateGradientSize();
|
||||||
|
resizeFrame();
|
||||||
|
};
|
||||||
|
|
||||||
|
animate();
|
||||||
|
resizeCanvas(viewportSize.width, viewportSize.height);
|
||||||
|
|
||||||
|
const unload = () => {
|
||||||
|
isActive = false;
|
||||||
|
unloadGradinet();
|
||||||
|
renderer.dispose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
resizeCanvas,
|
||||||
|
unload,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Gradients() {
|
||||||
|
const rootRef = useRef<HTMLDivElement>(null);
|
||||||
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
|
const [timeOffset] = useState(Math.random() * 1000);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
const canvas = threeCanvas(
|
||||||
|
canvasRef.current as HTMLCanvasElement,
|
||||||
|
{
|
||||||
|
width: rootRef.current?.clientWidth || 0,
|
||||||
|
height: rootRef.current?.clientHeight || 0,
|
||||||
|
},
|
||||||
|
timeOffset
|
||||||
|
);
|
||||||
|
|
||||||
|
const resize = () => {
|
||||||
|
canvas.resizeCanvas(
|
||||||
|
rootRef.current?.clientWidth || 0,
|
||||||
|
rootRef.current?.clientHeight || 0
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('resize', resize);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', resize);
|
||||||
|
|
||||||
|
canvas.unload();
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}, [timeOffset]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.root} ref={rootRef}>
|
||||||
|
<canvas className={styles.canvas} ref={canvasRef}></canvas>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,75 @@
|
|||||||
|
#ifdef GL_ES
|
||||||
|
precision mediump float;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const float PI = 3.14159265359;
|
||||||
|
const float SCALE = 0.0002;
|
||||||
|
const float SPEED = 0.5;
|
||||||
|
const float NOISE_FREQ = 3.5;
|
||||||
|
const float NOISE_AMP = 0.3;
|
||||||
|
|
||||||
|
varying vec2 vUv;
|
||||||
|
varying float vHeight;
|
||||||
|
varying float vColor;
|
||||||
|
uniform float uTime;
|
||||||
|
uniform sampler2D map;
|
||||||
|
|
||||||
|
float rand(vec2 co, float l, float t) {
|
||||||
|
return fract(sin(dot(co.xy + vec2(l, t), vec2(12.9898, 78.233))) * 43758.5453);
|
||||||
|
}
|
||||||
|
|
||||||
|
float perlin(vec2 p, float dim, float time) {
|
||||||
|
vec2 pos = floor(p * dim);
|
||||||
|
vec2 posx = pos + vec2(1.0, 0.0);
|
||||||
|
vec2 posy = pos + vec2(0.0, 1.0);
|
||||||
|
vec2 posxy = pos + vec2(1.0);
|
||||||
|
|
||||||
|
float c = rand(pos, dim, time);
|
||||||
|
float cx = rand(posx, dim, time);
|
||||||
|
float cy = rand(posy, dim, time);
|
||||||
|
float cxy = rand(posxy, dim, time);
|
||||||
|
|
||||||
|
vec2 d = fract(p * dim);
|
||||||
|
d = -0.5 * cos(d * PI) + 0.5;
|
||||||
|
|
||||||
|
float ccx = mix(c, cx, d.x);
|
||||||
|
float cycxy = mix(cy, cxy, d.x);
|
||||||
|
float center = mix(ccx, cycxy, d.y);
|
||||||
|
|
||||||
|
return center * 2.0 - 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
float snoiseFoam(vec2 pos, const float freq) {
|
||||||
|
return perlin(vec2(pos.y * freq, pos.x * freq), 9.0, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float snoiseWater(vec2 pos, const float freq) {
|
||||||
|
return perlin(vec2(pos.y * freq, pos.x * freq), 6.0, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
mat2 rotate2d(float _angle) {
|
||||||
|
return mat2(cos(_angle), -sin(_angle), sin(_angle), cos(_angle));
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vUv = uv;
|
||||||
|
|
||||||
|
vec2 rTimeSpeed = vec2(uTime) * rotate2d(PI * (70.0 / 180.0)) * 0.15 * SPEED;
|
||||||
|
vec2 rTimeSlow = vec2(uTime) * rotate2d(PI * (250.0 / 180.0)) * 0.1 * SPEED;
|
||||||
|
vec2 rTimeMiddle = vec2(uTime + 100.0) * rotate2d(PI * (300.0 / 180.0)) * 0.12 * SPEED;
|
||||||
|
|
||||||
|
vec2 freqPos = position.xy * SCALE * NOISE_FREQ;
|
||||||
|
|
||||||
|
float waveBig = snoiseWater(freqPos + rTimeSlow, 0.08) * 0.4;
|
||||||
|
float waveMiddle = snoiseFoam(freqPos + rTimeMiddle, 0.1) * 0.3;
|
||||||
|
float waveSmall = snoiseFoam(freqPos + rTimeSpeed, 0.1) * 0.3;
|
||||||
|
|
||||||
|
vColor = waveMiddle;
|
||||||
|
|
||||||
|
vHeight = (waveBig + waveSmall + waveMiddle) * NOISE_AMP;
|
||||||
|
|
||||||
|
vec3 pos = position;
|
||||||
|
pos.z += vHeight / SCALE;
|
||||||
|
|
||||||
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
.root {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvas {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
.main {
|
||||||
|
position: absolute;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
@ -0,0 +1,95 @@
|
|||||||
|
uniform float uTime;
|
||||||
|
uniform sampler2D tDiffuse;
|
||||||
|
|
||||||
|
varying vec2 vUv;
|
||||||
|
|
||||||
|
#define SHOW_NOISE 0
|
||||||
|
#define SRGB 0
|
||||||
|
// 0: Addition, 1: Screen, 2: Overlay, 3: Soft Light, 4: Lighten-Only
|
||||||
|
#define BLEND_MODE 0
|
||||||
|
|
||||||
|
//0 to 1
|
||||||
|
#define SPEED 0.000001
|
||||||
|
|
||||||
|
#define INTENSITY 0.02
|
||||||
|
// What gray level noise should tend to.
|
||||||
|
#define MEAN 0.0
|
||||||
|
|
||||||
|
// Controls the contrast/variance of noise.
|
||||||
|
#define VARIANCE .3
|
||||||
|
|
||||||
|
float rand(vec2 co) {
|
||||||
|
return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453);
|
||||||
|
}
|
||||||
|
float rand(vec2 co, float l) {
|
||||||
|
return rand(vec2(rand(co), l));
|
||||||
|
}
|
||||||
|
float rand(vec2 co, float l, float t) {
|
||||||
|
return rand(vec2(rand(co, l), t));
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 channel_mix(vec3 a, vec3 b, vec3 w) {
|
||||||
|
return vec3(mix(a.r, b.r, w.r), mix(a.g, b.g, w.g), mix(a.b, b.b, w.b));
|
||||||
|
}
|
||||||
|
|
||||||
|
float gaussian(float z, float u, float o) {
|
||||||
|
return (1.0 / (o * sqrt(2.0 * 3.1415))) * exp(-(((z - u) * (z - u)) / (2.0 * (o * o))));
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 madd(vec3 a, vec3 b, float w) {
|
||||||
|
return a + a * b * w;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 screen(vec3 a, vec3 b, float w) {
|
||||||
|
return mix(a, vec3(1.0) - (vec3(1.0) - a) * (vec3(1.0) - b), w);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 overlay(vec3 a, vec3 b, float w) {
|
||||||
|
return mix(a, channel_mix(2.0 * a * b, vec3(1.0) - 2.0 * (vec3(1.0) - a) * (vec3(1.0) - b), step(vec3(0.5), a)), w);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 soft_light(vec3 a, vec3 b, float w) {
|
||||||
|
return mix(a, pow(a, pow(vec3(2.0), 2.0 * (vec3(0.5) - b))), w);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
float t = uTime * float(SPEED);
|
||||||
|
|
||||||
|
vec2 coord = gl_FragCoord.xy;
|
||||||
|
vec2 ps = vec2(1.0) / gl_FragCoord.xy;
|
||||||
|
vec2 uv = coord * ps;
|
||||||
|
vec4 color = texture2D(tDiffuse, vUv);
|
||||||
|
#if SRGB
|
||||||
|
color = pow(color, vec4(2.2));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
float noise = gaussian(rand(ps, 1.0 / (color.r + color.g + color.b), t), float(MEAN), float(VARIANCE) * float(VARIANCE));
|
||||||
|
|
||||||
|
#if SHOW_NOISE
|
||||||
|
color = vec4(noise);
|
||||||
|
gl_FragColor = vec4(vec3(0.0), color.r);
|
||||||
|
#else
|
||||||
|
float w = float(INTENSITY);
|
||||||
|
|
||||||
|
vec3 grain = vec3(noise);//vec3(noise) * (1.0 - color.rgb);
|
||||||
|
|
||||||
|
#if BLEND_MODE == 0
|
||||||
|
color.rgb += grain * w;
|
||||||
|
#elif BLEND_MODE == 1
|
||||||
|
color.rgb = screen(color.rgb, grain, w);
|
||||||
|
#elif BLEND_MODE == 2
|
||||||
|
color.rgb = overlay(color.rgb, grain, w);
|
||||||
|
#elif BLEND_MODE == 3
|
||||||
|
color.rgb = soft_light(color.rgb, grain, w);
|
||||||
|
#elif BLEND_MODE == 4
|
||||||
|
color.rgb = max(color.rgb, grain * w);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if SRGB
|
||||||
|
color = pow(color, vec4(1.0 / 2.2));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
gl_FragColor = color;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer';
|
||||||
|
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass';
|
||||||
|
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass';
|
||||||
|
import { GrainShader } from './grainPostprocessingShader';
|
||||||
|
|
||||||
|
export function initGrainFilter(
|
||||||
|
renderer: THREE.WebGLRenderer,
|
||||||
|
scene: THREE.Scene,
|
||||||
|
camera: THREE.OrthographicCamera
|
||||||
|
) {
|
||||||
|
const width = window.innerWidth || 2;
|
||||||
|
const height = window.innerHeight || 2;
|
||||||
|
|
||||||
|
const effectFilm = new ShaderPass(GrainShader);
|
||||||
|
|
||||||
|
const rtParameters = {
|
||||||
|
stencilBuffer: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderScenePass = new RenderPass(scene, camera);
|
||||||
|
|
||||||
|
let composer2 = new EffectComposer(
|
||||||
|
renderer,
|
||||||
|
new THREE.WebGLRenderTarget(width, height, rtParameters)
|
||||||
|
);
|
||||||
|
|
||||||
|
composer2.setPixelRatio(window.devicePixelRatio);
|
||||||
|
|
||||||
|
composer2.addPass(renderScenePass);
|
||||||
|
composer2.addPass(effectFilm);
|
||||||
|
|
||||||
|
return {
|
||||||
|
render: (elapsed: number, delta: number) => {
|
||||||
|
composer2.render(delta);
|
||||||
|
|
||||||
|
effectFilm.uniforms['uTime'].value = elapsed;
|
||||||
|
},
|
||||||
|
resize: () => {
|
||||||
|
const viewportSize = renderer.getSize(new THREE.Vector2());
|
||||||
|
|
||||||
|
composer2.setSize(viewportSize.width, viewportSize.height);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
import grainFragmentShader from './grain.fragmentShader.glsl?raw';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Full-screen textured quad shader
|
||||||
|
*/
|
||||||
|
|
||||||
|
const GrainShader = {
|
||||||
|
name: 'GrainShader',
|
||||||
|
|
||||||
|
uniforms: {
|
||||||
|
tDiffuse: { value: null },
|
||||||
|
opacity: { value: 1.0 },
|
||||||
|
uTime: { value: 0 },
|
||||||
|
},
|
||||||
|
|
||||||
|
vertexShader: /* glsl */ `
|
||||||
|
|
||||||
|
varying vec2 vUv;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
|
||||||
|
vUv = uv;
|
||||||
|
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
|
||||||
|
|
||||||
|
}`,
|
||||||
|
|
||||||
|
fragmentShader: grainFragmentShader,
|
||||||
|
};
|
||||||
|
|
||||||
|
export { GrainShader };
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from './gradient';
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
import gradientFragmentShader from './gradient.fragmentShader.glsl?raw';
|
||||||
|
import gradientVertexShader from './gradient.vertexShader.glsl?raw';
|
||||||
|
|
||||||
|
function getMaterial() {
|
||||||
|
var uniforms = {
|
||||||
|
uTime: {
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
var material = new THREE.ShaderMaterial({
|
||||||
|
uniforms: uniforms,
|
||||||
|
vertexShader: gradientVertexShader,
|
||||||
|
fragmentShader: gradientFragmentShader,
|
||||||
|
//wireframe: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return material;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initGradient(
|
||||||
|
scene: THREE.Scene,
|
||||||
|
renderer: THREE.WebGLRenderer
|
||||||
|
) {
|
||||||
|
const quality = 0.02;
|
||||||
|
|
||||||
|
const material = getMaterial();
|
||||||
|
|
||||||
|
const plane = new THREE.Mesh(new THREE.PlaneGeometry(), material);
|
||||||
|
scene.add(plane);
|
||||||
|
|
||||||
|
const render = (elapsed: number, delta: number) => {
|
||||||
|
material.uniforms.uTime.value = elapsed;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateSize = () => {
|
||||||
|
const viewportSize = renderer.getSize(new THREE.Vector2());
|
||||||
|
plane.geometry.dispose();
|
||||||
|
plane.geometry = new THREE.PlaneGeometry(
|
||||||
|
viewportSize.width,
|
||||||
|
viewportSize.height * 4,
|
||||||
|
viewportSize.width * quality,
|
||||||
|
viewportSize.height * 4 * quality
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
updateSize();
|
||||||
|
|
||||||
|
const unload = () => {
|
||||||
|
scene.remove(plane);
|
||||||
|
plane.geometry.dispose();
|
||||||
|
material.dispose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return { render, updateSize, unload };
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
export function initScene(renderer: THREE.WebGLRenderer) {
|
||||||
|
const scene = new THREE.Scene();
|
||||||
|
|
||||||
|
scene.background = new THREE.Color(0xffffff);
|
||||||
|
|
||||||
|
const camera = new THREE.OrthographicCamera(-1, -1, -1, -1, -100000, 100000);
|
||||||
|
camera.position.z = 5;
|
||||||
|
camera.position.y = 8;
|
||||||
|
camera.lookAt(scene.position);
|
||||||
|
|
||||||
|
const updateSize = () => {
|
||||||
|
const viewportSize = renderer.getSize(new THREE.Vector2());
|
||||||
|
|
||||||
|
camera.left = viewportSize.width / -2;
|
||||||
|
camera.right = viewportSize.width / 2;
|
||||||
|
camera.top = viewportSize.height / 2;
|
||||||
|
camera.bottom = -viewportSize.height / 2;
|
||||||
|
camera.updateProjectionMatrix();
|
||||||
|
};
|
||||||
|
|
||||||
|
updateSize();
|
||||||
|
|
||||||
|
return { scene, camera, updateSize };
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
export type InitThreeOptions = {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function initThree(
|
||||||
|
canvas: HTMLCanvasElement,
|
||||||
|
{ width, height }: InitThreeOptions
|
||||||
|
) {
|
||||||
|
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
|
||||||
|
renderer.setSize(width, height);
|
||||||
|
renderer.setPixelRatio(window.devicePixelRatio);
|
||||||
|
|
||||||
|
const clock = new THREE.Clock();
|
||||||
|
|
||||||
|
return { renderer, clock };
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from './langSwitcher';
|
||||||
@ -0,0 +1,102 @@
|
|||||||
|
.langSwitcherRoot {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.langSwitcher {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.currentLanguage {
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
font: inherit;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--on-background);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 18px;
|
||||||
|
cursor: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selector {
|
||||||
|
position: absolute;
|
||||||
|
width: 200px;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: var(--background);
|
||||||
|
padding: 8px 8px;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 1px solid var(--divider);
|
||||||
|
box-shadow: 0px 4px 16px #00000014;
|
||||||
|
background-color: #fafafa;
|
||||||
|
z-index: 1000;
|
||||||
|
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
color: #6f6f6f;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 4px 16px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
button {
|
||||||
|
font: inherit;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 6px 16px;
|
||||||
|
cursor: none !important;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--divider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.openSelector {
|
||||||
|
pointer-events: all;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hideCurrent {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 570px) {
|
||||||
|
.currentLangLabel {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 540px) {
|
||||||
|
.selector {
|
||||||
|
width: calc(100vw - 48px);
|
||||||
|
right: -24px;
|
||||||
|
box-shadow: 0px 4px 180px rgb(0 0 0 / 31%);
|
||||||
|
|
||||||
|
.header {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
li {
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,99 @@
|
|||||||
|
import useLocale, { LocalesMap } from '@/utils/useLocale';
|
||||||
|
import { clsx } from 'clsx';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
import styles from './langSwitcher.module.scss';
|
||||||
|
import LanguageIcon from '@/assets/icons/language.svg';
|
||||||
|
import { CursorEffect } from '@/components/cursor';
|
||||||
|
|
||||||
|
type LangSwitcherProps = React.DetailedHTMLProps<
|
||||||
|
React.HTMLAttributes<HTMLDivElement>,
|
||||||
|
HTMLDivElement
|
||||||
|
> & {};
|
||||||
|
|
||||||
|
const locales: LocalesMap = {
|
||||||
|
ru: {
|
||||||
|
chooseHeader: 'Выберите язык',
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
chooseHeader: 'Choose language',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const localesMap: { [key: string]: string } = {
|
||||||
|
en: 'English',
|
||||||
|
ru: 'Русский',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function LangSwitcher(props: LangSwitcherProps) {
|
||||||
|
const { className: restClassName, ...restProps } = props;
|
||||||
|
const router = useRouter();
|
||||||
|
const selectorRef = useRef(null);
|
||||||
|
const buttonRef = useRef(null);
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const t = useLocale(locales);
|
||||||
|
const { pathname, asPath, query } = router;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) return;
|
||||||
|
|
||||||
|
const handleClose = (event: any) => {
|
||||||
|
setOpen(
|
||||||
|
event.composedPath().includes(selectorRef.current) ||
|
||||||
|
event.composedPath().includes(buttonRef.current)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
addEventListener('click', handleClose, false);
|
||||||
|
|
||||||
|
return () => removeEventListener('click', handleClose, false);
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
const handleSetLanguage = (locale: string) => () => {
|
||||||
|
router.push({ pathname, query }, asPath, { locale });
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={clsx(styles.langSwitcher, restClassName)} {...restProps}>
|
||||||
|
<CursorEffect
|
||||||
|
effectDistance={48}
|
||||||
|
effectForce={4}
|
||||||
|
cursorPadding={0}
|
||||||
|
className={clsx(styles.langSwitcherRoot)}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
ref={buttonRef}
|
||||||
|
className={clsx(styles.currentLanguage, open && styles.hideCurrent)}
|
||||||
|
onClick={() => setOpen(true)}
|
||||||
|
>
|
||||||
|
<LanguageIcon />{' '}
|
||||||
|
<span className={styles.currentLangLabel}>
|
||||||
|
{localesMap[router.locale || 'en']}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</CursorEffect>
|
||||||
|
<div
|
||||||
|
ref={selectorRef}
|
||||||
|
className={clsx(styles.selector, open && styles.openSelector)}
|
||||||
|
>
|
||||||
|
<h5 className={styles.header}>{t('chooseHeader')}</h5>
|
||||||
|
<ul>
|
||||||
|
{Object.keys(localesMap).map((locale) => (
|
||||||
|
<CursorEffect
|
||||||
|
key={locale}
|
||||||
|
effectForce={2}
|
||||||
|
effectDistance={36}
|
||||||
|
cursorPadding={2}
|
||||||
|
cursorBorderRadius={8}
|
||||||
|
>
|
||||||
|
<button onClick={handleSetLanguage(locale)}>
|
||||||
|
{localesMap[locale]}
|
||||||
|
</button>
|
||||||
|
</CursorEffect>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from './logo';
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
.logo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vertical {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-weight: 800;
|
||||||
|
font-size: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 640px) {
|
||||||
|
.logo {
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
width: 40px;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||