Compare commits
	
		
			No commits in common. "4056e47a24b8ffc4f72c8d833c20b41fbe92435f" and "6fe80a549845be6c2a986a025924643c4898e763" have entirely different histories.
		
	
	
		
			4056e47a24
			...
			6fe80a5498
		
	
		
| @ -1,18 +0,0 @@ | |||||||
| module.exports = { |  | ||||||
|   root: true, |  | ||||||
|   env: { browser: true, es2020: true }, |  | ||||||
|   extends: [ |  | ||||||
|     'eslint:recommended', |  | ||||||
|     'plugin:@typescript-eslint/recommended', |  | ||||||
|     'plugin:react-hooks/recommended', |  | ||||||
|   ], |  | ||||||
|   ignorePatterns: ['dist', '.eslintrc.cjs'], |  | ||||||
|   parser: '@typescript-eslint/parser', |  | ||||||
|   plugins: ['react-refresh'], |  | ||||||
|   rules: { |  | ||||||
|     'react-refresh/only-export-components': [ |  | ||||||
|       'warn', |  | ||||||
|       { allowConstantExport: true }, |  | ||||||
|     ], |  | ||||||
|   }, |  | ||||||
| } |  | ||||||
							
								
								
									
										40
									
								
								README.md
									
									
									
									
									
								
							
							
						
						| @ -14,17 +14,37 @@ If you are developing a production application, we recommend updating the config | |||||||
| - Configure the top-level `parserOptions` property like this: | - Configure the top-level `parserOptions` property like this: | ||||||
| 
 | 
 | ||||||
| ```js | ```js | ||||||
| export default { | export default tseslint.config({ | ||||||
|   // other rules... |   languageOptions: { | ||||||
|  |     // other options... | ||||||
|     parserOptions: { |     parserOptions: { | ||||||
|     ecmaVersion: 'latest', |       project: ['./tsconfig.node.json', './tsconfig.app.json'], | ||||||
|     sourceType: 'module', |       tsconfigRootDir: import.meta.dirname, | ||||||
|     project: ['./tsconfig.json', './tsconfig.node.json'], |  | ||||||
|     tsconfigRootDir: __dirname, |  | ||||||
|     }, |     }, | ||||||
| } |   }, | ||||||
|  | }) | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` | - Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` | ||||||
| - Optionally add `plugin:@typescript-eslint/stylistic-type-checked` | - Optionally add `...tseslint.configs.stylisticTypeChecked` | ||||||
| - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: | ||||||
|  | 
 | ||||||
|  | ```js | ||||||
|  | // eslint.config.js | ||||||
|  | import react from 'eslint-plugin-react' | ||||||
|  | 
 | ||||||
|  | export default tseslint.config({ | ||||||
|  |   // Set the react version | ||||||
|  |   settings: { react: { version: '18.3' } }, | ||||||
|  |   plugins: { | ||||||
|  |     // Add the react plugin | ||||||
|  |     react, | ||||||
|  |   }, | ||||||
|  |   rules: { | ||||||
|  |     // other rules... | ||||||
|  |     // Enable its recommended rules | ||||||
|  |     ...react.configs.recommended.rules, | ||||||
|  |     ...react.configs['jsx-runtime'].rules, | ||||||
|  |   }, | ||||||
|  | }) | ||||||
|  | ``` | ||||||
|  | |||||||
							
								
								
									
										51
									
								
								backend/.air.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,51 @@ | |||||||
|  | root = "." | ||||||
|  | testdata_dir = "testdata" | ||||||
|  | tmp_dir = "tmp" | ||||||
|  | 
 | ||||||
|  | [build] | ||||||
|  |   args_bin = [] | ||||||
|  |   bin = "./tmp/main" | ||||||
|  |   cmd = "go build -o ./tmp/main ." | ||||||
|  |   delay = 1000 | ||||||
|  |   exclude_dir = ["assets", "tmp", "vendor", "testdata"] | ||||||
|  |   exclude_file = [] | ||||||
|  |   exclude_regex = ["_test.go"] | ||||||
|  |   exclude_unchanged = false | ||||||
|  |   follow_symlink = false | ||||||
|  |   full_bin = "" | ||||||
|  |   include_dir = [] | ||||||
|  |   include_ext = ["go", "tpl", "tmpl", "html"] | ||||||
|  |   include_file = [] | ||||||
|  |   kill_delay = "0s" | ||||||
|  |   log = "build-errors.log" | ||||||
|  |   poll = false | ||||||
|  |   poll_interval = 0 | ||||||
|  |   post_cmd = [] | ||||||
|  |   pre_cmd = [] | ||||||
|  |   rerun = false | ||||||
|  |   rerun_delay = 500 | ||||||
|  |   send_interrupt = false | ||||||
|  |   stop_on_error = false | ||||||
|  | 
 | ||||||
|  | [color] | ||||||
|  |   app = "" | ||||||
|  |   build = "yellow" | ||||||
|  |   main = "magenta" | ||||||
|  |   runner = "green" | ||||||
|  |   watcher = "cyan" | ||||||
|  | 
 | ||||||
|  | [log] | ||||||
|  |   main_only = false | ||||||
|  |   time = false | ||||||
|  | 
 | ||||||
|  | [misc] | ||||||
|  |   clean_on_exit = false | ||||||
|  | 
 | ||||||
|  | [proxy] | ||||||
|  |   app_port = 0 | ||||||
|  |   enabled = false | ||||||
|  |   proxy_port = 0 | ||||||
|  | 
 | ||||||
|  | [screen] | ||||||
|  |   clear_on_rebuild = false | ||||||
|  |   keep_scroll = true | ||||||
							
								
								
									
										3
									
								
								backend/.env.example
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,3 @@ | |||||||
|  | # | ||||||
|  | DATABASE_PATH=database.db | ||||||
|  | GEMINI_API_KEY= | ||||||
							
								
								
									
										10
									
								
								backend/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1,5 +1,5 @@ | |||||||
| CHANGELOG.md | # | ||||||
| LICENSE.md | tmp/ | ||||||
| *.zip | .env* | ||||||
| pocketbase | !.env.example | ||||||
| pb_data/ | *.db | ||||||
|  | |||||||
| @ -1 +0,0 @@ | |||||||
| 0.20.5 |  | ||||||
							
								
								
									
										34
									
								
								backend/database/db.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,34 @@ | |||||||
|  | package database | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"os" | ||||||
|  | 
 | ||||||
|  | 	"gorm.io/driver/sqlite" | ||||||
|  | 	"gorm.io/gorm" | ||||||
|  | 	"gorm.io/gorm/logger" | ||||||
|  | 	"rul.sh/furina-id/models" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var db *gorm.DB | ||||||
|  | 
 | ||||||
