Compare commits
No commits in common. "main" and "old" have entirely different histories.
18
.eslintrc.cjs
Normal file
@ -0,0 +1,18 @@
|
||||
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 },
|
||||
],
|
||||
},
|
||||
}
|
42
README.md
@ -14,37 +14,17 @@ If you are developing a production application, we recommend updating the config
|
||||
- Configure the top-level `parserOptions` property like this:
|
||||
|
||||
```js
|
||||
export default tseslint.config({
|
||||
languageOptions: {
|
||||
// other options...
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
export default {
|
||||
// other rules...
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
project: ['./tsconfig.json', './tsconfig.node.json'],
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
|
||||
- Optionally add `...tseslint.configs.stylisticTypeChecked`
|
||||
- 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,
|
||||
},
|
||||
})
|
||||
```
|
||||
- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
|
||||
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
|
||||
- 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
|
||||
|
@ -1,51 +0,0 @@
|
||||
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
|
@ -1,3 +0,0 @@
|
||||
#
|
||||
DATABASE_PATH=database.db
|
||||
GEMINI_API_KEY=
|
11
backend/.gitignore
vendored
@ -1,6 +1,5 @@
|
||||
#
|
||||
tmp/
|
||||
.env*
|
||||
!.env.example
|
||||
*.db
|
||||
main
|
||||
CHANGELOG.md
|
||||
LICENSE.md
|
||||
*.zip
|
||||
pocketbase
|
||||
pb_data/
|
||||
|
1
backend/.pbversion
Normal file
@ -0,0 +1 @@
|
||||
0.20.5
|
@ -1,34 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
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
@ -1,201 +0,0 @@
|
||||
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=
|
@ -1,161 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
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)))
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
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"`
|
||||
// }
|
@ -1,10 +0,0 @@
|
||||
package models
|
||||
|
||||
type Chat struct {
|
||||
Base
|
||||
|
||||
SessionID uint `json:"session_id"`
|
||||
Session Session `json:"session"`
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package models
|
||||
|
||||
type Session struct {
|
||||
Base
|
||||
|
||||
UUID string `gorm:"uniqueIndex" json:"uuid"`
|
||||
}
|
13
backend/package.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"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"
|
||||
}
|
11
backend/pb_hooks/main.pb.js
Normal file
@ -0,0 +1,11 @@
|
||||
/* 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");
|
161
backend/pb_migrations/1704899482_collections_snapshot.js
Normal file
@ -0,0 +1,161 @@
|
||||
/// <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;
|
||||
})
|
31
backend/pb_migrations/1704952067_updated_artworks.js
Normal file
@ -0,0 +1,31 @@
|
||||
/// <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)
|
||||
})
|
43
backend/pb_migrations/1705455990_created_wallpapers.js
Normal file
@ -0,0 +1,43 @@
|
||||
/// <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);
|
||||
})
|
18
backend/pb_migrations/1705456227_updated_wallpapers.js
Normal file
@ -0,0 +1,18 @@
|
||||
/// <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)
|
||||
})
|
@ -1,71 +0,0 @@
|
||||
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
|
||||
}
|
17
components.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"$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"
|
||||
}
|
||||
}
|
4
deploy.sh
Executable file
@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
npm run build
|
||||
scp -r ./dist/* khai:/var/www/furina.id
|
@ -1,28 +0,0 @@
|
||||
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 },
|
||||
],
|
||||
},
|
||||
},
|
||||
)
|
19
index.html
@ -1,4 +1,4 @@
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
@ -12,8 +12,23 @@
|
||||
<link rel="icon" type="image/gif" sizes="32x32" href="/favicon.gif" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<body class="light">
|
||||
<div id="root"></div>
|
||||
<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>
|
||||
</html>
|
||||
|
74
package.json
@ -1,40 +1,58 @@
|
||||
{
|
||||
"name": "furina.id",
|
||||
"name": "furina-web",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
"build": "tsc && vite build",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview",
|
||||
"test": "NODE_OPTIONS=\"--loader ts-node/esm\" mocha \"./src/**/*.test.ts\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "^5.52.2",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"lucide-react": "^0.436.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-loader-spinner": "^6.1.6",
|
||||
"react-markdown": "^9.0.1",
|
||||
"tailwind-merge": "^2.5.2"
|
||||
"@radix-ui/react-dialog": "^1.0.5",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.0",
|
||||
"dayjs": "^1.11.10",
|
||||
"howler": "^2.2.4",
|
||||
"lucide-react": "^0.306.0",
|
||||
"pixi.js": "^7.3.3",
|
||||
"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": {
|
||||
"@eslint/js": "^9.9.0",
|
||||
"@types/node": "^22.5.1",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^9.9.0",
|
||||
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.9",
|
||||
"globals": "^15.9.0",
|
||||
"postcss": "^8.4.41",
|
||||
"tailwindcss": "^3.4.10",
|
||||
"typescript": "^5.5.3",
|
||||
"typescript-eslint": "^8.0.1",
|
||||
"vite": "^5.4.1"
|
||||
"@types/chai": "^4.3.11",
|
||||
"@types/howler": "^2.2.11",
|
||||
"@types/mocha": "^10.0.6",
|
||||
"@types/node": "^20.10.6",
|
||||
"@types/react": "^18.2.43",
|
||||
"@types/react-dom": "^18.2.17",
|
||||
"@types/react-helmet": "^6.1.11",
|
||||
"@types/uuid": "^9.0.7",
|
||||
"@typescript-eslint/eslint-plugin": "^6.14.0",
|
||||
"@typescript-eslint/parser": "^6.14.0",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"chai": "^5.0.0",
|
||||
"eslint": "^8.55.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
3184
pnpm-lock.yaml
generated
@ -3,4 +3,4 @@ export default {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
0
public/android-chrome-192x192.png
Executable file → Normal file
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
0
public/android-chrome-512x512.png
Executable file → Normal file
Before Width: | Height: | Size: 208 KiB After Width: | Height: | Size: 208 KiB |
0
public/apple-touch-icon.png
Executable file → Normal file
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
BIN
public/assets/images/furina-beeg.webp
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
public/assets/images/furina-curious.webp
Normal file
After Width: | Height: | Size: 65 KiB |
BIN
public/assets/images/furina-happy.webp
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
public/assets/images/hand.webp
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
public/assets/images/handpet/handpet_00.webp
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/assets/images/handpet/handpet_01.webp
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/assets/images/handpet/handpet_02.webp
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
public/assets/images/handpet/handpet_03.webp
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/assets/images/handpet/handpet_04.webp
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/assets/images/handpet/handpet_05.webp
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/assets/images/handpet/handpet_06.webp
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/assets/images/handpet/handpet_07.webp
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
public/assets/images/handpet/handpet_08.webp
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/assets/images/handpet/handpet_09.webp
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/assets/images/handpet/handpet_10.webp
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/assets/sfx/kuru-kuru-kururin.ogg
Normal file
BIN
public/assets/sfx/kururina-intro.mp3
Normal file
BIN
public/assets/sfx/kururina-intro.ogg
Normal file
BIN
public/assets/sfx/kururina-loop.mp3
Normal file
BIN
public/assets/sfx/pet-the-peepo-prepare.ogg
Normal file
BIN
public/assets/sfx/pet-the-peepo.ogg
Normal file
BIN
public/assets/ui/btn_play.png
Executable file
After Width: | Height: | Size: 9.4 KiB |
BIN
public/assets/ui/btn_touch.png
Normal file
After Width: | Height: | Size: 8.3 KiB |
0
public/favicon-16x16.png
Executable file → Normal file
Before Width: | Height: | Size: 836 B After Width: | Height: | Size: 836 B |
0
public/favicon-32x32.png
Executable file → Normal file
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
0
public/favicon.gif
Executable file → Normal file
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
0
public/favicon.ico
Executable file → Normal file
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
0
public/site.webmanifest
Executable file → Normal file
1
public/vite.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="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
20
src/App.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
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;
|
12
src/Loader.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
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;
|
43
src/Router.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
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
@ -1,20 +0,0 @@
|
||||
//
|
||||
|
||||
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");
|
||||
}
|
||||
};
|
177
src/app.tsx
@ -1,177 +0,0 @@
|
||||
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";
|
||||
import { useEffect, useState } from "react";
|
||||
import dayjs from "dayjs";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { CloudSun, GitPullRequest } from "lucide-react";
|
||||
import { cn } from "./utils";
|
||||
|
||||
const App = () => {
|
||||
const [isMounted, setIsMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setIsMounted(true);
|
||||
}, []);
|
||||
|
||||
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">
|
||||
<img
|
||||
src={furinaImg}
|
||||
className={cn(
|
||||
"max-h-screen w-full aspect-[0.666] object-contain -translate-x-1/2 transition-all ease-out duration-500",
|
||||
isMounted ? "opacity-100" : "opacity-0 scale-110"
|
||||
)}
|
||||
/>
|
||||
</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={cn(
|
||||
"w-[120px] md:w-[200px] transition-all delay-100 duration-300",
|
||||
isMounted ? "opacity-100" : "opacity-0"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</ParallaxView>
|
||||
|
||||
<p className="absolute left-4 md:left-0 bottom-10 text-white text-sm">
|
||||
Furina artwork by{" "}
|
||||
<a
|
||||
href="https://www.pixiv.net/en/users/98144454"
|
||||
target="_blank"
|
||||
rel="nofollow"
|
||||
className="font-bold"
|
||||
>
|
||||
Rine
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p className="absolute left-4 md:left-auto md:right-0 bottom-4 md:bottom-10 text-white text-sm">
|
||||
<GitPullRequest className="inline mr-2" size={16} />
|
||||
Fork{" "}
|
||||
<a
|
||||
href="https://git.rul.sh/khairul169/furina.id"
|
||||
target="_blank"
|
||||
className="font-bold"
|
||||
>
|
||||
Repo
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<Clock
|
||||
className={cn(
|
||||
"transition-all delay-200 duration-500",
|
||||
isMounted ? "opacity-100" : "opacity-0"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ChatWindow />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
type ClockProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const Clock = ({ className }: ClockProps) => {
|
||||
const [time, setTime] = useState(new Date());
|
||||
const { data: weather } = useQuery({
|
||||
queryKey: ["forecast"],
|
||||
queryFn: async () => {
|
||||
const url =
|
||||
"https://api.open-meteo.com/v1/forecast?latitude=-6.2297401&longitude=106.7469453¤t_weather=true";
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) {
|
||||
throw new Error(res.statusText);
|
||||
}
|
||||
const data = await res.json();
|
||||
return data?.current_weather;
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setTime(new Date());
|
||||
}, 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ParallaxView
|
||||
depth={0.01}
|
||||
className={cn(
|
||||
"bg-white/10 border border-white/40 backdrop-blur-sm text-white p-4 rounded-lg absolute right-4 md:right-0 bottom-24 md:bottom-[10%]",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<p className="text-right">{dayjs(time).format("dddd, DD MMM YYYY")}</p>
|
||||
|
||||
<p className="text-3xl md:text-5xl font-mono mt-1">
|
||||
{dayjs(time).format("hh:mm:ss")}
|
||||
</p>
|
||||
|
||||
{weather ? (
|
||||
<div className="flex flex-row items-center gap-2 mt-0.5 justify-end md:text-2xl">
|
||||
<CloudSun size={20} />
|
||||
<p>
|
||||
{weather?.temperature}
|
||||
<span className="text-sm"> °C</span>
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
</ParallaxView>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
BIN
src/assets/audio/VO_JA_Furina_Elemental_Burst_03.ogg
Normal file
BIN
src/assets/audio/VO_JA_Furina_Elemental_Skill_1_04.ogg
Normal file
BIN
src/assets/audio/VO_JA_Furina_Opening_Treasure_Chest_02.ogg
Normal file
@ -1,362 +0,0 @@
|
||||
<?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>
|
Before Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 715 KiB |
Before Width: | Height: | Size: 134 KiB |
1
src/assets/icons/close-outline.svg
Normal file
@ -0,0 +1 @@
|
||||
<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>
|
After Width: | Height: | Size: 227 B |
1
src/assets/icons/copy-outline.svg
Normal file
@ -0,0 +1 @@
|
||||
<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>
|
After Width: | Height: | Size: 430 B |
1
src/assets/icons/play-outline.svg
Normal file
@ -0,0 +1 @@
|
||||
<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>
|
After Width: | Height: | Size: 290 B |
BIN
src/assets/images/113932900_p0_master1200.webp
Normal file
After Width: | Height: | Size: 62 KiB |
49
src/assets/images/furina-ahoge.svg
Normal file
@ -0,0 +1,49 @@
|
||||
<?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>
|
After Width: | Height: | Size: 2.5 KiB |
BIN
src/assets/images/furina-build.webp
Normal file
After Width: | Height: | Size: 242 KiB |
BIN
src/assets/images/furina-cover.webp
Normal file
After Width: | Height: | Size: 134 KiB |
BIN
src/assets/images/header-img.gif
Normal file
After Width: | Height: | Size: 213 KiB |
BIN
src/assets/images/l9fsdoa2j7vb1.gif
Normal file
After Width: | Height: | Size: 35 KiB |
325
src/assets/images/title-img.svg
Normal file
@ -0,0 +1,325 @@
|
||||
<?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>
|
After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 14 KiB |
@ -1,264 +0,0 @@
|
||||
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-all ease-linear sm:traansition-none translate-y-[110%] sm:translate-y-0 opacity-100",
|
||||
messages == null && "-translate-x-8 opacity-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;
|
80
src/components/containers/AppBar.tsx
Normal file
@ -0,0 +1,80 @@
|
||||
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;
|
41
src/components/containers/PageMetadata.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
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;
|
30
src/components/layouts/MainLayout.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
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;
|
@ -1,35 +0,0 @@
|
||||
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;
|
36
src/components/ui/Badge.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
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;
|
58
src/components/ui/Button.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
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;
|
120
src/components/ui/Dialog.tsx
Normal file
@ -0,0 +1,120 @@
|
||||
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,
|
||||
};
|
50
src/components/ui/LazyImage.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
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;
|
58
src/components/ui/Modal.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
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;
|
161
src/components/ui/Sheet.tsx
Normal file
@ -0,0 +1,161 @@
|
||||
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;
|
15
src/components/ui/Skeleton.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
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 }
|
11
src/global.css
Normal file
@ -0,0 +1,11 @@
|
||||
@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;
|
||||
}
|
21
src/hooks/useModal.ts
Normal file
@ -0,0 +1,21 @@
|
||||
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;
|
@ -1,23 +0,0 @@
|
||||
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
|
40
src/main.css
@ -1,40 +0,0 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,300;0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&display=swap');
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
html, body {
|
||||
font-family: 'Poppins', sans-serif;
|
||||
}
|
||||
|
||||
/* 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,13 +1,10 @@
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import App from "./app.tsx";
|
||||
import Providers from "./providers.tsx";
|
||||
import "./main.css";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import Loader from "./Loader";
|
||||
import "./global.css";
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
<Providers>
|
||||
<App />
|
||||
</Providers>
|
||||
</StrictMode>
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<Loader />
|
||||
</React.StrictMode>
|
||||
);
|
||||
|