Compare commits

..

No commits in common. "6fe80a549845be6c2a986a025924643c4898e763" and "4056e47a24b8ffc4f72c8d833c20b41fbe92435f" have entirely different histories.

132 changed files with 6305 additions and 4883 deletions

18
.eslintrc.cjs Normal file
View 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 },
],
},
}

View File

@ -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

View File

@ -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

View File

@ -1,3 +0,0 @@
#
DATABASE_PATH=database.db
GEMINI_API_KEY=

10
backend/.gitignore vendored
View File

@ -1,5 +1,5 @@
#
tmp/
.env*
!.env.example
*.db
CHANGELOG.md
LICENSE.md
*.zip
pocketbase
pb_data/

1
backend/.pbversion Normal file
View File

@ -0,0 +1 @@
0.20.5

View File

@ -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
}

View File

@ -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
)

View File

@ -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=

View File

@ -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
}

View File

@ -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)))
}

View File

@ -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"`
// }

View File

@ -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"`
}

View File

@ -1,7 +0,0 @@
package models
type Session struct {
Base
UUID string `gorm:"uniqueIndex" json:"uuid"`
}

13
backend/package.json Normal file
View 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"
}

View 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");

View 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;
})

View 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)
})

View 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);
})

View 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)
})

View File

@ -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
View 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
View File

@ -0,0 +1,4 @@
#!/bin/bash
npm run build
scp -r ./dist/* khai:/var/www/furina.id

View File

@ -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 },
],
},
},
)

View File

@ -1,13 +1,34 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<title>˚ʚ Furina.id ɞ˚</title>
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="icon" type="image/gif" sizes="32x32" href="/favicon.gif" />
<link rel="manifest" href="/site.webmanifest" />
</head>
<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>

View File

@ -1,39 +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",
"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"
}
}

3176
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -3,4 +3,4 @@ export default {
tailwindcss: {},
autoprefixer: {},
},
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

BIN
public/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
public/assets/ui/btn_play.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

BIN
public/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 836 B

BIN
public/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
public/favicon.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

1
public/site.webmanifest Normal file
View File

@ -0,0 +1 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}

20
src/App.tsx Normal file
View 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
View 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
View 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;

View File

@ -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");
}
};

View File

@ -1,71 +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";
const App = () => {
return (
<div
className="bg-[#353771] min-h-screen max-h-[100dvh] overflow-hidden relative"
style={{
background: "linear-gradient(145deg, #353771 30%, #8ea6f4 150%)",
}}
>
<ParallaxView
depth={0.005}
className="absolute inset-0 bg-white"
style={{
maskImage: `url(${dotPattern})`,
maskRepeat: "repeat",
background:
"radial-gradient(circle, rgba(255,255,255,0.3) 0%, rgba(255,255,255,0) 90%)",
transform: "scale(1.2)",
}}
/>
{/* <ParallaxView
depth={0.01}
className="absolute w-[90vw] max-w-[800px] bottom-[10%] left-1/2"
>
<div className="animate-[wiggle_10s_ease-in-out_infinite] pointer-events-none">
<div className="-translate-x-[46%] rotate-6 w-full aspect-video bg-[#d0ecf7] drop-shadow-[-40px_-40px_0px_#869de8]" />
</div>
</ParallaxView> */}
<ParallaxView
depth={0.015}
className="absolute right-[10%] top-[10%] md:top-[10%]"
>
<img
src={hydroElement}
className="h-[80vw] md:h-[40vw] blur-sm opacity-20 pointer-events-none"
/>
</ParallaxView>
<ParallaxView
depth={0.005}
className="absolute left-1/2 -bottom-[5%] w-full"
>
<div className="animate-[updown_8s_ease-in-out_infinite] w-full pointer-events-none">
<img
src={furinaImg}
className="max-h-screen w-full aspect-[0.666] object-contain -translate-x-1/2"
/>
</div>
</ParallaxView>
<div className="absolute left-1/2 -translate-x-1/2 top-0 h-full w-full max-w-3xl">
<ParallaxView
depth={0.01}
className="absolute top-0 md:top-[10%] left-[10%] md:left-[1%]"
>
<div className="animate-[wiggle_10s_ease-in-out_infinite] pointer-events-none">
<img src={logo} className="w-[120px] md:w-[200px]" />
</div>
</ParallaxView>
</div>
<ChatWindow />
</div>
);
};
export default App;

