sillytavern 1.13.3 → 1.13.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/CONTRIBUTING.md +14 -3
  2. package/Dockerfile +1 -1
  3. package/config.yaml +38 -8
  4. package/default/config.yaml +38 -8
  5. package/default/content/presets/openai/Default.json +6 -3
  6. package/default/content/settings.json +1 -1
  7. package/default/public/error/host-not-allowed.html +21 -0
  8. package/package.json +9 -8
  9. package/public/css/backgrounds.css +302 -0
  10. package/public/css/mobile-styles.css +72 -8
  11. package/public/css/popup.css +25 -8
  12. package/public/css/world-info.css +4 -0
  13. package/public/error/host-not-allowed.html +21 -0
  14. package/public/global.d.ts +11 -11
  15. package/public/img/apple-icon-192x192.png +0 -0
  16. package/public/img/apple-icon-512x512.png +0 -0
  17. package/public/img/azure_openai.svg +1 -0
  18. package/public/img/electronhub.svg +1 -0
  19. package/public/index.html +240 -202
  20. package/public/lib/pagination.js +69 -3
  21. package/public/locales/ar-sa.json +3 -1
  22. package/public/locales/de-de.json +3 -1
  23. package/public/locales/es-es.json +3 -1
  24. package/public/locales/fr-fr.json +4 -2
  25. package/public/locales/is-is.json +1 -1
  26. package/public/locales/it-it.json +3 -1
  27. package/public/locales/ja-jp.json +3 -1
  28. package/public/locales/ko-kr.json +3 -1
  29. package/public/locales/lang.json +66 -17
  30. package/public/locales/nl-nl.json +1 -1
  31. package/public/locales/pt-pt.json +3 -1
  32. package/public/locales/ru-ru.json +271 -110
  33. package/public/locales/th-th.json +1461 -0
  34. package/public/locales/uk-ua.json +3 -1
  35. package/public/locales/vi-vn.json +3 -1
  36. package/public/locales/zh-cn.json +395 -187
  37. package/public/locales/zh-tw.json +4 -2
  38. package/public/login.html +1 -1
  39. package/public/manifest.json +10 -0
  40. package/public/script.js +400 -277
  41. package/public/scripts/RossAscends-mods.js +54 -42
  42. package/public/scripts/a11y.js +116 -0
  43. package/public/scripts/backgrounds.js +288 -179
  44. package/public/scripts/chats.js +30 -4
  45. package/public/scripts/constants.js +1 -0
  46. package/public/scripts/custom-request.js +3 -1
  47. package/public/scripts/dom-handlers.js +66 -0
  48. package/public/scripts/dynamic-styles.js +52 -16
  49. package/public/scripts/events.js +4 -0
  50. package/public/scripts/extensions/assets/index.js +37 -3
  51. package/public/scripts/extensions/assets/style.css +27 -2
  52. package/public/scripts/extensions/caption/index.js +23 -5
  53. package/public/scripts/extensions/caption/settings.html +51 -51
  54. package/public/scripts/extensions/connection-manager/index.js +33 -2
  55. package/public/scripts/extensions/gallery/index.js +45 -5
  56. package/public/scripts/extensions/gallery/style.css +3 -2
  57. package/public/scripts/extensions/memory/index.js +1 -1
  58. package/public/scripts/extensions/regex/debugger.css +263 -0
  59. package/public/scripts/extensions/regex/debugger.html +179 -0
  60. package/public/scripts/extensions/regex/dropdown.html +55 -3
  61. package/public/scripts/extensions/regex/engine.js +227 -27
  62. package/public/scripts/extensions/regex/importTarget.html +6 -0
  63. package/public/scripts/extensions/regex/index.js +1394 -137
  64. package/public/scripts/extensions/regex/presetEmbeddedScripts.html +5 -0
  65. package/public/scripts/extensions/regex/scriptTemplate.html +20 -11
  66. package/public/scripts/extensions/regex/style.css +39 -28
  67. package/public/scripts/extensions/shared.js +20 -6
  68. package/public/scripts/extensions/stable-diffusion/dropdown.html +1 -1
  69. package/public/scripts/extensions/stable-diffusion/index.js +130 -14
  70. package/public/scripts/extensions/stable-diffusion/settings.html +10 -6
  71. package/public/scripts/extensions/translate/index.js +4 -2
  72. package/public/scripts/extensions/tts/css/minimax-tts.css +5 -0
  73. package/public/scripts/extensions/tts/electronhub.js +455 -0
  74. package/public/scripts/extensions/tts/gpt-sovits-v2.js +4 -5
  75. package/public/scripts/extensions/tts/index.js +79 -4
  76. package/public/scripts/extensions/tts/minimax.js +44 -16
  77. package/public/scripts/extensions/tts/settings.html +2 -2
  78. package/public/scripts/extensions/tts/system.js +5 -4
  79. package/public/scripts/extensions/vectors/index.js +1 -1
  80. package/public/scripts/extensions/vectors/settings.html +1 -0
  81. package/public/scripts/extensions.js +5 -0
  82. package/public/scripts/group-chats.js +56 -39
  83. package/public/scripts/keyboard.js +1 -1
  84. package/public/scripts/loader.js +1 -1
  85. package/public/scripts/login.js +4 -0
  86. package/public/scripts/macros.js +31 -1
  87. package/public/scripts/openai.js +511 -170
  88. package/public/scripts/personas.js +39 -22
  89. package/public/scripts/popup.js +18 -4
  90. package/public/scripts/power-user.js +10 -1
  91. package/public/scripts/preset-manager.js +14 -10
  92. package/public/scripts/reasoning.js +32 -1
  93. package/public/scripts/secrets.js +32 -8
  94. package/public/scripts/slash-commands/SlashCommand.js +14 -15
  95. package/public/scripts/slash-commands.js +512 -512
  96. package/public/scripts/st-context.js +2 -0
  97. package/public/scripts/tags.js +5 -0
  98. package/public/scripts/templates/macros.html +2 -0
  99. package/public/scripts/textgen-models.js +2 -0
  100. package/public/scripts/tokenizers.js +55 -0
  101. package/public/scripts/tool-calling.js +12 -2
  102. package/public/scripts/util/stream-fadein.js +69 -0
  103. package/public/scripts/utils.js +132 -4
  104. package/public/scripts/world-info.js +330 -70
  105. package/public/style.css +29 -175
  106. package/src/command-line.js +8 -0
  107. package/src/config-init.js +10 -0
  108. package/src/constants.js +41 -0
  109. package/src/electron/package-lock.json +3 -3
  110. package/src/endpoints/backends/chat-completions.js +436 -80
  111. package/src/endpoints/backends/text-completions.js +2 -1
  112. package/src/endpoints/characters.js +2 -0
  113. package/src/endpoints/chats.js +5 -2
  114. package/src/endpoints/content-manager.js +4 -1
  115. package/src/endpoints/openai.js +114 -0
  116. package/src/endpoints/openrouter.js +1 -1
  117. package/src/endpoints/secrets.js +5 -2
  118. package/src/endpoints/stable-diffusion.js +113 -0
  119. package/src/endpoints/vectors.js +1 -1
  120. package/src/middleware/hostWhitelist.js +48 -0
  121. package/src/middleware/webpack-serve.js +2 -1
  122. package/src/server-main.js +3 -0
  123. package/src/server-startup.js +2 -0
  124. package/src/users.js +30 -3
  125. package/tests/package-lock.json +7 -7