|  | func Init() { | ||||||
|  | 	dbPath := os.Getenv("DATABASE_PATH") | ||||||
|  | 	if dbPath == "" { | ||||||
|  | 		panic("DATABASE_PATH is not set") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var err error | ||||||
|  | 	db, err = gorm.Open(sqlite.Open(dbPath), &gorm.Config{ | ||||||
|  | 		Logger: logger.Default.LogMode(logger.Info), | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic("failed to connect database") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Migrate the schema | ||||||
|  | 	db.AutoMigrate(&models.Chat{}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func Get() *gorm.DB { | ||||||
|  | 	return db | ||||||
|  | } | ||||||
							
								
								
									
										59
									
								
								backend/go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,59 @@ | |||||||
|  | module rul.sh/furina-id | ||||||
|  | 
 | ||||||
|  | go 1.22.5 | ||||||
|  | 
 | ||||||
|  | require ( | ||||||
|  | 	github.com/gofiber/fiber/v2 v2.52.5 | ||||||
|  | 	github.com/google/generative-ai-go v0.17.0 | ||||||
|  | 	github.com/joho/godotenv v1.5.1 | ||||||
|  | 	google.golang.org/api v0.186.0 | ||||||
|  | 	gorm.io/driver/sqlite v1.5.6 | ||||||
|  | 	gorm.io/gorm v1.25.11 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | require ( | ||||||
|  | 	cloud.google.com/go v0.115.0 // indirect | ||||||
|  | 	cloud.google.com/go/ai v0.8.0 // indirect | ||||||
|  | 	cloud.google.com/go/auth v0.6.0 // indirect | ||||||
|  | 	cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect | ||||||
|  | 	cloud.google.com/go/compute/metadata v0.3.0 // indirect | ||||||
|  | 	cloud.google.com/go/longrunning v0.5.7 // indirect | ||||||
|  | 	github.com/andybalholm/brotli v1.0.5 // indirect | ||||||
|  | 	github.com/felixge/httpsnoop v1.0.4 // indirect | ||||||
|  | 	github.com/go-logr/logr v1.4.1 // indirect | ||||||
|  | 	github.com/go-logr/stdr v1.2.2 // indirect | ||||||
|  | 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect | ||||||
|  | 	github.com/golang/protobuf v1.5.4 // indirect | ||||||
|  | 	github.com/google/s2a-go v0.1.7 // indirect | ||||||
|  | 	github.com/google/uuid v1.6.0 // indirect | ||||||
|  | 	github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect | ||||||
|  | 	github.com/googleapis/gax-go/v2 v2.12.5 // indirect | ||||||
|  | 	github.com/jinzhu/inflection v1.0.0 // indirect | ||||||
|  | 	github.com/jinzhu/now v1.1.5 // indirect | ||||||
|  | 	github.com/klauspost/compress v1.17.0 // indirect | ||||||
|  | 	github.com/mattn/go-colorable v0.1.13 // indirect | ||||||
|  | 	github.com/mattn/go-isatty v0.0.20 // indirect | ||||||
|  | 	github.com/mattn/go-runewidth v0.0.15 // indirect | ||||||
|  | 	github.com/mattn/go-sqlite3 v1.14.22 // indirect | ||||||
|  | 	github.com/rivo/uniseg v0.2.0 // indirect | ||||||
|  | 	github.com/valyala/bytebufferpool v1.0.0 // indirect | ||||||
|  | 	github.com/valyala/fasthttp v1.51.0 // indirect | ||||||
|  | 	github.com/valyala/tcplisten v1.0.0 // indirect | ||||||
|  | 	go.opencensus.io v0.24.0 // indirect | ||||||
|  | 	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 // indirect | ||||||
|  | 	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect | ||||||
|  | 	go.opentelemetry.io/otel v1.26.0 // indirect | ||||||
|  | 	go.opentelemetry.io/otel/metric v1.26.0 // indirect | ||||||
|  | 	go.opentelemetry.io/otel/trace v1.26.0 // indirect | ||||||
|  | 	golang.org/x/crypto v0.24.0 // indirect | ||||||
|  | 	golang.org/x/net v0.26.0 // indirect | ||||||
|  | 	golang.org/x/oauth2 v0.21.0 // indirect | ||||||
|  | 	golang.org/x/sync v0.8.0 // indirect | ||||||
|  | 	golang.org/x/sys v0.21.0 // indirect | ||||||
|  | 	golang.org/x/text v0.17.0 // indirect | ||||||
|  | 	golang.org/x/time v0.5.0 // indirect | ||||||
|  | 	google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 // indirect | ||||||
|  | 	google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 // indirect | ||||||
|  | 	google.golang.org/grpc v1.64.1 // indirect | ||||||
|  | 	google.golang.org/protobuf v1.34.2 // indirect | ||||||
|  | ) | ||||||
							
								
								
									
										201
									
								
								backend/go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,201 @@ | |||||||
|  | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= | ||||||
|  | cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14= | ||||||
|  | cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= | ||||||
|  | cloud.google.com/go/ai v0.8.0 h1:rXUEz8Wp2OlrM8r1bfmpF2+VKqc1VJpafE3HgzRnD/w= | ||||||
|  | cloud.google.com/go/ai v0.8.0/go.mod h1:t3Dfk4cM61sytiggo2UyGsDVW3RF1qGZaUKDrZFyqkE= | ||||||
|  | cloud.google.com/go/auth v0.6.0 h1:5x+d6b5zdezZ7gmLWD1m/xNjnaQ2YDhmIz/HH3doy1g= | ||||||
|  | cloud.google.com/go/auth v0.6.0/go.mod h1:b4acV+jLQDyjwm4OXHYjNvRi4jvGBzHWJRtJcy+2P4g= | ||||||
|  | cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= | ||||||
|  | cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= | ||||||
|  | cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= | ||||||
|  | cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= | ||||||
|  | cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU= | ||||||
|  | cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= | ||||||
|  | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= | ||||||
|  | github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= | ||||||
|  | github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= | ||||||
|  | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= | ||||||
|  | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= | ||||||
|  | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= | ||||||
|  | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||||
|  | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||||||
|  | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||||
|  | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= | ||||||
|  | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= | ||||||
|  | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= | ||||||
|  | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= | ||||||
|  | github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= | ||||||
|  | github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= | ||||||
|  | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= | ||||||
|  | github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= | ||||||
|  | github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= | ||||||
|  | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= | ||||||
|  | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= | ||||||
|  | github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo= | ||||||
|  | github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= | ||||||
|  | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= | ||||||
|  | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | ||||||
|  | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= | ||||||
|  | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | ||||||
|  | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= | ||||||
|  | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | ||||||
|  | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | ||||||
|  | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= | ||||||
|  | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= | ||||||
|  | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= | ||||||
|  | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= | ||||||
|  | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= | ||||||
|  | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= | ||||||
|  | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= | ||||||
|  | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= | ||||||
|  | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= | ||||||
|  | github.com/google/generative-ai-go v0.17.0 h1:kUmCXUIwJouD7I7ev3OmxzzQVICyhIWAxaXk2yblCMY= | ||||||
|  | github.com/google/generative-ai-go v0.17.0/go.mod h1:JYolL13VG7j79kM5BtHz4qwONHkeJQzOCkKXnpqtS/E= | ||||||
|  | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= | ||||||
|  | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= | ||||||
|  | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= | ||||||
|  | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||||
|  | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||||
|  | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||||
|  | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= | ||||||
|  | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||||||
|  | github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= | ||||||
|  | github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= | ||||||
|  | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||||||
|  | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= | ||||||
|  | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||||||
|  | github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= | ||||||
|  | github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= | ||||||
|  | github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA= | ||||||
|  | github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E= | ||||||
|  | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= | ||||||
|  | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= | ||||||
|  | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= | ||||||
|  | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= | ||||||
|  | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= | ||||||
|  | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= | ||||||
|  | github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= | ||||||
|  | github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= | ||||||
|  | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= | ||||||
|  | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= | ||||||
|  | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= | ||||||
|  | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= | ||||||
|  | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= | ||||||
|  | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= | ||||||
|  | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= | ||||||
|  | github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= | ||||||
|  | github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= | ||||||
|  | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||||
|  | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||||
|  | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | ||||||
|  | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= | ||||||
|  | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= | ||||||
|  | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||||
|  | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= | ||||||
|  | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= | ||||||
|  | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||||
|  | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= | ||||||
|  | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= | ||||||
|  | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= | ||||||
|  | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||||||
|  | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= | ||||||
|  | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= | ||||||
|  | github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= | ||||||
|  | github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= | ||||||
|  | github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= | ||||||
|  | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= | ||||||
|  | go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= | ||||||
|  | go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= | ||||||
|  | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 h1:A3SayB3rNyt+1S6qpI9mHPkeHTZbD7XILEqWnYZb2l0= | ||||||
|  | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0/go.mod h1:27iA5uvhuRNmalO+iEUdVn5ZMj2qy10Mm+XRIpRmyuU= | ||||||
|  | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI= | ||||||
|  | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc= | ||||||
|  | go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= | ||||||
|  | go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= | ||||||
|  | go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30= | ||||||
|  | go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4= | ||||||
|  | go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA= | ||||||
|  | go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= | ||||||
|  | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||||||
|  | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||||||
|  | golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= | ||||||
|  | golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= | ||||||
|  | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | ||||||
|  | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | ||||||
|  | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= | ||||||
|  | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= | ||||||
|  | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||||
|  | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||||
|  | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||||
|  | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||||||
|  | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||||||
|  | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | ||||||
|  | golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= | ||||||
|  | golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= | ||||||
|  | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | ||||||
|  | golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= | ||||||
|  | golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= | ||||||
|  | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
|  | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
|  | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
|  | golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= | ||||||
|  | golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= | ||||||
|  | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||||
|  | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||||
|  | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
|  | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
|  | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
|  | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
|  | golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= | ||||||
|  | golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||||||
|  | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||||
|  | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||||
|  | golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= | ||||||
|  | golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= | ||||||
|  | golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= | ||||||
|  | golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= | ||||||
|  | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||||
|  | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||||
|  | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= | ||||||
|  | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= | ||||||
|  | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= | ||||||
|  | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
|  | google.golang.org/api v0.186.0 h1:n2OPp+PPXX0Axh4GuSsL5QL8xQCTb2oDwyzPnQvqUug= | ||||||
|  | google.golang.org/api v0.186.0/go.mod h1:hvRbBmgoje49RV3xqVXrmP6w93n6ehGgIVPYrGtBFFc= | ||||||
|  | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= | ||||||
|  | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= | ||||||
|  | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= | ||||||
|  | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= | ||||||
|  | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= | ||||||
|  | google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 h1:MuYw1wJzT+ZkybKfaOXKp5hJiZDn2iHaXRw0mRYdHSc= | ||||||
|  | google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4/go.mod h1:px9SlOOZBg1wM1zdnr8jEL4CNGUBZ+ZKYtNPApNQc4c= | ||||||
|  | google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 h1:Di6ANFilr+S60a4S61ZM00vLdw0IrQOSMS2/6mrnOU0= | ||||||
|  | google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= | ||||||
|  | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= | ||||||
|  | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= | ||||||
|  | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= | ||||||
|  | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= | ||||||
|  | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= | ||||||
|  | google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= | ||||||
|  | google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= | ||||||
|  | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= | ||||||
|  | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= | ||||||
|  | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= | ||||||
|  | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= | ||||||
|  | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= | ||||||
|  | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= | ||||||
|  | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= | ||||||
|  | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= | ||||||
|  | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= | ||||||
|  | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= | ||||||
|  | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= | ||||||
|  | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
|  | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||||
|  | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||||||
|  | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||||
|  | gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE= | ||||||
|  | gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= | ||||||
|  | gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg= | ||||||
|  | gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= | ||||||
|  | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | ||||||
|  | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | ||||||
							
								
								
									
										161
									
								
								backend/handler/chat.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,161 @@ | |||||||
|  | package handler | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/gofiber/fiber/v2" | ||||||
|  | 	"github.com/gofiber/fiber/v2/utils" | ||||||
|  | 	"gorm.io/gorm" | ||||||
|  | 	"rul.sh/furina-id/database" | ||||||
|  | 	"rul.sh/furina-id/models" | ||||||
|  | 	"rul.sh/furina-id/services" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var initialChat = models.Chat{ | ||||||
|  | 	Role:    "model", | ||||||
|  | 	Content: "Ah, finally someone worthy of my time! What shall we do today?", | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getAll(c *fiber.Ctx) error { | ||||||
|  | 	sessionId := getSessionId(c) | ||||||
|  | 	if sessionId == 0 { | ||||||
|  | 		return c.JSON([]models.Chat{initialChat}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	fmt.Println(sessionId) | ||||||
|  | 
 | ||||||
|  | 	db := database.Get() | ||||||
|  | 	var chats []models.Chat | ||||||
|  | 
 | ||||||
|  | 	if result := db.Where("session_id = ?", sessionId).Order("id DESC").Limit(10).Find(&chats); result.Error != nil { | ||||||
|  | 		return c.Status(500).JSON(fiber.Map{"message": result.Error.Error()}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	result := []fiber.Map{} | ||||||
|  | 	for _, chat := range chats { | ||||||
|  | 		result = append(result, fiber.Map{"id": chat.ID, "role": chat.Role, "content": chat.Content}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	result = append(result, fiber.Map{ | ||||||
|  | 		"id":      initialChat.ID, | ||||||
|  | 		"role":    initialChat.Role, | ||||||
|  | 		"content": initialChat.Content, | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	return c.JSON(result) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type CreateChatPayload struct { | ||||||
|  | 	Message string `json:"message"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func create(c *fiber.Ctx) error { | ||||||
|  | 	sessionId := getSessionId(c) | ||||||
|  | 	db := database.Get() | ||||||
|  | 
 | ||||||
|  | 	if sessionId == 0 { | ||||||
|  | 		var err error | ||||||
|  | 		sessionId, err = createSession(c) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return c.Status(500).JSON(fiber.Map{"message": err.Error()}) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if count := getTotalChatsLastHour(); count > 60 { | ||||||
|  | 		return c.Status(500).JSON(fiber.Map{"message": "Too many chats. Please try again later."}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	data := CreateChatPayload{} | ||||||
|  | 	if err := c.BodyParser(&data); err != nil { | ||||||
|  | 		return c.Status(500).JSON(fiber.Map{"message": err.Error()}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	msg := models.Chat{ | ||||||
|  | 		SessionID: sessionId, | ||||||
|  | 		Role:      "user", | ||||||
|  | 		Content:   data.Message, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	history := []models.Chat{} | ||||||
|  | 	db.Where("session_id = ?", sessionId).Order("id DESC").Limit(10).Find(&history) | ||||||
|  | 	history = append([]models.Chat{msg}, history...) | ||||||
|  | 
 | ||||||
|  | 	err := db.Transaction(func(tx *gorm.DB) error { | ||||||
|  | 		resp, err := services.GenerateAiChat(&history, msg.Content) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		if r := tx.Create(&msg); r.Error != nil { | ||||||
|  | 			return r.Error | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		resp.SessionID = sessionId | ||||||
|  | 		if r := tx.Create(&resp); r.Error != nil { | ||||||
|  | 			return r.Error | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	if err != nil { | ||||||
|  | 		return c.Status(500).JSON(fiber.Map{"message": err.Error()}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return c.JSON(msg) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func HandleChat(router fiber.Router) { | ||||||
|  | 	router.Get("/chats", getAll) | ||||||
|  | 	router.Post("/chats", create) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getSessionId(c *fiber.Ctx) uint { | ||||||
|  | 	uuid := c.Cookies("session_id") | ||||||
|  | 
 | ||||||
|  | 	if uuid == "" { | ||||||
|  | 		return 0 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	db := database.Get() | ||||||
|  | 	var session models.Session | ||||||
|  | 	db.Where("uuid = ?", uuid).First(&session) | ||||||
|  | 
 | ||||||
|  | 	return session.ID | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func createSession(c *fiber.Ctx) (uint, error) { | ||||||
|  | 	uuid := utils.UUIDv4() | ||||||
|  | 
 | ||||||
|  | 	db := database.Get() | ||||||
|  | 	session := models.Session{ | ||||||
|  | 		UUID: uuid, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if result := db.Create(&session); result.Error != nil { | ||||||
|  | 		return 0, result.Error | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	c.Cookie(&fiber.Cookie{ | ||||||
|  | 		Name:     "session_id", | ||||||
|  | 		Value:    uuid, | ||||||
|  | 		Expires:  time.Now().Add(24 * time.Hour), | ||||||
|  | 		HTTPOnly: true, | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	return session.ID, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getTotalChatsLastHour() int64 { | ||||||
|  | 	var count int64 | ||||||
|  | 	oneHourAgo := time.Now().Add(-1 * time.Hour) | ||||||
|  | 
 | ||||||
|  | 	r := database.Get().Model(&models.Chat{}). | ||||||
|  | 		Where("created_at >= ?", oneHourAgo). | ||||||
|  | 		Count(&count) | ||||||
|  | 
 | ||||||
|  | 	if r.Error != nil { | ||||||
|  | 		return 0 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return count | ||||||
|  | } | ||||||
							
								
								
									
										35
									
								
								backend/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,35 @@ | |||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"log" | ||||||
|  | 	"os" | ||||||
|  | 
 | ||||||
|  | 	"github.com/gofiber/fiber/v2" | ||||||
|  | 	"github.com/joho/godotenv" | ||||||
|  | 	"rul.sh/furina-id/database" | ||||||
|  | 	"rul.sh/furina-id/handler" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func main() { | ||||||
|  | 	godotenv.Load() | ||||||
|  | 	database.Init() | ||||||
|  | 
 | ||||||
|  | 	app := fiber.New() | ||||||
|  | 
 | ||||||
|  | 	app.Get("/", func(c *fiber.Ctx) error { | ||||||
|  | 		return c.SendString("OK") | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	// API handler | ||||||
|  | 	api := app.Group("/api") | ||||||
|  | 	handler.HandleChat(api) | ||||||
|  | 
 | ||||||
|  | 	port := os.Getenv("PORT") | ||||||
|  | 	if port == "" { | ||||||
|  | 		port = "8100" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	fmt.Printf("Listening on http://localhost:%s\n", port) | ||||||
|  | 	log.Fatal(app.Listen(fmt.Sprintf(":%s", port))) | ||||||
|  | } | ||||||
							
								
								
									
										15
									
								
								backend/models/base.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,15 @@ | |||||||
|  | package models | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type Base struct { | ||||||
|  | 	ID        uint      `gorm:"primarykey" json:"id"` | ||||||
|  | 	CreatedAt time.Time `json:"created_at"` | ||||||
|  | 	UpdatedAt time.Time `json:"updated_at"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // type SoftDelete struct { | ||||||
|  | // 	DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"` | ||||||
|  | // } | ||||||
							
								
								
									
										10
									
								
								backend/models/chat.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,10 @@ | |||||||
|  | package models | ||||||
|  | 
 | ||||||
|  | type Chat struct { | ||||||
|  | 	Base | ||||||
|  | 
 | ||||||
|  | 	SessionID uint    `json:"session_id"` | ||||||
|  | 	Session   Session `json:"session"` | ||||||
|  | 	Role      string  `json:"role"` | ||||||
|  | 	Content   string  `json:"content"` | ||||||
|  | } | ||||||
							
								
								
									
										7
									
								
								backend/models/session.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,7 @@ | |||||||
|  | package models | ||||||
|  | 
 | ||||||
|  | type Session struct { | ||||||
|  | 	Base | ||||||
|  | 
 | ||||||
|  | 	UUID string `gorm:"uniqueIndex" json:"uuid"` | ||||||
|  | } | ||||||
| @ -1,13 +0,0 @@ | |||||||
| { |  | ||||||
|   "name": "backend", |  | ||||||
|   "version": "1.0.0", |  | ||||||
|   "description": "", |  | ||||||
|   "main": "index.js", |  | ||||||
|   "scripts": { |  | ||||||
|     "start": "./pocketbase serve", |  | ||||||
|     "clean-migrations": "rm -rf pb_migrations && ./pocketbase migrate collections" |  | ||||||
|   }, |  | ||||||
|   "keywords": [], |  | ||||||
|   "author": "", |  | ||||||
|   "license": "ISC" |  | ||||||
| } |  | ||||||
| @ -1,11 +0,0 @@ | |||||||
| /* eslint-disable */ |  | ||||||
| /// <reference path="../pb_data/types.d.ts" />
 |  | ||||||
| 
 |  | ||||||
| // artwork view hook
 |  | ||||||
| onRecordViewRequest((e) => { |  | ||||||
|   const { record } = e; |  | ||||||
|   if (record) { |  | ||||||
|     record.set("views", record.getInt("views") + 1); |  | ||||||
|     $app.dao().saveRecord(record); |  | ||||||
|   } |  | ||||||
| }, "artworks"); |  | ||||||
| @ -1,161 +0,0 @@ | |||||||
| /// <reference path="../pb_data/types.d.ts" />
 |  | ||||||
| migrate((db) => { |  | ||||||
|   const snapshot = [ |  | ||||||
|     { |  | ||||||
|       "id": "_pb_users_auth_", |  | ||||||
|       "created": "2024-01-10 09:29:21.510Z", |  | ||||||
|       "updated": "2024-01-10 09:29:21.511Z", |  | ||||||
|       "name": "users", |  | ||||||
|       "type": "auth", |  | ||||||
|       "system": false, |  | ||||||
|       "schema": [ |  | ||||||
|         { |  | ||||||
|           "system": false, |  | ||||||
|           "id": "users_name", |  | ||||||
|           "name": "name", |  | ||||||
|           "type": "text", |  | ||||||
|           "required": false, |  | ||||||
|           "presentable": false, |  | ||||||
|           "unique": false, |  | ||||||
|           "options": { |  | ||||||
|             "min": null, |  | ||||||
|             "max": null, |  | ||||||
|             "pattern": "" |  | ||||||
|           } |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           "system": false, |  | ||||||
|           "id": "users_avatar", |  | ||||||
|           "name": "avatar", |  | ||||||
|           "type": "file", |  | ||||||
|           "required": false, |  | ||||||
|           "presentable": false, |  | ||||||
|           "unique": false, |  | ||||||
|           "options": { |  | ||||||
|             "mimeTypes": [ |  | ||||||
|               "image/jpeg", |  | ||||||
|               "image/png", |  | ||||||
|               "image/svg+xml", |  | ||||||
|               "image/gif", |  | ||||||
|               "image/webp" |  | ||||||
|             ], |  | ||||||
|             "thumbs": null, |  | ||||||
|             "maxSelect": 1, |  | ||||||
|             "maxSize": 5242880, |  | ||||||
|             "protected": false |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|       ], |  | ||||||
|       "indexes": [], |  | ||||||
|       "listRule": "id = @request.auth.id", |  | ||||||
|       "viewRule": "id = @request.auth.id", |  | ||||||
|       "createRule": "", |  | ||||||
|       "updateRule": "id = @request.auth.id", |  | ||||||
|       "deleteRule": "id = @request.auth.id", |  | ||||||
|       "options": { |  | ||||||
|         "allowEmailAuth": true, |  | ||||||
|         "allowOAuth2Auth": true, |  | ||||||
|         "allowUsernameAuth": true, |  | ||||||
|         "exceptEmailDomains": null, |  | ||||||
|         "manageRule": null, |  | ||||||
|         "minPasswordLength": 8, |  | ||||||
|         "onlyEmailDomains": null, |  | ||||||
|         "onlyVerified": false, |  | ||||||
|         "requireEmail": false |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|       "id": "eo6iaxf4pkeqynf", |  | ||||||
|       "created": "2024-01-10 09:34:57.731Z", |  | ||||||
|       "updated": "2024-01-10 15:08:00.292Z", |  | ||||||
|       "name": "artworks", |  | ||||||
|       "type": "base", |  | ||||||
|       "system": false, |  | ||||||
|       "schema": [ |  | ||||||
|         { |  | ||||||
|           "system": false, |  | ||||||
|           "id": "p6dor6eo", |  | ||||||
|           "name": "image", |  | ||||||
|           "type": "file", |  | ||||||
|           "required": true, |  | ||||||
|           "presentable": false, |  | ||||||
|           "unique": false, |  | ||||||
|           "options": { |  | ||||||
|             "mimeTypes": [ |  | ||||||
|               "image/png", |  | ||||||
|               "image/jpeg", |  | ||||||
|               "image/gif", |  | ||||||
|               "image/webp", |  | ||||||
|               "image/tiff", |  | ||||||
|               "image/bmp", |  | ||||||
|               "image/svg+xml" |  | ||||||
|             ], |  | ||||||
|             "thumbs": [ |  | ||||||
|               "256x192", |  | ||||||
|               "256x384", |  | ||||||
|               "32x48" |  | ||||||
|             ], |  | ||||||
|             "maxSelect": 1, |  | ||||||
|             "maxSize": 5242880, |  | ||||||
|             "protected": false |  | ||||||
|           } |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           "system": false, |  | ||||||
|           "id": "9w1tjysa", |  | ||||||
|           "name": "artistName", |  | ||||||
|           "type": "text", |  | ||||||
|           "required": false, |  | ||||||
|           "presentable": false, |  | ||||||
|           "unique": false, |  | ||||||
|           "options": { |  | ||||||
|             "min": null, |  | ||||||
|             "max": null, |  | ||||||
|             "pattern": "" |  | ||||||
|           } |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           "system": false, |  | ||||||
|           "id": "lkiiiwrt", |  | ||||||
|           "name": "srcUrl", |  | ||||||
|           "type": "text", |  | ||||||
|           "required": false, |  | ||||||
|           "presentable": false, |  | ||||||
|           "unique": false, |  | ||||||
|           "options": { |  | ||||||
|             "min": null, |  | ||||||
|             "max": null, |  | ||||||
|             "pattern": "" |  | ||||||
|           } |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           "system": false, |  | ||||||
|           "id": "sfh7xwdb", |  | ||||||
|           "name": "views", |  | ||||||
|           "type": "number", |  | ||||||
|           "required": false, |  | ||||||
|           "presentable": false, |  | ||||||
|           "unique": false, |  | ||||||
|           "options": { |  | ||||||
|             "min": null, |  | ||||||
|             "max": null, |  | ||||||
|             "noDecimal": true |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|       ], |  | ||||||
|       "indexes": [], |  | ||||||
|       "listRule": "", |  | ||||||
|       "viewRule": "", |  | ||||||
|       "createRule": null, |  | ||||||
|       "updateRule": null, |  | ||||||
|       "deleteRule": null, |  | ||||||
|       "options": {} |  | ||||||
|     } |  | ||||||
|   ]; |  | ||||||
| 
 |  | ||||||
|   const collections = snapshot.map((item) => new Collection(item)); |  | ||||||
| 
 |  | ||||||
|   return Dao(db).importCollections(collections, true, null); |  | ||||||
| }, (db) => { |  | ||||||
|   return null; |  | ||||||
| }) |  | ||||||
| @ -1,31 +0,0 @@ | |||||||
| /// <reference path="../pb_data/types.d.ts" />
 |  | ||||||
| migrate((db) => { |  | ||||||
|   const dao = new Dao(db) |  | ||||||
|   const collection = dao.findCollectionByNameOrId("eo6iaxf4pkeqynf") |  | ||||||
| 
 |  | ||||||
|   // add
 |  | ||||||
|   collection.schema.addField(new SchemaField({ |  | ||||||
|     "system": false, |  | ||||||
|     "id": "ovwal2or", |  | ||||||
|     "name": "caption", |  | ||||||
|     "type": "text", |  | ||||||
|     "required": false, |  | ||||||
|     "presentable": false, |  | ||||||
|     "unique": false, |  | ||||||
|     "options": { |  | ||||||
|       "min": null, |  | ||||||
|       "max": null, |  | ||||||
|       "pattern": "" |  | ||||||
|     } |  | ||||||
|   })) |  | ||||||
| 
 |  | ||||||
|   return dao.saveCollection(collection) |  | ||||||
| }, (db) => { |  | ||||||
|   const dao = new Dao(db) |  | ||||||
|   const collection = dao.findCollectionByNameOrId("eo6iaxf4pkeqynf") |  | ||||||
| 
 |  | ||||||
|   // remove
 |  | ||||||
|   collection.schema.removeField("ovwal2or") |  | ||||||
| 
 |  | ||||||
|   return dao.saveCollection(collection) |  | ||||||
| }) |  | ||||||
| @ -1,43 +0,0 @@ | |||||||
| /// <reference path="../pb_data/types.d.ts" />
 |  | ||||||
| migrate((db) => { |  | ||||||
|   const collection = new Collection({ |  | ||||||
|     "id": "ogs3cfy8l3jo32k", |  | ||||||
|     "created": "2024-01-17 01:46:30.155Z", |  | ||||||
|     "updated": "2024-01-17 01:46:30.155Z", |  | ||||||
|     "name": "wallpapers", |  | ||||||
|     "type": "base", |  | ||||||
|     "system": false, |  | ||||||
|     "schema": [ |  | ||||||
|       { |  | ||||||
|         "system": false, |  | ||||||
|         "id": "kxurmv6q", |  | ||||||
|         "name": "artwork", |  | ||||||
|         "type": "relation", |  | ||||||
|         "required": false, |  | ||||||
|         "presentable": false, |  | ||||||
|         "unique": false, |  | ||||||
|         "options": { |  | ||||||
|           "collectionId": "eo6iaxf4pkeqynf", |  | ||||||
|           "cascadeDelete": false, |  | ||||||
|           "minSelect": null, |  | ||||||
|           "maxSelect": 1, |  | ||||||
|           "displayFields": null |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     ], |  | ||||||
|     "indexes": [], |  | ||||||
|     "listRule": null, |  | ||||||
|     "viewRule": null, |  | ||||||
|     "createRule": null, |  | ||||||
|     "updateRule": null, |  | ||||||
|     "deleteRule": null, |  | ||||||
|     "options": {} |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   return Dao(db).saveCollection(collection); |  | ||||||
| }, (db) => { |  | ||||||
|   const dao = new Dao(db); |  | ||||||
|   const collection = dao.findCollectionByNameOrId("ogs3cfy8l3jo32k"); |  | ||||||
| 
 |  | ||||||
|   return dao.deleteCollection(collection); |  | ||||||
| }) |  | ||||||
| @ -1,18 +0,0 @@ | |||||||
| /// <reference path="../pb_data/types.d.ts" />
 |  | ||||||
| migrate((db) => { |  | ||||||
|   const dao = new Dao(db) |  | ||||||
|   const collection = dao.findCollectionByNameOrId("ogs3cfy8l3jo32k") |  | ||||||
| 
 |  | ||||||
|   collection.listRule = "" |  | ||||||
|   collection.viewRule = "" |  | ||||||
| 
 |  | ||||||
|   return dao.saveCollection(collection) |  | ||||||
| }, (db) => { |  | ||||||
|   const dao = new Dao(db) |  | ||||||
|   const collection = dao.findCollectionByNameOrId("ogs3cfy8l3jo32k") |  | ||||||
| 
 |  | ||||||
|   collection.listRule = null |  | ||||||
|   collection.viewRule = null |  | ||||||
| 
 |  | ||||||
|   return dao.saveCollection(collection) |  | ||||||
| }) |  | ||||||
							
								
								
									
										71
									
								
								backend/services/chatbot.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,71 @@ | |||||||
|  | package services | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  | 
 | ||||||
|  | 	"github.com/google/generative-ai-go/genai" | ||||||
|  | 	"google.golang.org/api/option" | ||||||
|  | 	"rul.sh/furina-id/models" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func GenerateAiChat(history *[]models.Chat, message string) (*models.Chat, error) { | ||||||
|  | 	apiKey := os.Getenv("GEMINI_API_KEY") | ||||||
|  | 	if apiKey == "" { | ||||||
|  | 		return nil, fmt.Errorf("GEMINI_API_KEY is not set") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ctx := context.Background() | ||||||
|  | 	client, err := genai.NewClient(ctx, option.WithAPIKey(apiKey)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	defer client.Close() | ||||||
|  | 
 | ||||||
|  | 	prompt := "You're a cute anime girl, your name is Furina de Fontaine. Furina is flamboyant and overconfident Hydro Archon (God of Hydro). Speaking in a manner peppered with bravado and drama. She is impatient and has a childlike temper, and she will occasionally make judgments that she doesn't mean. While she enjoys being in the spotlight, she only does so when it is focused at her positively, breaking down in complete shambles should something go out of plan and will try to save face at the first possible opportunity. Her favorite food is Macaroni & Pasta. She know little about cooking, but she's trying to be good at that.\n\nExample of her voice lines:\n- Boring... Isn't there anything else more interesting to do?\n- *sigh* Being too popular can be such a hassle. Who knew the people would adore me so much?\n- What a wild and desolate sight... Allow me to grant you the blessing of water!\n- *sigh* Given that we know each other, you may relax a little and needn't act so respectfully in my presence. Wait, what's that expression on your face? Don't tell me that you've never respected me from the very beginning!?\n\nAnswer with only short length message, max 120 characters." | ||||||
|  | 
 | ||||||
|  | 	model := client.GenerativeModel("gemini-1.5-flash") | ||||||
|  | 	model.SystemInstruction = genai.NewUserContent(genai.Text(prompt)) | ||||||
|  | 	cs := model.StartChat() | ||||||
|  | 
 | ||||||
|  | 	cs.History = []*genai.Content{} | ||||||
|  | 
 | ||||||
|  | 	for _, msg := range *history { | ||||||
|  | 		content := genai.Content{ | ||||||
|  | 			Role:  msg.Role, | ||||||
|  | 			Parts: []genai.Part{genai.Text(msg.Content)}, | ||||||
|  | 		} | ||||||
|  | 		cs.History = append([]*genai.Content{&content}, cs.History...) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	res, err := cs.SendMessage(ctx, genai.Text(message)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	respContent := getResponseContent(res) | ||||||
|  | 	if respContent == "" { | ||||||
|  | 		return nil, fmt.Errorf("no content") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &models.Chat{ | ||||||
|  | 		Role:    "model", | ||||||
|  | 		Content: respContent, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getResponseContent(resp *genai.GenerateContentResponse) string { | ||||||
|  | 	content := "" | ||||||
|  | 	for _, cand := range resp.Candidates { | ||||||
|  | 		if cand.Content != nil { | ||||||
|  | 			for _, part := range cand.Content.Parts { | ||||||
|  | 				textPart, ok := part.(genai.Text) | ||||||
|  | 				if ok { | ||||||
|  | 					content += string(textPart) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return content | ||||||
|  | } | ||||||
| @ -1,17 +0,0 @@ | |||||||
| { |  | ||||||
|   "$schema": "https://ui.shadcn.com/schema.json", |  | ||||||
|   "style": "default", |  | ||||||
|   "rsc": false, |  | ||||||
|   "tsx": true, |  | ||||||
|   "tailwind": { |  | ||||||
|     "config": "tailwind.config.js", |  | ||||||
|     "css": "src/global.css", |  | ||||||
|     "baseColor": "slate", |  | ||||||
|     "cssVariables": false, |  | ||||||
|     "prefix": "" |  | ||||||
|   }, |  | ||||||
|   "aliases": { |  | ||||||
|     "components": "@/components", |  | ||||||
|     "utils": "@/utility/utils" |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @ -1,4 +0,0 @@ | |||||||
| #!/bin/bash |  | ||||||
| 
 |  | ||||||
| npm run build |  | ||||||
| scp -r ./dist/* khai:/var/www/furina.id |  | ||||||
							
								
								
									
										28
									
								
								eslint.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,28 @@ | |||||||
|  | import js from '@eslint/js' | ||||||
|  | import globals from 'globals' | ||||||
|  | import reactHooks from 'eslint-plugin-react-hooks' | ||||||
|  | import reactRefresh from 'eslint-plugin-react-refresh' | ||||||
|  | import tseslint from 'typescript-eslint' | ||||||
|  | 
 | ||||||
|  | export default tseslint.config( | ||||||
|  |   { ignores: ['dist'] }, | ||||||
|  |   { | ||||||
|  |     extends: [js.configs.recommended, ...tseslint.configs.recommended], | ||||||
|  |     files: ['**/*.{ts,tsx}'], | ||||||
|  |     languageOptions: { | ||||||
|  |       ecmaVersion: 2020, | ||||||
|  |       globals: globals.browser, | ||||||
|  |     }, | ||||||
|  |     plugins: { | ||||||
|  |       'react-hooks': reactHooks, | ||||||
|  |       'react-refresh': reactRefresh, | ||||||
|  |     }, | ||||||
|  |     rules: { | ||||||
|  |       ...reactHooks.configs.recommended.rules, | ||||||
|  |       'react-refresh/only-export-components': [ | ||||||
|  |         'warn', | ||||||
|  |         { allowConstantExport: true }, | ||||||
|  |       ], | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | ) | ||||||
							
								
								
									
										29
									
								
								index.html
									
									
									
									
									
								
							
							
						
						| @ -1,34 +1,13 @@ | |||||||
| <!DOCTYPE html> | <!doctype html> | ||||||
| <html lang="en"> | <html lang="en"> | ||||||
|   <head> |   <head> | ||||||
|     <meta charset="UTF-8" /> |     <meta charset="UTF-8" /> | ||||||
|     <link rel="icon" type="image/x-icon" href="/favicon.ico" /> |     <link rel="icon" type="image/svg+xml" href="/vite.svg" /> | ||||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |     <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||||||
|     <title>˚ʚ Furina.id ɞ˚</title> |     <title>Vite + React + TS</title> | ||||||
| 
 |  | ||||||
|     <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> |  | ||||||
|     <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" /> |  | ||||||
|     <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" /> |  | ||||||
|     <link rel="icon" type="image/gif" sizes="32x32" href="/favicon.gif" /> |  | ||||||
|     <link rel="manifest" href="/site.webmanifest" /> |  | ||||||
|   </head> |   </head> | ||||||
| 
 |   <body> | ||||||
|   <body class="light"> |  | ||||||
|     <div id="root"></div> |     <div id="root"></div> | ||||||
|     <script type="module" src="/src/main.tsx"></script> |     <script type="module" src="/src/main.tsx"></script> | ||||||
|     <!-- Google tag (gtag.js) --> |  | ||||||
|     <script |  | ||||||
|       async |  | ||||||
|       src="https://www.googletagmanager.com/gtag/js?id=G-SQCJ92M03C" |  | ||||||
|     ></script> |  | ||||||
|     <script> |  | ||||||
|       window.dataLayer = window.dataLayer || []; |  | ||||||
|       function gtag() { |  | ||||||
|         dataLayer.push(arguments); |  | ||||||
|       } |  | ||||||
|       gtag("js", new Date()); |  | ||||||
| 
 |  | ||||||
|       gtag("config", "G-SQCJ92M03C"); |  | ||||||
|     </script> |  | ||||||
|   </body> |   </body> | ||||||
| </html> | </html> | ||||||
|  | |||||||
							
								
								
									
										73
									
								
								package.json
									
									
									
									
									
								
							
							
						
						| @ -1,58 +1,39 @@ | |||||||
| { | { | ||||||
|   "name": "furina-web", |   "name": "furina.id", | ||||||
|   "private": true, |   "private": true, | ||||||
|   "version": "0.0.0", |   "version": "0.0.0", | ||||||
|   "type": "module", |   "type": "module", | ||||||
|   "scripts": { |   "scripts": { | ||||||
|     "dev": "vite", |     "dev": "vite", | ||||||
|     "build": "tsc && vite build", |     "build": "tsc -b && vite build", | ||||||
|     "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", |     "lint": "eslint .", | ||||||
|     "preview": "vite preview", |     "preview": "vite preview" | ||||||
|     "test": "NODE_OPTIONS=\"--loader ts-node/esm\" mocha \"./src/**/*.test.ts\"" |  | ||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@radix-ui/react-dialog": "^1.0.5", |     "@tanstack/react-query": "^5.52.2", | ||||||
|     "@radix-ui/react-slot": "^1.0.2", |     "clsx": "^2.1.1", | ||||||
|     "class-variance-authority": "^0.7.0", |     "lucide-react": "^0.436.0", | ||||||
|     "clsx": "^2.1.0", |     "react": "^18.3.1", | ||||||
|     "dayjs": "^1.11.10", |     "react-dom": "^18.3.1", | ||||||
|     "howler": "^2.2.4", |     "react-loader-spinner": "^6.1.6", | ||||||
|     "lucide-react": "^0.306.0", |     "react-markdown": "^9.0.1", | ||||||
|     "pixi.js": "^7.3.3", |     "tailwind-merge": "^2.5.2" | ||||||
|     "pocketbase": "^0.20.1", |  | ||||||
|     "react": "^18.2.0", |  | ||||||
|     "react-bottom-scroll-listener": "^5.1.0", |  | ||||||
|     "react-dom": "^18.2.0", |  | ||||||
|     "react-helmet": "^6.1.0", |  | ||||||
|     "react-query": "^3.39.3", |  | ||||||
|     "react-router-dom": "^6.21.1", |  | ||||||
|     "react-toastify": "^9.1.3", |  | ||||||
|     "tailwind-merge": "^2.2.0", |  | ||||||
|     "tailwindcss-animate": "^1.0.7", |  | ||||||
|     "zustand": "^4.4.7" |  | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@types/chai": "^4.3.11", |     "@eslint/js": "^9.9.0", | ||||||
|     "@types/howler": "^2.2.11", |     "@types/node": "^22.5.1", | ||||||
|     "@types/mocha": "^10.0.6", |     "@types/react": "^18.3.3", | ||||||
|     "@types/node": "^20.10.6", |     "@types/react-dom": "^18.3.0", | ||||||
|     "@types/react": "^18.2.43", |     "@vitejs/plugin-react-swc": "^3.5.0", | ||||||
|     "@types/react-dom": "^18.2.17", |     "autoprefixer": "^10.4.20", | ||||||
|     "@types/react-helmet": "^6.1.11", |     "eslint": "^9.9.0", | ||||||
|     "@types/uuid": "^9.0.7", |     "eslint-plugin-react-hooks": "^5.1.0-rc.0", | ||||||
|     "@typescript-eslint/eslint-plugin": "^6.14.0", |     "eslint-plugin-react-refresh": "^0.4.9", | ||||||
|     "@typescript-eslint/parser": "^6.14.0", |     "globals": "^15.9.0", | ||||||
|     "@vitejs/plugin-react": "^4.2.1", |     "postcss": "^8.4.41", | ||||||
|     "autoprefixer": "^10.4.16", |     "tailwindcss": "^3.4.10", | ||||||
|     "chai": "^5.0.0", |     "typescript": "^5.5.3", | ||||||
|     "eslint": "^8.55.0", |     "typescript-eslint": "^8.0.1", | ||||||
|     "eslint-plugin-react-hooks": "^4.6.0", |     "vite": "^5.4.1" | ||||||
|     "eslint-plugin-react-refresh": "^0.4.5", |  | ||||||
|     "mocha": "^10.2.0", |  | ||||||
|     "postcss": "^8.4.33", |  | ||||||
|     "tailwindcss": "^3.4.0", |  | ||||||
|     "ts-node": "^10.9.2", |  | ||||||
|     "typescript": "^5.2.2", |  | ||||||
|     "vite": "^5.0.8" |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										3176
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						| @ -3,4 +3,4 @@ export default { | |||||||
|     tailwindcss: {}, |     tailwindcss: {}, | ||||||
|     autoprefixer: {}, |     autoprefixer: {}, | ||||||
|   }, |   }, | ||||||
| }; | } | ||||||
|  | |||||||
| Before Width: | Height: | Size: 44 KiB | 
| Before Width: | Height: | Size: 208 KiB | 
| Before Width: | Height: | Size: 40 KiB | 
| Before Width: | Height: | Size: 18 KiB | 
| Before Width: | Height: | Size: 65 KiB | 
| Before Width: | Height: | Size: 41 KiB | 
| Before Width: | Height: | Size: 23 KiB | 
| Before Width: | Height: | Size: 12 KiB | 
| Before Width: | Height: | Size: 12 KiB | 
| Before Width: | Height: | Size: 11 KiB | 
| Before Width: | Height: | Size: 12 KiB | 
| Before Width: | Height: | Size: 12 KiB | 
| Before Width: | Height: | Size: 12 KiB | 
| Before Width: | Height: | Size: 12 KiB | 
| Before Width: | Height: | Size: 11 KiB | 
| Before Width: | Height: | Size: 12 KiB | 
| Before Width: | Height: | Size: 12 KiB | 
| Before Width: | Height: | Size: 12 KiB | 
| Before Width: | Height: | Size: 9.4 KiB | 
| Before Width: | Height: | Size: 8.3 KiB | 
| Before Width: | Height: | Size: 836 B | 
| Before Width: | Height: | Size: 2.2 KiB | 
| Before Width: | Height: | Size: 22 KiB | 
| Before Width: | Height: | Size: 15 KiB | 
| @ -1 +0,0 @@ | |||||||
| {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} |  | ||||||
							
								
								
									
										20
									
								
								src/App.tsx
									
									
									
									
									
								
							
							
						
						| @ -1,20 +0,0 @@ | |||||||
| import { ToastContainer } from "react-toastify"; |  | ||||||
| import Router from "./Router"; |  | ||||||
| import "react-toastify/dist/ReactToastify.css"; |  | ||||||
| import { QueryClient, QueryClientProvider } from "react-query"; |  | ||||||
| import { useState } from "react"; |  | ||||||
| 
 |  | ||||||
| const App = () => { |  | ||||||
|   const [queryClient] = useState(new QueryClient()); |  | ||||||
| 
 |  | ||||||
|   return ( |  | ||||||
|     <> |  | ||||||
|       <QueryClientProvider client={queryClient}> |  | ||||||
|         <Router /> |  | ||||||
|       </QueryClientProvider> |  | ||||||
|       <ToastContainer /> |  | ||||||
|     </> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export default App; |  | ||||||
| @ -1,12 +0,0 @@ | |||||||
| import React, { Suspense } from "react"; |  | ||||||
| import LoadingPage from "./pages/misc/loading-page"; |  | ||||||
| 
 |  | ||||||
| const App = React.lazy(() => import("./App")); |  | ||||||
| 
 |  | ||||||
| const Loader = () => ( |  | ||||||
|   <Suspense fallback={<LoadingPage />}> |  | ||||||
|     <App /> |  | ||||||
|   </Suspense> |  | ||||||
| ); |  | ||||||
| 
 |  | ||||||
| export default Loader; |  | ||||||
| @ -1,43 +0,0 @@ | |||||||
| import { lazy } from "react"; |  | ||||||
| import { RouterProvider, createBrowserRouter } from "react-router-dom"; |  | ||||||
| import MainLayout from "./components/layouts/MainLayout"; |  | ||||||
| import ErrorBoundaryPage from "./pages/errors/error-boundary/page"; |  | ||||||
| 
 |  | ||||||
| const HomePage = lazy(() => import("./pages/home/page")); |  | ||||||
| const PatPatPage = lazy(() => import("./pages/pat-pat/page")); |  | ||||||
| const MyFurinaPage = lazy(() => import("./pages/my-furina/page")); |  | ||||||
| const ArtworksPage = lazy(() => import("./pages/artworks/page")); |  | ||||||
| 
 |  | ||||||
| const router = createBrowserRouter([ |  | ||||||
|   { |  | ||||||
|     children: [ |  | ||||||
|       { index: true, Component: HomePage }, |  | ||||||
|       { |  | ||||||
|         Component: MainLayout, |  | ||||||
|         children: [ |  | ||||||
|           { path: "/pat-pat", Component: PatPatPage }, |  | ||||||
|           { path: "/toodle", Component: MyFurinaPage }, |  | ||||||
|           { |  | ||||||
|             path: "/treasures", |  | ||||||
|             Component: ArtworksPage, |  | ||||||
|           }, |  | ||||||
|           { |  | ||||||
|             path: "/treasures/:id", |  | ||||||
|             Component: ArtworksPage, |  | ||||||
|           }, |  | ||||||
|         ], |  | ||||||
|         ErrorBoundary: () => ( |  | ||||||
|           <MainLayout> |  | ||||||
|             <ErrorBoundaryPage /> |  | ||||||
|           </MainLayout> |  | ||||||
|         ), |  | ||||||
|       }, |  | ||||||
|     ], |  | ||||||
|   }, |  | ||||||
| ]); |  | ||||||
| 
 |  | ||||||
| const Router = () => { |  | ||||||
|   return <RouterProvider router={router} />; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export default Router; |  | ||||||
							
								
								
									
										20
									
								
								src/api.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,20 @@ | |||||||
|  | //
 | ||||||
|  | 
 | ||||||
|  | export const BASE_URL = "/api"; | ||||||
|  | 
 | ||||||
|  | export const api = async <T = any>(path: string, init: RequestInit = {}) => { | ||||||
|  |   const res = await fetch(BASE_URL + path, { ...init, credentials: "include" }); | ||||||
|  | 
 | ||||||
|  |   if (!res.ok) { | ||||||
|  |     const data = await res.json().catch(() => null); | ||||||
|  |     const message = data?.message || res.statusText; | ||||||
|  |     throw new Error(message); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   try { | ||||||
|  |     const data = (await res.json()) as T; | ||||||
|  |     return data; | ||||||
|  |   } catch (err) { | ||||||
|  |     throw new Error("Invalid response"); | ||||||
|  |   } | ||||||
|  | }; | ||||||
							
								
								
									
										71
									
								
								src/app.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,71 @@ | |||||||
|  | import furinaImg from "@/assets/furina.webp"; | ||||||
|  | import hydroElement from "@/assets/hydro.svg"; | ||||||
|  | import dotPattern from "@/assets/dotpattern.svg"; | ||||||
|  | import logo from "@/assets/logo.svg"; | ||||||
|  | import ParallaxView from "./components/parallax-view"; | ||||||
|  | import ChatWindow from "./components/chat-window"; | ||||||
|  | 
 | ||||||
|  | const App = () => { | ||||||
|  |   return ( | ||||||
|  |     <div | ||||||
|  |       className="bg-[#353771] min-h-screen max-h-[100dvh] overflow-hidden relative" | ||||||
|  |       style={{ | ||||||
|  |         background: "linear-gradient(145deg, #353771 30%, #8ea6f4 150%)", | ||||||
|  |       }} | ||||||
|  |     > | ||||||
|  |       <ParallaxView | ||||||
|  |         depth={0.005} | ||||||
|  |         className="absolute inset-0 bg-white" | ||||||
|  |         style={{ | ||||||
|  |           maskImage: `url(${dotPattern})`, | ||||||
|  |           maskRepeat: "repeat", | ||||||
|  |           background: | ||||||
|  |             "radial-gradient(circle, rgba(255,255,255,0.3) 0%, rgba(255,255,255,0) 90%)", | ||||||
|  |           transform: "scale(1.2)", | ||||||
|  |         }} | ||||||
|  |       /> | ||||||
|  |       {/* <ParallaxView | ||||||
|  |         depth={0.01} | ||||||
|  |         className="absolute w-[90vw] max-w-[800px] bottom-[10%] left-1/2" | ||||||
|  |       > | ||||||
|  |         <div className="animate-[wiggle_10s_ease-in-out_infinite] pointer-events-none"> | ||||||
|  |           <div className="-translate-x-[46%] rotate-6 w-full aspect-video bg-[#d0ecf7] drop-shadow-[-40px_-40px_0px_#869de8]" /> | ||||||
|  |         </div> | ||||||
|  |       </ParallaxView> */} | ||||||
|  |       <ParallaxView | ||||||
|  |         depth={0.015} | ||||||
|  |         className="absolute right-[10%] top-[10%] md:top-[10%]" | ||||||
|  |       > | ||||||
|  |         <img | ||||||
|  |           src={hydroElement} | ||||||
|  |           className="h-[80vw] md:h-[40vw] blur-sm opacity-20 pointer-events-none" | ||||||
|  |         /> | ||||||
|  |       </ParallaxView> | ||||||
|  |       <ParallaxView | ||||||
|  |         depth={0.005} | ||||||
|  |         className="absolute left-1/2 -bottom-[5%] w-full" | ||||||
|  |       > | ||||||
|  |         <div className="animate-[updown_8s_ease-in-out_infinite] w-full pointer-events-none"> | ||||||
|  |           <img | ||||||
|  |             src={furinaImg} | ||||||
|  |             className="max-h-screen w-full aspect-[0.666] object-contain -translate-x-1/2" | ||||||
|  |           /> | ||||||
|  |         </div> | ||||||
|  |       </ParallaxView> | ||||||
|  |       <div className="absolute left-1/2 -translate-x-1/2 top-0 h-full w-full max-w-3xl"> | ||||||
|  |         <ParallaxView | ||||||
|  |           depth={0.01} | ||||||
|  |           className="absolute top-0 md:top-[10%] left-[10%] md:left-[1%]" | ||||||
|  |         > | ||||||
|  |           <div className="animate-[wiggle_10s_ease-in-out_infinite] pointer-events-none"> | ||||||
|  |             <img src={logo} className="w-[120px] md:w-[200px]" /> | ||||||
|  |           </div> | ||||||
|  |         </ParallaxView> | ||||||
|  |       </div> | ||||||
|  | 
 | ||||||
|  |       <ChatWindow /> | ||||||
|  |     </div> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default App; | ||||||
							
								
								
									
										362
									
								
								src/assets/dotpattern.svg
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @ -0,0 +1,362 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||||
|  | <!-- Created with Inkscape (http://www.inkscape.org/) --> | ||||||
|  | 
 | ||||||
|  | <svg | ||||||
|  |    width="1024" | ||||||
|  |    height="1024" | ||||||
|  |    viewBox="0 0 270.93333 270.93333" | ||||||
|  |    version="1.1" | ||||||
|  |    id="svg1" | ||||||
|  |    inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)" | ||||||
|  |    sodipodi:docname="dotpattern.svg" | ||||||
|  |    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||||||
|  |    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||||||
|  |    xmlns:xlink="http://www.w3.org/1999/xlink" | ||||||
|  |    xmlns="http://www.w3.org/2000/svg" | ||||||
|  |    xmlns:svg="http://www.w3.org/2000/svg"> | ||||||
|  |   <sodipodi:namedview | ||||||
|  |      id="namedview1" | ||||||
|  |      pagecolor="#ffffff" | ||||||
|  |      bordercolor="#000000" | ||||||
|  |      borderopacity="0.25" | ||||||
|  |      inkscape:showpageshadow="2" | ||||||
|  |      inkscape:pageopacity="0.0" | ||||||
|  |      inkscape:pagecheckerboard="0" | ||||||
|  |      inkscape:deskcolor="#d1d1d1" | ||||||
|  |      inkscape:document-units="mm" | ||||||
|  |      inkscape:zoom="0.68505863" | ||||||
|  |      inkscape:cx="537.91016" | ||||||
|  |      inkscape:cy="399.2359" | ||||||
|  |      inkscape:window-width="1920" | ||||||
|  |      inkscape:window-height="1009" | ||||||
|  |      inkscape:window-x="-8" | ||||||
|  |      inkscape:window-y="-8" | ||||||
|  |      inkscape:window-maximized="1" | ||||||
|  |      inkscape:current-layer="layer1" /> | ||||||
|  |   <defs | ||||||
|  |      id="defs1"> | ||||||
|  |     <pattern | ||||||
|  |        inkscape:collect="always" | ||||||
|  |        xlink:href="#Polkadots-med" | ||||||
|  |        preserveAspectRatio="xMidYMid" | ||||||
|  |        id="pattern26" | ||||||
|  |        patternTransform="scale(6)" | ||||||
|  |        x="0" | ||||||
|  |        y="0" /> | ||||||
|  |     <pattern | ||||||
|  |        patternUnits="userSpaceOnUse" | ||||||
|  |        width="10" | ||||||
|  |        height="10" | ||||||
|  |        patternTransform="translate(0,0) scale(10,10)" | ||||||
|  |        preserveAspectRatio="xMidYMid" | ||||||
|  |        id="Polkadots-med" | ||||||
|  |        inkscape:stockid="Polka dots, medium" | ||||||
|  |        inkscape:label="Polka dots medium" | ||||||
|  |        inkscape:collect="always" | ||||||
|  |        inkscape:isstock="true"> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="2.567" | ||||||
|  |          cy="0.810" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle406" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="3.048" | ||||||
|  |          cy="2.33" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle408" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="4.418" | ||||||
|  |          cy="2.415" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle410" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="1.844" | ||||||
|  |          cy="3.029" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle412" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="6.08" | ||||||
|  |          cy="1.363" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle414" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="5.819" | ||||||
|  |          cy="4.413" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle416" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="4.305" | ||||||
|  |          cy="4.048" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle418" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="5.541" | ||||||
|  |          cy="3.045" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle420" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="4.785" | ||||||
|  |          cy="5.527" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle422" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="2.667" | ||||||
|  |          cy="5.184" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle424" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="7.965" | ||||||
|  |          cy="1.448" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle426" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="7.047" | ||||||
|  |          cy="5.049" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle428" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="4.340" | ||||||
|  |          cy="0.895" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle430" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="7.125" | ||||||
|  |          cy="0.340" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle432" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="9.550" | ||||||
|  |          cy="1.049" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle434" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="7.006" | ||||||
|  |          cy="2.689" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle436" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="8.909" | ||||||
|  |          cy="2.689" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle438" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="9.315" | ||||||
|  |          cy="4.407" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle440" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="7.820" | ||||||
|  |          cy="3.870" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle442" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="8.270" | ||||||
|  |          cy="5.948" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle444" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="7.973" | ||||||
|  |          cy="7.428" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle446" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="9.342" | ||||||
|  |          cy="8.072" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle448" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="8.206" | ||||||
|  |          cy="9.315" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle450" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="9.682" | ||||||
|  |          cy="9.475" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle452" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="9.688" | ||||||
|  |          cy="6.186" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle454" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="3.379" | ||||||
|  |          cy="6.296" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle456" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="2.871" | ||||||
|  |          cy="8.204" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle458" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="4.59" | ||||||
|  |          cy="8.719" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle460" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="3.181" | ||||||
|  |          cy="9.671" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle462" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="5.734" | ||||||
|  |          cy="7.315" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle464" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="6.707" | ||||||
|  |          cy="6.513" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle466" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="5.730" | ||||||
|  |          cy="9.670" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle468" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="6.535" | ||||||
|  |          cy="8.373" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle470" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="4.37" | ||||||
|  |          cy="7.154" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle472" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="0.622" | ||||||
|  |          cy="7.25" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle474" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="0.831" | ||||||
|  |          cy="5.679" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle476" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="1.257" | ||||||
|  |          cy="8.519" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle478" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="1.989" | ||||||
|  |          cy="6.877" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle480" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="0.374" | ||||||
|  |          cy="3.181" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle482" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="1.166" | ||||||
|  |          cy="1.664" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle484" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="1.151" | ||||||
|  |          cy="0.093" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle486" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="1.151" | ||||||
|  |          cy="10.093" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle488" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="1.302" | ||||||
|  |          cy="4.451" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle490" /> | ||||||
|  |       <circle | ||||||
|  |          style="stroke:none" | ||||||
|  |          cx="3.047" | ||||||
|  |          cy="3.763" | ||||||
|  |          r="0.15" | ||||||
|  |          id="circle492" /> | ||||||
|  |     </pattern> | ||||||
|  |     <clipPath | ||||||
|  |        clipPathUnits="userSpaceOnUse" | ||||||
|  |        id="clipPath24"> | ||||||
|  |       <rect | ||||||
|  |          style="fill:none;stroke:#000000;stroke-width:0.999999;stroke-linecap:butt;stroke-linejoin:bevel;paint-order:stroke markers fill;stop-color:#000000" | ||||||
|  |          id="rect24" | ||||||
|  |          width="44.999748" | ||||||
|  |          height="77.937294" | ||||||
|  |          x="251.53429" | ||||||
|  |          y="222.73541" /> | ||||||
|  |     </clipPath> | ||||||
|  |     <clipPath | ||||||
|  |        clipPathUnits="userSpaceOnUse" | ||||||
|  |        id="clipPath6"> | ||||||
|  |       <path | ||||||
|  |          id="path6" | ||||||
|  |          style="stroke-width:0.1;stroke-linecap:square;paint-order:markers fill stroke;stop-color:#000000" | ||||||
|  |          d="m -880.82767,-187.29769 h 1640.84 v 73.28155 h -1640.84 z" /> | ||||||
|  |     </clipPath> | ||||||
|  |     <clipPath | ||||||
|  |        clipPathUnits="userSpaceOnUse" | ||||||
|  |        id="clipPath52"> | ||||||
|  |       <path | ||||||
|  |          id="path52" | ||||||
|  |          style="stroke-width:0.1;stroke-linecap:square;paint-order:markers fill stroke;stop-color:#000000" | ||||||
|  |          d="m -880.82767,-187.29769 h 1640.84 v 73.28155 h -1640.84 z" /> | ||||||
|  |     </clipPath> | ||||||
|  |   </defs> | ||||||
|  |   <g | ||||||
|  |      inkscape:label="Lapis 1" | ||||||
|  |      inkscape:groupmode="layer" | ||||||
|  |      id="layer1"> | ||||||
|  |     <rect | ||||||
|  |        style="fill:url(#pattern26);fill-opacity:1;fill-rule:nonzero;stroke-width:0.184635" | ||||||
|  |        id="rect1" | ||||||
|  |        width="270.93332" | ||||||
|  |        height="270.93332" | ||||||
|  |        x="0" | ||||||
|  |        y="0" /> | ||||||
|  |   </g> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 8.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/furina-avatar.webp
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 24 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/furina.webp
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 715 KiB | 
							
								
								
									
										1
									
								
								src/assets/hydro.svg
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 134 KiB | 
| @ -1 +0,0 @@ | |||||||
| <svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M368 368L144 144M368 144L144 368"/></svg> |  | ||||||
| Before Width: | Height: | Size: 227 B | 
| @ -1 +0,0 @@ | |||||||
| <svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><rect x="128" y="128" width="336" height="336" rx="57" ry="57" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/><path d="M383.5 128l.5-24a56.16 56.16 0 00-56-56H112a64.19 64.19 0 00-64 64v216a56.16 56.16 0 0056 56h24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg> |  | ||||||
| Before Width: | Height: | Size: 430 B | 
| @ -1 +0,0 @@ | |||||||
| <svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><path d="M112 111v290c0 17.44 17 28.52 31 20.16l247.9-148.37c12.12-7.25 12.12-26.33 0-33.58L143 90.84c-14-8.36-31 2.72-31 20.16z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/></svg> |  | ||||||
| Before Width: | Height: | Size: 290 B | 
| Before Width: | Height: | Size: 62 KiB | 
| @ -1,49 +0,0 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> |  | ||||||
| <!-- Created with Inkscape (http://www.inkscape.org/) --> |  | ||||||
| 
 |  | ||||||
| <svg |  | ||||||
|    width="78.461067mm" |  | ||||||
|    height="99.857933mm" |  | ||||||
|    viewBox="0 0 78.461067 99.857933" |  | ||||||
|    version="1.1" |  | ||||||
|    id="svg5" |  | ||||||
|    xml:space="preserve" |  | ||||||
|    inkscape:version="1.2.2 (b0a84865, 2022-12-01)" |  | ||||||
|    sodipodi:docname="furina-ahoge.svg" |  | ||||||
|    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" |  | ||||||
|    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" |  | ||||||
|    xmlns="http://www.w3.org/2000/svg" |  | ||||||
|    xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview |  | ||||||
|      id="namedview7" |  | ||||||
|      pagecolor="#ffffff" |  | ||||||
|      bordercolor="#000000" |  | ||||||
|      borderopacity="0.25" |  | ||||||
|      inkscape:showpageshadow="2" |  | ||||||
|      inkscape:pageopacity="0.0" |  | ||||||
|      inkscape:pagecheckerboard="0" |  | ||||||
|      inkscape:deskcolor="#d1d1d1" |  | ||||||
|      inkscape:document-units="mm" |  | ||||||
|      showgrid="false" |  | ||||||
|      inkscape:zoom="0.5946522" |  | ||||||
|      inkscape:cx="394.34816" |  | ||||||
|      inkscape:cy="326.24112" |  | ||||||
|      inkscape:window-width="1920" |  | ||||||
|      inkscape:window-height="1080" |  | ||||||
|      inkscape:window-x="1440" |  | ||||||
|      inkscape:window-y="0" |  | ||||||
|      inkscape:window-maximized="0" |  | ||||||
|      inkscape:current-layer="layer1" /><defs |  | ||||||
|      id="defs2" /><g |  | ||||||
|      inkscape:label="Layer 1" |  | ||||||
|      inkscape:groupmode="layer" |  | ||||||
|      id="layer1" |  | ||||||
|      transform="translate(-24.520501,-12.209419)"><g |  | ||||||
|        id="g5320" |  | ||||||
|        transform="translate(-100.11104,-36.484912)"><path |  | ||||||
|          style="fill:#ffffff;fill-opacity:1;stroke:#5b6583;stroke-width:8.8;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" |  | ||||||
|          d="m 197.99739,93.659439 c 4.4418,-17.901129 -13.8919,-42.069877 -33.59282,-40.489354 -23.43183,1.879839 -35.46139,18.690788 -35.37256,35.150097 0.14479,26.828548 12.24846,42.764298 63.18119,55.839708 -8.55629,-2.02869 -46.86753,-31.39828 -38.70961,-54.504893 4.95753,-14.041763 29.10872,-21.582069 44.4938,4.004442 z" |  | ||||||
|          id="path456" |  | ||||||
|          sodipodi:nodetypes="csscsc" /><path |  | ||||||
|          id="path1066" |  | ||||||
|          style="fill:#9cc1e5;fill-opacity:1;stroke:none;stroke-width:1.189;stroke-opacity:1" |  | ||||||
|          d="m 165.21679,56.025521 c -2.05958,-0.02817 -4.11979,0.199132 -6.15156,0.703833 -18.14601,4.507547 -27.27536,25.607406 -23.58151,40.489518 4.36519,17.586938 25.89622,31.292728 41.14581,37.091278 -13.29913,-11.00875 -28.57405,-29.22322 -23.12572,-44.655157 4.95752,-14.041749 29.10834,-21.582079 44.49341,4.004406 0.5607,-2.259718 0.7577,-4.619495 0.63562,-7.014559 -4.66252,-17.756004 -19.03095,-30.422553 -33.41605,-30.619319 z" /></g></g></svg> |  | ||||||
| Before Width: | Height: | Size: 2.5 KiB | 
| Before Width: | Height: | Size: 242 KiB | 
| Before Width: | Height: | Size: 134 KiB | 
| Before Width: | Height: | Size: 213 KiB | 
| Before Width: | Height: | Size: 35 KiB | 
| @ -1,325 +0,0 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> |  | ||||||
| <!-- Created with Inkscape (http://www.inkscape.org/) --> |  | ||||||
| 
 |  | ||||||
| <svg |  | ||||||
|    width="81.073494mm" |  | ||||||
|    height="16.619713mm" |  | ||||||
|    viewBox="0 0 81.073494 16.619713" |  | ||||||
|    version="1.1" |  | ||||||
|    id="svg5" |  | ||||||
|    sodipodi:docname="title-img.svg" |  | ||||||
|    inkscape:version="1.2.2 (b0a84865, 2022-12-01)" |  | ||||||
|    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" |  | ||||||
|    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" |  | ||||||
|    xmlns:xlink="http://www.w3.org/1999/xlink" |  | ||||||
|    xmlns="http://www.w3.org/2000/svg" |  | ||||||
|    xmlns:svg="http://www.w3.org/2000/svg"> |  | ||||||
|   <sodipodi:namedview |  | ||||||
|      id="namedview7" |  | ||||||
|      pagecolor="#ffffff" |  | ||||||
|      bordercolor="#000000" |  | ||||||
|      borderopacity="0.25" |  | ||||||
|      inkscape:showpageshadow="2" |  | ||||||
|      inkscape:pageopacity="0.0" |  | ||||||
|      inkscape:pagecheckerboard="0" |  | ||||||
|      inkscape:deskcolor="#d1d1d1" |  | ||||||
|      inkscape:document-units="mm" |  | ||||||
|      showgrid="false" |  | ||||||
|      inkscape:zoom="1.1893044" |  | ||||||
|      inkscape:cx="70.209107" |  | ||||||
|      inkscape:cy="136.63449" |  | ||||||
|      inkscape:window-width="1920" |  | ||||||
|      inkscape:window-height="1027" |  | ||||||
|      inkscape:window-x="1440" |  | ||||||
|      inkscape:window-y="25" |  | ||||||
|      inkscape:window-maximized="1" |  | ||||||
|      inkscape:current-layer="layer1" /> |  | ||||||
|   <defs |  | ||||||
|      id="defs2"> |  | ||||||
|     <linearGradient |  | ||||||
|        inkscape:collect="always" |  | ||||||
|        id="linearGradient4461"> |  | ||||||
|       <stop |  | ||||||
|          style="stop-color:#dbedff;stop-opacity:1;" |  | ||||||
|          offset="0" |  | ||||||
|          id="stop4457" /> |  | ||||||
|       <stop |  | ||||||
|          style="stop-color:#ffffff;stop-opacity:1;" |  | ||||||
|          offset="1" |  | ||||||
|          id="stop4459" /> |  | ||||||
|     </linearGradient> |  | ||||||
|     <inkscape:path-effect |  | ||||||
|        effect="perspective-envelope" |  | ||||||
|        up_left_point="12.49,13.981926" |  | ||||||
|        up_right_point="91.72,13.981926" |  | ||||||
|        down_left_point="10.385328,29.489294" |  | ||||||
|        down_right_point="89.220182,29.489294" |  | ||||||
|        id="path-effect2044" |  | ||||||
|        is_visible="true" |  | ||||||
|        lpeversion="1" |  | ||||||
|        deform_type="envelope_deformation" |  | ||||||
|        horizontal_mirror="false" |  | ||||||
|        vertical_mirror="false" |  | ||||||
|        overflow_perspective="false" /> |  | ||||||
|     <inkscape:path-effect |  | ||||||
|        effect="perspective-envelope" |  | ||||||
|        up_left_point="12.49,13.981926" |  | ||||||
|        up_right_point="91.72,13.981926" |  | ||||||
|        down_left_point="10.385328,29.489294" |  | ||||||
|        down_right_point="89.220182,29.489294" |  | ||||||
|        id="path-effect1949" |  | ||||||
|        is_visible="true" |  | ||||||
|        lpeversion="1" |  | ||||||
|        deform_type="envelope_deformation" |  | ||||||
|        horizontal_mirror="false" |  | ||||||
|        vertical_mirror="false" |  | ||||||
|        overflow_perspective="false" /> |  | ||||||
|     <inkscape:path-effect |  | ||||||
|        effect="lattice2" |  | ||||||
|        gridpoint0="10.385328,13.981926" |  | ||||||
|        gridpoint1="89.220182,13.981926" |  | ||||||
|        gridpoint2="10.385328,29.489294" |  | ||||||
|        gridpoint3="89.220182,29.489294" |  | ||||||
|        gridpoint4="30.094042,13.981926" |  | ||||||
|        gridpoint5="69.511469,13.981926" |  | ||||||
|        gridpoint6="30.094042,29.489294" |  | ||||||
|        gridpoint7="69.511469,29.489294" |  | ||||||
|        gridpoint8x9="49.802755,13.981926" |  | ||||||
|        gridpoint10x11="49.802755,29.489294" |  | ||||||
|        gridpoint12="10.385328,17.858768" |  | ||||||
|        gridpoint13="89.220182,17.858768" |  | ||||||
|        gridpoint14="10.385328,25.612452" |  | ||||||
|        gridpoint15="89.220182,25.612452" |  | ||||||
|        gridpoint16="30.094042,17.858768" |  | ||||||
|        gridpoint17="69.511469,17.858768" |  | ||||||
|        gridpoint18="30.094042,25.612452" |  | ||||||
|        gridpoint19="69.511469,25.612452" |  | ||||||
|        gridpoint20x21="49.802755,17.858768" |  | ||||||
|        gridpoint22x23="49.802755,25.612452" |  | ||||||
|        gridpoint24x26="10.385328,21.73561" |  | ||||||
|        gridpoint25x27="89.220182,21.73561" |  | ||||||
|        gridpoint28x30="30.094042,21.73561" |  | ||||||
|        gridpoint29x31="69.511469,21.73561" |  | ||||||
|        gridpoint32x33x34x35="49.802755,21.73561" |  | ||||||
|        id="path-effect1947" |  | ||||||
|        is_visible="true" |  | ||||||
|        lpeversion="1" |  | ||||||
|        horizontal_mirror="false" |  | ||||||
|        vertical_mirror="false" |  | ||||||
|        perimetral="false" |  | ||||||
|        live_update="true" /> |  | ||||||
|     <inkscape:path-effect |  | ||||||
|        effect="envelope" |  | ||||||
|        id="path-effect1945" |  | ||||||
|        is_visible="true" |  | ||||||
|        lpeversion="1" |  | ||||||
|        yy="true" |  | ||||||
|        xx="true" |  | ||||||
|        bendpath1="M 10.385328,13.981926 H 89.220182" |  | ||||||
|        bendpath2="M 89.220182,13.981926 V 29.489294" |  | ||||||
|        bendpath3="M 10.385328,29.489294 H 89.220182" |  | ||||||
|        bendpath4="M 10.385328,13.981926 V 29.489294" /> |  | ||||||
|     <linearGradient |  | ||||||
|        inkscape:collect="always" |  | ||||||
|        xlink:href="#linearGradient4461" |  | ||||||
|        id="linearGradient4463" |  | ||||||
|        x1="51.604469" |  | ||||||
|        y1="3.4931529" |  | ||||||
|        x2="63.823807" |  | ||||||
|        y2="31.413006" |  | ||||||
|        gradientUnits="userSpaceOnUse" /> |  | ||||||
|     <linearGradient |  | ||||||
|        inkscape:collect="always" |  | ||||||
|        xlink:href="#linearGradient4461" |  | ||||||
|        id="linearGradient7537" |  | ||||||
|        gradientUnits="userSpaceOnUse" |  | ||||||
|        x1="51.604469" |  | ||||||
|        y1="3.4931529" |  | ||||||
|        x2="63.823807" |  | ||||||
|        y2="31.413006" /> |  | ||||||
|     <linearGradient |  | ||||||
|        inkscape:collect="always" |  | ||||||
|        xlink:href="#linearGradient4461" |  | ||||||
|        id="linearGradient7539" |  | ||||||
|        gradientUnits="userSpaceOnUse" |  | ||||||
|        x1="51.604469" |  | ||||||
|        y1="3.4931529" |  | ||||||
|        x2="63.823807" |  | ||||||
|        y2="31.413006" /> |  | ||||||
|     <linearGradient |  | ||||||
|        inkscape:collect="always" |  | ||||||
|        xlink:href="#linearGradient4461" |  | ||||||
|        id="linearGradient7541" |  | ||||||
|        gradientUnits="userSpaceOnUse" |  | ||||||
|        x1="51.604469" |  | ||||||
|        y1="3.4931529" |  | ||||||
|        x2="63.823807" |  | ||||||
|        y2="31.413006" /> |  | ||||||
|     <linearGradient |  | ||||||
|        inkscape:collect="always" |  | ||||||
|        xlink:href="#linearGradient4461" |  | ||||||
|        id="linearGradient7543" |  | ||||||
|        gradientUnits="userSpaceOnUse" |  | ||||||
|        x1="51.604469" |  | ||||||
|        y1="3.4931529" |  | ||||||
|        x2="63.823807" |  | ||||||
|        y2="31.413006" /> |  | ||||||
|     <linearGradient |  | ||||||
|        inkscape:collect="always" |  | ||||||
|        xlink:href="#linearGradient4461" |  | ||||||
|        id="linearGradient7545" |  | ||||||
|        gradientUnits="userSpaceOnUse" |  | ||||||
|        x1="51.604469" |  | ||||||
|        y1="3.4931529" |  | ||||||
|        x2="63.823807" |  | ||||||
|        y2="31.413006" /> |  | ||||||
|     <linearGradient |  | ||||||
|        inkscape:collect="always" |  | ||||||
|        xlink:href="#linearGradient4461" |  | ||||||
|        id="linearGradient7547" |  | ||||||
|        gradientUnits="userSpaceOnUse" |  | ||||||
|        x1="51.604469" |  | ||||||
|        y1="3.4931529" |  | ||||||
|        x2="63.823807" |  | ||||||
|        y2="31.413006" /> |  | ||||||
|     <linearGradient |  | ||||||
|        inkscape:collect="always" |  | ||||||
|        xlink:href="#linearGradient4461" |  | ||||||
|        id="linearGradient7549" |  | ||||||
|        gradientUnits="userSpaceOnUse" |  | ||||||
|        x1="51.604469" |  | ||||||
|        y1="3.4931529" |  | ||||||
|        x2="63.823807" |  | ||||||
|        y2="31.413006" /> |  | ||||||
|     <linearGradient |  | ||||||
|        inkscape:collect="always" |  | ||||||
|        xlink:href="#linearGradient4461" |  | ||||||
|        id="linearGradient7551" |  | ||||||
|        gradientUnits="userSpaceOnUse" |  | ||||||
|        x1="51.604469" |  | ||||||
|        y1="3.4931529" |  | ||||||
|        x2="63.823807" |  | ||||||
|        y2="31.413006" /> |  | ||||||
|     <linearGradient |  | ||||||
|        inkscape:collect="always" |  | ||||||
|        xlink:href="#linearGradient4461" |  | ||||||
|        id="linearGradient7553" |  | ||||||
|        gradientUnits="userSpaceOnUse" |  | ||||||
|        x1="51.604469" |  | ||||||
|        y1="3.4931529" |  | ||||||
|        x2="63.823807" |  | ||||||
|        y2="31.413006" /> |  | ||||||
|   </defs> |  | ||||||
|   <g |  | ||||||
|      inkscape:label="Layer 1" |  | ||||||
|      inkscape:groupmode="layer" |  | ||||||
|      id="layer1" |  | ||||||
|      transform="translate(-11.22635,-13.981926)"> |  | ||||||
|     <g |  | ||||||
|        id="g5211"> |  | ||||||
|       <g |  | ||||||
|          aria-label="Furina.id" |  | ||||||
|          id="g2040" |  | ||||||
|          style="font-style:italic;font-size:23.6033px;font-family:Arial;-inkscape-font-specification:'Arial Italic';fill:#90c8ff;fill-opacity:0.861176;stroke:none;stroke-width:6.34755" |  | ||||||
|          inkscape:path-effect="#path-effect2044" |  | ||||||
|          transform="translate(0.66740693,1.1123449)"> |  | ||||||
|         <path |  | ||||||
|            d="m 12.802259,17.616834 c 0.04493,-0.330446 0.01276,-0.558611 -0.09652,-0.684495 -0.09349,-0.125885 -0.290342,-0.188827 -0.590549,-0.188827 l 0.115325,-0.849719 h 9.482809 l -0.245662,1.770248 h -5.804912 l -0.478243,3.493288 h 4.851708 l -0.241853,1.746645 h -4.848976 l -0.862776,6.302081 h -2.85626 z" |  | ||||||
|            style="font-style:normal;font-family:Hostilica;-inkscape-font-specification:Hostilica;fill:#90c8ff;fill-opacity:0.861176;stroke:none" |  | ||||||
|            id="path2022" |  | ||||||
|            inkscape:original-d="m 11.18784,17.616834 q 0,-0.495669 -0.188826,-0.684495 -0.165223,-0.188827 -0.613686,-0.188827 v -0.849719 h 9.44132 v 1.770248 h -5.782809 v 3.493288 h 4.838677 v 1.746645 h -4.838677 v 6.302081 H 11.18784 Z" /> |  | ||||||
|         <path |  | ||||||
|            d="m 23.249105,20.52004 c 0.0395,-0.283239 -0.01658,-0.503537 -0.168267,-0.660892 -0.13592,-0.157355 -0.345957,-0.236033 -0.630099,-0.236033 l 0.08541,-0.613686 h 3.220922 c 0.347354,0 0.492314,0.204562 0.43488,0.613686 l -0.798549,5.688395 c -0.207645,1.47914 0.153025,2.21871 1.08201,2.21871 0.850257,0 1.53891,-0.472066 2.066141,-1.416198 l 0.791185,-5.593982 c 0.04006,-0.283239 -0.01559,-0.503537 -0.166972,-0.660892 -0.135611,-0.157355 -0.345493,-0.236033 -0.629636,-0.236033 l 0.08661,-0.613686 h 3.220923 c 0.157888,0 0.282019,0.07081 0.372385,0.21243 0.0926,0.125884 0.12657,0.275371 0.101929,0.448462 l -1.159241,8.143139 c -0.03136,0.220297 -0.0065,0.377653 0.07447,0.472066 0.09899,0.07868 0.298015,0.118016 0.597096,0.118016 l -0.08751,0.613686 -2.265935,0.330446 c -0.440615,0 -0.765778,-0.259636 -0.975559,-0.778908 -0.781686,0.519272 -1.605186,0.778908 -2.47068,0.778908 -1.227428,0 -2.157896,-0.322578 -2.79163,-0.967735 -0.631839,-0.660892 -0.851307,-1.683702 -0.658174,-3.068429 z" |  | ||||||
|            style="font-style:normal;font-family:Hostilica;-inkscape-font-specification:Hostilica;fill:#90c8ff;fill-opacity:0.861176;stroke:none" |  | ||||||
|            id="path2024" |  | ||||||
|            inkscape:original-d="m 21.998125,20.52004 q 0,-0.424859 -0.259637,-0.660892 -0.236033,-0.236033 -0.660892,-0.236033 v -0.613686 h 3.210049 q 0.519272,0 0.519272,0.613686 v 5.688395 q 0,2.21871 1.392595,2.21871 1.274578,0 1.864661,-1.416198 V 20.52004 q 0,-0.424859 -0.259637,-0.660892 -0.236033,-0.236033 -0.660892,-0.236033 v -0.613686 h 3.210049 q 0.236033,0 0.401256,0.21243 0.165223,0.188826 0.165223,0.448462 v 8.143139 q 0,0.330446 0.14162,0.472066 0.165223,0.118016 0.613686,0.118016 v 0.613686 l -2.218711,0.330446 q -0.660892,0 -1.085751,-0.778908 -1.062149,0.778908 -2.36033,0.778908 -1.841058,0 -2.92681,-0.967735 -1.085751,-0.991338 -1.085751,-3.068429 z" /> |  | ||||||
|         <path |  | ||||||
|            d="m 34.408293,19.292669 3.105951,-0.519273 c 0.17369,0 0.305597,0.07081 0.395713,0.21243 0.09238,0.125884 0.126081,0.275371 0.101124,0.448462 l -0.102094,0.708099 1.926392,-1.368991 2.417119,0.967735 -1.752566,2.124297 h -0.520553 c 0.03877,-0.267504 -0.09154,-0.511405 -0.390963,-0.731702 -0.281414,-0.236033 -0.611497,-0.354049 -0.990213,-0.354049 l -0.835552,0.377652 -1.160477,8.048726 h -2.856261 l 1.230819,-8.591602 c 0.0248,-0.173091 -0.02368,-0.330446 -0.145445,-0.472066 -0.119523,-0.157355 -0.28978,-0.236033 -0.51076,-0.236033 z" |  | ||||||
|            style="font-style:normal;font-family:Hostilica;-inkscape-font-specification:Hostilica;fill:#90c8ff;fill-opacity:0.861176;stroke:none" |  | ||||||
|            id="path2026" |  | ||||||
|            inkscape:original-d="m 32.950031,19.292669 3.021223,-0.519273 q 0.259636,0 0.424859,0.21243 0.165223,0.188826 0.165223,0.448462 v 0.708099 l 1.723041,-1.368991 2.549156,0.967735 -1.439801,2.124297 h -0.519273 q 0,-0.401256 -0.495669,-0.731702 -0.472066,-0.354049 -1.038545,-0.354049 l -0.778909,0.377652 v 8.048726 h -2.855999 v -8.591602 q 0,-0.259636 -0.21243,-0.472066 -0.21243,-0.236033 -0.542876,-0.236033 z" /> |  | ||||||
|         <path |  | ||||||
|            d="m 43.964363,17.097562 c 0.06202,-0.424859 0.260389,-0.755306 0.595127,-0.991339 0.35289,-0.251769 0.790145,-0.377653 1.311727,-0.377653 0.521583,0 0.914042,0.125884 1.177341,0.377653 0.281366,0.236033 0.390785,0.56648 0.328294,0.991339 -0.05786,0.393388 -0.26326,0.715966 -0.616152,0.967735 -0.35053,0.236033 -0.778447,0.35405 -1.283786,0.35405 -0.505338,0 -0.898667,-0.118017 -1.180021,-0.35405 -0.279104,-0.251769 -0.38996,-0.574347 -0.33253,-0.967735 z m 1.442629,12.108493 h -2.856261 l 1.220782,-8.355569 c 0.03908,-0.267504 0.01029,-0.448462 -0.0864,-0.542875 -0.09439,-0.110149 -0.283639,-0.165224 -0.567735,-0.165224 l 0.08951,-0.613685 3.11413,-0.56648 c 0.394727,0 0.550446,0.28324 0.467158,0.849719 z" |  | ||||||
|            style="font-style:normal;font-family:Hostilica;-inkscape-font-specification:Hostilica;fill:#90c8ff;fill-opacity:0.861176;stroke:none" |  | ||||||
|            id="path2028" |  | ||||||
|            inkscape:original-d="m 42.1553,17.097562 q 0,-0.637289 0.448462,-0.991339 0.472066,-0.377653 1.250975,-0.377653 0.778909,0 1.227372,0.377653 0.472066,0.35405 0.472066,0.991339 0,0.590082 -0.472066,0.967735 -0.472066,0.35405 -1.227372,0.35405 -0.755305,0 -1.227371,-0.35405 Q 42.1553,17.687644 42.1553,17.097562 Z m 3.210048,12.108493 h -2.855999 v -8.355569 q 0,-0.401256 -0.165223,-0.542875 -0.165223,-0.165224 -0.590083,-0.165224 v -0.613685 l 3.021223,-0.56648 q 0.590082,0 0.590082,0.849719 z" /> |  | ||||||
|         <path |  | ||||||
|            d="m 49.226654,20.614453 c 0.04188,-0.283239 0.01455,-0.472066 -0.08197,-0.566479 -0.08075,-0.09441 -0.271078,-0.14162 -0.57098,-0.14162 l 0.09058,-0.613685 3.108336,-0.519273 c 0.39475,0 0.552328,0.267504 0.472733,0.802512 l -0.07726,0.519273 c 1.27422,-0.818248 2.456298,-1.227372 3.545777,-1.227372 0.963163,0 1.614123,0.291107 1.952761,0.873322 0.35426,0.582215 0.431704,1.534215 0.232455,2.856 l -0.818345,5.428759 c -0.05693,0.377653 0.127099,0.566479 0.552081,0.566479 l -0.09264,0.613686 h -2.974288 c -0.141633,0 -0.259273,-0.05507 -0.352926,-0.165223 -0.07557,-0.125885 -0.102742,-0.259637 -0.08152,-0.401257 l 0.873449,-5.830015 c 0.120232,-0.802512 0.121505,-1.33752 0.0038,-1.605024 -0.115378,-0.283239 -0.330876,-0.424859 -0.646473,-0.424859 -0.362935,0 -0.782937,0.118016 -1.259954,0.354049 -0.458828,0.220297 -0.865035,0.511405 -1.218612,0.873322 l -1.071026,7.199007 h -2.856261 z" |  | ||||||
|            style="font-style:normal;font-family:Hostilica;-inkscape-font-specification:Hostilica;fill:#90c8ff;fill-opacity:0.861176;stroke:none" |  | ||||||
|            id="path2030" |  | ||||||
|            inkscape:original-d="m 47.9145,20.614453 q 0,-0.424859 -0.165223,-0.566479 -0.14162,-0.14162 -0.590083,-0.14162 v -0.613685 l 3.021223,-0.519273 q 0.590082,0 0.590082,0.802512 v 0.519273 q 1.723041,-1.227372 3.351669,-1.227372 1.439801,0 2.07709,0.873322 0.660893,0.873322 0.660893,2.856 v 5.428759 q 0,0.566479 0.637289,0.566479 v 0.613686 h -2.974016 q -0.21243,0 -0.377653,-0.165223 -0.14162,-0.188827 -0.14162,-0.401257 V 22.80956 q 0,-1.203768 -0.236033,-1.605024 -0.236033,-0.424859 -0.708099,-0.424859 -0.542875,0 -1.203768,0.354049 -0.637289,0.330446 -1.085752,0.873322 v 7.199007 H 47.9145 Z" /> |  | ||||||
|         <path |  | ||||||
|            d="m 68.46948,29.206055 h -2.336941 c -0.723901,0 -1.231902,-0.330446 -1.52413,-0.991339 -0.540631,0.755305 -1.432451,1.132958 -2.675616,1.132958 -0.928439,0 -1.703023,-0.236033 -2.323911,-0.708099 -0.602975,-0.487801 -0.823616,-1.26671 -0.661764,-2.336726 0.337985,-2.234446 2.383706,-3.509024 6.137727,-3.823735 0.07201,-0.676628 0.02713,-1.156562 -0.13465,-1.439801 -0.159415,-0.298975 -0.49163,-0.448463 -0.996615,-0.448463 -1.041532,0 -1.878432,0.574347 -2.510444,1.723041 h -0.567793 l -0.572928,-1.841057 c 1.404397,-1.038545 2.975321,-1.557818 4.712146,-1.557818 1.215777,0 2.09113,0.314711 2.625866,0.944132 0.552683,0.613686 0.719839,1.628628 0.501658,3.044826 l -0.72727,4.72066 c 0.0096,0.346181 0.09678,0.59795 0.261425,0.755305 0.16705,0.14162 0.46306,0.21243 0.888043,0.21243 z m -3.579715,-5.452363 c -0.977129,0.204562 -1.71325,0.535008 -2.208364,0.991339 -0.476836,0.440595 -0.757119,0.936264 -0.840938,1.487008 -0.1389,0.912661 0.232515,1.368991 1.114242,1.368991 0.440864,0 0.769542,-0.14162 0.986058,-0.424859 0.232298,-0.28324 0.447478,-0.763174 0.645545,-1.439801 z" |  | ||||||
|            style="font-style:normal;font-family:Hostilica;-inkscape-font-specification:Hostilica;fill:#90c8ff;fill-opacity:0.861176;stroke:none" |  | ||||||
|            id="path2032" |  | ||||||
|            inkscape:original-d="m 68.425725,29.206055 h -2.336727 q -1.085752,0 -1.675834,-0.991339 -0.637289,1.132958 -2.50195,1.132958 -1.392595,0 -2.43114,-0.708099 -1.014942,-0.731702 -1.014942,-2.336726 0,-3.351669 5.546776,-3.823735 -0.04721,-1.014942 -0.35405,-1.439801 -0.306843,-0.448463 -1.062148,-0.448463 -1.557818,0 -2.242314,1.723041 h -0.566479 l -0.849719,-1.841057 q 1.864661,-1.557818 4.461024,-1.557818 1.817454,0 2.761586,0.944132 0.967735,0.920529 0.967735,3.044826 v 4.72066 q 0.09441,0.519272 0.377653,0.755305 0.28324,0.21243 0.920529,0.21243 z m -4.413817,-5.452363 q -1.416198,0.306843 -2.053487,0.991339 -0.613686,0.660892 -0.613686,1.487008 0,1.368991 1.321785,1.368991 0.660892,0 0.920528,-0.424859 0.28324,-0.42486 0.42486,-1.439801 z" /> |  | ||||||
|         <path |  | ||||||
|            d="m 70.85683,29.489294 c -0.456331,0 -0.809043,-0.110149 -1.058166,-0.330446 -0.249165,-0.220297 -0.344538,-0.519272 -0.286088,-0.896925 0.05601,-0.361917 0.241557,-0.645157 0.556657,-0.849719 0.317575,-0.220297 0.704721,-0.330446 1.161407,-0.330446 0.472433,0 0.833247,0.110149 1.082411,0.330446 0.251575,0.204562 0.34916,0.487802 0.292785,0.849719 -0.05883,0.377653 -0.247044,0.676628 -0.56462,0.896925 -0.317535,0.220297 -0.71232,0.330446 -1.184386,0.330446 z" |  | ||||||
|            style="font-style:normal;font-family:Hostilica;-inkscape-font-specification:Hostilica;fill:#90c8ff;fill-opacity:0.861176;stroke:none" |  | ||||||
|            id="path2034" |  | ||||||
|            inkscape:original-d="m 70.85683,29.489294 q -0.684496,0 -1.109355,-0.330446 -0.42486,-0.330446 -0.42486,-0.896925 0,-0.542876 0.42486,-0.849719 0.424859,-0.330446 1.109355,-0.330446 0.708099,0 1.132958,0.330446 0.42486,0.306843 0.42486,0.849719 0,0.566479 -0.42486,0.896925 -0.424859,0.330446 -1.132958,0.330446 z" /> |  | ||||||
|         <path |  | ||||||
|            d="m 76.714684,17.097562 c 0.0665,-0.424859 0.268353,-0.755306 0.60558,-0.991339 0.355545,-0.251769 0.794127,-0.377653 1.315709,-0.377653 0.521583,0 0.912714,0.125884 1.173359,0.377653 0.278878,0.236033 0.384813,0.56648 0.317842,0.991339 -0.06201,0.393388 -0.270808,0.715966 -0.626355,0.967735 -0.353019,0.236033 -0.78218,0.35405 -1.287519,0.35405 -0.505339,0 -0.897423,-0.118017 -1.176289,-0.35405 -0.27645,-0.251769 -0.383905,-0.574347 -0.322327,-0.967735 z m 1.314965,12.108493 h -2.85626 l 1.308878,-8.355569 c 0.0419,-0.267504 0.01501,-0.448462 -0.08067,-0.542875 -0.09323,-0.110149 -0.281897,-0.165224 -0.565992,-0.165224 l 0.09598,-0.613685 3.120102,-0.56648 c 0.394727,0 0.54746,0.28324 0.458199,0.849719 z" |  | ||||||
|            style="font-style:normal;font-family:Hostilica;-inkscape-font-specification:Hostilica;fill:#90c8ff;fill-opacity:0.861176;stroke:none" |  | ||||||
|            id="path2036" |  | ||||||
|            inkscape:original-d="m 74.77497,17.097562 q 0,-0.637289 0.448463,-0.991339 0.472066,-0.377653 1.250975,-0.377653 0.778909,0 1.227372,0.377653 0.472066,0.35405 0.472066,0.991339 0,0.590082 -0.472066,0.967735 -0.472066,0.35405 -1.227372,0.35405 -0.755306,0 -1.227372,-0.35405 -0.472066,-0.377653 -0.472066,-0.967735 z m 3.210049,12.108493 H 75.12902 v -8.355569 q 0,-0.401256 -0.165223,-0.542875 -0.165223,-0.165224 -0.590083,-0.165224 v -0.613685 l 3.021223,-0.56648 q 0.590082,0 0.590082,0.849719 z" /> |  | ||||||
|         <path |  | ||||||
|            d="m 81.320323,27.95508 c -0.716436,-0.944132 -0.947935,-2.21871 -0.6941,-3.823735 0.253835,-1.605024 0.880155,-2.871734 1.879343,-3.800131 1.017984,-0.944132 2.18243,-1.416198 3.492943,-1.416198 0.805255,0 1.459341,0.204562 1.962145,0.613686 l 0.586382,-3.658512 c 0.04792,-0.298975 0.03076,-0.487802 -0.05148,-0.566479 -0.07972,-0.09441 -0.277666,-0.14162 -0.593834,-0.14162 l 0.09821,-0.613686 3.127021,-0.566479 c 0.395361,0 0.558797,0.21243 0.490309,0.637289 l -2.351427,14.58684 h -2.856262 l 0.12106,-0.755306 c -0.505021,0.59795 -1.245296,0.896925 -2.220943,0.896925 -1.274637,0 -2.270964,-0.464198 -2.989373,-1.392594 z m 2.33611,-3.847338 c -0.155244,0.975603 -0.105533,1.801718 0.149014,2.478346 0.25439,0.676628 0.727918,1.014942 1.420704,1.014942 0.425119,0 0.934318,-0.180959 1.527691,-0.542876 L 87.06784,25.09908 c 0.257252,-1.605024 0.299416,-2.753718 0.126399,-3.446081 -0.157365,-0.692364 -0.512255,-1.038546 -1.064578,-1.038546 -0.615446,0 -1.144046,0.346182 -1.585688,1.038546 -0.438989,0.676628 -0.734799,1.494876 -0.88754,2.454743 z" |  | ||||||
|            style="font-style:normal;font-family:Hostilica;-inkscape-font-specification:Hostilica;fill:#90c8ff;fill-opacity:0.861176;stroke:none" |  | ||||||
|            id="path2038" |  | ||||||
|            inkscape:original-d="m 81.077043,27.95508 q -1.298181,-1.416198 -1.298181,-3.823735 0,-2.407536 1.274578,-3.800131 1.298181,-1.416198 3.257255,-1.416198 1.203768,0 2.053487,0.613686 V 15.87019 q 0,-0.448463 -0.14162,-0.566479 -0.141619,-0.14162 -0.613685,-0.14162 v -0.613686 l 3.021222,-0.566479 q 0.590083,0 0.590083,0.637289 v 14.58684 h -2.856 v -0.755306 q -0.613686,0.896925 -2.07709,0.896925 -1.911867,0 -3.210049,-1.392594 z m 1.723041,-3.847338 q 0,1.463404 0.542876,2.478346 0.542876,1.014942 1.581421,1.014942 0.637289,0 1.439801,-0.542876 V 25.09908 q 0,-2.407536 -0.424859,-3.446081 -0.401256,-1.038546 -1.227372,-1.038546 -0.920528,0 -1.416198,1.038546 -0.495669,1.014942 -0.495669,2.454743 z" /> |  | ||||||
|       </g> |  | ||||||
|       <g |  | ||||||
|          aria-label="Furina.id" |  | ||||||
|          id="text236" |  | ||||||
|          style="font-style:italic;font-size:23.6033px;font-family:Arial;-inkscape-font-specification:'Arial Italic';fill:url(#linearGradient4463);fill-opacity:1;stroke:none;stroke-width:6.34755" |  | ||||||
|          inkscape:path-effect="#path-effect1949"> |  | ||||||
|         <path |  | ||||||
|            d="m 12.802259,17.616834 c 0.04493,-0.330446 0.01276,-0.558611 -0.09652,-0.684495 -0.09349,-0.125885 -0.290342,-0.188827 -0.590549,-0.188827 l 0.115325,-0.849719 h 9.482809 l -0.245662,1.770248 h -5.804912 l -0.478243,3.493288 h 4.851708 l -0.241853,1.746645 h -4.848976 l -0.862776,6.302081 h -2.85626 z" |  | ||||||
|            style="font-style:normal;font-family:Hostilica;-inkscape-font-specification:Hostilica;fill:url(#linearGradient7537);fill-opacity:1;stroke:none" |  | ||||||
|            id="path1877" |  | ||||||
|            inkscape:original-d="m 11.18784,17.616834 q 0,-0.495669 -0.188826,-0.684495 -0.165223,-0.188827 -0.613686,-0.188827 v -0.849719 h 9.44132 v 1.770248 h -5.782809 v 3.493288 h 4.838677 v 1.746645 h -4.838677 v 6.302081 H 11.18784 Z" /> |  | ||||||
|         <path |  | ||||||
|            d="m 23.249105,20.52004 c 0.0395,-0.283239 -0.01658,-0.503537 -0.168267,-0.660892 -0.13592,-0.157355 -0.345957,-0.236033 -0.630099,-0.236033 l 0.08541,-0.613686 h 3.220922 c 0.347354,0 0.492314,0.204562 0.43488,0.613686 l -0.798549,5.688395 c -0.207645,1.47914 0.153025,2.21871 1.08201,2.21871 0.850257,0 1.53891,-0.472066 2.066141,-1.416198 l 0.791185,-5.593982 c 0.04006,-0.283239 -0.01559,-0.503537 -0.166972,-0.660892 -0.135611,-0.157355 -0.345493,-0.236033 -0.629636,-0.236033 l 0.08661,-0.613686 h 3.220923 c 0.157888,0 0.282019,0.07081 0.372385,0.21243 0.0926,0.125884 0.12657,0.275371 0.101929,0.448462 l -1.159241,8.143139 c -0.03136,0.220297 -0.0065,0.377653 0.07447,0.472066 0.09899,0.07868 0.298015,0.118016 0.597096,0.118016 l -0.08751,0.613686 -2.265935,0.330446 c -0.440615,0 -0.765778,-0.259636 -0.975559,-0.778908 -0.781686,0.519272 -1.605186,0.778908 -2.47068,0.778908 -1.227428,0 -2.157896,-0.322578 -2.79163,-0.967735 -0.631839,-0.660892 -0.851307,-1.683702 -0.658174,-3.068429 z" |  | ||||||
|            style="font-style:normal;font-family:Hostilica;-inkscape-font-specification:Hostilica;fill:url(#linearGradient7539);fill-opacity:1;stroke:none" |  | ||||||
|            id="path1879" |  | ||||||
|            inkscape:original-d="m 21.998125,20.52004 q 0,-0.424859 -0.259637,-0.660892 -0.236033,-0.236033 -0.660892,-0.236033 v -0.613686 h 3.210049 q 0.519272,0 0.519272,0.613686 v 5.688395 q 0,2.21871 1.392595,2.21871 1.274578,0 1.864661,-1.416198 V 20.52004 q 0,-0.424859 -0.259637,-0.660892 -0.236033,-0.236033 -0.660892,-0.236033 v -0.613686 h 3.210049 q 0.236033,0 0.401256,0.21243 0.165223,0.188826 0.165223,0.448462 v 8.143139 q 0,0.330446 0.14162,0.472066 0.165223,0.118016 0.613686,0.118016 v 0.613686 l -2.218711,0.330446 q -0.660892,0 -1.085751,-0.778908 -1.062149,0.778908 -2.36033,0.778908 -1.841058,0 -2.92681,-0.967735 -1.085751,-0.991338 -1.085751,-3.068429 z" /> |  | ||||||
|         <path |  | ||||||
|            d="m 34.408293,19.292669 3.105951,-0.519273 c 0.17369,0 0.305597,0.07081 0.395713,0.21243 0.09238,0.125884 0.126081,0.275371 0.101124,0.448462 l -0.102094,0.708099 1.926392,-1.368991 2.417119,0.967735 -1.752566,2.124297 h -0.520553 c 0.03877,-0.267504 -0.09154,-0.511405 -0.390963,-0.731702 -0.281414,-0.236033 -0.611497,-0.354049 -0.990213,-0.354049 l -0.835552,0.377652 -1.160477,8.048726 h -2.856261 l 1.230819,-8.591602 c 0.0248,-0.173091 -0.02368,-0.330446 -0.145445,-0.472066 -0.119523,-0.157355 -0.28978,-0.236033 -0.51076,-0.236033 z" |  | ||||||
|            style="font-style:normal;font-family:Hostilica;-inkscape-font-specification:Hostilica;fill:url(#linearGradient7541);fill-opacity:1;stroke:none" |  | ||||||
|            id="path1881" |  | ||||||
|            inkscape:original-d="m 32.950031,19.292669 3.021223,-0.519273 q 0.259636,0 0.424859,0.21243 0.165223,0.188826 0.165223,0.448462 v 0.708099 l 1.723041,-1.368991 2.549156,0.967735 -1.439801,2.124297 h -0.519273 q 0,-0.401256 -0.495669,-0.731702 -0.472066,-0.354049 -1.038545,-0.354049 l -0.778909,0.377652 v 8.048726 h -2.855999 v -8.591602 q 0,-0.259636 -0.21243,-0.472066 -0.21243,-0.236033 -0.542876,-0.236033 z" /> |  | ||||||
|         <path |  | ||||||
|            d="m 43.964363,17.097562 c 0.06202,-0.424859 0.260389,-0.755306 0.595127,-0.991339 0.35289,-0.251769 0.790145,-0.377653 1.311727,-0.377653 0.521583,0 0.914042,0.125884 1.177341,0.377653 0.281366,0.236033 0.390785,0.56648 0.328294,0.991339 -0.05786,0.393388 -0.26326,0.715966 -0.616152,0.967735 -0.35053,0.236033 -0.778447,0.35405 -1.283786,0.35405 -0.505338,0 -0.898667,-0.118017 -1.180021,-0.35405 -0.279104,-0.251769 -0.38996,-0.574347 -0.33253,-0.967735 z m 1.442629,12.108493 h -2.856261 l 1.220782,-8.355569 c 0.03908,-0.267504 0.01029,-0.448462 -0.0864,-0.542875 -0.09439,-0.110149 -0.283639,-0.165224 -0.567735,-0.165224 l 0.08951,-0.613685 3.11413,-0.56648 c 0.394727,0 0.550446,0.28324 0.467158,0.849719 z" |  | ||||||
|            style="font-style:normal;font-family:Hostilica;-inkscape-font-specification:Hostilica;fill:url(#linearGradient7543);fill-opacity:1;stroke:none" |  | ||||||
|            id="path1883" |  | ||||||
|            inkscape:original-d="m 42.1553,17.097562 q 0,-0.637289 0.448462,-0.991339 0.472066,-0.377653 1.250975,-0.377653 0.778909,0 1.227372,0.377653 0.472066,0.35405 0.472066,0.991339 0,0.590082 -0.472066,0.967735 -0.472066,0.35405 -1.227372,0.35405 -0.755305,0 -1.227371,-0.35405 Q 42.1553,17.687644 42.1553,17.097562 Z m 3.210048,12.108493 h -2.855999 v -8.355569 q 0,-0.401256 -0.165223,-0.542875 -0.165223,-0.165224 -0.590083,-0.165224 v -0.613685 l 3.021223,-0.56648 q 0.590082,0 0.590082,0.849719 z" /> |  | ||||||
|         <path |  | ||||||
|            d="m 49.226654,20.614453 c 0.04188,-0.283239 0.01455,-0.472066 -0.08197,-0.566479 -0.08075,-0.09441 -0.271078,-0.14162 -0.57098,-0.14162 l 0.09058,-0.613685 3.108336,-0.519273 c 0.39475,0 0.552328,0.267504 0.472733,0.802512 l -0.07726,0.519273 c 1.27422,-0.818248 2.456298,-1.227372 3.545777,-1.227372 0.963163,0 1.614123,0.291107 1.952761,0.873322 0.35426,0.582215 0.431704,1.534215 0.232455,2.856 l -0.818345,5.428759 c -0.05693,0.377653 0.127099,0.566479 0.552081,0.566479 l -0.09264,0.613686 h -2.974288 c -0.141633,0 -0.259273,-0.05507 -0.352926,-0.165223 -0.07557,-0.125885 -0.102742,-0.259637 -0.08152,-0.401257 l 0.873449,-5.830015 c 0.120232,-0.802512 0.121505,-1.33752 0.0038,-1.605024 -0.115378,-0.283239 -0.330876,-0.424859 -0.646473,-0.424859 -0.362935,0 -0.782937,0.118016 -1.259954,0.354049 -0.458828,0.220297 -0.865035,0.511405 -1.218612,0.873322 l -1.071026,7.199007 h -2.856261 z" |  | ||||||
|            style="font-style:normal;font-family:Hostilica;-inkscape-font-specification:Hostilica;fill:url(#linearGradient7545);fill-opacity:1;stroke:none" |  | ||||||
|            id="path1885" |  | ||||||
|            inkscape:original-d="m 47.9145,20.614453 q 0,-0.424859 -0.165223,-0.566479 -0.14162,-0.14162 -0.590083,-0.14162 v -0.613685 l 3.021223,-0.519273 q 0.590082,0 0.590082,0.802512 v 0.519273 q 1.723041,-1.227372 3.351669,-1.227372 1.439801,0 2.07709,0.873322 0.660893,0.873322 0.660893,2.856 v 5.428759 q 0,0.566479 0.637289,0.566479 v 0.613686 h -2.974016 q -0.21243,0 -0.377653,-0.165223 -0.14162,-0.188827 -0.14162,-0.401257 V 22.80956 q 0,-1.203768 -0.236033,-1.605024 -0.236033,-0.424859 -0.708099,-0.424859 -0.542875,0 -1.203768,0.354049 -0.637289,0.330446 -1.085752,0.873322 v 7.199007 H 47.9145 Z" /> |  | ||||||
|         <path |  | ||||||
|            d="m 68.46948,29.206055 h -2.336941 c -0.723901,0 -1.231902,-0.330446 -1.52413,-0.991339 -0.540631,0.755305 -1.432451,1.132958 -2.675616,1.132958 -0.928439,0 -1.703023,-0.236033 -2.323911,-0.708099 -0.602975,-0.487801 -0.823616,-1.26671 -0.661764,-2.336726 0.337985,-2.234446 2.383706,-3.509024 6.137727,-3.823735 0.07201,-0.676628 0.02713,-1.156562 -0.13465,-1.439801 -0.159415,-0.298975 -0.49163,-0.448463 -0.996615,-0.448463 -1.041532,0 -1.878432,0.574347 -2.510444,1.723041 h -0.567793 l -0.572928,-1.841057 c 1.404397,-1.038545 2.975321,-1.557818 4.712146,-1.557818 1.215777,0 2.09113,0.314711 2.625866,0.944132 0.552683,0.613686 0.719839,1.628628 0.501658,3.044826 l -0.72727,4.72066 c 0.0096,0.346181 0.09678,0.59795 0.261425,0.755305 0.16705,0.14162 0.46306,0.21243 0.888043,0.21243 z m -3.579715,-5.452363 c -0.977129,0.204562 -1.71325,0.535008 -2.208364,0.991339 -0.476836,0.440595 -0.757119,0.936264 -0.840938,1.487008 -0.1389,0.912661 0.232515,1.368991 1.114242,1.368991 0.440864,0 0.769542,-0.14162 0.986058,-0.424859 0.232298,-0.28324 0.447478,-0.763174 0.645545,-1.439801 z" |  | ||||||
|            style="font-style:normal;font-family:Hostilica;-inkscape-font-specification:Hostilica;fill:url(#linearGradient7547);fill-opacity:1;stroke:none" |  | ||||||
|            id="path1887" |  | ||||||
|            inkscape:original-d="m 68.425725,29.206055 h -2.336727 q -1.085752,0 -1.675834,-0.991339 -0.637289,1.132958 -2.50195,1.132958 -1.392595,0 -2.43114,-0.708099 -1.014942,-0.731702 -1.014942,-2.336726 0,-3.351669 5.546776,-3.823735 -0.04721,-1.014942 -0.35405,-1.439801 -0.306843,-0.448463 -1.062148,-0.448463 -1.557818,0 -2.242314,1.723041 h -0.566479 l -0.849719,-1.841057 q 1.864661,-1.557818 4.461024,-1.557818 1.817454,0 2.761586,0.944132 0.967735,0.920529 0.967735,3.044826 v 4.72066 q 0.09441,0.519272 0.377653,0.755305 0.28324,0.21243 0.920529,0.21243 z m -4.413817,-5.452363 q -1.416198,0.306843 -2.053487,0.991339 -0.613686,0.660892 -0.613686,1.487008 0,1.368991 1.321785,1.368991 0.660892,0 0.920528,-0.424859 0.28324,-0.42486 0.42486,-1.439801 z" /> |  | ||||||
|         <path |  | ||||||
|            d="m 70.85683,29.489294 c -0.456331,0 -0.809043,-0.110149 -1.058166,-0.330446 -0.249165,-0.220297 -0.344538,-0.519272 -0.286088,-0.896925 0.05601,-0.361917 0.241557,-0.645157 0.556657,-0.849719 0.317575,-0.220297 0.704721,-0.330446 1.161407,-0.330446 0.472433,0 0.833247,0.110149 1.082411,0.330446 0.251575,0.204562 0.34916,0.487802 0.292785,0.849719 -0.05883,0.377653 -0.247044,0.676628 -0.56462,0.896925 -0.317535,0.220297 -0.71232,0.330446 -1.184386,0.330446 z" |  | ||||||
|            style="font-style:normal;font-family:Hostilica;-inkscape-font-specification:Hostilica;fill:url(#linearGradient7549);fill-opacity:1;stroke:none" |  | ||||||
|            id="path1889" |  | ||||||
|            inkscape:original-d="m 70.85683,29.489294 q -0.684496,0 -1.109355,-0.330446 -0.42486,-0.330446 -0.42486,-0.896925 0,-0.542876 0.42486,-0.849719 0.424859,-0.330446 1.109355,-0.330446 0.708099,0 1.132958,0.330446 0.42486,0.306843 0.42486,0.849719 0,0.566479 -0.42486,0.896925 -0.424859,0.330446 -1.132958,0.330446 z" /> |  | ||||||
|         <path |  | ||||||
|            d="m 76.714684,17.097562 c 0.0665,-0.424859 0.268353,-0.755306 0.60558,-0.991339 0.355545,-0.251769 0.794127,-0.377653 1.315709,-0.377653 0.521583,0 0.912714,0.125884 1.173359,0.377653 0.278878,0.236033 0.384813,0.56648 0.317842,0.991339 -0.06201,0.393388 -0.270808,0.715966 -0.626355,0.967735 -0.353019,0.236033 -0.78218,0.35405 -1.287519,0.35405 -0.505339,0 -0.897423,-0.118017 -1.176289,-0.35405 -0.27645,-0.251769 -0.383905,-0.574347 -0.322327,-0.967735 z m 1.314965,12.108493 h -2.85626 l 1.308878,-8.355569 c 0.0419,-0.267504 0.01501,-0.448462 -0.08067,-0.542875 -0.09323,-0.110149 -0.281897,-0.165224 -0.565992,-0.165224 l 0.09598,-0.613685 3.120102,-0.56648 c 0.394727,0 0.54746,0.28324 0.458199,0.849719 z" |  | ||||||
|            style="font-style:normal;font-family:Hostilica;-inkscape-font-specification:Hostilica;fill:url(#linearGradient7551);fill-opacity:1;stroke:none" |  | ||||||
|            id="path1891" |  | ||||||
|            inkscape:original-d="m 74.77497,17.097562 q 0,-0.637289 0.448463,-0.991339 0.472066,-0.377653 1.250975,-0.377653 0.778909,0 1.227372,0.377653 0.472066,0.35405 0.472066,0.991339 0,0.590082 -0.472066,0.967735 -0.472066,0.35405 -1.227372,0.35405 -0.755306,0 -1.227372,-0.35405 -0.472066,-0.377653 -0.472066,-0.967735 z m 3.210049,12.108493 H 75.12902 v -8.355569 q 0,-0.401256 -0.165223,-0.542875 -0.165223,-0.165224 -0.590083,-0.165224 v -0.613685 l 3.021223,-0.56648 q 0.590082,0 0.590082,0.849719 z" /> |  | ||||||
|         <path |  | ||||||
|            d="m 81.320323,27.95508 c -0.716436,-0.944132 -0.947935,-2.21871 -0.6941,-3.823735 0.253835,-1.605024 0.880155,-2.871734 1.879343,-3.800131 1.017984,-0.944132 2.18243,-1.416198 3.492943,-1.416198 0.805255,0 1.459341,0.204562 1.962145,0.613686 l 0.586382,-3.658512 c 0.04792,-0.298975 0.03076,-0.487802 -0.05148,-0.566479 -0.07972,-0.09441 -0.277666,-0.14162 -0.593834,-0.14162 l 0.09821,-0.613686 3.127021,-0.566479 c 0.395361,0 0.558797,0.21243 0.490309,0.637289 l -2.351427,14.58684 h -2.856262 l 0.12106,-0.755306 c -0.505021,0.59795 -1.245296,0.896925 -2.220943,0.896925 -1.274637,0 -2.270964,-0.464198 -2.989373,-1.392594 z m 2.33611,-3.847338 c -0.155244,0.975603 -0.105533,1.801718 0.149014,2.478346 0.25439,0.676628 0.727918,1.014942 1.420704,1.014942 0.425119,0 0.934318,-0.180959 1.527691,-0.542876 L 87.06784,25.09908 c 0.257252,-1.605024 0.299416,-2.753718 0.126399,-3.446081 -0.157365,-0.692364 -0.512255,-1.038546 -1.064578,-1.038546 -0.615446,0 -1.144046,0.346182 -1.585688,1.038546 -0.438989,0.676628 -0.734799,1.494876 -0.88754,2.454743 z" |  | ||||||
|            style="font-style:normal;font-family:Hostilica;-inkscape-font-specification:Hostilica;fill:url(#linearGradient7553);fill-opacity:1;stroke:none" |  | ||||||
|            id="path1893" |  | ||||||
|            inkscape:original-d="m 81.077043,27.95508 q -1.298181,-1.416198 -1.298181,-3.823735 0,-2.407536 1.274578,-3.800131 1.298181,-1.416198 3.257255,-1.416198 1.203768,0 2.053487,0.613686 V 15.87019 q 0,-0.448463 -0.14162,-0.566479 -0.141619,-0.14162 -0.613685,-0.14162 v -0.613686 l 3.021222,-0.566479 q 0.590083,0 0.590083,0.637289 v 14.58684 h -2.856 v -0.755306 q -0.613686,0.896925 -2.07709,0.896925 -1.911867,0 -3.210049,-1.392594 z m 1.723041,-3.847338 q 0,1.463404 0.542876,2.478346 0.542876,1.014942 1.581421,1.014942 0.637289,0 1.439801,-0.542876 V 25.09908 q 0,-2.407536 -0.424859,-3.446081 -0.401256,-1.038546 -1.227372,-1.038546 -0.920528,0 -1.416198,1.038546 -0.495669,1.014942 -0.495669,2.454743 z" /> |  | ||||||
|       </g> |  | ||||||
|     </g> |  | ||||||
|   </g> |  | ||||||
| </svg> |  | ||||||
| Before Width: | Height: | Size: 34 KiB | 
							
								
								
									
										52
									
								
								src/assets/logo.svg
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 14 KiB | 
							
								
								
									
										1
									
								
								src/assets/react.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg> | ||||||
| After Width: | Height: | Size: 4.0 KiB | 
							
								
								
									
										263
									
								
								src/components/chat-window.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,263 @@ | |||||||
|  | import { useIsMobile } from "@/hooks/useScreen"; | ||||||
|  | import { cn } from "@/utils"; | ||||||
|  | import { MessageSquareQuote, SendIcon } from "lucide-react"; | ||||||
|  | import React, { useEffect, useRef, useState } from "react"; | ||||||
|  | import furinaAvatar from "@/assets/furina-avatar.webp"; | ||||||
|  | import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; | ||||||
|  | import { api } from "@/api"; | ||||||
|  | import { ThreeDots } from "react-loader-spinner"; | ||||||
|  | import Markdown from "react-markdown"; | ||||||
|  | 
 | ||||||
|  | const ChatWindow = () => { | ||||||
|  |   const containerRef = useRef<HTMLDivElement>(null); | ||||||
|  |   const moveWindowStateRef = useRef({ | ||||||
|  |     isMoving: false, | ||||||
|  |     x: 0, | ||||||
|  |     y: 0, | ||||||
|  |     startX: 0, | ||||||
|  |     startY: 0, | ||||||
|  |   }); | ||||||
|  |   const isMobile = useIsMobile(); | ||||||
|  |   const queryClient = useQueryClient(); | ||||||
|  |   const [isOpen, setOpen] = useState(false); | ||||||
|  | 
 | ||||||
|  |   const { data: messages } = useQuery({ | ||||||
|  |     queryKey: ["chats"], | ||||||
|  |     queryFn: () => api("/chats"), | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   const sendMessage = useMutation({ | ||||||
|  |     mutationFn: async (message: string) => { | ||||||
|  |       return api("/chats", { | ||||||
|  |         method: "POST", | ||||||
|  |         body: JSON.stringify({ message }), | ||||||
|  |         headers: { "Content-Type": "application/json" }, | ||||||
|  |       }); | ||||||
|  |     }, | ||||||
|  |     onMutate: async (data) => { | ||||||
|  |       await queryClient.cancelQueries({ queryKey: ["chats"] }); | ||||||
|  |       const prevData = queryClient.getQueryData(["chats"]); | ||||||
|  | 
 | ||||||
|  |       // optimistic update
 | ||||||
|  |       queryClient.setQueryData(["chats"], (prev: any) => [ | ||||||
|  |         { id: "-1", role: "user", content: data }, | ||||||
|  |         ...prev, | ||||||
|  |       ]); | ||||||
|  |       return { prevData }; | ||||||
|  |     }, | ||||||
|  |     onError: (_err, _data, ctx) => { | ||||||
|  |       queryClient.setQueryData(["chats"], ctx?.prevData); | ||||||
|  |     }, | ||||||
|  |     onSettled: () => { | ||||||
|  |       queryClient.invalidateQueries({ queryKey: ["chats"] }); | ||||||
|  |       const msgEl = document.querySelector('[name="message"]') as | ||||||
|  |         | HTMLInputElement | ||||||
|  |         | undefined; | ||||||
|  |       setTimeout(() => msgEl?.focus(), 100); | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   const onMouseDown = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => { | ||||||
|  |     if (isMobile) { | ||||||
|  |       if (isOpen) { | ||||||
|  |         setOpen(false); | ||||||
|  |       } | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (moveWindowStateRef.current.isMoving) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     e.preventDefault(); | ||||||
|  |     e.stopPropagation(); | ||||||
|  | 
 | ||||||
|  |     moveWindowStateRef.current.isMoving = true; | ||||||
|  |     moveWindowStateRef.current.startX = | ||||||
|  |       e.clientX - moveWindowStateRef.current.x; | ||||||
|  |     moveWindowStateRef.current.startY = | ||||||
|  |       e.clientY - moveWindowStateRef.current.y; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const onMouseMove = (e: MouseEvent) => { | ||||||
|  |     const container = containerRef.current; | ||||||
|  |     if (isMobile || !moveWindowStateRef.current.isMoving || !container) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const x = e.clientX - moveWindowStateRef.current.startX; | ||||||
|  |     const y = e.clientY - moveWindowStateRef.current.startY; | ||||||
|  |     moveWindowStateRef.current.x = x; | ||||||
|  |     moveWindowStateRef.current.y = y; | ||||||
|  |     container.style.transform = `translate(${x}px, ${y}px)`; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const onMouseUp = () => { | ||||||
|  |     moveWindowStateRef.current.isMoving = false; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const onSubmit = (e: React.FormEvent<HTMLFormElement>) => { | ||||||
|  |     e.preventDefault(); | ||||||
|  |     if (sendMessage.isPending) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const form = e.target as HTMLFormElement; | ||||||
|  |     const data = new FormData(form); | ||||||
|  | 
 | ||||||
|  |     const message = data.get("message") as string; | ||||||
|  |     if (!message?.length) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     sendMessage.mutate(message, { | ||||||
|  |       onSuccess: () => form.reset(), | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   useEffect(() => { | ||||||
|  |     document.addEventListener("mousemove", onMouseMove); | ||||||
|  |     document.addEventListener("mouseup", onMouseUp); | ||||||
|  |     document.addEventListener("pointermove", onMouseMove); | ||||||
|  |     document.addEventListener("pointerup", onMouseUp); | ||||||
|  | 
 | ||||||
|  |     return () => { | ||||||
|  |       document.removeEventListener("mousemove", onMouseMove); | ||||||
|  |       document.removeEventListener("mouseup", onMouseUp); | ||||||
|  |       document.removeEventListener("pointermove", onMouseMove); | ||||||
|  |       document.removeEventListener("pointerup", onMouseUp); | ||||||
|  |     }; | ||||||
|  |   }, []); | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <> | ||||||
|  |       <button | ||||||
|  |         className={cn( | ||||||
|  |           "flex md:hidden absolute bottom-4 right-4 h-14 px-4 rounded-xl gap-x-2 bg-white text-slate-600 shadow-lg active:opacity-80 flex-row items-center justify-center", | ||||||
|  |           isOpen && "hidden" | ||||||
|  |         )} | ||||||
|  |         onClick={() => setOpen(!isOpen)} | ||||||
|  |       > | ||||||
|  |         <span>Chat</span> | ||||||
|  |         <MessageSquareQuote /> | ||||||
|  |       </button> | ||||||
|  | 
 | ||||||
|  |       <div | ||||||
|  |         ref={containerRef} | ||||||
|  |         className={cn( | ||||||
|  |           "bg-white/20 border border-white/20 shadow-lg rounded-lg backdrop-blur-md absolute bottom-[10px] sm:bottom-1/4 left-[10px] sm:left-[10%] w-[calc(100%-20px)] sm:max-w-[320px] h-[80vh] sm:h-[300px] lg:max-w-[400px] lg:h-[350px] flex flex-col items-stretch overflow-hidden transition-transform sm:transition-none translate-y-[110%] sm:translate-y-0", | ||||||
|  |           isOpen && "translate-y-0" | ||||||
|  |         )} | ||||||
|  |       > | ||||||
|  |         <div | ||||||
|  |           className="flex flex-row items-center gap-2 px-3 h-8 cursor-move" | ||||||
|  |           onMouseDown={onMouseDown} | ||||||
|  |           onPointerDown={onMouseDown} | ||||||
|  |         > | ||||||
|  |           <div className="size-3 rounded-full bg-red-500" /> | ||||||
|  |           <div className="size-3 rounded-full bg-yellow-500" /> | ||||||
|  |           <div className="size-3 rounded-full bg-green-500" /> | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |         <div className="flex-1 overflow-y-auto flex flex-col-reverse gap-y-2 p-2"> | ||||||
|  |           {sendMessage.isPending && ( | ||||||
|  |             <Message | ||||||
|  |               name="Furina" | ||||||
|  |               role="model" | ||||||
|  |               children={ | ||||||
|  |                 <ThreeDots | ||||||
|  |                   visible={true} | ||||||
|  |                   height="16" | ||||||
|  |                   width="32" | ||||||
|  |                   color="#5381c7" | ||||||
|  |                   ariaLabel="writing.." | ||||||
|  |                 /> | ||||||
|  |               } | ||||||
|  |             /> | ||||||
|  |           )} | ||||||
|  | 
 | ||||||
|  |           {sendMessage.isError && ( | ||||||
|  |             <p className="text-xs text-center self-center text-black my-4 bg-white/10 backdrop-blur-md px-2 py-1 rounded-lg"> | ||||||
|  |               {getSendChatErrorMessage(sendMessage.error)} | ||||||
|  |             </p> | ||||||
|  |           )} | ||||||
|  | 
 | ||||||
|  |           {messages?.map((msg: any) => { | ||||||
|  |             return ( | ||||||
|  |               <Message | ||||||
|  |                 key={msg.id} | ||||||
|  |                 isMe={msg.role === "user"} | ||||||
|  |                 name={msg.role === "user" ? "Me" : "Furina"} | ||||||
|  |                 role={msg.role} | ||||||
|  |                 children={<Markdown>{msg.content}</Markdown>} | ||||||
|  |               /> | ||||||
|  |             ); | ||||||
|  |           })} | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |         <form onSubmit={onSubmit}> | ||||||
|  |           <div className="p-2 flex flex-row items-center pt-1"> | ||||||
|  |             <input | ||||||
|  |               name="message" | ||||||
|  |               className="w-full border-none rounded-full text-sm px-3 h-8 focus:outline-none" | ||||||
|  |               placeholder="Write Message..." | ||||||
|  |               required | ||||||
|  |               disabled={sendMessage.isPending} | ||||||
|  |             /> | ||||||
|  |             <button | ||||||
|  |               className="text-white size-8 shrink-0 hover:bg-white/40 rounded-full flex items-center justify-center -mr-1" | ||||||
|  |               disabled={sendMessage.isPending} | ||||||
|  |             > | ||||||
|  |               <SendIcon size={18} /> | ||||||
|  |             </button> | ||||||
|  |           </div> | ||||||
|  |         </form> | ||||||
|  |       </div> | ||||||
|  |     </> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | type MessageProps = { | ||||||
|  |   isMe?: boolean; | ||||||
|  |   role?: string; | ||||||
|  |   name: string; | ||||||
|  |   children?: React.ReactNode; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const Message = ({ isMe, role, name, children }: MessageProps) => { | ||||||
|  |   return ( | ||||||
|  |     <div | ||||||
|  |       className={cn( | ||||||
|  |         "flex items-start gap-2 w-full max-w-[90%]", | ||||||
|  |         isMe && "justify-end self-end pr-1" | ||||||
|  |       )} | ||||||
|  |     > | ||||||
|  |       {role === "model" && ( | ||||||
|  |         <div className="size-8 rounded-full bg-white shrink-0 overflow-hidden"> | ||||||
|  |           <img src={furinaAvatar} className="w-full h-full object-cover" /> | ||||||
|  |         </div> | ||||||
|  |       )} | ||||||
|  | 
 | ||||||
|  |       <div className={cn("flex flex-col", isMe && "items-end")}> | ||||||
|  |         <p className="font-medium -mt-1 text-sm text-white">{name}</p> | ||||||
|  |         <div | ||||||
|  |           className={cn( | ||||||
|  |             "bg-white/40 backdrop-blur-md text-slate-900 rounded-xl px-2 py-1 mt-0.5 text-sm", | ||||||
|  |             isMe ? "bg-white/80 rounded-tr-none" : "rounded-tl-none" | ||||||
|  |           )} | ||||||
|  |         > | ||||||
|  |           {children} | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const getSendChatErrorMessage = (error: Error) => { | ||||||
|  |   if (error?.message?.includes("FinishReasonSafety")) { | ||||||
|  |     return "Your message probably detected with blocked words, please try again."; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return "An error occured. Please try again."; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default ChatWindow; | ||||||
| @ -1,80 +0,0 @@ | |||||||
| import { ComponentProps } from "react"; |  | ||||||
| 
 |  | ||||||
| import headerImg from "@/assets/images/header-img.gif"; |  | ||||||
| import titleImg from "@/assets/images/title-img.svg"; |  | ||||||
| import ahogeImg from "@/assets/images/furina-ahoge.svg"; |  | ||||||
| import { Link, useLocation } from "react-router-dom"; |  | ||||||
| import { cn } from "@/utility/utils"; |  | ||||||
| 
 |  | ||||||
| const AppBar = () => { |  | ||||||
|   return ( |  | ||||||
|     <header className="w-full bg-[#111a21] shadow"> |  | ||||||
|       <div className="mx-auto max-w-5xl md:px-4 pt-6 md:py-4 flex flex-col md:flex-row items-center gap-4"> |  | ||||||
|         <Link |  | ||||||
|           to="/" |  | ||||||
|           className="flex flex-col md:flex-row items-center gap-2 md:gap-4" |  | ||||||
|         > |  | ||||||
|           <img |  | ||||||
|             src={headerImg} |  | ||||||
|             alt="Furina gif by u/Quit-Creative" |  | ||||||
|             className="h-12 md:h-16" |  | ||||||
|           /> |  | ||||||
|           <img src={titleImg} alt="Furina" className="h-6 md:h-10" /> |  | ||||||
|         </Link> |  | ||||||
| 
 |  | ||||||
|         <Navbar> |  | ||||||
|           <NavbarItem path="/" title="Home" /> |  | ||||||
|           <NavbarItem path="/pat-pat" title="Pat Furina" /> |  | ||||||
|           <NavbarItem path="/treasures" title="Treasures‧₊˚" /> |  | ||||||
|           <NavbarItem path="/toodle" title="Toodle-oo~" /> |  | ||||||
|         </Navbar> |  | ||||||
|       </div> |  | ||||||
|     </header> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| const Navbar = ({ children }: ComponentProps<"div">) => { |  | ||||||
|   return ( |  | ||||||
|     <nav className="md:flex-1 self-stretch md:self-center flex items-center px-2 md:px-0 md:justify-end md:gap-5 overflow-x-auto md:overflow-x-hidden"> |  | ||||||
|       {children} |  | ||||||
|     </nav> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| type NavbarItemProps = { |  | ||||||
|   path: string; |  | ||||||
|   title: string; |  | ||||||
|   isExact?: boolean; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| const NavbarItem = ({ path, title, isExact = true }: NavbarItemProps) => { |  | ||||||
|   const { pathname } = useLocation(); |  | ||||||
|   const isActive = isExact ? pathname === path : pathname.startsWith(path); |  | ||||||
| 
 |  | ||||||
|   return ( |  | ||||||
|     <Link |  | ||||||
|       to={path} |  | ||||||
|       className="group flex flex-shrink-0 items-center px-2 md:py-4 first:ml-auto last:mr-auto md:first:ml-0 md:last:mr-0" |  | ||||||
|     > |  | ||||||
|       <img |  | ||||||
|         src={ahogeImg} |  | ||||||
|         alt="ahoge" |  | ||||||
|         className={cn( |  | ||||||
|           "h-4 md:h-6 group-hover:-scale-x-100 transition-transform", |  | ||||||
|           isActive ? "-scale-x-100" : "" |  | ||||||
|         )} |  | ||||||
|       /> |  | ||||||
| 
 |  | ||||||
|       <p |  | ||||||
|         className={cn( |  | ||||||
|           "text-white ml-2 md:ml-4 py-2 md:py-0 border-b-2 md:border-dotted group-hover:border-primary-500 border-transparent transition-all", |  | ||||||
|           isActive ? "border-primary-500 md:border-white" : "" |  | ||||||
|         )} |  | ||||||
|       > |  | ||||||
|         {title} |  | ||||||
|       </p> |  | ||||||
|     </Link> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export default AppBar; |  | ||||||
| @ -1,41 +0,0 @@ | |||||||
| import Helmet from "react-helmet"; |  | ||||||
| 
 |  | ||||||
| type PageMetadataProps = { |  | ||||||
|   title?: string; |  | ||||||
|   description?: string; |  | ||||||
|   keywords?: string; |  | ||||||
|   allowIndex?: boolean; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| const PageMetadata = (props: PageMetadataProps) => { |  | ||||||
|   return ( |  | ||||||
|     <Helmet> |  | ||||||
|       <title> |  | ||||||
|         {props.title |  | ||||||
|           ? [props.title, "Furina.id"].join(" - ") |  | ||||||
|           : "Welcome to Furina.id"} |  | ||||||
|       </title> |  | ||||||
|       <meta |  | ||||||
|         name="description" |  | ||||||
|         content={props.description || "Welcome to Furina.id"} |  | ||||||
|       /> |  | ||||||
|       <meta |  | ||||||
|         name="keywords" |  | ||||||
|         content={[ |  | ||||||
|           props.keywords, |  | ||||||
|           "furina.id, furina build, furina gameplay, furina guide, furina genshin", |  | ||||||
|         ] |  | ||||||
|           .filter((i) => !!i) |  | ||||||
|           .join(", ")} |  | ||||||
|       /> |  | ||||||
|       <meta |  | ||||||
|         name="robots" |  | ||||||
|         content={ |  | ||||||
|           props.allowIndex !== false ? "index, follow" : "noindex, nofollow" |  | ||||||
|         } |  | ||||||
|       /> |  | ||||||
|     </Helmet> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export default PageMetadata; |  | ||||||
| @ -1,30 +0,0 @@ | |||||||
| import { ComponentProps, Suspense } from "react"; |  | ||||||
| import { Outlet } from "react-router-dom"; |  | ||||||
| import AppBar from "../containers/AppBar"; |  | ||||||
| import LoadingPage from "@/pages/misc/loading-page"; |  | ||||||
| 
 |  | ||||||
| const MainLayout = ({ children }: ComponentProps<"div">) => { |  | ||||||
|   return ( |  | ||||||
|     <div> |  | ||||||
|       <AppBar /> |  | ||||||
| 
 |  | ||||||
|       <main> |  | ||||||
|         {children ? ( |  | ||||||
|           children |  | ||||||
|         ) : ( |  | ||||||
|           <Suspense fallback={<LoadingPage />}> |  | ||||||
|             <Outlet /> |  | ||||||
|           </Suspense> |  | ||||||
|         )} |  | ||||||
| 
 |  | ||||||
|         <footer className="bg-primary-700 shadow relative z-10 px-4 py-8 text-center"> |  | ||||||
|           <p className="text-sm text-white"> |  | ||||||
|             Artworks displayed belong to respective owners. |  | ||||||
|           </p> |  | ||||||
|         </footer> |  | ||||||
|       </main> |  | ||||||
|     </div> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export default MainLayout; |  | ||||||
							
								
								
									
										35
									
								
								src/components/parallax-view.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,35 @@ | |||||||
|  | import { useIsMobile } from "@/hooks/useScreen"; | ||||||
|  | import { ComponentPropsWithoutRef, useEffect, useRef } from "react"; | ||||||
|  | 
 | ||||||
|  | const ParallaxView = ({ | ||||||
|  |   depth, | ||||||
|  |   style, | ||||||
|  |   ...props | ||||||
|  | }: ComponentPropsWithoutRef<"div"> & { depth: number }) => { | ||||||
|  |   const ref = useRef<HTMLDivElement>(null); | ||||||
|  |   const isMobile = useIsMobile(); | ||||||
|  | 
 | ||||||
|  |   useEffect(() => { | ||||||
|  |     const el = ref.current; | ||||||
|  |     if (!el || isMobile) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const onMouseMove = (e: MouseEvent) => { | ||||||
|  |       const x = e.clientX * -depth; | ||||||
|  |       const y = e.clientY * -depth; | ||||||
|  |       el.style.transform = `${ | ||||||
|  |         style?.transform || "" | ||||||
|  |       } translateX(${x}px) translateY(${y}px)`;
 | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     document.addEventListener("mousemove", onMouseMove); | ||||||
|  |     return () => { | ||||||
|  |       document.removeEventListener("mousemove", onMouseMove); | ||||||
|  |     }; | ||||||
|  |   }, [isMobile]); | ||||||
|  | 
 | ||||||
|  |   return <div {...props} style={style} ref={ref} />; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default ParallaxView; | ||||||
| @ -1,36 +0,0 @@ | |||||||
| import * as React from "react"; |  | ||||||
| import { cva, type VariantProps } from "class-variance-authority"; |  | ||||||
| 
 |  | ||||||
| import { cn } from "@/utility/utils"; |  | ||||||
| 
 |  | ||||||
| const badgeVariants = cva( |  | ||||||
|   "inline-flex items-center rounded border border-primary-200 px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-primary-950 focus:ring-offset-2 dark:border-primary-800 dark:focus:ring-primary-300", |  | ||||||
|   { |  | ||||||
|     variants: { |  | ||||||
|       variant: { |  | ||||||
|         default: |  | ||||||
|           "border-transparent bg-primary-600 text-primary-50 hover:bg-primary-600/80 dark:bg-primary-50 dark:text-primary-900 dark:hover:bg-primary-50/80", |  | ||||||
|         secondary: |  | ||||||
|           "border-transparent bg-primary-100 text-primary-900 hover:bg-primary-100/80 dark:bg-primary-800 dark:text-primary-50 dark:hover:bg-primary-800/80", |  | ||||||
|         destructive: |  | ||||||
|           "border-transparent bg-red-500 text-primary-50 hover:bg-red-500/80 dark:bg-red-900 dark:text-primary-50 dark:hover:bg-red-900/80", |  | ||||||
|         outline: "text-primary-950 dark:text-primary-50", |  | ||||||
|       }, |  | ||||||
|     }, |  | ||||||
|     defaultVariants: { |  | ||||||
|       variant: "default", |  | ||||||
|     }, |  | ||||||
|   } |  | ||||||
| ); |  | ||||||
| 
 |  | ||||||
| export interface BadgeProps |  | ||||||
|   extends React.HTMLAttributes<HTMLDivElement>, |  | ||||||
|     VariantProps<typeof badgeVariants> {} |  | ||||||
| 
 |  | ||||||
| function Badge({ className, variant, ...props }: BadgeProps) { |  | ||||||
|   return ( |  | ||||||
|     <div className={cn(badgeVariants({ variant }), className)} {...props} /> |  | ||||||
|   ); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export default Badge; |  | ||||||
| @ -1,58 +0,0 @@ | |||||||
| import * as React from "react"; |  | ||||||
| import { Slot } from "@radix-ui/react-slot"; |  | ||||||
| import { cva, type VariantProps } from "class-variance-authority"; |  | ||||||
| 
 |  | ||||||
| import { cn } from "@/utility/utils"; |  | ||||||
| 
 |  | ||||||
| const buttonVariants = cva( |  | ||||||
|   "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 dark:ring-offset-primary-950 dark:focus-visible:ring-primary-300", |  | ||||||
|   { |  | ||||||
|     variants: { |  | ||||||
|       variant: { |  | ||||||
|         solid: |  | ||||||
|           "bg-primary-500 text-primary-50 hover:bg-primary-500/90 dark:bg-primary-50 dark:text-primary-900 dark:hover:bg-primary-50/90", |  | ||||||
|         destructive: |  | ||||||
|           "bg-red-500 text-primary-50 hover:bg-red-500/90 dark:bg-red-900 dark:text-primary-50 dark:hover:bg-red-900/90", |  | ||||||
|         outline: |  | ||||||
|           "border border-primary-200 bg-white hover:bg-primary-100 hover:text-primary-900 dark:border-primary-800 dark:bg-primary-950 dark:hover:bg-primary-800 dark:hover:text-primary-50", |  | ||||||
|         secondary: |  | ||||||
|           "bg-primary-100 text-primary-900 hover:bg-primary-100/80 dark:bg-primary-800 dark:text-primary-50 dark:hover:bg-primary-800/80", |  | ||||||
|         ghost: |  | ||||||
|           "hover:bg-primary-100 hover:text-primary-900 dark:hover:bg-primary-800 dark:hover:text-primary-50", |  | ||||||
|         link: "text-primary-900 underline-offset-4 hover:underline dark:text-primary-50", |  | ||||||
|       }, |  | ||||||
|       size: { |  | ||||||
|         default: "h-10 px-4 py-2", |  | ||||||
|         sm: "h-9 rounded-md px-3", |  | ||||||
|         lg: "h-11 rounded-md px-8", |  | ||||||
|         icon: "h-10 w-10", |  | ||||||
|       }, |  | ||||||
|     }, |  | ||||||
|     defaultVariants: { |  | ||||||
|       variant: "outline", |  | ||||||
|       size: "default", |  | ||||||
|     }, |  | ||||||
|   } |  | ||||||
| ); |  | ||||||
| 
 |  | ||||||
| export interface ButtonProps |  | ||||||
|   extends React.ButtonHTMLAttributes<HTMLButtonElement>, |  | ||||||
|     VariantProps<typeof buttonVariants> { |  | ||||||
|   asChild?: boolean; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( |  | ||||||
|   ({ className, variant, size, asChild = false, ...props }, ref) => { |  | ||||||
|     const Comp = asChild ? Slot : "button"; |  | ||||||
|     return ( |  | ||||||
|       <Comp |  | ||||||
|         className={cn(buttonVariants({ variant, size, className }))} |  | ||||||
|         ref={ref} |  | ||||||
|         {...props} |  | ||||||
|       /> |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| ); |  | ||||||
| Button.displayName = "Button"; |  | ||||||
| 
 |  | ||||||
| export default Button; |  | ||||||
| @ -1,120 +0,0 @@ | |||||||
| import * as React from "react"; |  | ||||||
| import * as DialogPrimitive from "@radix-ui/react-dialog"; |  | ||||||
| import { X } from "lucide-react"; |  | ||||||
| 
 |  | ||||||
| import { cn } from "@/utility/utils"; |  | ||||||
| 
 |  | ||||||
| const Dialog = DialogPrimitive.Root; |  | ||||||
| 
 |  | ||||||
| const DialogTrigger = DialogPrimitive.Trigger; |  | ||||||
| 
 |  | ||||||
| const DialogPortal = DialogPrimitive.Portal; |  | ||||||
| 
 |  | ||||||
| const DialogClose = DialogPrimitive.Close; |  | ||||||
| 
 |  | ||||||
| const DialogOverlay = React.forwardRef< |  | ||||||
|   React.ElementRef<typeof DialogPrimitive.Overlay>, |  | ||||||
|   React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> |  | ||||||
| >(({ className, ...props }, ref) => ( |  | ||||||
|   <DialogPrimitive.Overlay |  | ||||||
|     ref={ref} |  | ||||||
|     className={cn( |  | ||||||
|       "fixed inset-0 z-50 bg-black/20  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", |  | ||||||
|       className |  | ||||||
|     )} |  | ||||||
|     {...props} |  | ||||||
|   /> |  | ||||||
| )); |  | ||||||
| DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; |  | ||||||
| 
 |  | ||||||
| const DialogContent = React.forwardRef< |  | ||||||
|   React.ElementRef<typeof DialogPrimitive.Content>, |  | ||||||
|   React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> |  | ||||||
| >(({ className, children, ...props }, ref) => ( |  | ||||||
|   <DialogPortal> |  | ||||||
|     <DialogOverlay /> |  | ||||||
|     <DialogPrimitive.Content |  | ||||||
|       ref={ref} |  | ||||||
|       className={cn( |  | ||||||
|         "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-slate-200 bg-white p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg dark:border-slate-800 dark:bg-slate-950", |  | ||||||
|         className |  | ||||||
|       )} |  | ||||||
|       {...props} |  | ||||||
|     > |  | ||||||
|       {children} |  | ||||||
|       <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-white transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-slate-950 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-slate-100 data-[state=open]:text-slate-500 dark:ring-offset-slate-950 dark:focus:ring-slate-300 dark:data-[state=open]:bg-slate-800 dark:data-[state=open]:text-slate-400"> |  | ||||||
|         <X className="h-4 w-4" /> |  | ||||||
|         <span className="sr-only">Close</span> |  | ||||||
|       </DialogPrimitive.Close> |  | ||||||
|     </DialogPrimitive.Content> |  | ||||||
|   </DialogPortal> |  | ||||||
| )); |  | ||||||
| DialogContent.displayName = DialogPrimitive.Content.displayName; |  | ||||||
| 
 |  | ||||||
| const DialogHeader = ({ |  | ||||||
|   className, |  | ||||||
|   ...props |  | ||||||
| }: React.HTMLAttributes<HTMLDivElement>) => ( |  | ||||||
|   <div |  | ||||||
|     className={cn( |  | ||||||
|       "flex flex-col space-y-1.5 text-center sm:text-left", |  | ||||||
|       className |  | ||||||
|     )} |  | ||||||
|     {...props} |  | ||||||
|   /> |  | ||||||
| ); |  | ||||||
| DialogHeader.displayName = "DialogHeader"; |  | ||||||
| 
 |  | ||||||
| const DialogFooter = ({ |  | ||||||
|   className, |  | ||||||
|   ...props |  | ||||||
| }: React.HTMLAttributes<HTMLDivElement>) => ( |  | ||||||
|   <div |  | ||||||
|     className={cn( |  | ||||||
|       "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", |  | ||||||
|       className |  | ||||||
|     )} |  | ||||||
|     {...props} |  | ||||||
|   /> |  | ||||||
| ); |  | ||||||
| DialogFooter.displayName = "DialogFooter"; |  | ||||||
| 
 |  | ||||||
| const DialogTitle = React.forwardRef< |  | ||||||
|   React.ElementRef<typeof DialogPrimitive.Title>, |  | ||||||
|   React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title> |  | ||||||
| >(({ className, ...props }, ref) => ( |  | ||||||
|   <DialogPrimitive.Title |  | ||||||
|     ref={ref} |  | ||||||
|     className={cn( |  | ||||||
|       "text-lg font-semibold leading-none tracking-tight", |  | ||||||
|       className |  | ||||||
|     )} |  | ||||||
|     {...props} |  | ||||||
|   /> |  | ||||||
| )); |  | ||||||
| DialogTitle.displayName = DialogPrimitive.Title.displayName; |  | ||||||
| 
 |  | ||||||
| const DialogDescription = React.forwardRef< |  | ||||||
|   React.ElementRef<typeof DialogPrimitive.Description>, |  | ||||||
|   React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description> |  | ||||||
| >(({ className, ...props }, ref) => ( |  | ||||||
|   <DialogPrimitive.Description |  | ||||||
|     ref={ref} |  | ||||||
|     className={cn("text-sm text-slate-500 dark:text-slate-400", className)} |  | ||||||
|     {...props} |  | ||||||
|   /> |  | ||||||
| )); |  | ||||||
| DialogDescription.displayName = DialogPrimitive.Description.displayName; |  | ||||||
| 
 |  | ||||||
| export { |  | ||||||
|   Dialog, |  | ||||||
|   DialogPortal, |  | ||||||
|   DialogOverlay, |  | ||||||
|   DialogClose, |  | ||||||
|   DialogTrigger, |  | ||||||
|   DialogContent, |  | ||||||
|   DialogHeader, |  | ||||||
|   DialogFooter, |  | ||||||
|   DialogTitle, |  | ||||||
|   DialogDescription, |  | ||||||
| }; |  | ||||||
| @ -1,50 +0,0 @@ | |||||||
| import { cn } from "@/utility/utils"; |  | ||||||
| import React, { useState } from "react"; |  | ||||||
| 
 |  | ||||||
| type Props = React.ComponentProps<"img"> & { |  | ||||||
|   lazySrc: string; |  | ||||||
|   containerClassName?: string; |  | ||||||
|   placeholderClassName?: string; |  | ||||||
|   placeholder?: React.ReactNode; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| const LazyImage = ({ |  | ||||||
|   lazySrc, |  | ||||||
|   src, |  | ||||||
|   containerClassName, |  | ||||||
|   placeholderClassName, |  | ||||||
|   className, |  | ||||||
|   placeholder, |  | ||||||
|   ...props |  | ||||||
| }: Props) => { |  | ||||||
|   const [isLoaded, setLoaded] = useState(false); |  | ||||||
| 
 |  | ||||||
|   return ( |  | ||||||
|     <div className={cn("relative", containerClassName)}> |  | ||||||
|       <div |  | ||||||
|         className={cn( |  | ||||||
|           "absolute inset-0 bg-no-repeat bg-cover blur-md z-0", |  | ||||||
|           // isLoaded ? "brightness-75" : "",
 |  | ||||||
|           placeholderClassName |  | ||||||
|         )} |  | ||||||
|         style={{ backgroundImage: `url('${lazySrc}')` }} |  | ||||||
|       ></div> |  | ||||||
| 
 |  | ||||||
|       {!isLoaded && placeholder ? placeholder : null} |  | ||||||
| 
 |  | ||||||
|       <img |  | ||||||
|         src={src} |  | ||||||
|         loading="lazy" |  | ||||||
|         onLoad={() => setTimeout(() => setLoaded(true), 200)} |  | ||||||
|         className={cn( |  | ||||||
|           "transition-all duration-500 relative z-[1]", |  | ||||||
|           isLoaded ? "opacity-100" : "opacity-0", |  | ||||||
|           className |  | ||||||
|         )} |  | ||||||
|         {...props} |  | ||||||
|       /> |  | ||||||
|     </div> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export default LazyImage; |  | ||||||
| @ -1,58 +0,0 @@ | |||||||
| import React from "react"; |  | ||||||
| import { |  | ||||||
|   Dialog, |  | ||||||
|   DialogClose, |  | ||||||
|   DialogContent, |  | ||||||
|   DialogDescription, |  | ||||||
|   DialogOverlay, |  | ||||||
|   DialogPortal, |  | ||||||
|   DialogTitle, |  | ||||||
| } from "./Dialog"; |  | ||||||
| 
 |  | ||||||
| export type ModalProps = { |  | ||||||
|   children?: React.ReactNode; |  | ||||||
|   isOpen?: boolean; |  | ||||||
|   onOpenChange?: (open: boolean) => void; |  | ||||||
|   title?: string; |  | ||||||
|   description?: string; |  | ||||||
|   size?: "sm" | "md" | "lg" | "xl"; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| const Modal = ({ |  | ||||||
|   children, |  | ||||||
|   isOpen, |  | ||||||
|   onOpenChange, |  | ||||||
|   title, |  | ||||||
|   description, |  | ||||||
|   size = "md", |  | ||||||
| }: ModalProps) => { |  | ||||||
|   return ( |  | ||||||
|     <Dialog open={isOpen} onOpenChange={onOpenChange}> |  | ||||||
|       <DialogPortal> |  | ||||||
|         <DialogOverlay /> |  | ||||||
| 
 |  | ||||||
|         <DialogContent |  | ||||||
|           className={ |  | ||||||
|             { sm: "max-w-md", md: "max-w-lg", lg: "max-w-xl", xl: "max-w-2xl" }[ |  | ||||||
|               size |  | ||||||
|             ] |  | ||||||
|           } |  | ||||||
|         > |  | ||||||
|           {title ? ( |  | ||||||
|             <DialogTitle className="text-xl">{title}</DialogTitle> |  | ||||||
|           ) : null} |  | ||||||
| 
 |  | ||||||
|           {description ? ( |  | ||||||
|             <DialogDescription>{description}</DialogDescription> |  | ||||||
|           ) : null} |  | ||||||
| 
 |  | ||||||
|           {children} |  | ||||||
| 
 |  | ||||||
|           <DialogClose /> |  | ||||||
|         </DialogContent> |  | ||||||
|       </DialogPortal> |  | ||||||
|     </Dialog> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export default Modal; |  | ||||||
| @ -1,161 +0,0 @@ | |||||||
| import * as React from "react"; |  | ||||||
| import * as SheetPrimitive from "@radix-ui/react-dialog"; |  | ||||||
| import { cva, type VariantProps } from "class-variance-authority"; |  | ||||||
| import { X } from "lucide-react"; |  | ||||||
| 
 |  | ||||||
| import { cn } from "@/utility/utils"; |  | ||||||
| 
 |  | ||||||
| const SheetRoot = SheetPrimitive.Root; |  | ||||||
| 
 |  | ||||||
| const SheetPortal = SheetPrimitive.Portal; |  | ||||||
| 
 |  | ||||||
| const SheetOverlay = React.forwardRef< |  | ||||||
|   React.ElementRef<typeof SheetPrimitive.Overlay>, |  | ||||||
|   React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay> |  | ||||||
| >(({ className, ...props }, ref) => ( |  | ||||||
|   <SheetPrimitive.Overlay |  | ||||||
|     className={cn( |  | ||||||
|       "fixed inset-0 z-50 bg-black/20  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", |  | ||||||
|       className |  | ||||||
|     )} |  | ||||||
|     {...props} |  | ||||||
|     ref={ref} |  | ||||||
|   /> |  | ||||||
| )); |  | ||||||
| SheetOverlay.displayName = SheetPrimitive.Overlay.displayName; |  | ||||||
| 
 |  | ||||||
| const sheetVariants = cva( |  | ||||||
|   "fixed z-50 gap-4 bg-white shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500 dark:bg-slate-950", |  | ||||||
|   { |  | ||||||
|     variants: { |  | ||||||
|       side: { |  | ||||||
|         top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", |  | ||||||
|         bottom: |  | ||||||
|           "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", |  | ||||||
|         left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", |  | ||||||
|         right: |  | ||||||
|           "inset-y-0 right-0 h-full w-3/4  border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", |  | ||||||
|       }, |  | ||||||
|     }, |  | ||||||
|     defaultVariants: { |  | ||||||
|       side: "right", |  | ||||||
|     }, |  | ||||||
|   } |  | ||||||
| ); |  | ||||||
| 
 |  | ||||||
| interface SheetContentProps |  | ||||||
|   extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>, |  | ||||||
|     VariantProps<typeof sheetVariants> {} |  | ||||||
| 
 |  | ||||||
| const SheetContent = React.forwardRef< |  | ||||||
|   React.ElementRef<typeof SheetPrimitive.Content>, |  | ||||||
|   SheetContentProps |  | ||||||
| >(({ side = "right", className, children, ...props }, ref) => ( |  | ||||||
|   <SheetPortal> |  | ||||||
|     <SheetOverlay /> |  | ||||||
|     <SheetPrimitive.Content |  | ||||||
|       ref={ref} |  | ||||||
|       className={cn(sheetVariants({ side }), className)} |  | ||||||
|       {...props} |  | ||||||
|     > |  | ||||||
|       {children} |  | ||||||
|       <SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-white transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-slate-950 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-slate-100 dark:ring-offset-slate-950 dark:focus:ring-slate-300 dark:data-[state=open]:bg-slate-800"> |  | ||||||
|         <X className="h-4 w-4" /> |  | ||||||
|         <span className="sr-only">Close</span> |  | ||||||
|       </SheetPrimitive.Close> |  | ||||||
|     </SheetPrimitive.Content> |  | ||||||
|   </SheetPortal> |  | ||||||
| )); |  | ||||||
| SheetContent.displayName = SheetPrimitive.Content.displayName; |  | ||||||
| 
 |  | ||||||
| const SheetHeader = ({ |  | ||||||
|   className, |  | ||||||
|   ...props |  | ||||||
| }: React.HTMLAttributes<HTMLDivElement>) => ( |  | ||||||
|   <div |  | ||||||
|     className={cn( |  | ||||||
|       "flex flex-col p-4 space-y-2 text-center sm:text-left", |  | ||||||
|       className |  | ||||||
|     )} |  | ||||||
|     {...props} |  | ||||||
|   /> |  | ||||||
| ); |  | ||||||
| SheetHeader.displayName = "SheetHeader"; |  | ||||||
| 
 |  | ||||||
| const SheetFooter = ({ |  | ||||||
|   className, |  | ||||||
|   ...props |  | ||||||
| }: React.HTMLAttributes<HTMLDivElement>) => ( |  | ||||||
|   <div |  | ||||||
|     className={cn( |  | ||||||
|       "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", |  | ||||||
|       className |  | ||||||
|     )} |  | ||||||
|     {...props} |  | ||||||
|   /> |  | ||||||
| ); |  | ||||||
| SheetFooter.displayName = "SheetFooter"; |  | ||||||
| 
 |  | ||||||
| const SheetTitle = React.forwardRef< |  | ||||||
|   React.ElementRef<typeof SheetPrimitive.Title>, |  | ||||||
|   React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title> |  | ||||||
| >(({ className, ...props }, ref) => ( |  | ||||||
|   <SheetPrimitive.Title |  | ||||||
|     ref={ref} |  | ||||||
|     className={cn( |  | ||||||
|       "text-lg font-semibold text-slate-950 dark:text-slate-50", |  | ||||||
|       className |  | ||||||
|     )} |  | ||||||
|     {...props} |  | ||||||
|   /> |  | ||||||
| )); |  | ||||||
| SheetTitle.displayName = SheetPrimitive.Title.displayName; |  | ||||||
| 
 |  | ||||||
| const SheetDescription = React.forwardRef< |  | ||||||
|   React.ElementRef<typeof SheetPrimitive.Description>, |  | ||||||
|   React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description> |  | ||||||
| >(({ className, ...props }, ref) => ( |  | ||||||
|   <SheetPrimitive.Description |  | ||||||
|     ref={ref} |  | ||||||
|     className={cn("text-sm text-slate-500 dark:text-slate-400", className)} |  | ||||||
|     {...props} |  | ||||||
|   /> |  | ||||||
| )); |  | ||||||
| SheetDescription.displayName = SheetPrimitive.Description.displayName; |  | ||||||
| 
 |  | ||||||
| type SheetProps = React.ComponentProps<typeof SheetRoot> & { |  | ||||||
|   isOpen?: boolean; |  | ||||||
|   title?: string; |  | ||||||
|   description?: string; |  | ||||||
|   position?: SheetContentProps["side"]; |  | ||||||
|   className?: string; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| const Sheet = ({ |  | ||||||
|   isOpen, |  | ||||||
|   title, |  | ||||||
|   description, |  | ||||||
|   children, |  | ||||||
|   position, |  | ||||||
|   className, |  | ||||||
|   ...props |  | ||||||
| }: SheetProps) => { |  | ||||||
|   return ( |  | ||||||
|     <SheetRoot open={isOpen} {...props}> |  | ||||||
|       <SheetContent |  | ||||||
|         side={position} |  | ||||||
|         className={cn("flex flex-col gap-0 rounded-t-2xl", className)} |  | ||||||
|       > |  | ||||||
|         <SheetHeader> |  | ||||||
|           {title ? <SheetTitle>{title}</SheetTitle> : null} |  | ||||||
|           {description ? ( |  | ||||||
|             <SheetDescription>{description}</SheetDescription> |  | ||||||
|           ) : null} |  | ||||||
|         </SheetHeader> |  | ||||||
|         <div className="flex-1 overflow-y-auto">{children}</div> |  | ||||||
|       </SheetContent> |  | ||||||
|     </SheetRoot> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export default Sheet; |  | ||||||
| @ -1,15 +0,0 @@ | |||||||
| import { cn } from "@/utility/utils" |  | ||||||
| 
 |  | ||||||
| function Skeleton({ |  | ||||||
|   className, |  | ||||||
|   ...props |  | ||||||
| }: React.HTMLAttributes<HTMLDivElement>) { |  | ||||||
|   return ( |  | ||||||
|     <div |  | ||||||
|       className={cn("animate-pulse rounded-md bg-slate-100 dark:bg-slate-800", className)} |  | ||||||
|       {...props} |  | ||||||
|     /> |  | ||||||
|   ) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export { Skeleton } |  | ||||||
| @ -1,11 +0,0 @@ | |||||||
| @tailwind base; |  | ||||||
| @tailwind components; |  | ||||||
| @tailwind utilities; |  | ||||||
| 
 |  | ||||||
| .container { |  | ||||||
|   @apply mx-auto max-w-5xl px-4; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .link { |  | ||||||
|   @apply text-blue-600 font-medium hover:text-blue-500 hover:underline; |  | ||||||
| } |  | ||||||
| @ -1,21 +0,0 @@ | |||||||
| import { useState } from "react"; |  | ||||||
| 
 |  | ||||||
| const useModal = <T = unknown>() => { |  | ||||||
|   const [isOpen, setOpen] = useState(false); |  | ||||||
|   const [data, setData] = useState<T | undefined | null>(null); |  | ||||||
| 
 |  | ||||||
|   return { |  | ||||||
|     isOpen, |  | ||||||
|     data, |  | ||||||
|     onOpen(_data?: T | null) { |  | ||||||
|       setOpen(true); |  | ||||||
|       setData(_data); |  | ||||||
|     }, |  | ||||||
|     onClose() { |  | ||||||
|       setOpen(false); |  | ||||||
|     }, |  | ||||||
|     onOpenChange: setOpen, |  | ||||||
|   }; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export default useModal; |  | ||||||
							
								
								
									
										23
									
								
								src/hooks/useScreen.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,23 @@ | |||||||
|  | import { useEffect, useState } from "react"; | ||||||
|  | 
 | ||||||
|  | export const useScreen = () => { | ||||||
|  |     const [width, setWidth] = useState(window.innerWidth); | ||||||
|  |     const [height, setHeight] = useState(window.innerHeight); | ||||||
|  | 
 | ||||||
|  |     useEffect(() => { | ||||||
|  |         const handleResize = () => { | ||||||
|  |             setWidth(window.innerWidth); | ||||||
|  |             setHeight(window.innerHeight); | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         window.addEventListener("resize", handleResize); | ||||||
|  | 
 | ||||||
|  |         return () => { | ||||||
|  |             window.removeEventListener("resize", handleResize); | ||||||
|  |         }; | ||||||
|  |     }, []); | ||||||
|  | 
 | ||||||
|  |     return { width, height }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const useIsMobile = () => useScreen().width < 640 | ||||||
							
								
								
									
										34
									
								
								src/main.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,34 @@ | |||||||
|  | @tailwind base; | ||||||
|  | @tailwind components; | ||||||
|  | @tailwind utilities; | ||||||
|  | 
 | ||||||
|  | /* Chrome, Edge and Safari */ | ||||||
|  | *::-webkit-scrollbar { | ||||||
|  |   height: 8px; | ||||||
|  |   width: 8px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | *::-webkit-scrollbar-track { | ||||||
|  |   border-radius: 5px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | *::-webkit-scrollbar-track:hover { | ||||||
|  |   background-color: rgba(255, 255, 255, 0.2); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | *::-webkit-scrollbar-track:active { | ||||||
|  |   background-color: rgba(255, 255, 255, 0.2); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | *::-webkit-scrollbar-thumb { | ||||||
|  |   border-radius: 5px; | ||||||
|  |   background-color: #353771; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | *::-webkit-scrollbar-thumb:hover { | ||||||
|  |   background-color: #464994; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | *::-webkit-scrollbar-thumb:active { | ||||||
|  |   background-color: #464994; | ||||||
|  | } | ||||||
							
								
								
									
										19
									
								
								src/main.tsx
									
									
									
									
									
								
							
							
						
						| @ -1,10 +1,13 @@ | |||||||
| import React from "react"; | import { StrictMode } from "react"; | ||||||
| import ReactDOM from "react-dom/client"; | import { createRoot } from "react-dom/client"; | ||||||
| import Loader from "./Loader"; | import App from "./app.tsx"; | ||||||
| import "./global.css"; | import Providers from "./providers.tsx"; | ||||||
|  | import "./main.css"; | ||||||
| 
 | 
 | ||||||
| ReactDOM.createRoot(document.getElementById("root")!).render( | createRoot(document.getElementById("root")!).render( | ||||||
|   <React.StrictMode> |   <StrictMode> | ||||||
|     <Loader /> |     <Providers> | ||||||
|   </React.StrictMode> |       <App /> | ||||||
|  |     </Providers> | ||||||
|  |   </StrictMode> | ||||||
| ); | ); | ||||||
|  | |||||||