lithop 0.3.0 → 0.4.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 +1 -1
- data/README.md +14 -11
- data/RELEASING.md +3 -3
- data/docs/README.md +4 -3
- data/docs/artifacts.md +27 -15
- data/docs/bambu-studio.md +12 -10
- data/docs/calibration.md +4 -4
- data/docs/color-model.md +20 -18
- data/docs/command.md +28 -12
- data/docs/config.md +42 -30
- data/docs/getting-started.md +12 -6
- data/docs/local-ui.md +12 -9
- data/docs/tiling.md +18 -19
- data/docs/troubleshooting.md +3 -3
- data/ext/lithop_native/Cargo.toml +1 -1
- data/lib/lithop/calibration/target.rb +5 -5
- data/lib/lithop/cli/image_adjustment_options.rb +33 -0
- data/lib/lithop/cli/image_options.rb +5 -18
- data/lib/lithop/cli/options.rb +4 -4
- data/lib/lithop/export/color_metadata.rb +46 -23
- data/lib/lithop/export/generation.rb +8 -10
- data/lib/lithop/export/planning.rb +82 -45
- data/lib/lithop/export/print_readme.rb +20 -4
- data/lib/lithop/formats/three_mf/bambu_metadata.rb +11 -8
- data/lib/lithop/formats/three_mf/model_writer.rb +2 -5
- data/lib/lithop/image/pipeline.rb +40 -26
- data/lib/lithop/image/svg_loader.rb +11 -2
- data/lib/lithop/layer_structure.rb +0 -9
- data/lib/lithop/model/filament_profile.rb +3 -3
- data/lib/lithop/model/material_display_map.rb +2 -1
- data/lib/lithop/model/z_region_model.rb +44 -0
- data/lib/lithop/settings.rb +147 -15
- data/lib/lithop/settings_file.rb +1 -0
- data/lib/lithop/version.rb +1 -1
- data/lib/lithop/web/app.rb +9 -5
- data/lib/lithop/web/public/app.css +48 -37
- data/lib/lithop/web/public/app.js +149 -47
- data/lib/lithop/web/settings_form.rb +8 -5
- data/lib/lithop/web/views/index.erb +129 -75
- data/lib/lithop.rb +2 -1
- data/sig/lithop.rbs +16 -0
- metadata +3 -2
- data/lib/lithop/model/layer_region_model.rb +0 -24
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lithop
|
|
4
|
+
# Restricts a material model to an arbitrary contiguous Z range.
|
|
5
|
+
class ZRegionModel
|
|
6
|
+
Cell = Data.define(:materials)
|
|
7
|
+
|
|
8
|
+
attr_reader :columns, :rows, :x_boundaries, :y_boundaries, :z_boundaries, :cells, :z_range
|
|
9
|
+
|
|
10
|
+
def initialize(model:, z_range:)
|
|
11
|
+
@columns = model.columns
|
|
12
|
+
@rows = model.rows
|
|
13
|
+
@x_boundaries = model.x_boundaries
|
|
14
|
+
@y_boundaries = model.y_boundaries
|
|
15
|
+
@z_range = z_range
|
|
16
|
+
source_layers = overlapping_layers(model.z_boundaries)
|
|
17
|
+
@z_boundaries = clipped_boundaries(model.z_boundaries, source_layers).freeze
|
|
18
|
+
@cells = model.cells.map do |cell|
|
|
19
|
+
Cell.new(materials: source_layers.map { |layer| cell.materials.fetch(layer) }.freeze)
|
|
20
|
+
end.freeze
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def materials = @materials ||= cells.flat_map(&:materials).uniq.freeze
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def overlapping_layers(boundaries)
|
|
28
|
+
layers = (0...(boundaries.length - 1)).select do |layer|
|
|
29
|
+
boundaries.fetch(layer) < z_range.end && boundaries.fetch(layer + 1) > z_range.begin
|
|
30
|
+
end
|
|
31
|
+
raise InputError, "Z region must overlap the material model" if layers.empty?
|
|
32
|
+
|
|
33
|
+
layers.freeze
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def clipped_boundaries(boundaries, layers)
|
|
37
|
+
inner = layers.filter_map do |layer|
|
|
38
|
+
boundary = boundaries.fetch(layer + 1)
|
|
39
|
+
boundary if boundary > z_range.begin && boundary < z_range.end
|
|
40
|
+
end
|
|
41
|
+
[z_range.begin, *inner, z_range.end]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
data/lib/lithop/settings.rb
CHANGED
|
@@ -119,10 +119,9 @@ module Lithop
|
|
|
119
119
|
|
|
120
120
|
def validate_z_parts!
|
|
121
121
|
raise ConfigurationError, "separate_z_parts must be a boolean" unless [true, false].include?(separate_z_parts)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
end
|
|
122
|
+
return if color_region_parts.is_a?(Integer) && color_region_parts.between?(1, Settings::MAXIMUM_Z_PARTS)
|
|
123
|
+
|
|
124
|
+
raise ConfigurationError, "color_region_parts must be an integer from 1 to #{Settings::MAXIMUM_Z_PARTS}"
|
|
126
125
|
end
|
|
127
126
|
|
|
128
127
|
def validate_layer_structure!
|
|
@@ -187,6 +186,17 @@ module Lithop
|
|
|
187
186
|
|
|
188
187
|
raise ConfigurationError, "#{key} must be a finite percentage from -100 to 100"
|
|
189
188
|
end
|
|
189
|
+
unless yellow_mix_percent.is_a?(Numeric) && yellow_mix_percent.finite? &&
|
|
190
|
+
yellow_mix_percent.between?(0.0, 100.0)
|
|
191
|
+
raise ConfigurationError, "yellow_mix_percent must be a finite percentage from 0 to 100"
|
|
192
|
+
end
|
|
193
|
+
unless Settings::YELLOW_MIX_COLORS.include?(yellow_mix_color)
|
|
194
|
+
raise ConfigurationError,
|
|
195
|
+
"yellow_mix_color must be one of: #{Settings::YELLOW_MIX_COLORS.join(", ")}"
|
|
196
|
+
end
|
|
197
|
+
return if [true, false].include?(suppress_pale_colors)
|
|
198
|
+
|
|
199
|
+
raise ConfigurationError, "suppress_pale_colors must be a boolean"
|
|
190
200
|
end
|
|
191
201
|
|
|
192
202
|
def validate_nozzle_diameter!
|
|
@@ -227,16 +237,27 @@ module Lithop
|
|
|
227
237
|
# Validated dimensions, print parameters, and output limits for a generation job.
|
|
228
238
|
# rubocop:disable-next Metrics/ClassLength
|
|
229
239
|
class Settings
|
|
240
|
+
MAXIMUM_Z_PARTS = 3
|
|
241
|
+
MINIMUM_Z_PART_THICKNESS = 0.32
|
|
242
|
+
MINIMUM_Z_SOURCE_THICKNESS = MINIMUM_Z_PART_THICKNESS * MAXIMUM_Z_PARTS
|
|
243
|
+
Z_SEPARATED_COLOR_LAYER_COUNT = 8
|
|
244
|
+
Z_SEPARATED_FIRST_LAYER_HEIGHT = 0.1
|
|
245
|
+
Z_SEPARATED_LAYER_HEIGHT = 0.08
|
|
246
|
+
Z_PART_BASE_WHITE_LAYERS = 1
|
|
247
|
+
Z_PART_MINIMUM_TOTAL_LAYERS = 4
|
|
230
248
|
include SettingsValidation
|
|
231
249
|
|
|
232
250
|
MICRONS_PER_MM = LayerStructure::MICRONS_PER_MM
|
|
233
251
|
COLOR_MODELS = %w[cmy].freeze
|
|
234
252
|
NOZZLE_DIAMETERS = [0.2, 0.4].freeze
|
|
235
|
-
COLOR_LAYER_COUNTS = [4, 5, 7].freeze
|
|
253
|
+
COLOR_LAYER_COUNTS = [4, 5, 7, 8].freeze
|
|
236
254
|
LAYOUT_PROFILES = ["standard"].freeze
|
|
237
255
|
IMAGE_FITS = %w[cover contain].freeze
|
|
238
256
|
TILE_LAYOUTS = %w[auto columns rows grid].freeze
|
|
257
|
+
YELLOW_MIX_COLORS = %w[cyan magenta].freeze
|
|
239
258
|
PIGMENTS = %i[cyan magenta yellow].freeze
|
|
259
|
+
PIGMENT_ORDERS = PIGMENTS.permutation.map(&:freeze).freeze
|
|
260
|
+
DEFAULT_PIGMENT_ORDER = %i[yellow magenta cyan].freeze
|
|
240
261
|
MINIMUM_TOP_WHITE_LAYERS = LayerStructure::MINIMUM_TOP_WHITE_LAYERS
|
|
241
262
|
DEFAULTS = {
|
|
242
263
|
layout_profile: "standard",
|
|
@@ -253,8 +274,11 @@ module Lithop
|
|
|
253
274
|
brightness: 0.0,
|
|
254
275
|
contrast: 0.0,
|
|
255
276
|
saturation: 0.0,
|
|
256
|
-
|
|
257
|
-
|
|
277
|
+
suppress_pale_colors: false,
|
|
278
|
+
yellow_mix_color: "cyan",
|
|
279
|
+
yellow_mix_percent: 0.0,
|
|
280
|
+
minimum_thickness: 0.74,
|
|
281
|
+
maximum_thickness: 1.94,
|
|
258
282
|
pixel_size: 0.22,
|
|
259
283
|
color_pixel_size: nil,
|
|
260
284
|
grid_columns: 637,
|
|
@@ -266,16 +290,16 @@ module Lithop
|
|
|
266
290
|
separate_z_parts: false,
|
|
267
291
|
color_region_parts: 3,
|
|
268
292
|
first_layer_height: 0.1,
|
|
269
|
-
layer_height: 0.
|
|
270
|
-
base_white_layers:
|
|
271
|
-
top_layer_height: 0.
|
|
272
|
-
top_white_start:
|
|
273
|
-
minimum_top_white_layers:
|
|
274
|
-
exact_maximum_thickness:
|
|
293
|
+
layer_height: 0.08,
|
|
294
|
+
base_white_layers: 1,
|
|
295
|
+
top_layer_height: 0.08,
|
|
296
|
+
top_white_start: nil,
|
|
297
|
+
minimum_top_white_layers: 1,
|
|
298
|
+
exact_maximum_thickness: false,
|
|
275
299
|
nozzle_diameter: 0.2,
|
|
276
300
|
color_model: "cmy",
|
|
277
301
|
color_layer_count: 7,
|
|
278
|
-
pigment_order:
|
|
302
|
+
pigment_order: DEFAULT_PIGMENT_ORDER,
|
|
279
303
|
bambu_project: true,
|
|
280
304
|
maximum_input_pixels: 20_000_000,
|
|
281
305
|
maximum_triangles: 5_000_000
|
|
@@ -290,6 +314,7 @@ module Lithop
|
|
|
290
314
|
settings = DEFAULTS.merge(values).merge(layout_profile: profile)
|
|
291
315
|
DEFAULTS.each_key { |key| instance_variable_set("@#{key}", settings.fetch(key)) }
|
|
292
316
|
@pigment_order = normalize_pigment_order(@pigment_order)
|
|
317
|
+
apply_z_separated_layer_structure! if @separate_z_parts
|
|
293
318
|
validate!
|
|
294
319
|
@layer_structure = LayerStructure.new(
|
|
295
320
|
first_layer_height:, layer_height:, color_layer_count:, minimum_thickness:, maximum_thickness:,
|
|
@@ -304,6 +329,7 @@ module Lithop
|
|
|
304
329
|
|
|
305
330
|
validate_physical_minimum!
|
|
306
331
|
validate_separated_top_white!
|
|
332
|
+
validate_z_separated_thickness_range!
|
|
307
333
|
end
|
|
308
334
|
|
|
309
335
|
def without_default_grid(values)
|
|
@@ -315,6 +341,18 @@ module Lithop
|
|
|
315
341
|
end
|
|
316
342
|
private :without_default_grid
|
|
317
343
|
|
|
344
|
+
def apply_z_separated_layer_structure!
|
|
345
|
+
@color_layer_count = Z_SEPARATED_COLOR_LAYER_COUNT
|
|
346
|
+
@first_layer_height = Z_SEPARATED_FIRST_LAYER_HEIGHT
|
|
347
|
+
@layer_height = Z_SEPARATED_LAYER_HEIGHT
|
|
348
|
+
@top_layer_height = Z_SEPARATED_LAYER_HEIGHT
|
|
349
|
+
@top_white_start = ((@base_white_layers * @first_layer_height) +
|
|
350
|
+
(@color_layer_count * @layer_height)).round(9)
|
|
351
|
+
@minimum_thickness = [@minimum_thickness, @top_white_start + @top_layer_height].max.round(9)
|
|
352
|
+
@exact_maximum_thickness = false
|
|
353
|
+
end
|
|
354
|
+
private :apply_z_separated_layer_structure!
|
|
355
|
+
|
|
318
356
|
def columns = sampling_grid.columns
|
|
319
357
|
def rows = sampling_grid.rows
|
|
320
358
|
def effective_color_pixel_size = color_pixel_size || pixel_size
|
|
@@ -334,6 +372,77 @@ module Lithop
|
|
|
334
372
|
def resolved_tile_rows = resolved_tile_shape.fetch(0)
|
|
335
373
|
def resolved_tile_columns = resolved_tile_shape.fetch(1)
|
|
336
374
|
|
|
375
|
+
def resolved_z_part_boundaries
|
|
376
|
+
@resolved_z_part_boundaries ||= begin
|
|
377
|
+
start = layer_structure.base_white_thickness
|
|
378
|
+
source_thickness = z_separated_minimum_thickness / color_region_parts
|
|
379
|
+
boundaries = Array.new(color_region_parts) do |index|
|
|
380
|
+
(start + (index * source_thickness)).round(9)
|
|
381
|
+
end
|
|
382
|
+
[*boundaries, z_separated_source_end].freeze
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
def resolved_z_part_ranges
|
|
387
|
+
@resolved_z_part_ranges ||= resolved_z_part_boundaries.each_cons(2).map do |start, finish|
|
|
388
|
+
start...finish
|
|
389
|
+
end.freeze
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
def resolved_z_color_ranges
|
|
393
|
+
color_start = layer_structure.color_region_start
|
|
394
|
+
color_end = layer_structure.color_region_end
|
|
395
|
+
resolved_z_part_ranges.map do |range|
|
|
396
|
+
[range.begin, color_start].max...[range.end, color_end].min
|
|
397
|
+
end.freeze
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
def resolved_z_part_count = color_region_parts
|
|
401
|
+
|
|
402
|
+
def z_separated_minimum_thickness
|
|
403
|
+
MINIMUM_Z_SOURCE_THICKNESS.round(9)
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
def z_separated_source_end
|
|
407
|
+
(layer_structure.base_white_thickness + z_separated_minimum_thickness).round(9)
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
def z_part_base_white_layers(index = 0)
|
|
411
|
+
return Z_PART_BASE_WHITE_LAYERS unless separate_z_parts
|
|
412
|
+
|
|
413
|
+
source_layers = (z_part_minimum_source_thickness(index) / layer_height).round
|
|
414
|
+
[Z_PART_BASE_WHITE_LAYERS, Z_PART_MINIMUM_TOTAL_LAYERS - source_layers].max
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
def z_part_base_white_thickness(index = 0)
|
|
418
|
+
layers = z_part_base_white_layers(index)
|
|
419
|
+
(first_layer_height + ((layers - 1) * layer_height)).round(9)
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
def z_part_minimum_build_thickness
|
|
423
|
+
resolved_z_part_ranges.each_index.map do |index|
|
|
424
|
+
(z_part_minimum_source_thickness(index) + z_part_base_white_thickness(index)).round(9)
|
|
425
|
+
end.min
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
def z_separated_output_minimum_thickness
|
|
429
|
+
without_source_base = effective_minimum_thickness - layer_structure.base_white_thickness
|
|
430
|
+
(without_source_base + z_part_base_white_thicknesses.sum).round(9)
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
def z_separated_output_maximum_thickness
|
|
434
|
+
without_source_base = effective_maximum_thickness - layer_structure.base_white_thickness
|
|
435
|
+
(without_source_base + z_part_base_white_thicknesses.sum).round(9)
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
def z_part_base_white_layers_by_plate
|
|
439
|
+
resolved_z_part_ranges.each_index.map { |index| z_part_base_white_layers(index) }.freeze
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
def z_part_base_white_thicknesses
|
|
443
|
+
resolved_z_part_ranges.each_index.map { |index| z_part_base_white_thickness(index) }.freeze
|
|
444
|
+
end
|
|
445
|
+
|
|
337
446
|
def estimated_triangles
|
|
338
447
|
(4 * columns * rows) + (4 * (columns + rows))
|
|
339
448
|
end
|
|
@@ -372,6 +481,15 @@ module Lithop
|
|
|
372
481
|
effective_color_pixel_size: effective_color_pixel_size,
|
|
373
482
|
effective_minimum_thickness: effective_minimum_thickness,
|
|
374
483
|
effective_maximum_thickness: effective_maximum_thickness,
|
|
484
|
+
minimum_z_part_thickness: MINIMUM_Z_PART_THICKNESS,
|
|
485
|
+
z_part_base_white_layers: Z_PART_BASE_WHITE_LAYERS,
|
|
486
|
+
z_part_base_white_thickness: z_part_base_white_thickness,
|
|
487
|
+
z_part_minimum_build_thickness: z_part_minimum_build_thickness,
|
|
488
|
+
resolved_z_part_count: resolved_z_part_count,
|
|
489
|
+
z_separated_minimum_thickness: z_separated_minimum_thickness,
|
|
490
|
+
z_separated_output_minimum_thickness: z_separated_output_minimum_thickness,
|
|
491
|
+
z_separated_output_maximum_thickness: z_separated_output_maximum_thickness,
|
|
492
|
+
z_separated_source_end: z_separated_source_end,
|
|
375
493
|
columns: columns, rows: rows, pixel_size_x: pixel_size_x, pixel_size_y: pixel_size_y,
|
|
376
494
|
resolved_tile_rows: resolved_tile_rows, resolved_tile_columns: resolved_tile_columns
|
|
377
495
|
)
|
|
@@ -379,6 +497,12 @@ module Lithop
|
|
|
379
497
|
|
|
380
498
|
private
|
|
381
499
|
|
|
500
|
+
def z_part_minimum_source_thickness(index)
|
|
501
|
+
range = resolved_z_part_ranges.fetch(index)
|
|
502
|
+
source_end = [range.end, effective_minimum_thickness].min
|
|
503
|
+
[source_end - range.begin, 0.0].max.round(9)
|
|
504
|
+
end
|
|
505
|
+
|
|
382
506
|
def automatic_tile_shape
|
|
383
507
|
factors = factor_shapes.map do |row_count, column_count|
|
|
384
508
|
tile_width = width.fdiv(column_count)
|
|
@@ -402,9 +526,17 @@ module Lithop
|
|
|
402
526
|
|
|
403
527
|
def validate_separated_top_white!
|
|
404
528
|
return unless separate_z_parts
|
|
405
|
-
return if
|
|
529
|
+
return if z_separated_source_end > layer_structure.color_region_end
|
|
406
530
|
|
|
407
531
|
raise ConfigurationError, "separate_z_parts requires a positive top White thickness"
|
|
408
532
|
end
|
|
533
|
+
|
|
534
|
+
def validate_z_separated_thickness_range!
|
|
535
|
+
return unless separate_z_parts
|
|
536
|
+
return if z_separated_source_end <= effective_maximum_thickness
|
|
537
|
+
|
|
538
|
+
raise ConfigurationError,
|
|
539
|
+
"Z separation requires maximum_thickness of at least #{z_separated_source_end} mm"
|
|
540
|
+
end
|
|
409
541
|
end
|
|
410
542
|
end
|
data/lib/lithop/settings_file.rb
CHANGED
|
@@ -39,6 +39,7 @@ module Lithop
|
|
|
39
39
|
values[:image_fit] = values[:image_fit].downcase if values[:image_fit].is_a?(String)
|
|
40
40
|
values[:layout_profile] = values[:layout_profile].downcase if values[:layout_profile].is_a?(String)
|
|
41
41
|
values[:tile_layout] = values[:tile_layout].downcase if values[:tile_layout].is_a?(String)
|
|
42
|
+
values[:yellow_mix_color] = values[:yellow_mix_color].downcase if values[:yellow_mix_color].is_a?(String)
|
|
42
43
|
values
|
|
43
44
|
end
|
|
44
45
|
private_class_method :normalize_values
|
data/lib/lithop/version.rb
CHANGED
data/lib/lithop/web/app.rb
CHANGED
|
@@ -155,7 +155,7 @@ module Lithop
|
|
|
155
155
|
content_type :json
|
|
156
156
|
JSON.generate(
|
|
157
157
|
tile_count: resolved.tile_count, tile_rows: layout.rows, tile_columns: layout.columns,
|
|
158
|
-
z_plate_count: resolved.separate_z_parts ? resolved.
|
|
158
|
+
z_plate_count: resolved.separate_z_parts ? resolved.resolved_z_part_count : 1
|
|
159
159
|
)
|
|
160
160
|
rescue Error, SystemCallError => e
|
|
161
161
|
halt 422, json_error(e.message)
|
|
@@ -360,10 +360,7 @@ module Lithop
|
|
|
360
360
|
def prepare_page(submitted: nil)
|
|
361
361
|
form = SettingsForm.new(project_dir: settings.project_dir)
|
|
362
362
|
@values = form.values
|
|
363
|
-
if submitted
|
|
364
|
-
SettingsForm::FIELD_NAMES.each { |key| @values[key] = submitted[key] if submitted.key?(key) }
|
|
365
|
-
@values["separate_z_parts"] = submitted["separate_z_parts"] == "1"
|
|
366
|
-
end
|
|
363
|
+
apply_submitted_values(submitted) if submitted
|
|
367
364
|
@jobs = settings.job_store.recent.map do |job|
|
|
368
365
|
job.merge(settings.job_store.summary(job.fetch("job_id")))
|
|
369
366
|
end
|
|
@@ -378,6 +375,13 @@ module Lithop
|
|
|
378
375
|
@csrf_token = settings.csrf_token
|
|
379
376
|
end
|
|
380
377
|
|
|
378
|
+
def apply_submitted_values(submitted)
|
|
379
|
+
SettingsForm::FIELD_NAMES.each { |key| @values[key] = submitted[key] if submitted.key?(key) }
|
|
380
|
+
SettingsForm::BOOLEAN_FIELDS.each do |key|
|
|
381
|
+
@values[key.to_s] = submitted[key.to_s] == "1"
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
|
|
381
385
|
def prepare_navigation
|
|
382
386
|
@selected_job_id = params["job"]
|
|
383
387
|
requested_view = params["view"].to_s
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
--yellow: #f4d800;
|
|
11
11
|
--green: #1f6d50;
|
|
12
12
|
--danger: #a83131;
|
|
13
|
+
--site-header-height: 116px;
|
|
13
14
|
font-family: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
14
15
|
}
|
|
15
16
|
|
|
@@ -31,7 +32,8 @@ button:focus-visible, input:focus-visible, select:focus-visible, summary:focus-v
|
|
|
31
32
|
outline-offset: 2px;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
.site-header { position: sticky; top: 0; z-index: 20;
|
|
35
|
+
.site-header { position: sticky; top: 0; z-index: 20; 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); }
|
|
36
|
+
.site-header-main { display: flex; align-items: center; gap: 14px; min-height: 64px; padding: 9px max(20px, calc((100vw - 1800px) / 2)); }
|
|
35
37
|
.brand-block { display: grid; gap: 1px; min-width: 128px; }
|
|
36
38
|
.brand { color: var(--ink); font: 500 1.8rem/.9 Georgia,"Times New Roman",serif; letter-spacing: -.045em; text-decoration: none; }
|
|
37
39
|
.eyebrow, .step { margin: 0 0 10px; color: var(--green); font-size: 0.72rem; font-weight: 800; letter-spacing: 0.17em; }
|
|
@@ -40,30 +42,32 @@ button:focus-visible, input:focus-visible, select:focus-visible, summary:focus-v
|
|
|
40
42
|
.header-context .project-name { overflow: hidden; max-width: 220px; text-overflow: ellipsis; white-space: nowrap; }
|
|
41
43
|
.header-context strong { color: var(--ink); }
|
|
42
44
|
.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
|
|
44
|
-
.header-action:hover
|
|
45
|
+
.header-action { border: 0; border-radius: 8px; padding: 8px 10px; color: var(--ink); background: transparent; cursor: pointer; font-size: .74rem; font-weight: 800; }
|
|
46
|
+
.header-action:hover { background: #e6e9e3; }
|
|
45
47
|
#global-job-link { display: flex; align-items: baseline; gap: 8px; }
|
|
46
48
|
#global-job-link small { color: var(--muted); font: .68rem ui-monospace,monospace; }
|
|
47
|
-
.menu-toggle { display: none; font-size: 1.1rem; }
|
|
48
49
|
.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
50
|
.context-badge { border-radius: 999px; padding: 7px 12px; color: var(--green); background: #e8f2ec; font-size: 0.72rem; font-weight: 800; }
|
|
50
51
|
|
|
51
52
|
.workspace { width: min(1800px, calc(100% - 40px)); margin-inline: auto; padding: 20px 0 80px; }
|
|
52
|
-
.app-shell {
|
|
53
|
+
.app-shell { min-width: 0; }
|
|
53
54
|
.app-content, .app-view { min-width: 0; }
|
|
54
55
|
.view-title { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
|
|
55
|
-
.primary-navigation {
|
|
56
|
-
.primary-navigation button { display:
|
|
57
|
-
.primary-navigation button span {
|
|
58
|
-
.primary-navigation button
|
|
56
|
+
.primary-navigation { display: flex; gap: 4px; overflow-x: auto; padding: 0 max(20px, calc((100vw - 1800px) / 2)); scrollbar-width: thin; }
|
|
57
|
+
.primary-navigation button { flex: none; display: flex; align-items: baseline; gap: 7px; border: 0; border-bottom: 3px solid transparent; border-radius: 7px 7px 0 0; padding: 10px 14px 9px; color: var(--ink); background: transparent; cursor: pointer; }
|
|
58
|
+
.primary-navigation button span { color: var(--muted); font: .66rem ui-monospace,monospace; }
|
|
59
|
+
.primary-navigation button strong { font-size: .8rem; }
|
|
60
|
+
.primary-navigation button small { color: var(--muted); font-size: .68rem; font-weight: 500; }
|
|
59
61
|
.primary-navigation button:hover { background: rgba(255,255,255,.62); }
|
|
60
|
-
.primary-navigation button[aria-current="page"] { border-color: var(--
|
|
62
|
+
.primary-navigation button[aria-current="page"] { border-bottom-color: var(--green); background: rgba(255,255,255,.72); }
|
|
61
63
|
.workspace-primary { display: grid; grid-template-columns: minmax(360px,400px) minmax(0,1fr); align-items: start; gap: 18px; }
|
|
62
64
|
.jobs-workspace { display: grid; grid-template-columns: minmax(300px,350px) minmax(0,1fr); grid-template-areas: "history detail"; align-items: start; gap: 18px; }
|
|
63
|
-
.
|
|
65
|
+
.presets-workspace { display: grid; grid-template-columns: minmax(240px,300px) minmax(0,880px); align-items: start; gap: 18px; }
|
|
66
|
+
.jobs-workspace .history-panel { grid-area: history; position: sticky; top: calc(var(--site-header-height) + 20px); max-height: calc(100vh - var(--site-header-height) - 40px); overflow: auto; }
|
|
67
|
+
.preset-list-panel { position: sticky; top: calc(var(--site-header-height) + 20px); }
|
|
64
68
|
.jobs-workspace .job-panel { grid-area: detail; }
|
|
65
69
|
.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:
|
|
70
|
+
.preview-panel { position: sticky; top: calc(var(--site-header-height) + 20px); }
|
|
67
71
|
.notice { padding: 13px 16px; border-radius: 12px; }
|
|
68
72
|
.notice.error { color: var(--danger); background: #fff0ed; border: 1px solid #efc0b8; }
|
|
69
73
|
.notice.info { color: #405049; background: #eef4f0; border: 1px solid #cbd9d0; }
|
|
@@ -80,7 +84,15 @@ button:focus-visible, input:focus-visible, select:focus-visible, summary:focus-v
|
|
|
80
84
|
.file-title { font-weight: 800; }
|
|
81
85
|
#file-name { color: var(--muted); font-size: 0.84rem; }
|
|
82
86
|
|
|
83
|
-
.
|
|
87
|
+
.conversion-actions { display: grid; gap: 9px; margin-top: 12px; border: 1px solid var(--line); border-radius: 14px; padding: 10px; background: rgba(255,253,248,.97); box-shadow: 0 10px 28px rgba(20,35,29,.11); }
|
|
88
|
+
.conversion-action-row { display: flex; align-items: center; gap: 14px; }
|
|
89
|
+
#draft-state { flex: 1; color: var(--muted); font-size: .76rem; line-height: 1.45; }
|
|
90
|
+
.conversion-actions .notice { margin: 0; }
|
|
91
|
+
.settings-navigation { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 14px; padding-bottom: 12px; border-bottom: 1px solid var(--line); }
|
|
92
|
+
.settings-navigation button { border: 1px solid var(--line); border-radius: 999px; padding: 7px 10px; color: var(--muted); background: white; cursor: pointer; font-size: .72rem; font-weight: 800; }
|
|
93
|
+
.settings-navigation button:hover { border-color: #aeb8b2; color: var(--ink); }
|
|
94
|
+
.settings-navigation button[aria-current="true"] { border-color: var(--green); color: var(--green); background: #edf7f0; }
|
|
95
|
+
.settings-section { scroll-margin-top: calc(var(--site-header-height) + 20px); margin-top: 12px; border: 1px solid var(--line); border-radius: 14px; background: rgba(255,255,255,0.5); }
|
|
84
96
|
.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
97
|
.settings-section > summary::-webkit-details-marker { display: none; }
|
|
86
98
|
.settings-section > summary::after { content: "+"; order: 3; color: var(--green); font-size: 1.2rem; }
|
|
@@ -109,16 +121,21 @@ input:invalid:not(:placeholder-shown) { border-color: var(--danger); }
|
|
|
109
121
|
.danger-text { color: var(--danger); }
|
|
110
122
|
.settings-library { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 10px 14px; padding-top: 14px; }
|
|
111
123
|
.settings-library > label { display: grid; gap: 5px; color: var(--muted); font-size: .76rem; }
|
|
124
|
+
.preset-select-label { display: grid; gap: 6px; color: var(--muted); font-size: .76rem; }
|
|
125
|
+
.preset-apply-button { width: 100%; margin-top: 10px; }
|
|
126
|
+
.draft-apply { display: grid; gap: 6px; margin-top: 18px; border-top: 1px solid var(--line); padding-top: 16px; }
|
|
127
|
+
.draft-apply small { color: var(--muted); font-size: .72rem; line-height: 1.45; }
|
|
128
|
+
.draft-apply .secondary-button { margin-top: 3px; }
|
|
112
129
|
.library-actions { grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; }
|
|
113
130
|
.draft-actions { padding-top: 10px; border-top: 1px solid var(--line); }
|
|
114
131
|
.settings-library .fine-print { grid-column: 1 / -1; margin: 0; }
|
|
115
|
-
.
|
|
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; }
|
|
132
|
+
.preset-intro { color: var(--muted); font-size: .74rem; line-height: 1.5; }
|
|
120
133
|
.preset-panel .settings-library { margin-top: 18px; border-top: 1px solid var(--line); }
|
|
121
|
-
.draft-summary {
|
|
134
|
+
.draft-summary { border-top: 1px solid var(--line); padding: 9px 3px 1px; }
|
|
135
|
+
.draft-summary > summary { cursor: pointer; list-style: none; }
|
|
136
|
+
.draft-summary > summary::-webkit-details-marker { display: none; }
|
|
137
|
+
.draft-summary > summary::after { content: "詳細を見る"; color: var(--green); font-size: .7rem; font-weight: 800; }
|
|
138
|
+
.draft-summary[open] > summary::after { content: "詳細を閉じる"; }
|
|
122
139
|
.summary-heading { display: flex; justify-content: space-between; gap: 16px; font-size: 0.8rem; }
|
|
123
140
|
.summary-heading span { color: var(--muted); }
|
|
124
141
|
.summary-heading span[data-changed="true"] { color: #925c00; font-weight: 800; }
|
|
@@ -126,8 +143,6 @@ input:invalid:not(:placeholder-shown) { border-color: var(--danger); }
|
|
|
126
143
|
.draft-summary dt { color: var(--muted); }
|
|
127
144
|
.draft-summary dd { overflow-wrap: anywhere; margin: 0; }
|
|
128
145
|
|
|
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
146
|
.primary-button, .secondary-button, .danger-button { border: 0; border-radius: 11px; padding: 12px 18px; font-weight: 800; cursor: pointer; }
|
|
132
147
|
.primary-button { color: white; background: var(--ink); }
|
|
133
148
|
.primary-button:hover { background: var(--green); }
|
|
@@ -218,9 +233,12 @@ button:disabled { opacity: 0.5; cursor: wait; }
|
|
|
218
233
|
|
|
219
234
|
.history-controls { display: grid; grid-template-columns: minmax(180px,2fr) repeat(4,minmax(130px,1fr)); gap: 10px; margin-bottom: 14px; }
|
|
220
235
|
.history-controls label { display: grid; gap: 4px; color: var(--muted); font-size: 0.72rem; }
|
|
221
|
-
.history-selection-
|
|
222
|
-
.history-selection-
|
|
223
|
-
.history-selection-
|
|
236
|
+
.history-selection-toolbar { display: grid; gap: 9px; margin-bottom: 14px; border: 1px solid var(--line); border-radius: 12px; padding: 10px; background: #f8f7f1; }
|
|
237
|
+
.history-selection-summary { display: flex; align-items: center; justify-content: space-between; gap: 10px; min-height: 30px; }
|
|
238
|
+
.history-selection-summary span { min-width: 0; color: var(--muted); font-size: .76rem; }
|
|
239
|
+
.history-selection-summary .text-button { flex: none; padding: 6px 4px; }
|
|
240
|
+
.history-selection-actions { display: grid; grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr); gap: 8px; }
|
|
241
|
+
.history-selection-actions button { width: 100%; min-height: 38px; padding: 8px; font-size: .72rem; }
|
|
224
242
|
.job-list { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: 10px; }
|
|
225
243
|
.job-list li { position: relative; display: grid; grid-template-columns: auto minmax(0,1fr); align-items: start; gap: 8px; min-width: 0; }
|
|
226
244
|
.job-list .history-select { width: 18px; height: 18px; margin-top: 13px; accent-color: var(--green); }
|
|
@@ -232,8 +250,6 @@ button:disabled { opacity: 0.5; cursor: wait; }
|
|
|
232
250
|
.job-list small, .empty-copy { color: var(--muted); }
|
|
233
251
|
.job-list .history-thumbnail { width: 100%; aspect-ratio: 140 / 104; object-fit: contain; border-radius: 6px; background: #f0efe9; }
|
|
234
252
|
.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
253
|
.history-delete { grid-column: 2; margin-top: 6px; border: 0; padding: 3px 1px; color: #8d2525; background: transparent; cursor: pointer; font-size: 0.72rem; }
|
|
238
254
|
.load-more { display: block; margin: 18px auto 0; }
|
|
239
255
|
.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); }
|
|
@@ -264,12 +280,6 @@ button:disabled { opacity: 0.5; cursor: wait; }
|
|
|
264
280
|
[hidden] { display: none !important; }
|
|
265
281
|
|
|
266
282
|
@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
283
|
.workspace-primary { grid-template-columns: minmax(340px,.8fr) minmax(0,1.2fr); }
|
|
274
284
|
.jobs-workspace { grid-template-columns: minmax(290px,340px) minmax(0,1fr); }
|
|
275
285
|
}
|
|
@@ -277,6 +287,8 @@ button:disabled { opacity: 0.5; cursor: wait; }
|
|
|
277
287
|
@media (max-width: 900px) {
|
|
278
288
|
.workspace-primary { grid-template-columns: 1fr; }
|
|
279
289
|
.preview-panel { position: static; }
|
|
290
|
+
.presets-workspace { grid-template-columns: 1fr; }
|
|
291
|
+
.preset-list-panel { position: static; }
|
|
280
292
|
.jobs-workspace { grid-template-columns: 1fr; grid-template-areas: "history" "detail"; }
|
|
281
293
|
.jobs-workspace .history-panel { position: static; max-height: none; }
|
|
282
294
|
.workspace.has-job[data-view="jobs"]:not(.show-job-list) .jobs-workspace .history-panel { display: none; }
|
|
@@ -289,7 +301,8 @@ button:disabled { opacity: 0.5; cursor: wait; }
|
|
|
289
301
|
}
|
|
290
302
|
|
|
291
303
|
@media (max-width: 680px) {
|
|
292
|
-
.site-header { gap: 8px; padding-inline: 11px; }
|
|
304
|
+
.site-header-main { gap: 8px; padding-inline: 11px; }
|
|
305
|
+
.primary-navigation { padding-inline: 11px; }
|
|
293
306
|
.brand-block { min-width: 80px; }
|
|
294
307
|
.brand-block .eyebrow, .header-context .project-name, .header-context > span[aria-hidden], #help-button { display: none; }
|
|
295
308
|
.brand { font-size: 1.5rem; }
|
|
@@ -305,11 +318,9 @@ button:disabled { opacity: 0.5; cursor: wait; }
|
|
|
305
318
|
.dialog-preview-grid .result-preview { max-height: calc((100vh - 250px) / 2); }
|
|
306
319
|
.draft-summary dl { grid-template-columns: 1fr; gap: 2px; }
|
|
307
320
|
.draft-summary dd { margin-bottom: 7px; }
|
|
308
|
-
.
|
|
309
|
-
.
|
|
321
|
+
.conversion-action-row { align-items: stretch; flex-direction: column; }
|
|
322
|
+
.conversion-action-row .primary-button { width: 100%; }
|
|
310
323
|
.settings-section summary small { display: none; }
|
|
311
|
-
.settings-shortcut { align-items: stretch; flex-direction: column; }
|
|
312
|
-
.settings-shortcut .secondary-button { width: 100%; }
|
|
313
324
|
.job-detail-tabs { overflow-x: auto; }
|
|
314
325
|
}
|
|
315
326
|
|