package/CONTRIBUTING.md CHANGED
@@ -9,7 +9,7 @@
9
9
  ## Getting the code ready
10
10
 
11
11
  1. Register a GitHub account.
12
- 2. Fork this repository under your account.
12
+ 2. Fork this repository under your account.
13
13
  3. Clone the fork onto your machine.
14
14
  4. Open the cloned repository in the code editor.
15
15
  5. Create a git branch (recommended).
@@ -29,11 +29,22 @@
29
29
  - Updating GitHub Actions.
30
30
  - Hotfixing a critical bug.
31
31
  4. Project maintainers will test and can change your code before merging.
32
- 5. Write at least somewhat meaningful PR descriptions. There's no "right" way to do it, but the following may help with outlining a general structure:
32
+ 5. To make sure that your contribution remains testable and reviewable, try not to exceed a soft limit of **200 lines of code** (both additions and deletions) per pull request. If you have more to contribute, split it into multiple pull requests. We can also consider creating a separate feature branch for more substantial changes, but please discuss it with the maintainers first.
33
+ 6. Write at least somewhat meaningful PR descriptions and commit messages. There's no "right" way to do it, but the following may help with outlining a general structure:
33
34
  - What is the reason for a change?
34
35
  - What did you do to achieve this?
35
36
  - How would a reviewer test the change?
