lithop 0.2.0 → 0.3.0
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 +7 -0
- data/Cargo.lock +284 -0
- data/Cargo.toml +6 -0
- data/README.md +25 -7
- data/RELEASING.md +24 -6
- data/docs/README.md +3 -1
- data/docs/artifacts.md +9 -2
- data/docs/bambu-studio.md +5 -1
- data/docs/command.md +25 -2
- data/docs/config.md +1 -1
- data/docs/getting-started.md +8 -5
- data/docs/images/cmyw-lithophane-preview.png +0 -0
- data/docs/local-ui.md +114 -0
- data/docs/tiling.md +4 -2
- data/docs/troubleshooting.md +10 -0
- data/ext/lithop_native/Cargo.toml +17 -0
- data/ext/lithop_native/build.rs +4 -0
- data/ext/lithop_native/extconf.rb +6 -0
- data/ext/lithop_native/src/column_mesh.rs +388 -0
- data/ext/lithop_native/src/lib.rs +37 -0
- data/ext/lithop_native/src/mesh.rs +187 -0
- data/ext/lithop_native/src/palette.rs +67 -0
- data/ext/lithop_native/src/xml.rs +134 -0
- data/lib/lithop/calibration/export.rb +2 -2
- data/lib/lithop/cli/options.rb +13 -0
- data/lib/lithop/cli.rb +28 -0
- data/lib/lithop/export/generation.rb +30 -17
- data/lib/lithop/export/job.rb +2 -2
- data/lib/lithop/export/metadata.rb +8 -3
- data/lib/lithop/export/previews.rb +25 -1
- data/lib/lithop/formats/three_mf/mesh.rb +72 -3
- data/lib/lithop/formats/three_mf/model_writer.rb +19 -16
- data/lib/lithop/formats/three_mf/part_writer.rb +37 -0
- data/lib/lithop/formats/three_mf.rb +1 -1
- data/lib/lithop/image/pipeline.rb +50 -11
- data/lib/lithop/image/svg_loader.rb +5 -5
- data/lib/lithop/job_execution.rb +132 -4
- data/lib/lithop/mesh/column_geometry.rb +8 -0
- data/lib/lithop/mesh/column_topology.rb +2 -0
- data/lib/lithop/model/material_model.rb +17 -5
- data/lib/lithop/model/palette.rb +21 -3
- data/lib/lithop/native.rb +39 -0
- data/lib/lithop/tiling.rb +3 -2
- data/lib/lithop/version.rb +1 -1
- data/lib/lithop/web/app.rb +513 -0
- data/lib/lithop/web/bambu_studio_launcher.rb +82 -0
- data/lib/lithop/web/job_launcher.rb +214 -0
- data/lib/lithop/web/job_store.rb +390 -0
- data/lib/lithop/web/preview_store.rb +182 -0
- data/lib/lithop/web/public/app.css +319 -0
- data/lib/lithop/web/public/app.js +1847 -0
- data/lib/lithop/web/server.rb +21 -0
- data/lib/lithop/web/settings_form.rb +64 -0
- data/lib/lithop/web/settings_library.rb +154 -0
- data/lib/lithop/web/views/index.erb +364 -0
- data/lib/lithop/web/worker.rb +27 -0
- data/lib/lithop.rb +1 -0
- data/sig/lithop.rbs +25 -0
- metadata +84 -2
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "json"
|
|
6
|
+
require "securerandom"
|
|
7
|
+
require "time"
|
|
8
|
+
require_relative "job_launcher"
|
|
9
|
+
|
|
10
|
+
module Lithop
|
|
11
|
+
module Web
|
|
12
|
+
# Owns short-lived input files used for pre-conversion browser previews.
|
|
13
|
+
# rubocop:disable-next Metrics/ClassLength
|
|
14
|
+
class PreviewStore
|
|
15
|
+
DIRECTORY = ".previews"
|
|
16
|
+
MAXIMUM_AGE = 24 * 60 * 60
|
|
17
|
+
MAXIMUM_CACHE_FILES = 8
|
|
18
|
+
MAXIMUM_CACHE_BYTES = 32 * 1024 * 1024
|
|
19
|
+
ID_PATTERN = /\A[0-9a-f]{32}\z/
|
|
20
|
+
CACHE_KEY_PATTERN = /\A[0-9a-f]{64}\z/
|
|
21
|
+
|
|
22
|
+
def initialize(project_dir:, random: SecureRandom, clock: Time)
|
|
23
|
+
@root = File.join(File.expand_path(project_dir), "build", DIRECTORY)
|
|
24
|
+
@random = random
|
|
25
|
+
@clock = clock
|
|
26
|
+
@mutex = Mutex.new
|
|
27
|
+
@preview_locks = {}
|
|
28
|
+
recover
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def stage(upload:, filename:)
|
|
32
|
+
extension = validate_extension(filename)
|
|
33
|
+
@mutex.synchronize do
|
|
34
|
+
validate_root!
|
|
35
|
+
preview_id = unique_id
|
|
36
|
+
directory = File.join(@root, preview_id)
|
|
37
|
+
Dir.mkdir(directory, 0o700)
|
|
38
|
+
input_path = File.join(directory, "input#{extension}")
|
|
39
|
+
upload.rewind if upload.respond_to?(:rewind)
|
|
40
|
+
File.open(input_path, "wb", 0o600) { |file| IO.copy_stream(upload, file) }
|
|
41
|
+
File.write(
|
|
42
|
+
File.join(directory, "preview.json"),
|
|
43
|
+
JSON.generate(id: preview_id, filename: File.basename(filename.to_s), created_at: timestamp) << "\n"
|
|
44
|
+
)
|
|
45
|
+
preview_id
|
|
46
|
+
rescue StandardError
|
|
47
|
+
FileUtils.rm_r(directory) if defined?(directory) && File.directory?(directory) && !File.symlink?(directory)
|
|
48
|
+
raise
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def with_input(preview_id)
|
|
53
|
+
lock_for(preview_id).synchronize do
|
|
54
|
+
input_path, filename, = resolve_input(preview_id)
|
|
55
|
+
yield input_path, filename
|
|
56
|
+
end
|
|
57
|
+
rescue JSON::ParserError, KeyError, SystemCallError
|
|
58
|
+
raise InputError, "preview input not found"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def render(preview_id, cache_key)
|
|
62
|
+
raise InputError, "invalid preview cache key" unless cache_key.to_s.match?(CACHE_KEY_PATTERN)
|
|
63
|
+
|
|
64
|
+
lock_for(preview_id).synchronize do
|
|
65
|
+
input_path, filename, directory = resolve_input(preview_id)
|
|
66
|
+
cache_path = File.join(directory, "render-#{cache_key}.png")
|
|
67
|
+
if File.file?(cache_path) && !File.symlink?(cache_path)
|
|
68
|
+
File.utime(@clock.now, @clock.now, cache_path)
|
|
69
|
+
return [File.binread(cache_path), true]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
contents = yield input_path, filename
|
|
73
|
+
temporary = File.join(directory, ".render-#{cache_key}-#{SecureRandom.hex(6)}.tmp")
|
|
74
|
+
File.binwrite(temporary, contents)
|
|
75
|
+
File.rename(temporary, cache_path)
|
|
76
|
+
prune_cache(directory, keep: cache_path)
|
|
77
|
+
[contents, false]
|
|
78
|
+
ensure
|
|
79
|
+
FileUtils.rm_f(temporary) if defined?(temporary) && temporary
|
|
80
|
+
end
|
|
81
|
+
rescue JSON::ParserError, KeyError, SystemCallError
|
|
82
|
+
raise InputError, "preview input not found"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def discard(preview_id)
|
|
86
|
+
lock_for(preview_id).synchronize do
|
|
87
|
+
directory = directory_for(preview_id)
|
|
88
|
+
return false unless safe_directory?(directory)
|
|
89
|
+
|
|
90
|
+
FileUtils.rm_r(directory)
|
|
91
|
+
true
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
def lock_for(preview_id)
|
|
98
|
+
directory_for(preview_id)
|
|
99
|
+
@mutex.synchronize { @preview_locks[preview_id] ||= Mutex.new }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def resolve_input(preview_id)
|
|
103
|
+
directory = directory_for(preview_id)
|
|
104
|
+
raise InputError, "preview input not found" unless safe_directory?(directory)
|
|
105
|
+
|
|
106
|
+
metadata = JSON.parse(File.read(File.join(directory, "preview.json")))
|
|
107
|
+
extension = validate_extension(metadata.fetch("filename"))
|
|
108
|
+
input_path = File.join(directory, "input#{extension}")
|
|
109
|
+
raise InputError, "preview input not found" if File.symlink?(input_path) || !File.file?(input_path)
|
|
110
|
+
|
|
111
|
+
[input_path, metadata.fetch("filename"), directory]
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def prune_cache(directory, keep:)
|
|
115
|
+
paths = Dir.glob("render-*.png", base: directory).map { |name| File.join(directory, name) }
|
|
116
|
+
paths.select! { |path| File.file?(path) && !File.symlink?(path) }
|
|
117
|
+
total = paths.sum { |path| File.size(path) }
|
|
118
|
+
remaining = paths.length
|
|
119
|
+
paths.sort_by { |path| File.mtime(path) }.each do |path|
|
|
120
|
+
break if remaining <= MAXIMUM_CACHE_FILES && total <= MAXIMUM_CACHE_BYTES
|
|
121
|
+
next if path == keep
|
|
122
|
+
|
|
123
|
+
total -= File.size(path)
|
|
124
|
+
FileUtils.rm_f(path)
|
|
125
|
+
remaining -= 1
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def validate_root!
|
|
130
|
+
build_dir = File.dirname(@root)
|
|
131
|
+
raise Error, "refusing to use a symbolic build directory" if File.symlink?(build_dir)
|
|
132
|
+
raise Error, "refusing to use a symbolic preview directory" if File.symlink?(@root)
|
|
133
|
+
|
|
134
|
+
FileUtils.mkdir_p(@root)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def validate_extension(filename)
|
|
138
|
+
extension = File.extname(filename.to_s).downcase
|
|
139
|
+
return extension if JobLauncher::ALLOWED_EXTENSIONS.include?(extension)
|
|
140
|
+
|
|
141
|
+
raise InputError, "image must be a JPEG, PNG, or SVG file"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def directory_for(preview_id)
|
|
145
|
+
raise InputError, "invalid preview ID" unless preview_id.to_s.match?(ID_PATTERN)
|
|
146
|
+
|
|
147
|
+
File.join(@root, preview_id)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def safe_directory?(directory)
|
|
151
|
+
File.directory?(directory) && !File.symlink?(directory) && File.dirname(directory) == @root
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def unique_id
|
|
155
|
+
10.times do
|
|
156
|
+
candidate = @random.hex(16)
|
|
157
|
+
return candidate if candidate.match?(ID_PATTERN) && !File.exist?(File.join(@root, candidate))
|
|
158
|
+
end
|
|
159
|
+
raise Error, "could not allocate preview input"
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def recover
|
|
163
|
+
return unless File.directory?(@root) && !File.symlink?(@root)
|
|
164
|
+
|
|
165
|
+
cutoff = @clock.now - MAXIMUM_AGE
|
|
166
|
+
Dir.children(@root).each do |name|
|
|
167
|
+
next unless name.match?(ID_PATTERN)
|
|
168
|
+
|
|
169
|
+
directory = File.join(@root, name)
|
|
170
|
+
next unless safe_directory?(directory)
|
|
171
|
+
next unless File.mtime(directory) < cutoff
|
|
172
|
+
|
|
173
|
+
FileUtils.rm_r(directory)
|
|
174
|
+
end
|
|
175
|
+
rescue SystemCallError
|
|
176
|
+
nil
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def timestamp = @clock.now.utc.iso8601(6)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
color-scheme: light;
|
|
3
|
+
--ink: #14231d;
|
|
4
|
+
--muted: #617068;
|
|
5
|
+
--paper: #f3f1e9;
|
|
6
|
+
--panel: #fffdf8;
|
|
7
|
+
--line: #d9d7cd;
|
|
8
|
+
--cyan: #0086d6;
|
|
9
|
+
--magenta: #ec008c;
|
|
10
|
+
--yellow: #f4d800;
|
|
11
|
+
--green: #1f6d50;
|
|
12
|
+
--danger: #a83131;
|
|
13
|
+
font-family: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
* { box-sizing: border-box; }
|
|
17
|
+
html { scroll-behavior: smooth; }
|
|
18
|
+
body {
|
|
19
|
+
margin: 0;
|
|
20
|
+
color: var(--ink);
|
|
21
|
+
background:
|
|
22
|
+
radial-gradient(circle at 12% 2%, rgba(0, 134, 214, 0.12), transparent 28rem),
|
|
23
|
+
radial-gradient(circle at 88% 4%, rgba(236, 0, 140, 0.09), transparent 26rem),
|
|
24
|
+
var(--paper);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
button, input, select { font: inherit; }
|
|
28
|
+
button, summary, select, input[type="range"] { touch-action: manipulation; }
|
|
29
|
+
button:focus-visible, input:focus-visible, select:focus-visible, summary:focus-visible, a:focus-visible, .file-picker:focus-visible {
|
|
30
|
+
outline: 3px solid rgba(0, 134, 214, 0.35);
|
|
31
|
+
outline-offset: 2px;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.site-header { position: sticky; top: 0; z-index: 20; display: flex; align-items: center; gap: 14px; min-height: 64px; padding: 9px max(20px, calc((100vw - 1800px) / 2)); border-bottom: 1px solid rgba(217,215,205,.9); background: rgba(243,241,233,.94); box-shadow: 0 8px 28px rgba(38,48,42,.06); backdrop-filter: blur(14px); }
|
|
35
|
+
.brand-block { display: grid; gap: 1px; min-width: 128px; }
|
|
36
|
+
.brand { color: var(--ink); font: 500 1.8rem/.9 Georgia,"Times New Roman",serif; letter-spacing: -.045em; text-decoration: none; }
|
|
37
|
+
.eyebrow, .step { margin: 0 0 10px; color: var(--green); font-size: 0.72rem; font-weight: 800; letter-spacing: 0.17em; }
|
|
38
|
+
.brand-block .eyebrow { margin: 0; font-size: .57rem; }
|
|
39
|
+
.header-context { display: flex; align-items: center; gap: 8px; min-width: 0; color: var(--muted); font-size: .76rem; }
|
|
40
|
+
.header-context .project-name { overflow: hidden; max-width: 220px; text-overflow: ellipsis; white-space: nowrap; }
|
|
41
|
+
.header-context strong { color: var(--ink); }
|
|
42
|
+
.global-job-control { display: flex; align-items: center; gap: 4px; margin-left: auto; border: 1px solid var(--line); border-radius: 11px; padding: 3px; background: rgba(255,255,255,.72); }
|
|
43
|
+
.header-action, .menu-toggle { border: 0; border-radius: 8px; padding: 8px 10px; color: var(--ink); background: transparent; cursor: pointer; font-size: .74rem; font-weight: 800; }
|
|
44
|
+
.header-action:hover, .menu-toggle:hover { background: #e6e9e3; }
|
|
45
|
+
#global-job-link { display: flex; align-items: baseline; gap: 8px; }
|
|
46
|
+
#global-job-link small { color: var(--muted); font: .68rem ui-monospace,monospace; }
|
|
47
|
+
.menu-toggle { display: none; font-size: 1.1rem; }
|
|
48
|
+
.local-badge, .state-pill, .dimension-badge { border: 1px solid var(--line); border-radius: 999px; padding: 8px 13px; background: rgba(255, 255, 255, 0.72); font: 700 0.72rem ui-monospace, SFMono-Regular, Menlo, monospace; }
|
|
49
|
+
.context-badge { border-radius: 999px; padding: 7px 12px; color: var(--green); background: #e8f2ec; font-size: 0.72rem; font-weight: 800; }
|
|
50
|
+
|
|
51
|
+
.workspace { width: min(1800px, calc(100% - 40px)); margin-inline: auto; padding: 20px 0 80px; }
|
|
52
|
+
.app-shell { display: grid; grid-template-columns: 160px minmax(0,1fr); align-items: start; gap: 18px; }
|
|
53
|
+
.app-content, .app-view { min-width: 0; }
|
|
54
|
+
.view-title { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
|
|
55
|
+
.primary-navigation { position: sticky; top: 84px; display: grid; gap: 7px; }
|
|
56
|
+
.primary-navigation button { display: grid; grid-template-columns: 26px minmax(0,1fr); gap: 1px 7px; width: 100%; border: 1px solid transparent; border-radius: 12px; padding: 11px; color: var(--ink); background: transparent; text-align: left; cursor: pointer; font-weight: 800; }
|
|
57
|
+
.primary-navigation button span { grid-row: 1 / 3; color: var(--muted); font: .66rem ui-monospace,monospace; }
|
|
58
|
+
.primary-navigation button small { color: var(--muted); font-size: .66rem; font-weight: 500; }
|
|
59
|
+
.primary-navigation button:hover { background: rgba(255,255,255,.62); }
|
|
60
|
+
.primary-navigation button[aria-current="page"] { border-color: var(--line); background: var(--panel); box-shadow: 0 8px 24px rgba(38,48,42,.06); }
|
|
61
|
+
.workspace-primary { display: grid; grid-template-columns: minmax(360px,400px) minmax(0,1fr); align-items: start; gap: 18px; }
|
|
62
|
+
.jobs-workspace { display: grid; grid-template-columns: minmax(300px,350px) minmax(0,1fr); grid-template-areas: "history detail"; align-items: start; gap: 18px; }
|
|
63
|
+
.jobs-workspace .history-panel { grid-area: history; position: sticky; top: 84px; max-height: calc(100vh - 104px); overflow: auto; }
|
|
64
|
+
.jobs-workspace .job-panel { grid-area: detail; }
|
|
65
|
+
.panel { min-width: 0; border: 1px solid var(--line); border-radius: 22px; background: rgba(255, 253, 248, 0.96); box-shadow: 0 18px 50px rgba(38, 48, 42, 0.07); padding: clamp(20px, 3vw, 34px); }
|
|
66
|
+
.preview-panel { position: sticky; top: 84px; }
|
|
67
|
+
.notice { padding: 13px 16px; border-radius: 12px; }
|
|
68
|
+
.notice.error { color: var(--danger); background: #fff0ed; border: 1px solid #efc0b8; }
|
|
69
|
+
.notice.info { color: #405049; background: #eef4f0; border: 1px solid #cbd9d0; }
|
|
70
|
+
|
|
71
|
+
.section-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; margin-bottom: 22px; }
|
|
72
|
+
.section-heading h2 { margin: 0; font: 500 clamp(1.6rem, 3vw, 2.3rem) Georgia, serif; }
|
|
73
|
+
.section-heading > p { margin: 4px 0 0; color: var(--muted); font-size: 0.82rem; text-align: right; }
|
|
74
|
+
.compact-heading { align-items: center; }
|
|
75
|
+
|
|
76
|
+
.file-picker { display: grid; gap: 6px; min-height: 104px; place-content: center; text-align: center; border: 1.5px dashed #94aaa0; border-radius: 16px; background: #f7faf7; cursor: pointer; transition: border-color 150ms, background 150ms, transform 150ms; }
|
|
77
|
+
.file-picker:hover, .file-picker.is-dragging { border-color: var(--green); background: #edf7f0; }
|
|
78
|
+
.file-picker.is-dragging { transform: scale(1.01); }
|
|
79
|
+
.file-picker input { position: absolute; inline-size: 1px; block-size: 1px; opacity: 0; }
|
|
80
|
+
.file-title { font-weight: 800; }
|
|
81
|
+
#file-name { color: var(--muted); font-size: 0.84rem; }
|
|
82
|
+
|
|
83
|
+
.settings-section { margin-top: 12px; border: 1px solid var(--line); border-radius: 14px; background: rgba(255,255,255,0.5); }
|
|
84
|
+
.settings-section > summary { display: flex; align-items: baseline; justify-content: space-between; gap: 18px; padding: 15px 16px; cursor: pointer; list-style: none; font-weight: 800; }
|
|
85
|
+
.settings-section > summary::-webkit-details-marker { display: none; }
|
|
86
|
+
.settings-section > summary::after { content: "+"; order: 3; color: var(--green); font-size: 1.2rem; }
|
|
87
|
+
.settings-section[open] > summary::after { content: "−"; }
|
|
88
|
+
.settings-section summary small { margin-left: auto; color: var(--muted); font-weight: 500; }
|
|
89
|
+
.settings-body { padding: 0 16px 16px; border-top: 1px solid #ebe9e0; }
|
|
90
|
+
.form-grid, .adjustment-grid, .quad-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
|
|
91
|
+
.adjustment-grid { grid-template-columns: 1fr; }
|
|
92
|
+
.split-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
93
|
+
.form-grid > label, .quad-grid > label, .range-field { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 7px; margin-top: 12px; font-size: 0.84rem; }
|
|
94
|
+
.form-grid label span, .quad-grid label span, .range-field span { color: var(--muted); font-size: 0.72rem; }
|
|
95
|
+
input[type="number"], input[type="text"], input[type="search"], input[type="date"], select { grid-column: 1 / -1; width: 100%; min-height: 40px; border: 1px solid #c9cdc8; border-radius: 9px; padding: 7px 9px; color: var(--ink); background: white; }
|
|
96
|
+
select { appearance: none; padding-right: 40px; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='m1 1 5 5 5-5' fill='none' stroke='%23192a23' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 14px center; background-size: 12px 8px; }
|
|
97
|
+
input:invalid:not(:placeholder-shown) { border-color: var(--danger); }
|
|
98
|
+
.range-field .range-control { grid-column: 1; width: 100%; accent-color: var(--green); }
|
|
99
|
+
.range-field input[type="number"] { grid-column: 2; grid-row: 2; width: 92px; }
|
|
100
|
+
.check-label { display: flex !important; grid-template-columns: none !important; align-items: center !important; gap: 8px !important; }
|
|
101
|
+
.check-label input { width: 18px; height: 18px; accent-color: var(--green); }
|
|
102
|
+
.z-parts-toggle { grid-column: 1 / -1; }
|
|
103
|
+
.z-parts-diagram { display: flex; gap: 5px; min-height: 42px; margin-top: 9px; }
|
|
104
|
+
.z-parts-diagram span { display: grid; flex: 1; place-items: center; min-width: 0; border: 1px solid var(--line); border-radius: 7px; padding: 7px 4px; background: linear-gradient(#fff 0 36%,rgba(0,134,214,.2) 36% 58%,rgba(236,0,140,.18) 58% 79%,rgba(244,216,0,.28) 79%); font-size: 0.66rem; text-align: center; }
|
|
105
|
+
.z-parts-diagram span:last-child { background: linear-gradient(#fff 0 82%,#e8e8e4 82%); }
|
|
106
|
+
.inline-actions, .section-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: center; gap: 10px; margin-top: 12px; }
|
|
107
|
+
.inline-actions .check-label { margin-right: auto; }
|
|
108
|
+
.text-button { border: 0; padding: 5px 2px; color: var(--green); background: transparent; cursor: pointer; font-size: 0.76rem; font-weight: 800; }
|
|
109
|
+
.danger-text { color: var(--danger); }
|
|
110
|
+
.settings-library { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 10px 14px; padding-top: 14px; }
|
|
111
|
+
.settings-library > label { display: grid; gap: 5px; color: var(--muted); font-size: .76rem; }
|
|
112
|
+
.library-actions { grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; }
|
|
113
|
+
.draft-actions { padding-top: 10px; border-top: 1px solid var(--line); }
|
|
114
|
+
.settings-library .fine-print { grid-column: 1 / -1; margin: 0; }
|
|
115
|
+
.settings-shortcut { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 14px; border: 1px solid var(--line); border-radius: 14px; padding: 13px 15px; background: #f8f8f3; }
|
|
116
|
+
.settings-shortcut div { display: grid; gap: 3px; }
|
|
117
|
+
.settings-shortcut small, .preset-intro { color: var(--muted); font-size: .74rem; line-height: 1.5; }
|
|
118
|
+
.settings-shortcut .secondary-button { flex: none; padding: 9px 12px; font-size: .74rem; }
|
|
119
|
+
.preset-panel { max-width: 880px; }
|
|
120
|
+
.preset-panel .settings-library { margin-top: 18px; border-top: 1px solid var(--line); }
|
|
121
|
+
.draft-summary { margin-top: 16px; border: 1px solid var(--line); border-radius: 14px; padding: 14px 16px; background: #f8f8f3; }
|
|
122
|
+
.summary-heading { display: flex; justify-content: space-between; gap: 16px; font-size: 0.8rem; }
|
|
123
|
+
.summary-heading span { color: var(--muted); }
|
|
124
|
+
.summary-heading span[data-changed="true"] { color: #925c00; font-weight: 800; }
|
|
125
|
+
.draft-summary dl { display: grid; grid-template-columns: max-content minmax(0,1fr); gap: 6px 14px; margin: 12px 0 0; font-size: 0.74rem; }
|
|
126
|
+
.draft-summary dt { color: var(--muted); }
|
|
127
|
+
.draft-summary dd { overflow-wrap: anywhere; margin: 0; }
|
|
128
|
+
|
|
129
|
+
.submit-bar { position: sticky; bottom: 12px; z-index: 4; display: flex; align-items: center; gap: 14px; margin-top: 18px; padding: 10px; border: 1px solid var(--line); border-radius: 14px; background: rgba(255,253,248,0.95); box-shadow: 0 10px 30px rgba(20,35,29,0.12); backdrop-filter: blur(10px); }
|
|
130
|
+
#draft-state { flex: 1; color: var(--muted); font-size: 0.76rem; }
|
|
131
|
+
.primary-button, .secondary-button, .danger-button { border: 0; border-radius: 11px; padding: 12px 18px; font-weight: 800; cursor: pointer; }
|
|
132
|
+
.primary-button { color: white; background: var(--ink); }
|
|
133
|
+
.primary-button:hover { background: var(--green); }
|
|
134
|
+
.secondary-button { color: var(--ink); background: #e8ebe7; }
|
|
135
|
+
.danger-button { color: #8d2525; background: #f6e4e1; }
|
|
136
|
+
button:disabled { opacity: 0.5; cursor: wait; }
|
|
137
|
+
|
|
138
|
+
.preview-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 9px; }
|
|
139
|
+
.preview-toolbar button { min-width: 34px; min-height: 34px; border: 1px solid var(--line); border-radius: 8px; color: var(--ink); background: white; cursor: pointer; }
|
|
140
|
+
.preview-toolbar output { min-width: 48px; text-align: center; color: var(--muted); font: 0.72rem ui-monospace, monospace; }
|
|
141
|
+
.preview-toolbar label { display: flex; align-items: center; gap: 6px; color: var(--muted); font-size: 0.72rem; }
|
|
142
|
+
.preview-toolbar label:first-of-type { margin-left: auto; }
|
|
143
|
+
.preview-toolbar .overlay-toggle input { accent-color: var(--green); }
|
|
144
|
+
.preview-toolbar select { min-height: 34px; padding: 3px 36px 3px 9px; background-position: right 12px center; }
|
|
145
|
+
.image-stage, .result-preview { position: relative; overflow: hidden; display: grid; place-items: center; border: 1px solid var(--line); border-radius: 15px; user-select: none; }
|
|
146
|
+
.image-stage, .result-preview { min-height: 0; aspect-ratio: 140 / 104; }
|
|
147
|
+
.preview-viewport[data-background="checker"], .comparison-source { background: linear-gradient(45deg,#ecebe5 25%,transparent 25%),linear-gradient(-45deg,#ecebe5 25%,transparent 25%),linear-gradient(45deg,transparent 75%,#ecebe5 75%),linear-gradient(-45deg,transparent 75%,#ecebe5 75%),#faf9f4; background-position: 0 0,0 10px,10px -10px,-10px 0; background-size: 20px 20px; }
|
|
148
|
+
.preview-viewport[data-background="light"] { background: #fff; }
|
|
149
|
+
.preview-viewport[data-background="gray"] { background: #737873; }
|
|
150
|
+
.preview-viewport[data-background="dark"] { background: #19201c; }
|
|
151
|
+
.preview-viewport.is-pannable { cursor: grab; touch-action: none; }
|
|
152
|
+
.preview-viewport.is-panning { cursor: grabbing; }
|
|
153
|
+
.image-stage img, .result-preview img { display: block; width: 100%; height: 100%; object-fit: contain; transform-origin: center; will-change: transform; }
|
|
154
|
+
.image-stage .tile-overlay { position: absolute; inset: 0; z-index: 2; pointer-events: none; }
|
|
155
|
+
.image-stage p { color: var(--muted); font-size: 0.86rem; }
|
|
156
|
+
.fine-print { color: var(--muted); font-size: 0.76rem; line-height: 1.55; }
|
|
157
|
+
|
|
158
|
+
.job-meta { display: flex; justify-content: space-between; gap: 20px; }
|
|
159
|
+
.job-meta > div { min-width: 0; }
|
|
160
|
+
.job-view-name { overflow: hidden; margin: 0 0 4px; font-weight: 800; text-overflow: ellipsis; white-space: nowrap; }
|
|
161
|
+
.job-id { color: var(--muted); font: 0.8rem ui-monospace, SFMono-Regular, Menlo, monospace; }
|
|
162
|
+
.progress-track { overflow: hidden; height: 9px; border-radius: 999px; background: #e2e5df; }
|
|
163
|
+
.progress-track span { display: block; width: 0; height: 100%; background: linear-gradient(90deg,var(--cyan),var(--magenta),var(--yellow)); transition: width 220ms ease; }
|
|
164
|
+
.job-copy { display: flex; gap: 14px; align-items: baseline; margin: 12px 0 20px; }
|
|
165
|
+
.job-copy strong { font: 500 1.8rem Georgia, serif; }
|
|
166
|
+
.job-copy span { color: var(--muted); }
|
|
167
|
+
.job-actions { display: flex; gap: 10px; align-items: center; }
|
|
168
|
+
.back-to-job-list { display: none; margin-bottom: 12px; }
|
|
169
|
+
.job-detail-tabs { display: flex; gap: 5px; margin-top: 22px; border-bottom: 1px solid var(--line); }
|
|
170
|
+
.job-detail-tabs button { border: 0; border-bottom: 3px solid transparent; padding: 10px 13px; color: var(--muted); background: transparent; cursor: pointer; font-size: .78rem; font-weight: 800; }
|
|
171
|
+
.job-detail-tabs button[aria-selected="true"] { border-bottom-color: var(--green); color: var(--ink); }
|
|
172
|
+
.job-setting-details { display: grid; grid-template-columns: max-content minmax(0,1fr); gap: 8px 16px; font-size: .78rem; }
|
|
173
|
+
.job-setting-details dt { color: var(--muted); }
|
|
174
|
+
.job-setting-details dd { margin: 0; overflow-wrap: anywhere; }
|
|
175
|
+
.job-freshness { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; margin: -10px 0 16px; color: var(--muted); font-size: 0.74rem; }
|
|
176
|
+
#job-connection[data-connection="online"] { color: var(--green); }
|
|
177
|
+
#job-connection[data-connection="offline"] { color: var(--danger); font-weight: 800; }
|
|
178
|
+
#job-error p { margin: 0; }
|
|
179
|
+
.recovery-actions { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 8px; }
|
|
180
|
+
.recovery-actions .text-button { color: var(--danger); }
|
|
181
|
+
.result-layout { margin-top: 26px; }
|
|
182
|
+
.result-tabs { display: flex; flex-wrap: wrap; gap: 7px; margin-bottom: 12px; }
|
|
183
|
+
.result-tabs button { border: 1px solid var(--line); border-radius: 999px; padding: 7px 11px; color: var(--ink); background: white; cursor: pointer; font-size: 0.76rem; }
|
|
184
|
+
.result-tabs button[aria-selected="true"] { color: white; border-color: var(--green); background: var(--green); }
|
|
185
|
+
.comparison-grid { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 16px; }
|
|
186
|
+
.comparison-grid figure { min-width: 0; margin: 0; }
|
|
187
|
+
.comparison-grid figcaption { margin-bottom: 7px; color: var(--muted); font-size: 0.76rem; font-weight: 800; }
|
|
188
|
+
.artifact-section { margin-top: 24px; }
|
|
189
|
+
.artifact-heading { display: flex; justify-content: space-between; align-items: center; gap: 12px; }
|
|
190
|
+
.artifact-heading > div { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
191
|
+
.artifact-heading .secondary-button { padding: 8px 11px; font-size: .74rem; }
|
|
192
|
+
.tile-results { margin-top: 24px; }
|
|
193
|
+
.tile-result-list { display: grid; grid-template-columns: repeat(auto-fit,minmax(210px,1fr)); gap: 14px; }
|
|
194
|
+
.tile-result-card { min-width: 0; margin: 0; padding: 12px; border: 1px solid var(--line); border-radius: 15px; background: var(--paper); }
|
|
195
|
+
.tile-result-card:focus-visible, .tile-result-card.is-selected { outline: 3px solid rgba(0,134,214,.3); outline-offset: 2px; border-color: var(--cyan); }
|
|
196
|
+
.tile-result-card img { display: block; width: 100%; aspect-ratio: 140 / 104; object-fit: contain; border-radius: 10px; background: #f1f0eb; image-rendering: auto; }
|
|
197
|
+
.tile-result-card figcaption { display: grid; gap: 9px; margin-top: 9px; }
|
|
198
|
+
.tile-result-card strong { overflow: hidden; font: 700 0.78rem ui-monospace, monospace; text-overflow: ellipsis; white-space: nowrap; }
|
|
199
|
+
.tile-result-card a { flex: none; color: var(--green); font-size: 0.78rem; font-weight: 700; }
|
|
200
|
+
.tile-model-actions, .artifact-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; }
|
|
201
|
+
.tile-model-actions .bambu-open-button, .artifact-actions .bambu-open-button { padding: 7px 9px; font-size: 0.72rem; }
|
|
202
|
+
.artifact-status { min-height: 1.2em; margin: 8px 0 12px; }
|
|
203
|
+
.artifact-status[data-state="success"] { color: var(--green); }
|
|
204
|
+
.artifact-status[data-state="error"] { color: var(--danger); font-weight: 800; }
|
|
205
|
+
.tile-result-card > small { display: block; margin-top: 8px; color: var(--muted); font-size: 0.7rem; line-height: 1.5; }
|
|
206
|
+
.tile-result-card .tile-preview-missing { display: grid; place-items: center; width: 100%; aspect-ratio: 140 / 104; border-radius: 10px; background: #f1f0eb; color: var(--muted); font-size: 0.78rem; }
|
|
207
|
+
.tile-map { display: grid; gap: 5px; margin: 0 0 14px; border: 1px solid var(--line); border-radius: 12px; padding: 8px; background: #eeece4; }
|
|
208
|
+
.tile-map-row { display: grid; grid-auto-columns: minmax(0,1fr); grid-auto-flow: column; gap: 5px; }
|
|
209
|
+
.tile-map button { min-height: 48px; border: 1px solid #bfc4bd; border-radius: 7px; color: var(--ink); background: white; cursor: pointer; font: 700 0.72rem ui-monospace, monospace; }
|
|
210
|
+
.tile-map button.is-selected { color: white; border-color: var(--green); background: var(--green); }
|
|
211
|
+
.artifact-list, .job-list { list-style: none; margin: 0; padding: 0; }
|
|
212
|
+
.artifact-list { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: 18px; }
|
|
213
|
+
.artifact-list li { min-width: 0; }
|
|
214
|
+
.artifact-list ul { list-style: none; margin: 8px 0 0; padding: 0; }
|
|
215
|
+
.artifact-list ul li { margin-bottom: 7px; }
|
|
216
|
+
.artifact-list strong { font-size: 0.78rem; }
|
|
217
|
+
.artifact-list a { display: block; overflow: hidden; min-width: 0; color: var(--green); font: 0.78rem ui-monospace, monospace; text-overflow: ellipsis; white-space: nowrap; }
|
|
218
|
+
|
|
219
|
+
.history-controls { display: grid; grid-template-columns: minmax(180px,2fr) repeat(4,minmax(130px,1fr)); gap: 10px; margin-bottom: 14px; }
|
|
220
|
+
.history-controls label { display: grid; gap: 4px; color: var(--muted); font-size: 0.72rem; }
|
|
221
|
+
.history-selection-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 9px; margin-bottom: 14px; }
|
|
222
|
+
.history-selection-actions span { margin-right: auto; color: var(--muted); font-size: .76rem; }
|
|
223
|
+
.history-selection-actions button { padding: 8px 12px; font-size: .74rem; }
|
|
224
|
+
.job-list { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: 10px; }
|
|
225
|
+
.job-list li { position: relative; display: grid; grid-template-columns: auto minmax(0,1fr); align-items: start; gap: 8px; min-width: 0; }
|
|
226
|
+
.job-list .history-select { width: 18px; height: 18px; margin-top: 13px; accent-color: var(--green); }
|
|
227
|
+
.job-list a { display: grid; gap: 5px; min-height: 105px; padding: 13px; border: 1px solid var(--line); border-radius: 10px; color: inherit; text-decoration: none; }
|
|
228
|
+
.job-list a:hover { border-color: #91a39a; }
|
|
229
|
+
.job-list strong, .job-list span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
230
|
+
.job-list strong { font-size: 0.86rem; }
|
|
231
|
+
.job-list span { color: var(--muted); font: 0.7rem ui-monospace, monospace; }
|
|
232
|
+
.job-list small, .empty-copy { color: var(--muted); }
|
|
233
|
+
.job-list .history-thumbnail { width: 100%; aspect-ratio: 140 / 104; object-fit: contain; border-radius: 6px; background: #f0efe9; }
|
|
234
|
+
.jobs-workspace .history-controls, .jobs-workspace .job-list { grid-template-columns: 1fr; }
|
|
235
|
+
.jobs-workspace .history-selection-actions { justify-content: flex-start; }
|
|
236
|
+
.jobs-workspace .history-selection-actions span { flex-basis: 100%; }
|
|
237
|
+
.history-delete { grid-column: 2; margin-top: 6px; border: 0; padding: 3px 1px; color: #8d2525; background: transparent; cursor: pointer; font-size: 0.72rem; }
|
|
238
|
+
.load-more { display: block; margin: 18px auto 0; }
|
|
239
|
+
.preview-dialog { width: min(1320px, calc(100vw - 32px)); max-width: none; height: min(880px, calc(100vh - 32px)); max-height: none; border: 1px solid var(--line); border-radius: 20px; padding: 22px; color: var(--ink); background: var(--panel); box-shadow: 0 28px 90px rgba(10,20,15,.35); }
|
|
240
|
+
.preview-dialog::backdrop { background: rgba(16,25,21,.72); backdrop-filter: blur(4px); }
|
|
241
|
+
.dialog-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; }
|
|
242
|
+
.dialog-heading h2 { margin: 0 0 18px; font: 500 clamp(1.4rem,3vw,2rem) Georgia,serif; }
|
|
243
|
+
.dialog-preview-grid { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 14px; }
|
|
244
|
+
.dialog-preview-grid figure { min-width: 0; margin: 0; }
|
|
245
|
+
.dialog-preview-grid figcaption { margin: 0 0 6px; color: var(--muted); font-size: .76rem; font-weight: 800; }
|
|
246
|
+
.dialog-preview-grid .result-preview { max-height: calc(100vh - 230px); }
|
|
247
|
+
.dialog-preview-grid.is-single { grid-template-columns: minmax(0,980px); justify-content: center; }
|
|
248
|
+
.job-compare-grid { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 16px; }
|
|
249
|
+
.job-compare-grid figure { min-width: 0; margin: 0; }
|
|
250
|
+
.job-compare-grid figcaption { display: grid; gap: 3px; margin-bottom: 7px; }
|
|
251
|
+
.job-compare-grid figcaption strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
252
|
+
.job-compare-grid figcaption span { color: var(--muted); font: .72rem ui-monospace,monospace; }
|
|
253
|
+
.job-compare-grid .result-preview p { color: var(--muted); font-size: .8rem; }
|
|
254
|
+
.compare-settings { margin-top: 18px; }
|
|
255
|
+
.compare-settings h3 { margin-bottom: 8px; }
|
|
256
|
+
.table-scroll { overflow-x: auto; }
|
|
257
|
+
.compare-settings table { width: 100%; border-collapse: collapse; font-size: .76rem; }
|
|
258
|
+
.compare-settings th, .compare-settings td { padding: 8px; border-bottom: 1px solid var(--line); text-align: left; }
|
|
259
|
+
.compare-settings th:first-child { color: var(--muted); }
|
|
260
|
+
.help-dialog { width: min(560px,calc(100vw - 32px)); border: 1px solid var(--line); border-radius: 18px; padding: 24px; color: var(--ink); background: var(--panel); box-shadow: 0 28px 90px rgba(10,20,15,.35); }
|
|
261
|
+
.help-dialog::backdrop { background: rgba(16,25,21,.6); backdrop-filter: blur(3px); }
|
|
262
|
+
.help-dialog ol { display: grid; gap: 12px; padding-left: 22px; }
|
|
263
|
+
.help-dialog li span { display: block; margin-top: 3px; color: var(--muted); font-size: .8rem; line-height: 1.5; }
|
|
264
|
+
[hidden] { display: none !important; }
|
|
265
|
+
|
|
266
|
+
@media (max-width: 1439px) {
|
|
267
|
+
.menu-toggle { display: block; }
|
|
268
|
+
.app-shell { grid-template-columns: 1fr; }
|
|
269
|
+
.primary-navigation { position: fixed; z-index: 31; top: 0; bottom: 0; left: 0; width: min(280px,calc(100vw - 52px)); align-content: start; padding: 78px 16px 20px; background: var(--panel); box-shadow: 18px 0 50px rgba(20,35,29,.2); transform: translateX(-105%); transition: transform 160ms ease; }
|
|
270
|
+
body.navigation-open .primary-navigation { transform: translateX(0); }
|
|
271
|
+
.navigation-scrim { position: fixed; z-index: 30; inset: 0; background: rgba(20,35,29,.42); }
|
|
272
|
+
body.navigation-open .navigation-scrim { display: block !important; }
|
|
273
|
+
.workspace-primary { grid-template-columns: minmax(340px,.8fr) minmax(0,1.2fr); }
|
|
274
|
+
.jobs-workspace { grid-template-columns: minmax(290px,340px) minmax(0,1fr); }
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
@media (max-width: 900px) {
|
|
278
|
+
.workspace-primary { grid-template-columns: 1fr; }
|
|
279
|
+
.preview-panel { position: static; }
|
|
280
|
+
.jobs-workspace { grid-template-columns: 1fr; grid-template-areas: "history" "detail"; }
|
|
281
|
+
.jobs-workspace .history-panel { position: static; max-height: none; }
|
|
282
|
+
.workspace.has-job[data-view="jobs"]:not(.show-job-list) .jobs-workspace .history-panel { display: none; }
|
|
283
|
+
.workspace.show-job-list[data-view="jobs"] .jobs-workspace .job-panel { display: none; }
|
|
284
|
+
.back-to-job-list { display: inline-block; }
|
|
285
|
+
.jobs-workspace .job-list { grid-template-columns: repeat(2,minmax(0,1fr)); }
|
|
286
|
+
.job-compare-grid { grid-template-columns: 1fr; }
|
|
287
|
+
.history-controls { grid-template-columns: repeat(2,minmax(0,1fr)); }
|
|
288
|
+
.artifact-heading { align-items: flex-start; flex-direction: column; }
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
@media (max-width: 680px) {
|
|
292
|
+
.site-header { gap: 8px; padding-inline: 11px; }
|
|
293
|
+
.brand-block { min-width: 80px; }
|
|
294
|
+
.brand-block .eyebrow, .header-context .project-name, .header-context > span[aria-hidden], #help-button { display: none; }
|
|
295
|
+
.brand { font-size: 1.5rem; }
|
|
296
|
+
.header-context { margin-right: auto; }
|
|
297
|
+
.global-job-control { margin-left: 0; }
|
|
298
|
+
#global-job-link { max-width: 135px; }
|
|
299
|
+
#global-job-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
300
|
+
.workspace { width: min(100% - 22px,1800px); }
|
|
301
|
+
.local-badge { display: none; }
|
|
302
|
+
.form-grid, .quad-grid, .split-grid, .comparison-grid, .job-list, .job-compare-grid, .settings-library, .history-controls, .jobs-workspace .job-list { grid-template-columns: 1fr; }
|
|
303
|
+
.artifact-list { grid-template-columns: 1fr; }
|
|
304
|
+
.dialog-preview-grid { grid-template-columns: 1fr; }
|
|
305
|
+
.dialog-preview-grid .result-preview { max-height: calc((100vh - 250px) / 2); }
|
|
306
|
+
.draft-summary dl { grid-template-columns: 1fr; gap: 2px; }
|
|
307
|
+
.draft-summary dd { margin-bottom: 7px; }
|
|
308
|
+
.submit-bar { align-items: stretch; flex-direction: column; }
|
|
309
|
+
.submit-bar .primary-button { width: 100%; }
|
|
310
|
+
.settings-section summary small { display: none; }
|
|
311
|
+
.settings-shortcut { align-items: stretch; flex-direction: column; }
|
|
312
|
+
.settings-shortcut .secondary-button { width: 100%; }
|
|
313
|
+
.job-detail-tabs { overflow-x: auto; }
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
@media (prefers-reduced-motion: reduce) {
|
|
317
|
+
html { scroll-behavior: auto; }
|
|
318
|
+
*, *::before, *::after { transition-duration: 0.01ms !important; }
|
|
319
|
+
}
|