Binary file not shown.

Binary file not shown.

View 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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 715 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 134 KiB

View 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

View 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

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View 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

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -1,263 +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-transform sm:transition-none translate-y-[110%] sm:translate-y-0",
isOpen && "translate-y-0"
)}
>
<div
className="flex flex-row items-center gap-2 px-3 h-8 cursor-move"
onMouseDown={onMouseDown}
onPointerDown={onMouseDown}
>
<div className="size-3 rounded-full bg-red-500" />
<div className="size-3 rounded-full bg-yellow-500" />
<div className="size-3 rounded-full bg-green-500" />
</div>
<div className="flex-1 overflow-y-auto flex flex-col-reverse gap-y-2 p-2">
{sendMessage.isPending && (
<Message
name="Furina"
role="model"
children={
<ThreeDots
visible={true}
height="16"
width="32"
color="#5381c7"
ariaLabel="writing.."
/>
}
/>
)}
{sendMessage.isError && (
<p className="text-xs text-center self-center text-black my-4 bg-white/10 backdrop-blur-md px-2 py-1 rounded-lg">
{getSendChatErrorMessage(sendMessage.error)}
</p>
)}
{messages?.map((msg: any) => {
return (
<Message
key={msg.id}
isMe={msg.role === "user"}
name={msg.role === "user" ? "Me" : "Furina"}
role={msg.role}
children={<Markdown>{msg.content}</Markdown>}
/>
);
})}
</div>
<form onSubmit={onSubmit}>
<div className="p-2 flex flex-row items-center pt-1">
<input
name="message"
className="w-full border-none rounded-full text-sm px-3 h-8 focus:outline-none"
placeholder="Write Message..."
required
disabled={sendMessage.isPending}
/>
<button
className="text-white size-8 shrink-0 hover:bg-white/40 rounded-full flex items-center justify-center -mr-1"
disabled={sendMessage.isPending}
>
<SendIcon size={18} />
</button>
</div>
</form>
</div>
</>
);
};
type MessageProps = {
isMe?: boolean;
role?: string;
name: string;
children?: React.ReactNode;
};
const Message = ({ isMe, role, name, children }: MessageProps) => {
return (
<div
className={cn(
"flex items-start gap-2 w-full max-w-[90%]",
isMe && "justify-end self-end pr-1"
)}
>
{role === "model" && (
<div className="size-8 rounded-full bg-white shrink-0 overflow-hidden">
<img src={furinaAvatar} className="w-full h-full object-cover" />
</div>
)}
<div className={cn("flex flex-col", isMe && "items-end")}>
<p className="font-medium -mt-1 text-sm text-white">{name}</p>
<div
className={cn(
"bg-white/40 backdrop-blur-md text-slate-900 rounded-xl px-2 py-1 mt-0.5 text-sm",
isMe ? "bg-white/80 rounded-tr-none" : "rounded-tl-none"
)}
>
{children}
</div>
</div>
</div>
);
};
const getSendChatErrorMessage = (error: Error) => {
if (error?.message?.includes("FinishReasonSafety")) {
return "Your message probably detected with blocked words, please try again.";
}
return "An error occured. Please try again.";
};
export default ChatWindow;

View 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;

View 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;

View 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;

View File

@ -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;

View 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;

View 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;

View 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,
};

View 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;

View 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
View 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;

View 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
View 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
View 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;

View File

@ -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

View File

@ -1,34 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Chrome, Edge and Safari */
*::-webkit-scrollbar {
height: 8px;
width: 8px;
}
*::-webkit-scrollbar-track {
border-radius: 5px;
}
*::-webkit-scrollbar-track:hover {
background-color: rgba(255, 255, 255, 0.2);
}
*::-webkit-scrollbar-track:active {
background-color: rgba(255, 255, 255, 0.2);
}
*::-webkit-scrollbar-thumb {
border-radius: 5px;
background-color: #353771;
}
*::-webkit-scrollbar-thumb:hover {
background-color: #464994;
}
*::-webkit-scrollbar-thumb:active {
background-color: #464994;
}

View File

@ -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>
);

Some files were not shown because too many files have changed in this diff Show More