36
- 6. Mind the license. Your contributions will be licensed under the GNU Affero General Public License. If you don't know what that implies, consult your lawyer.
37
+ 7. English is the primary language of communication in this project. Please use only English when writing commit messages, PR descriptions, comments and other text. This does not apply to contributions to localization files.
38
+ 8. Mind the license. Your contributions will be licensed under the GNU Affero General Public License. If you don't know what that implies, consult your lawyer.
39
+
40
+ ## Use of AI coding assistance tools ("Vibe Coding")
41
+
42
+ We do not prohibit nor encourage the use of AI tools for coding assistance to help you write code, documentation, etc. This includes specialized IDEs, plugins and add-ons, chat interfaces, etc. However, please keep in mind the following:
43
+
44
+ - No matter who (or what) wrote the code, you are responsible for it. Make sure to carefully review and test everything before committing, and be ready to discuss and fix any issues that may arise during the review.
45
+ - Maintainers can reject reviewing and accepting PRs of very low quality, i.e. if the time to fix the issues exceeds the time to write the code from scratch.
46
+ - Avoid common mistakes attributed to AI tools, such as: adding/removing unrelated comments, excessive logging, unawareness of the project context and conventions, etc.
47
+ - You are allowed, but not required, to trigger AI tools that are added to the project by maintainers (Gemini, Copilot, Codex). Keep in mind that any feedback (comments, suggestions) that these tools generate is not a call to action; make sure to properly assess it before applying.
37
48
 
38
49
  ## Further reading
39
50
 
package/Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
- FROM node:lts-alpine3.21
1
+ FROM node:lts-alpine3.22
2
2
 
3
3
  # Arguments
4
4
  ARG APP_HOME=/home/node/app
package/config.yaml CHANGED
@@ -40,9 +40,15 @@ browserLaunch:
40
40
  port: 8000
41
41
  # -- SSL options --
42
42
  ssl:
43
+ # Enable SSL/TLS encryption
43
44
  enabled: false
45
+ # Path to certificate (relative to server root)
44
46
  certPath: "./certs/cert.pem"
47
+ # Path to private key (relative to server root)
45
48
  keyPath: "./certs/privkey.pem"
49
+ # Private key passphrase (leave empty if not needed)
50
+ # For better security, use a CLI argument or an environment variable (SILLYTAVERN_SSL_KEYPASSPHRASE)
51
+ keyPassphrase: ""
46
52
  # -- SECURITY CONFIGURATION --
47
53
  # Toggle whitelist mode
48
54
  whitelistMode: true
@@ -76,19 +82,43 @@ requestProxy:
76
82
  enableUserAccounts: false
77
83
  # Enable discreet login mode: hides user list on the login screen
78
84
  enableDiscreetLogin: false
79
- # Enable's authlia based auto login. Only enable this if you
80
- # have setup and installed Authelia as a middle-ware on your
81
- # reverse proxy
82
- # https://www.authelia.com/
83
- # This will use auto login to an account with the same username
84
- # as that used for authlia. (Ensure the username in authlia
85
- # is an exact match in lowercase with that in sillytavern)
86
- autheliaAuth: false
87
85
  # If `basicAuthMode` and this are enabled then
88
86
  # the username and passwords for basic auth are the same as those
89
87
  # for the individual accounts
90
88
  perUserBasicAuth: false
91
89
 
