openclacky 1.2.15 → 1.2.17
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +25 -0
- data/lib/clacky/agent/session_serializer.rb +5 -2
- data/lib/clacky/default_skills/channel-manager/SKILL.md +4 -2
- data/lib/clacky/providers.rb +3 -0
- data/lib/clacky/server/http_server.rb +32 -3
- data/lib/clacky/telemetry.rb +20 -0
- data/lib/clacky/utils/model_pricing.rb +17 -0
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +305 -0
- data/lib/clacky/web/billing.js +144 -0
- data/lib/clacky/web/i18n.js +146 -3
- data/lib/clacky/web/index.html +11 -0
- data/lib/clacky/web/marked.min.js +55 -45
- data/lib/clacky/web/model-tester.js +2 -1
- data/lib/clacky/web/sessions.js +6 -1
- data/lib/clacky/web/settings.js +9 -6
- data/lib/clacky/web/share.js +843 -0
- data/lib/clacky/web/vendor/qrcode/qrcode.min.js +8 -0
- data/lib/clacky/web/ws-dispatcher.js +1 -0
- data/scripts/install.ps1 +48 -21
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3a0f79d1b7995b24b2b1a5d31dc36d4342ad1ff925b66749b7edb6268d0bcf29
|
|
4
|
+
data.tar.gz: b1f583cde8ffb619a4cb3558f074c369251c5445acce05116d1bc415e94eb3b4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 18921df7e5d4d4a7f3cbf1e19bd1b65bbc8360fa064d81ffc129375d86ec9a07ebfc324414d67a7d6df9782dfd8f966065755d0224d8af77b92604d6320195e2
|
|
7
|
+
data.tar.gz: 013db4dddea3c901bd3e8885241676b7fdd2ca2856a9a4df4cfde4b2475b7e57dc7795386d53db934acfe5786515501fd724e3ef559e8d5e786a6b07a3312db6
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.2.17] - 2026-06-12
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Session sharing to Web UI — share any session via a shareable link with billing integration
|
|
12
|
+
- Share telemetry tracking
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- Markdown rendering in certain edge cases
|
|
16
|
+
- Image blocks not detected in replay round counting, potentially causing history truncation
|
|
17
|
+
- History images served as base64 causing replay lag, now proxied through server
|
|
18
|
+
- WSL kernel repair getting stuck in infinite loop on pending state
|
|
19
|
+
- WeChat QR login fallback showing false stale-session errors
|
|
20
|
+
|
|
21
|
+
### More
|
|
22
|
+
- Background color styling update
|
|
23
|
+
|
|
24
|
+
## [1.2.16] - 2026-06-10
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
- Claude Fable 5 model support
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
- Model test failed when using saved API key
|
|
31
|
+
- Windows WSL install success rate
|
|
32
|
+
|
|
8
33
|
## [1.2.15] - 2026-06-10
|
|
9
34
|
|
|
10
35
|
### Added
|
|
@@ -201,8 +201,11 @@ module Clacky
|
|
|
201
201
|
if msg[:content].is_a?(String)
|
|
202
202
|
!msg[:content].start_with?("[SYSTEM]")
|
|
203
203
|
elsif msg[:content].is_a?(Array)
|
|
204
|
-
# Must contain at least one text or image block (not a tool_result array)
|
|
205
|
-
|
|
204
|
+
# Must contain at least one text or image block (not a tool_result array).
|
|
205
|
+
# "image_url" covers image-only messages (user sent a picture with no
|
|
206
|
+
# accompanying text); without it such messages start no round and get
|
|
207
|
+
# dropped on replay, making the image vanish on session reopen.
|
|
208
|
+
msg[:content].any? { |b| b.is_a?(Hash) && %w[text image image_url].include?(b[:type].to_s) }
|
|
206
209
|
else
|
|
207
210
|
false
|
|
208
211
|
end
|
|
@@ -253,13 +253,15 @@ browser(action="navigate", url="<qr_page_url>")
|
|
|
253
253
|
>
|
|
254
254
|
> `http://${CLACKY_SERVER_HOST}:${CLACKY_SERVER_PORT}/weixin-qr.html?url=<URL-encoded qrcode_url>`
|
|
255
255
|
>
|
|
256
|
-
> Scan the QR code with WeChat
|
|
256
|
+
> Scan the QR code with WeChat and confirm in the app. I'm already watching for your scan — no need to reply.
|
|
257
|
+
|
|
258
|
+
**Do NOT wait for the user to reply "done".** Immediately proceed to Step 3 and start polling — exactly as in the browser-succeeds path. The polling script must already be running while the user scans, so it can observe the `scaned → confirmed` transition; otherwise a real scan can be misread as a stale session.
|
|
257
259
|
|
|
258
260
|
The page renders a proper scannable QR code image. Do NOT open the raw `qrcode_url` directly — that page shows "请使用微信扫码打开" with no actual QR image.
|
|
259
261
|
|
|
260
262
|
#### Step 3 — Wait for scan and save credentials
|
|
261
263
|
|
|
262
|
-
|
|
264
|
+
As soon as the QR page has been presented to the user — whether you opened it via the browser tool **or** gave the user the manual link — immediately run the polling script in the background. **In both cases, do NOT wait for the user to confirm or reply "done" before starting the poll** — the script must already be running while the user scans:
|
|
263
265
|
|
|
264
266
|
```bash
|
|
265
267
|
ruby "SKILL_DIR/weixin_setup.rb" --qrcode-id "$QRCODE_ID"
|
data/lib/clacky/providers.rb
CHANGED
|
@@ -31,6 +31,7 @@ module Clacky
|
|
|
31
31
|
"api" => "bedrock",
|
|
32
32
|
"default_model" => "abs-claude-sonnet-4-6",
|
|
33
33
|
"models" => [
|
|
34
|
+
"abs-claude-fable-5",
|
|
34
35
|
"abs-claude-opus-4-8",
|
|
35
36
|
"abs-claude-opus-4-7",
|
|
36
37
|
"abs-claude-opus-4-6",
|
|
@@ -80,6 +81,7 @@ module Clacky
|
|
|
80
81
|
# themselves, so they're intentionally not listed here as keys —
|
|
81
82
|
# no injection happens when the default model is already lite-class.
|
|
82
83
|
"lite_models" => {
|
|
84
|
+
"abs-claude-fable-5" => "abs-claude-haiku-4-5",
|
|
83
85
|
"abs-claude-opus-4-8" => "abs-claude-haiku-4-5",
|
|
84
86
|
"abs-claude-opus-4-7" => "abs-claude-haiku-4-5",
|
|
85
87
|
"abs-claude-opus-4-6" => "abs-claude-haiku-4-5",
|
|
@@ -91,6 +93,7 @@ module Clacky
|
|
|
91
93
|
# Fallback chain: if a model is unavailable, try the next one in order.
|
|
92
94
|
# Keys are primary model names; values are the fallback model to use instead.
|
|
93
95
|
"fallback_models" => {
|
|
96
|
+
"abs-claude-fable-5" => "abs-claude-opus-4-8",
|
|
94
97
|
"abs-claude-sonnet-4-6" => "abs-claude-sonnet-4-5"
|
|
95
98
|
},
|
|
96
99
|
"website_url" => "https://www.openclacky.com/ai-keys"
|
|
@@ -45,7 +45,12 @@ module Clacky
|
|
|
45
45
|
if url
|
|
46
46
|
url
|
|
47
47
|
elsif type.to_s == "image" && path && File.exist?(path.to_s)
|
|
48
|
-
|
|
48
|
+
# Serve via the /api/local-image proxy instead of inlining a base64
|
|
49
|
+
# data URL. Inlining forced a synchronous disk-read + full base64
|
|
50
|
+
# encode + downscale on every history replay (2-3s lag for sessions
|
|
51
|
+
# with downgraded text-model images). The proxy lets the browser
|
|
52
|
+
# lazy-load + cache the image, keeping the replay response tiny.
|
|
53
|
+
"/api/local-image?path=#{CGI.escape(path.to_s)}"
|
|
49
54
|
elsif name
|
|
50
55
|
type.to_s == "image" ? "expired:#{name}" : "pdf:#{name}"
|
|
51
56
|
end
|
|
@@ -448,6 +453,7 @@ module Clacky
|
|
|
448
453
|
when ["POST", "/api/browser/configure"] then api_browser_configure(req, res)
|
|
449
454
|
when ["POST", "/api/browser/reload"] then api_browser_reload(res)
|
|
450
455
|
when ["POST", "/api/browser/toggle"] then api_browser_toggle(res)
|
|
456
|
+
when ["POST", "/api/telemetry"] then api_telemetry(req, res)
|
|
451
457
|
when ["POST", "/api/onboard/complete"] then api_onboard_complete(req, res)
|
|
452
458
|
when ["POST", "/api/onboard/skip-soul"] then api_onboard_skip_soul(req, res)
|
|
453
459
|
when ["GET", "/api/store/skills"] then api_store_skills(res)
|
|
@@ -854,6 +860,17 @@ module Clacky
|
|
|
854
860
|
json_response(res, 500, { ok: false, error: e.message })
|
|
855
861
|
end
|
|
856
862
|
|
|
863
|
+
# POST /api/telemetry
|
|
864
|
+
# Body: { "event": "share_open" | "share_download", ... }
|
|
865
|
+
# Fire-and-forget telemetry from the WebUI frontend.
|
|
866
|
+
def api_telemetry(req, res)
|
|
867
|
+
body = parse_json_body(req) || {}
|
|
868
|
+
Clacky::Telemetry.share!(event: body["event"], extra: body["extra"])
|
|
869
|
+
json_response(res, 200, { ok: true })
|
|
870
|
+
rescue StandardError => e
|
|
871
|
+
json_response(res, 500, { ok: false, error: e.message })
|
|
872
|
+
end
|
|
873
|
+
|
|
857
874
|
# POST /api/media/image
|
|
858
875
|
# Body: { "prompt": "...", "aspect_ratio": "landscape|square|portrait",
|
|
859
876
|
# "output_dir": "<absolute path, optional>" }
|
|
@@ -1442,6 +1459,8 @@ module Clacky
|
|
|
1442
1459
|
branded: true,
|
|
1443
1460
|
needs_activation: true,
|
|
1444
1461
|
product_name: brand.product_name,
|
|
1462
|
+
homepage_url: brand.homepage_url,
|
|
1463
|
+
logo_url: brand.logo_url,
|
|
1445
1464
|
test_mode: @brand_test,
|
|
1446
1465
|
distribution_refresh_pending: refresh_pending
|
|
1447
1466
|
})
|
|
@@ -1481,6 +1500,8 @@ module Clacky
|
|
|
1481
1500
|
branded: true,
|
|
1482
1501
|
needs_activation: false,
|
|
1483
1502
|
product_name: brand.product_name,
|
|
1503
|
+
homepage_url: brand.homepage_url,
|
|
1504
|
+
logo_url: brand.logo_url,
|
|
1484
1505
|
warning: warning,
|
|
1485
1506
|
test_mode: @brand_test,
|
|
1486
1507
|
user_licensed: brand.user_licensed?,
|
|
@@ -4141,8 +4162,16 @@ module Clacky
|
|
|
4141
4162
|
|
|
4142
4163
|
api_key = body["api_key"].to_s
|
|
4143
4164
|
if api_key.include?("****")
|
|
4144
|
-
|
|
4145
|
-
|
|
4165
|
+
model_id = body["id"].to_s
|
|
4166
|
+
entry = nil
|
|
4167
|
+
if !model_id.empty?
|
|
4168
|
+
entry = @agent_config.models.find { |m| m["id"] == model_id }
|
|
4169
|
+
end
|
|
4170
|
+
if entry.nil? && body.key?("index")
|
|
4171
|
+
entry = @agent_config.models[body["index"].to_i]
|
|
4172
|
+
end
|
|
4173
|
+
entry ||= @agent_config.models[@agent_config.current_model_index]
|
|
4174
|
+
api_key = entry ? entry["api_key"].to_s : ""
|
|
4146
4175
|
end
|
|
4147
4176
|
|
|
4148
4177
|
model = body["model"].to_s
|
data/lib/clacky/telemetry.rb
CHANGED
|
@@ -71,6 +71,26 @@ module Clacky
|
|
|
71
71
|
fire_and_forget("/api/v1/telemetry/task", payload.compact)
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
+
# Called from the WebUI when user opens the share modal or completes
|
|
75
|
+
# a share action (e.g. downloads poster). Tracks share engagement.
|
|
76
|
+
#
|
|
77
|
+
# @param event [String] "share_open" or "share_download"
|
|
78
|
+
# @param extra [Hash] optional context (platform, scorecard mode, etc.)
|
|
79
|
+
def share!(event:, extra: {})
|
|
80
|
+
return unless enabled?
|
|
81
|
+
|
|
82
|
+
brand = Clacky::BrandConfig.load
|
|
83
|
+
payload = {
|
|
84
|
+
device_id: resolve_device_id(brand),
|
|
85
|
+
version: Clacky::VERSION,
|
|
86
|
+
brand: brand.branded? ? brand.package_name : nil,
|
|
87
|
+
event: event
|
|
88
|
+
}
|
|
89
|
+
payload.merge!(extra) if extra.is_a?(Hash)
|
|
90
|
+
|
|
91
|
+
fire_and_forget("/api/v1/telemetry/share", payload.compact)
|
|
92
|
+
end
|
|
93
|
+
|
|
74
94
|
# ── private helpers ────────────────────────────────────────────────
|
|
75
95
|
|
|
76
96
|
private def enabled?
|
|
@@ -8,6 +8,21 @@ module Clacky
|
|
|
8
8
|
# All pricing is based on official API documentation
|
|
9
9
|
PRICING_TABLE = {
|
|
10
10
|
# Claude 4.5 models - tiered pricing based on prompt length
|
|
11
|
+
"claude-fable-5" => {
|
|
12
|
+
input: {
|
|
13
|
+
default: 10.00, # $10/MTok for prompts ≤ 200K tokens
|
|
14
|
+
over_200k: 10.00 # same for all tiers
|
|
15
|
+
},
|
|
16
|
+
output: {
|
|
17
|
+
default: 50.00, # $50/MTok for prompts ≤ 200K tokens
|
|
18
|
+
over_200k: 50.00 # same for all tiers
|
|
19
|
+
},
|
|
20
|
+
cache: {
|
|
21
|
+
write: 12.50, # $12.50/MTok cache write (5-min tier)
|
|
22
|
+
read: 1.00 # $1.00/MTok cache read
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
|
|
11
26
|
"claude-opus-4.5" => {
|
|
12
27
|
input: {
|
|
13
28
|
default: 5.00, # $5/MTok for prompts ≤ 200K tokens
|
|
@@ -633,6 +648,8 @@ module Clacky
|
|
|
633
648
|
# Support both dot and dash separators (e.g., "4.5", "4-5", "4-6")
|
|
634
649
|
# Also handles Bedrock cross-region prefixes (e.g. "jp.anthropic.claude-sonnet-4-6")
|
|
635
650
|
case model
|
|
651
|
+
when /claude.*fable.*5/i
|
|
652
|
+
"claude-fable-5"
|
|
636
653
|
when /claude.*opus.*4[.-]?[5-9]/i
|
|
637
654
|
"claude-opus-4.5"
|
|
638
655
|
when /claude.*sonnet.*4[.-]?[5-9]/i
|
data/lib/clacky/version.rb
CHANGED
data/lib/clacky/web/app.css
CHANGED
|
@@ -9150,6 +9150,22 @@ body.setup-mode[data-theme="dark"] {
|
|
|
9150
9150
|
color: var(--color-text-primary);
|
|
9151
9151
|
border-color: var(--color-error);
|
|
9152
9152
|
}
|
|
9153
|
+
.billing-share-btn {
|
|
9154
|
+
padding: 0.5rem 0.75rem;
|
|
9155
|
+
border: 1px solid var(--color-accent, #38bdf8);
|
|
9156
|
+
border-radius: 8px;
|
|
9157
|
+
background: var(--color-bg-secondary);
|
|
9158
|
+
color: var(--color-accent, #38bdf8);
|
|
9159
|
+
font-size: 0.8125rem;
|
|
9160
|
+
font-weight: 600;
|
|
9161
|
+
white-space: nowrap;
|
|
9162
|
+
cursor: pointer;
|
|
9163
|
+
transition: all 0.2s ease;
|
|
9164
|
+
}
|
|
9165
|
+
.billing-share-btn:hover {
|
|
9166
|
+
background: var(--color-accent, #38bdf8);
|
|
9167
|
+
color: #fff;
|
|
9168
|
+
}
|
|
9153
9169
|
.billing-clear-popup {
|
|
9154
9170
|
position: absolute;
|
|
9155
9171
|
top: 100%;
|
|
@@ -9238,6 +9254,51 @@ body.setup-mode[data-theme="dark"] {
|
|
|
9238
9254
|
}
|
|
9239
9255
|
|
|
9240
9256
|
/* ── Chart Row (Single Line) ─────────────────────────────────────────── */
|
|
9257
|
+
.billing-heatmap-row { width: 100%; }
|
|
9258
|
+
.billing-heat-dow-row {
|
|
9259
|
+
display: grid;
|
|
9260
|
+
grid-template-columns: repeat(7, 32px);
|
|
9261
|
+
gap: 6px;
|
|
9262
|
+
margin: 0.75rem 0 6px;
|
|
9263
|
+
justify-content: start;
|
|
9264
|
+
}
|
|
9265
|
+
.billing-heat-dow {
|
|
9266
|
+
text-align: center;
|
|
9267
|
+
font-size: 11px;
|
|
9268
|
+
color: var(--color-text-tertiary, var(--color-text-secondary));
|
|
9269
|
+
}
|
|
9270
|
+
.billing-heat-grid {
|
|
9271
|
+
display: grid;
|
|
9272
|
+
grid-template-columns: repeat(7, 32px);
|
|
9273
|
+
gap: 6px;
|
|
9274
|
+
justify-content: start;
|
|
9275
|
+
}
|
|
9276
|
+
.billing-heat-cell {
|
|
9277
|
+
width: 32px;
|
|
9278
|
+
height: 32px;
|
|
9279
|
+
border-radius: 6px;
|
|
9280
|
+
background: var(--color-bg-primary);
|
|
9281
|
+
}
|
|
9282
|
+
.billing-heat-cell.is-empty { background: transparent; }
|
|
9283
|
+
.billing-heat-cell[data-level="0"] { background: var(--color-bg-primary); border: 1px solid var(--color-border-primary); }
|
|
9284
|
+
.billing-heat-cell[data-level="1"] { background: #9be9a8; }
|
|
9285
|
+
.billing-heat-cell[data-level="2"] { background: #40c463; }
|
|
9286
|
+
.billing-heat-cell[data-level="3"] { background: #30a14e; }
|
|
9287
|
+
.billing-heat-cell[data-level="4"] { background: #216e39; }
|
|
9288
|
+
.billing-heat-cell[data-level="5"] { background: #0a4020; }
|
|
9289
|
+
.billing-heat-legend {
|
|
9290
|
+
display: flex;
|
|
9291
|
+
align-items: center;
|
|
9292
|
+
gap: 5px;
|
|
9293
|
+
font-size: 11px;
|
|
9294
|
+
color: var(--color-text-secondary);
|
|
9295
|
+
}
|
|
9296
|
+
.billing-heat-legend .billing-heat-cell {
|
|
9297
|
+
width: 13px;
|
|
9298
|
+
height: 13px;
|
|
9299
|
+
aspect-ratio: auto;
|
|
9300
|
+
}
|
|
9301
|
+
|
|
9241
9302
|
.billing-chart-row {
|
|
9242
9303
|
width: 100%;
|
|
9243
9304
|
}
|
|
@@ -10363,3 +10424,247 @@ body.setup-mode[data-theme="dark"] {
|
|
|
10363
10424
|
.msg-phase-empty .msg-phase-summary { cursor: default; }
|
|
10364
10425
|
.msg-phase-empty .msg-phase-summary::before { visibility: hidden; }
|
|
10365
10426
|
.msg-phase-empty .msg-phase-body { display: none; }
|
|
10427
|
+
|
|
10428
|
+
/* ── Share modal ──────────────────────────────────────────────────────── */
|
|
10429
|
+
.share-overlay {
|
|
10430
|
+
position: fixed;
|
|
10431
|
+
inset: 0;
|
|
10432
|
+
z-index: 1200;
|
|
10433
|
+
display: flex;
|
|
10434
|
+
align-items: center;
|
|
10435
|
+
justify-content: center;
|
|
10436
|
+
background: var(--color-bg-overlay);
|
|
10437
|
+
opacity: 0;
|
|
10438
|
+
transition: opacity .18s ease;
|
|
10439
|
+
padding: 20px;
|
|
10440
|
+
}
|
|
10441
|
+
.share-overlay.open { opacity: 1; }
|
|
10442
|
+
|
|
10443
|
+
.share-modal {
|
|
10444
|
+
position: relative;
|
|
10445
|
+
width: 100%;
|
|
10446
|
+
max-width: 660px;
|
|
10447
|
+
max-height: 90vh;
|
|
10448
|
+
overflow-y: auto;
|
|
10449
|
+
background: var(--color-bg-card);
|
|
10450
|
+
border: 1px solid var(--color-border-primary);
|
|
10451
|
+
border-radius: var(--radius-lg, 14px);
|
|
10452
|
+
box-shadow: 0 18px 48px rgba(15, 18, 28, 0.22);
|
|
10453
|
+
padding: 24px;
|
|
10454
|
+
transform: translateY(8px) scale(.98);
|
|
10455
|
+
transition: transform .18s ease;
|
|
10456
|
+
}
|
|
10457
|
+
.share-overlay.open .share-modal { transform: translateY(0) scale(1); }
|
|
10458
|
+
|
|
10459
|
+
.share-close {
|
|
10460
|
+
position: absolute;
|
|
10461
|
+
top: 12px;
|
|
10462
|
+
right: 14px;
|
|
10463
|
+
border: none;
|
|
10464
|
+
background: none;
|
|
10465
|
+
font-size: 22px;
|
|
10466
|
+
line-height: 1;
|
|
10467
|
+
color: var(--color-text-muted);
|
|
10468
|
+
cursor: pointer;
|
|
10469
|
+
}
|
|
10470
|
+
.share-close:hover { color: var(--color-text-primary); }
|
|
10471
|
+
|
|
10472
|
+
.share-title {
|
|
10473
|
+
margin: 0 0 4px;
|
|
10474
|
+
font-size: 17px;
|
|
10475
|
+
font-weight: 700;
|
|
10476
|
+
color: var(--color-text-primary);
|
|
10477
|
+
}
|
|
10478
|
+
.share-subtitle {
|
|
10479
|
+
margin: 0 0 16px;
|
|
10480
|
+
font-size: 13px;
|
|
10481
|
+
color: var(--color-text-tertiary);
|
|
10482
|
+
}
|
|
10483
|
+
|
|
10484
|
+
.share-body {
|
|
10485
|
+
display: flex;
|
|
10486
|
+
gap: 20px;
|
|
10487
|
+
align-items: flex-start;
|
|
10488
|
+
}
|
|
10489
|
+
.share-controls {
|
|
10490
|
+
flex: 1;
|
|
10491
|
+
min-width: 0;
|
|
10492
|
+
}
|
|
10493
|
+
|
|
10494
|
+
.share-poster-wrap {
|
|
10495
|
+
display: flex;
|
|
10496
|
+
justify-content: center;
|
|
10497
|
+
flex-shrink: 0;
|
|
10498
|
+
}
|
|
10499
|
+
.share-poster-img {
|
|
10500
|
+
width: 220px;
|
|
10501
|
+
border-radius: var(--radius-md, 10px);
|
|
10502
|
+
box-shadow: 0 6px 18px rgba(15, 18, 28, 0.16);
|
|
10503
|
+
}
|
|
10504
|
+
|
|
10505
|
+
@media (max-width: 560px) {
|
|
10506
|
+
.share-body { flex-direction: column; align-items: stretch; }
|
|
10507
|
+
.share-poster-wrap { margin-bottom: 4px; }
|
|
10508
|
+
.share-poster-img { width: 180px; }
|
|
10509
|
+
}
|
|
10510
|
+
|
|
10511
|
+
.share-row-label {
|
|
10512
|
+
font-size: 12px;
|
|
10513
|
+
font-weight: 600;
|
|
10514
|
+
color: var(--color-text-tertiary);
|
|
10515
|
+
}
|
|
10516
|
+
|
|
10517
|
+
.share-theme-row {
|
|
10518
|
+
display: flex;
|
|
10519
|
+
flex-direction: column;
|
|
10520
|
+
gap: 6px;
|
|
10521
|
+
margin-bottom: 14px;
|
|
10522
|
+
}
|
|
10523
|
+
.share-theme-chips {
|
|
10524
|
+
display: flex;
|
|
10525
|
+
gap: 8px;
|
|
10526
|
+
}
|
|
10527
|
+
.share-theme-chip {
|
|
10528
|
+
flex: 1;
|
|
10529
|
+
height: 34px;
|
|
10530
|
+
border: 2px solid transparent;
|
|
10531
|
+
border-radius: var(--radius-sm, 6px);
|
|
10532
|
+
cursor: pointer;
|
|
10533
|
+
position: relative;
|
|
10534
|
+
display: flex;
|
|
10535
|
+
align-items: center;
|
|
10536
|
+
justify-content: center;
|
|
10537
|
+
transition: border-color .12s ease, transform .12s ease;
|
|
10538
|
+
}
|
|
10539
|
+
.share-theme-chip:hover { transform: translateY(-1px); }
|
|
10540
|
+
.share-theme-chip.is-active { border-color: var(--color-button-primary); }
|
|
10541
|
+
.share-theme-name {
|
|
10542
|
+
font-size: 11px;
|
|
10543
|
+
font-weight: 700;
|
|
10544
|
+
color: rgba(0,0,0,.55);
|
|
10545
|
+
text-shadow: 0 1px 0 rgba(255,255,255,.4);
|
|
10546
|
+
pointer-events: none;
|
|
10547
|
+
}
|
|
10548
|
+
.share-theme-chip[data-theme="geek"] .share-theme-name {
|
|
10549
|
+
color: rgba(255,255,255,.92);
|
|
10550
|
+
text-shadow: 0 1px 2px rgba(0,0,0,.4);
|
|
10551
|
+
}
|
|
10552
|
+
|
|
10553
|
+
.share-periods {
|
|
10554
|
+
display: inline-flex;
|
|
10555
|
+
gap: 4px;
|
|
10556
|
+
padding: 3px;
|
|
10557
|
+
margin-bottom: 12px;
|
|
10558
|
+
border: 1px solid var(--color-border-primary);
|
|
10559
|
+
border-radius: var(--radius-sm, 6px);
|
|
10560
|
+
background: var(--color-bg-primary);
|
|
10561
|
+
}
|
|
10562
|
+
.share-period {
|
|
10563
|
+
padding: 6px 16px;
|
|
10564
|
+
border: none;
|
|
10565
|
+
border-radius: var(--radius-sm, 5px);
|
|
10566
|
+
background: transparent;
|
|
10567
|
+
color: var(--color-text-secondary);
|
|
10568
|
+
font-size: 13px;
|
|
10569
|
+
font-weight: 600;
|
|
10570
|
+
cursor: pointer;
|
|
10571
|
+
transition: background .12s ease, color .12s ease;
|
|
10572
|
+
}
|
|
10573
|
+
.share-period:hover { color: var(--color-text-primary); }
|
|
10574
|
+
.share-period.is-active {
|
|
10575
|
+
background: var(--color-button-primary);
|
|
10576
|
+
color: var(--color-button-primary-text);
|
|
10577
|
+
}
|
|
10578
|
+
|
|
10579
|
+
.share-platforms {
|
|
10580
|
+
display: grid;
|
|
10581
|
+
grid-template-columns: repeat(4, 1fr);
|
|
10582
|
+
gap: 8px;
|
|
10583
|
+
margin-bottom: 14px;
|
|
10584
|
+
}
|
|
10585
|
+
.share-platform {
|
|
10586
|
+
padding: 9px 4px;
|
|
10587
|
+
border: 1px solid var(--color-border-primary);
|
|
10588
|
+
border-radius: var(--radius-sm, 6px);
|
|
10589
|
+
background: var(--color-bg-primary);
|
|
10590
|
+
color: var(--color-text-secondary);
|
|
10591
|
+
font-size: 13px;
|
|
10592
|
+
font-weight: 600;
|
|
10593
|
+
cursor: pointer;
|
|
10594
|
+
transition: background .12s ease, border-color .12s ease, color .12s ease;
|
|
10595
|
+
}
|
|
10596
|
+
.share-platform:hover {
|
|
10597
|
+
background: var(--color-bg-hover);
|
|
10598
|
+
border-color: var(--color-border-strong);
|
|
10599
|
+
}
|
|
10600
|
+
.share-platform.is-active {
|
|
10601
|
+
background: var(--color-button-primary);
|
|
10602
|
+
color: var(--color-button-primary-text);
|
|
10603
|
+
border-color: var(--color-button-primary);
|
|
10604
|
+
}
|
|
10605
|
+
|
|
10606
|
+
.share-editor { margin-bottom: 14px; }
|
|
10607
|
+
.share-editor-head {
|
|
10608
|
+
display: flex;
|
|
10609
|
+
align-items: center;
|
|
10610
|
+
justify-content: space-between;
|
|
10611
|
+
margin-bottom: 6px;
|
|
10612
|
+
}
|
|
10613
|
+
.share-shuffle {
|
|
10614
|
+
border: none;
|
|
10615
|
+
background: none;
|
|
10616
|
+
color: var(--color-button-primary);
|
|
10617
|
+
font-size: 12px;
|
|
10618
|
+
font-weight: 600;
|
|
10619
|
+
cursor: pointer;
|
|
10620
|
+
padding: 2px 4px;
|
|
10621
|
+
}
|
|
10622
|
+
.share-shuffle:hover { text-decoration: underline; }
|
|
10623
|
+
.share-text {
|
|
10624
|
+
width: 100%;
|
|
10625
|
+
box-sizing: border-box;
|
|
10626
|
+
resize: vertical;
|
|
10627
|
+
min-height: 72px;
|
|
10628
|
+
padding: 10px 12px;
|
|
10629
|
+
border: 1px solid var(--color-border-primary);
|
|
10630
|
+
border-radius: var(--radius-sm, 6px);
|
|
10631
|
+
background: var(--color-bg-primary);
|
|
10632
|
+
color: var(--color-text-primary);
|
|
10633
|
+
font-size: 13px;
|
|
10634
|
+
line-height: 1.5;
|
|
10635
|
+
font-family: inherit;
|
|
10636
|
+
}
|
|
10637
|
+
.share-text:focus {
|
|
10638
|
+
outline: none;
|
|
10639
|
+
border-color: var(--color-button-primary);
|
|
10640
|
+
}
|
|
10641
|
+
|
|
10642
|
+
.share-actions {
|
|
10643
|
+
display: flex;
|
|
10644
|
+
flex-wrap: wrap;
|
|
10645
|
+
gap: 8px;
|
|
10646
|
+
}
|
|
10647
|
+
.share-btn-primary, .share-btn-secondary {
|
|
10648
|
+
padding: 10px;
|
|
10649
|
+
border-radius: var(--radius-sm, 6px);
|
|
10650
|
+
font-size: 13px;
|
|
10651
|
+
font-weight: 600;
|
|
10652
|
+
cursor: pointer;
|
|
10653
|
+
border: 1px solid var(--color-border-primary);
|
|
10654
|
+
}
|
|
10655
|
+
.share-btn-primary {
|
|
10656
|
+
flex: 1 1 100%;
|
|
10657
|
+
background: var(--color-button-primary);
|
|
10658
|
+
color: var(--color-button-primary-text);
|
|
10659
|
+
border-color: var(--color-button-primary);
|
|
10660
|
+
}
|
|
10661
|
+
.share-btn-primary:hover { background: var(--color-button-primary-hover); }
|
|
10662
|
+
.share-btn-secondary {
|
|
10663
|
+
flex: 1 1 calc(50% - 4px);
|
|
10664
|
+
min-width: 90px;
|
|
10665
|
+
background: var(--color-bg-primary);
|
|
10666
|
+
color: var(--color-text-secondary);
|
|
10667
|
+
}
|
|
10668
|
+
.share-btn-secondary:hover { background: var(--color-bg-hover); }
|
|
10669
|
+
|
|
10670
|
+
#share-toggle-header svg { display: block; }
|