90
+ # -- SSO LOGIN CONFIGURATION --
91
+ sso:
92
+ # Enable's authlia based auto login. Only enable this if you
93
+ # have setup and installed Authelia as a middle-ware on your
94
+ # reverse proxy
95
+ # https://www.authelia.com/
96
+ # This will use auto login to an account with the same username
97
+ # as that used for authlia. (Ensure the username in authlia
98
+ # is an exact match in lowercase with that in sillytavern)
99
+ autheliaAuth: false
100
+ # Enable's authentik based auto login. Only enable this if you
101
+ # have setup and installed Authentik as a middle-ware on your
102
+ # reverse proxy.
103
+ # https://goauthentik.io/
104
+ # This will use auto login to an account with the same username
105
+ # as that used for authentik. (Ensure the username in authentik
106
+ # is an exact match in lowercase with that in sillytavern).
107
+ authentikAuth: false
108
+
109
+ # Host whitelist configuration. Recommended if you're using a listen mode
110
+ hostWhitelist:
111
+ # Enable or disable host whitelisting
112
+ enabled: false
113
+ # Scan incoming requests for potential host header spoofing
114
+ scan: true
115
+ # List of allowed hosts. Do not include localhost or IPs, these are safe.
116
+ # Use a dot to create subdomain patterns.
117
+ # Examples:
118
+ # - example.com
119
+ # - .trycloudflare.com
120
+ hosts: []
121
+
92
122
  # User session timeout *in seconds* (defaults to 24 hours).
93
123
  ## Set to a positive number to expire session after a certain time of inactivity
94
124
  ## Set to 0 to expire session when the browser is closed
@@ -40,9 +40,15 @@ browserLaunch:
40
40
  port: 8000
41
41
  # -- SSL options --
42
42
  ssl:
43
+ # Enable SSL/TLS encryption
43
44
  enabled: false
45
+ # Path to certificate (relative to server root)
44
46
  certPath: "./certs/cert.pem"
47
+ # Path to private key (relative to server root)
45
48
  keyPath: "./certs/privkey.pem"
49
+ # Private key passphrase (leave empty if not needed)
50
+ # For better security, use a CLI argument or an environment variable (SILLYTAVERN_SSL_KEYPASSPHRASE)
51
+ keyPassphrase: ""
46
52
  # -- SECURITY CONFIGURATION --
47
53
  # Toggle whitelist mode
48
54
  whitelistMode: true
@@ -76,19 +82,43 @@ requestProxy:
76
82
  enableUserAccounts: false
77
83
  # Enable discreet login mode: hides user list on the login screen
78
84
  enableDiscreetLogin: false
79
- # Enable's authlia based auto login. Only enable this if you
80
- # have setup and installed Authelia as a middle-ware on your
81
- # reverse proxy
82
- # https://www.authelia.com/
83
- # This will use auto login to an account with the same username
84
- # as that used for authlia. (Ensure the username in authlia
85
- # is an exact match in lowercase with that in sillytavern)
86
- autheliaAuth: false
87
85
  # If `basicAuthMode` and this are enabled then
88
86
  # the username and passwords for basic auth are the same as those
89
87
  # for the individual accounts
90
88
  perUserBasicAuth: false
91
89
 
90
+ # -- SSO LOGIN CONFIGURATION --
91
+ sso:
92
+ # Enable's authlia based auto login. Only enable this if you
93
+ # have setup and installed Authelia as a middle-ware on your
94
+ # reverse proxy
95
+ # https://www.authelia.com/
96
+ # This will use auto login to an account with the same username
97
+ # as that used for authlia. (Ensure the username in authlia
98
+ # is an exact match in lowercase with that in sillytavern)
99
+ autheliaAuth: false
100
+ # Enable's authentik based auto login. Only enable this if you
101
+ # have setup and installed Authentik as a middle-ware on your
102
+ # reverse proxy.
103
+ # https://goauthentik.io/
104
+ # This will use auto login to an account with the same username
105
+ # as that used for authentik. (Ensure the username in authentik
106
+ # is an exact match in lowercase with that in sillytavern).
107
+ authentikAuth: false
108
+
109
+ # Host whitelist configuration. Recommended if you're using a listen mode
110
+ hostWhitelist:
111
+ # Enable or disable host whitelisting
112
+ enabled: false
113
+ # Scan incoming requests for potential host header spoofing
114
+ scan: true
115
+ # List of allowed hosts. Do not include localhost or IPs, these are safe.
116
+ # Use a dot to create subdomain patterns.
117
+ # Examples:
118
+ # - example.com
119
+ # - .trycloudflare.com
120
+ hosts: []
121
+
92
122
  # User session timeout *in seconds* (defaults to 24 hours).
93
123
  ## Set to a positive number to expire session after a certain time of inactivity
94
124
  ## Set to 0 to expire session when the browser is closed
@@ -1,20 +1,23 @@
1
1
  {
2
2
  "chat_completion_source": "openai",
3
3
  "openai_model": "gpt-4-turbo",
4
- "claude_model": "claude-3-5-sonnet-20240620",
4
+ "claude_model": "claude-sonnet-4-5",
5
5
  "openrouter_model": "OR_Website",
6
6
  "openrouter_use_fallback": false,
7
7
  "openrouter_group_models": false,
8
8
  "openrouter_sort_models": "alphabetically",
9
9
  "ai21_model": "jamba-large",
10
10
  "mistralai_model": "mistral-large-latest",
11
+ "electronhub_model": "gpt-4o-mini",
12
+ "electronhub_sort_models": "alphabetically",
13
+ "electronhub_group_models": false,
11
14
  "custom_model": "",
12
15
  "custom_url": "",
13
16
  "custom_include_body": "",
14
17
  "custom_exclude_body": "",
15
18
  "custom_include_headers": "",
16
- "google_model": "gemini-pro",
17
- "vertexai_model": "gemini-2.0-flash-001",
19
+ "google_model": "gemini-2.5-pro",
20
+ "vertexai_model": "gemini-2.5-pro",
18
21
  "temperature": 1,
19
22
  "frequency_penalty": 0,
20
23
  "presence_penalty": 0,
@@ -620,7 +620,7 @@
620
620
  },
621
621
  "wi_format": "{0}",
622
622
  "openai_model": "gpt-4-turbo",
623
- "claude_model": "claude-3-5-sonnet-20240620",
623
+ "claude_model": "claude-sonnet-4-5",
624
624
  "ai21_model": "jamba-large",
625
625
  "openrouter_model": "OR_Website",
626
626
  "reverse_proxy": "",
@@ -0,0 +1,21 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+
4
+ <head>
5
+ <title>Forbidden</title>
6
+ </head>
7
+
8
+ <body>
9
+ <h1>Forbidden</h1>
10
+ <p>
11
+ If you are the system administrator, add the hostname you are accessing from to the
12
+ host whitelist, or disable host whitelisting in the
13
+ <code>config.yaml</code> file located in the root directory of your installation.
14
+ </p>
15
+ <hr />
16
+ <p>
17
+ <em>Access from this host is not allowed. This attempt has been logged.</em>
18
+ </p>
19
+ </body>
20
+
21
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "dependencies": {
3
- "@adobe/css-tools": "^4.4.3",
3
+ "@adobe/css-tools": "^4.4.4",
4
4
  "@agnai/sentencepiece-js": "^1.1.1",
5
5
  "@agnai/web-tokenizers": "^0.1.3",
6
6
  "@iconfu/svg-inject": "^1.2.3",
@@ -32,9 +32,9 @@
32
32
  "archiver": "^7.0.1",
33
33
  "bing-translate-api": "^4.1.0",
34
34
  "body-parser": "^1.20.2",
35
- "bowser": "^2.11.0",
35
+ "bowser": "^2.12.1",
36
36
  "bytes": "^3.1.2",
37
- "chalk": "^5.4.1",
37
+ "chalk": "^5.6.0",
38
38
  "command-exists": "^1.2.9",
39
39
  "compression": "^1.8.1",
40
40
  "cookie-parser": "^1.4.6",
@@ -54,6 +54,7 @@
54
54
  "handlebars": "^4.7.8",
55
55
  "helmet": "^8.1.0",
56
56
  "highlight.js": "^11.11.1",
57
+ "host-validation-middleware": "^0.1.1",
57
58
  "html-entities": "^2.6.0",
58
59
  "iconv-lite": "^0.6.3",
59
60
  "ip-matching": "^2.1.2",
@@ -64,7 +65,7 @@
64
65
  "lodash": "^4.17.21",
65
66
  "mime-types": "^3.0.1",
66
67
  "moment": "^2.30.1",
67
- "morphdom": "^2.7.5",
68
+ "morphdom": "^2.7.7",
68
69
  "multer": "^2.0.2",
69
70
  "node-fetch": "^3.3.2",
70
71
  "node-persist": "^4.0.4",
@@ -80,14 +81,14 @@
80
81
  "sillytavern-transformers": "2.14.6",
81
82
  "simple-git": "^3.28.0",
82
83
  "slidetoggle": "^4.0.0",
83
- "tiktoken": "^1.0.21",
84
+ "tiktoken": "^1.0.22",
84
85
  "url-join": "^5.0.0",
85
86
  "vectra": "^0.2.2",
86
87
  "wavefile": "^11.0.0",
87
88
  "webpack": "^5.98.0",
88
89
  "write-file-atomic": "^5.0.1",
89
90
  "ws": "^8.18.3",
90
- "yaml": "^2.8.0",
91
+ "yaml": "^2.8.1",
91
92
  "yargs": "^17.7.1",
92
93
  "yauzl": "^3.2.0"
93
94
  },
@@ -112,7 +113,7 @@
112
113
  "type": "git",
113
114
  "url": "https://github.com/SillyTavern/SillyTavern.git"
114
115
  },
115
- "version": "1.13.3",
116
+ "version": "1.13.5",
116
117
  "scripts": {
117
118
  "start": "node server.js",
118
119
  "debug": "node --inspect server.js",
@@ -145,7 +146,7 @@
145
146
  "@types/cors": "^2.8.19",
146
147
  "@types/deno": "^2.3.0",
147
148
  "@types/express": "^4.17.23",
148
- "@types/jquery": "^3.5.32",
149
+ "@types/jquery": "^3.5.33",
149
150
  "@types/jquery-cropper": "^1.0.4",
150
151
  "@types/jquery.transit": "^0.9.33",
151
152
  "@types/jqueryui": "^1.12.24",
@@ -0,0 +1,302 @@
1
+ /* Main Page Backgrounds */
2
+ #bg1 {
3
+ background-repeat: no-repeat;
4
+ background-attachment: fixed;
5
+ background-size: cover;
6
+ position: absolute;
7
+ width: 100%;
8
+ height: 100%;
9
+ transition: background-image var(--animation-duration-3x) ease-in-out;
10
+ z-index: -1;
11
+ }
12
+
13
+ /* Fitting options */
14
+ #background_fitting {
15
+ max-width: 8em;
16
+ }
17
+
18
+ /* Fill/Cover - scales to fill width while maintaining aspect ratio */
19
+ #bg1.cover {
20
+ background-size: cover;
21
+ background-position: center;
22
+ }
23
+
24
+ /* Fit/Contain - shows entire image maintaining aspect ratio */
25
+ #bg1.contain {
26
+ background-size: contain;
27
+ background-position: center;
28
+ background-repeat: no-repeat;
29
+ }
30
+
31
+ /* Stretch - stretches to fill entire space */
32
+ #bg1.stretch {
33
+ background-size: 100% 100%;
34
+ }
35
+
36
+ /* Center - centers without scaling */
37
+ #bg1.center {
38
+ background-size: auto;
39
+ background-position: center;
40
+ background-repeat: no-repeat;
41
+ }
42
+
43
+ /* This is the main flex container for the entire drawer */
44
+ .drawer-content.openDrawer.bg-drawer-layout {
45
+ display: flex;
46
+ }
47
+
48
+ .bg-drawer-layout {
49
+ flex-direction: column;
50
+ padding: 0;
51
+ }
52
+
53
+ #bg-header-fixed {
54
+ flex-shrink: 0;
55
+ padding: 5px;
56
+ background-color: var(--SmartThemeBlurTintColor);
57
+ border-bottom: 1px solid var(--SmartThemeBorderColor);
58
+ width: 100%;
59
+ }
60
+
61
+ .bg-header-row-1,
62
+ .bg-header-row-2 {
63
+ display: flex;
64
+ gap: 5px;
65
+ width: 100%;
66
+ }
67
+
68
+ /* Control buttons in header */
69
+ .heading-container-with-controls {
70
+ position: relative;
71
+ }
72
+
73
+ .heading-container-with-controls .heading-text {
74
+ margin: 10px 0;
75
+ }
76
+
77
+ .heading-container-with-controls .heading-controls {
78
+ position: absolute;
79
+ right: 5px;
80
+ top: 50%;
81
+ transform: translateY(-50%);
82
+ display: flex;
83
+ gap: 5px;
84
+ }
85
+
86
+ #bg-scrollable-content {
87
+ flex-grow: 1;
88
+ overflow-y: auto;
89
+ overflow-x: hidden;
90
+ padding: 0 5px 15px;
91
+ position: relative;
92
+ }
93
+
94
+ #bg_menu_content,
95
+ #bg_custom_content {
96
+ display: grid;
97
+ gap: 5px;
98
+ width: 100%;
99
+ grid-template-columns: repeat(var(--bg-thumb-columns, 5), 1fr);
100
+ }
101
+
102
+ #bg-filter {
103
+ font-size: calc(var(--mainFontSize) * 0.95);
104
+ }
105
+
106
+ /* Thumbnails */
107
+ .bg_example:hover .BGSampleTitle {
108
+ opacity: 1;
109
+ }
110
+
111
+ .bg_example .mobile-only-menu-toggle {
112
+ display: none;
113
+ }
114
+
115
+ .bg_example {
116
+ cursor: pointer;
117
+ box-shadow: 0 0 7px var(--black50a);
118
+ position: relative;
119
+ overflow: hidden;
120
+ border-radius: 8px;
121
+ border: 0px solid transparent;
122
+ outline: 2px solid var(--SmartThemeBorderColor);
123
+ outline-offset: -1px;
124
+
125
+ height: auto;
126
+ aspect-ratio: 16 / 9;
127
+ }
128
+
129
+ .bg_example:focus-visible {
130
+ outline-offset: inherit;
131
+ outline-color: var(--interactable-outline-color);
132
+ }
133
+
134
+ .bg_example.locked-background {
135
+ outline: 2px solid var(--golden);
136
+ outline-offset: 0;
137
+ }
138
+
139
+ .bg_example.locked-background::after {
140
+ content: '\f023';
141
+ font-family: 'Font Awesome 6 Free';
142
+ font-weight: 900;
143
+
144
+ position: absolute;
145
+ bottom: 5px;
146
+ right: 5px;
147
+ z-index: 4;
148
+ color: var(--golden);
149
+ filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.8));
150
+ font-size: calc(var(--mainFontSize) * 0.8);
151
+ pointer-events: none;
152
+ }
153
+
154
+ .bg_example:not(.locked-background) .jg-unlock,
155
+ .bg_example.locked-background .jg-lock {
156
+ display: none;
157
+ }
158
+
159
+ .bg_example.selected-background {
160
+ outline: 2px solid white;
161
+ outline-offset: 0;
162
+ }
163
+
164
+ .bg_example.selected-background::before {
165
+ content: '\f00c';
166
+ font-family: 'Font Awesome 6 Free';
167
+ font-weight: 900;
168
+ position: absolute;
169
+ top: 5px;
170
+ left: 5px;
171
+ z-index: 4;
172
+ color: var(--white100);
173
+ filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.8));
174
+ font-size: calc(var(--mainFontSize) * 0.9);
175
+ pointer-events: none;
176
+ }
177
+
178
+ .bg_example .jg-menu {
179
+ display: flex;
180
+ position: absolute;
181
+ top: 2px;
182
+ right: 2px;
183
+ background-color: rgba(0, 0, 0, 0.5);
184
+ border-radius: 5px;
185
+ padding: 3px 3px;
186
+ z-index: 3;
187
+ backdrop-filter: blur(4px);
188
+ border: 1px solid var(--SmartThemeBorderColor);
189
+ justify-items: center;
190
+ align-items: center;
191
+
192
+ opacity: 0;
193
+ visibility: hidden;
194
+ transform: scale(0.9);
195
+ transform-origin: center;
196
+ transition: opacity var(--animation-duration) ease-out, visibility var(--animation-duration) ease-out, transform var(--animation-duration) ease-out;
197
+ }
198
+
199
+ .bg_example:hover .jg-menu,
200
+ .bg_example:focus-within .jg-menu {
201
+ opacity: 1;
202
+ visibility: visible;
203
+ transform: scale(1);
204
+ }
205
+
206
+ .bg_example .jg-button {
207
+ display: flex;
208
+ width: 24px;
209
+ height: 24px;
210
+ align-items: center;
211
+ justify-content: center;
212
+ color: white;
213
+ padding: 5px;
214
+ font-size: 1.1em;
215
+ border-radius: 5px;
216
+ transition: background-color var(--animation-duration) ease;
217
+ }
218
+
219
+ .bg_example .jg-button:hover {
220
+ background-color: rgba(255, 255, 255, 0.2);
221
+ }
222
+
223
+ /* Scroll-to-Top Button */
224
+ #bg-scroll-top {
225
+ position: absolute;
226
+ bottom: 20px;
227
+ right: 20px;
228
+ width: 42px;
229
+ height: 42px;
230
+ background: var(--SmartThemeBlurTintColor);
231
+ color: var(--SmartThemeBodyColor);
232
+ border: 1px solid var(--SmartThemeBorderColor);
233
+ border-radius: 50%;
234
+ cursor: pointer;
235
+ z-index: 10;
236
+ font-size: 18px;
237
+ display: inline-flex;
238
+ align-items: center;
239
+ justify-content: center;
240
+ opacity: 0;
241
+ transition: opacity var(--animation-duration) ease;
242
+ pointer-events: none;
243
+ }
244
+
245
+ #bg-scroll-top.visible {
246
+ opacity: 1;
247
+ pointer-events: auto;
248
+ }
249
+
250
+ #bg-scroll-top:hover {
251
+ filter: brightness(150%);
252
+ outline: 1px solid var(--interactable-outline-color);
253
+ }
254
+
255
+ #bg-scroll-top .fa-solid {
256
+ margin: 0;
257
+ padding: 0;
258
+ line-height: 1;
259
+ }
260
+
261
+ .thumbnail-clipper {
262
+ position: absolute;
263
+ top: -2px;
264
+ left: -2px;
265
+ right: -2px;
266
+ bottom: -2px;
267
+ overflow: hidden;
268
+ border-radius: inherit;
269
+ background-size: cover;
270
+ background-position: center;
271
+ }
272
+
273
+ .bg_example:not([custom="true"]) .jg-copy,
274
+ .bg_example[custom="true"] .jg-edit {
275
+ display: none;
276
+ }
277
+
278
+ /* Thumbnail Title */
279
+ .bg_example .BGSampleTitle {
280
+ position: absolute;
281
+ bottom: 0;
282
+ left: 0;
283
+ right: 0;
284
+ background: linear-gradient(transparent, rgba(0, 0, 0, 0.9));
285
+ color: var(--SmartThemeBodyColor);
286
+ font-size: 0.9em;
287
+ font-weight: 600;
288
+ padding: 0px 6px 2px;
289
+ text-align: center;
290
+ white-space: nowrap;
291
+ overflow: hidden;
292
+ text-overflow: ellipsis;
293
+ opacity: 0;
294
+ transition: opacity var(--animation-duration) ease-in-out;
295
+ pointer-events: none;
296
+ border-radius: 0 0 8px 8px;
297
+ }
298
+
299
+ .bg_example:hover .BGSampleTitle,
300
+ .bg_example:focus-within .BGSampleTitle {
301
+ opacity: 1;
302
+ }