From 30c2c8a154e770b3920bcfd7d553825fe9182bf4 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 12:57:47 -0300 Subject: Move the frame into draw.zig and the transitions out of layout.zig Pure moves, no behaviour change. draw.zig holds the whole core frame in order: render, the pane, tag, header and notice painters it calls, and the character-effect composition (Pardes.render stays a declaration alias). Presentation.zig is the panel presentation state as a file struct, and animation.zig the easing curves, transition kinds, tracks and boxes, the character effects' sources and the generic displayed-value transition, all of which lived in layout.zig. layout.zig keeps only layout. --- src/animation.zig | 744 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 744 insertions(+) create mode 100644 src/animation.zig (limited to 'src/animation.zig') diff --git a/src/animation.zig b/src/animation.zig new file mode 100644 index 00000000..b190c1a1 --- /dev/null +++ b/src/animation.zig @@ -0,0 +1,744 @@ +//! Animation curves and the panel transition records every shell reads: +//! easing presets, transition kinds and their tracks, the fractional boxes +//! they move through, the character effects' per-cell sources, and the +//! generic displayed-value Transition the chrome colours use. +const std = @import("std"); + +pub const ascii_max_movement_frames: u16 = 12; + +pub const Easing = enum(u8) { + linear, + smooth, + /// Quintic ease-in-out. It creeps at both ends and crosses the middle of + /// the distance fast, inside the same frame count a linear walk would use. + smoother, + in_cubic, + out_cubic, + out_back, +}; + +pub const Transition = enum(u8) { + // Numeric values are shared with the GUI shader ABI. + off = 0, + slide = 1, + zoom = 2, + dissolve = 3, + ascii = 4, + vertical = 5, + edges = 6, + fall = 7, + wave = 8, + curtain = 9, + scramble = 10, + typewriter = 11, + + pub fn easing(effect: Transition) Easing { + return switch (effect) { + .off, .dissolve, .wave => .smooth, + .slide, .vertical, .edges => .out_cubic, + .zoom => .out_back, + // Character walks and per-cell locks read best with a slow start, + // a fast middle, and a slow settle over their fixed frame count. + .ascii, .fall, .scramble => .smoother, + // A sweep and a typewriter are constant-rate by definition: easing + // their head would make the pass visibly hesitate mid-pane. + .curtain, .typewriter => .linear, + }; + } + + pub fn frames(effect: Transition) u16 { + return switch (effect) { + .off => 0, + .slide => 12, + .zoom => 14, + .dissolve => 10, + .ascii => ascii_max_movement_frames + 1, + .vertical => 12, + .edges, .curtain, .scramble => 12, + // Travelling motion needs a couple more samples than a lock or a + // rigid slide before it stops reading as a jump. + .fall, .wave, .typewriter => 14, + }; + } + + pub fn composedByCore(effect: Transition) bool { + return switch (effect) { + .ascii, .edges, .fall, .wave, .curtain, .scramble, .typewriter => true, + .off, .slide, .zoom, .dissolve, .vertical => false, + }; + } + + pub fn needsPreviousGrid(effect: Transition) bool { + return effect == .dissolve or effect == .vertical or effect.composedByCore(); + } + + pub fn lifecycleOnly(effect: Transition) bool { + return effect == .vertical; + } +}; + +pub const SceneEffect = struct { + crt: bool = false, + ripple: bool = false, + glitch: bool = false, +}; + +pub const Phase = enum(u8) { + opening = 0, + moving = 1, + /// Presentation-only content whose pane lifetime has already ended. + /// It is never a valid input target. + closing = 2, +}; + +pub const Box = extern struct { + x: f32 = 0, + y: f32 = 0, + w: f32 = 0, + h: f32 = 0, + + pub fn eql(a: Box, b: Box) bool { + return a.x == b.x and a.y == b.y and a.w == b.w and a.h == b.h; + } + + // A cell belongs to a fractional box when its center lies inside. + pub fn contains(box: Box, col: u16, row: u16) bool { + const x: f32 = @floatFromInt(col); + const y: f32 = @floatFromInt(row); + return x >= box.x and x < box.x + box.w and y >= box.y and y < box.y + box.h; + } +}; + +// Drawing and hit testing share this moving/opening/closing order. +pub fn paintOrder(live: []const ?Track, closing: []const Track, out: []Track) usize { + var len: usize = 0; + for ([_]Phase{ .moving, .opening }) |phase| for (live) |maybe| { + const track = maybe orelse continue; + if (!track.active() or track.phase != phase) continue; + if (len == out.len) return len; + out[len] = track; + len += 1; + }; + for (closing) |track| { + if (!track.active()) continue; + if (len == out.len) return len; + out[len] = track; + len += 1; + } + return len; +} + +/// One POD record is enough for every backend. `from` and `to` are logical +/// cell boxes; frontends convert them to pixels only at their render edge. +pub const Track = extern struct { + serial: u32 = 0, + pane: u8 = 0, + phase: Phase = .moving, + effect: Transition = .off, + _padding: u8 = 0, + frame: u16 = 0, + frame_count: u16 = 0, + from: Box = .{}, + to: Box = .{}, + + pub fn active(track: Track) bool { + return track.effect != .off and track.frame < track.frames(); + } + + pub fn frames(track: Track) u16 { + return if (track.frame_count != 0) track.frame_count else track.effect.frames(); + } + + pub fn amount(track: Track) f32 { + // Opening rises quickly and settles; closing reverses that motion and + // accelerates down out of the fixed clip. + if (track.phase == .closing and track.effect == .vertical) + return progressEased(.in_cubic, track.frames(), track.frame); + return progressEased(track.effect.easing(), track.frames(), track.frame); + } + + pub fn presented(track: Track) Box { + return lerpBox(track.from, track.to, track.amount()); + } + + pub fn visualBox(track: Track) Box { + return switch (track.effect) { + .slide, .zoom, .vertical => track.presented(), + .off, .dissolve => track.to, + // Every character effect stays inside the pane's final rectangle. + .ascii, .edges, .fall, .wave, .curtain, .scramble, .typewriter => track.to, + }; + } + + pub fn contentBox(track: Track) Box { + return if (track.phase == .closing) track.from else track.to; + } +}; + +pub fn openingBox(effect: Transition, target: Box, screen_width: u16) Box { + return switch (effect) { + .slide => blk: { + var from = target; + const middle = target.x + target.w * 0.5; + from.x = if (middle < @as(f32, @floatFromInt(screen_width)) * 0.5) + -target.w + else + @floatFromInt(screen_width); + break :blk from; + }, + .zoom => .{ + .x = target.x + target.w * 0.5, + .y = target.y + target.h * 0.5, + .w = 0, + .h = 0, + }, + .vertical => blk: { + var from = target; + from.y += target.h; + break :blk from; + }, + .off, .dissolve => target, + // Character effects own the glyphs inside a fixed rectangle, so their + // panel opens at exactly its final geometry. + .ascii, .edges, .fall, .wave, .curtain, .scramble, .typewriter => target, + }; +} + +pub fn closingBox(effect: Transition, source: Box) Box { + return switch (effect) { + .vertical => blk: { + var to = source; + to.y += source.h; + break :blk to; + }, + else => source, + }; +} + +pub fn sample(easing: Easing, raw: f32) f32 { + const t = std.math.clamp(raw, 0.0, 1.0); + return switch (easing) { + .linear => t, + .smooth => t * t * (3.0 - 2.0 * t), + .smoother => t * t * t * (t * (6.0 * t - 15.0) + 10.0), + .in_cubic => t * t * t, + .out_cubic => 1.0 - (1.0 - t) * (1.0 - t) * (1.0 - t), + // Robert Penner's ease-out-back polynomial. It intentionally travels + // a little past one before settling exactly on the endpoint. + .out_back => blk: { + const c1: f32 = 1.70158; + const c3 = c1 + 1.0; + const u = t - 1.0; + break :blk 1.0 + c3 * u * u * u + c1 * u * u; + }, + }; +} + +pub fn progress(effect: Transition, frame: u16) f32 { + return progressEased(effect.easing(), effect.frames(), frame); +} + +fn progressEased(easing: Easing, frames: u16, frame: u16) f32 { + if (frames <= 1 or frame >= frames - 1) return 1.0; + return sample(easing, @as(f32, @floatFromInt(frame)) / @as(f32, @floatFromInt(frames - 1))); +} + +pub fn lerpBox(from: Box, to: Box, t: f32) Box { + const u = @max(0.0, t); + return .{ + .x = from.x + (to.x - from.x) * u, + .y = from.y + (to.y - from.y) * u, + .w = @max(0.0, from.w + (to.w - from.w) * u), + .h = @max(0.0, from.h + (to.h - from.h) * u), + }; +} + +/// Stable cell noise shared by the TTY reveal and shader ports. Integer-only +/// hashing means resizing or repainting a frame does not make cells flicker. +pub fn cellNoise(serial: u32, col: u16, row: u16) f32 { + var x = serial ^ (@as(u32, col) *% 0x9e37_79b9) ^ (@as(u32, row) *% 0x85eb_ca6b); + x ^= x >> 16; + x *%= 0x7feb_352d; + x ^= x >> 15; + x *%= 0x846c_a68b; + x ^= x >> 16; + return @as(f32, @floatFromInt(x & 0xffff)) / 65535.0; +} + +/// Whether a changed dissolve cell has crossed from the frozen old grid to +/// the new one. Exact endpoints are part of the presentation contract. +pub fn dissolveRevealed(serial: u32, col: u16, row: u16, raw_progress: f32) bool { + const t = std.math.clamp(raw_progress, 0.0, 1.0); + if (t <= 0) return false; + if (t >= 1) return true; + return cellNoise(serial, col, row) < t; +} + +pub const CellArea = struct { + x0: u16 = 0, + y0: u16 = 0, + cols: u16 = 1, + rows: u16 = 1, + + pub fn of(box: Box) CellArea { + return .{ + .x0 = floorCell(box.x), + .y0 = floorCell(box.y), + .cols = ceilCell(box.w), + .rows = ceilCell(box.h), + }; + } +}; + +fn floorCell(value: f32) u16 { + return @intFromFloat(std.math.clamp(@floor(value), 0.0, @as(f32, std.math.maxInt(u16)))); +} + +fn ceilCell(value: f32) u16 { + return @intFromFloat(std.math.clamp(@ceil(value), 1.0, @as(f32, std.math.maxInt(u16)))); +} + +pub const CharSource = union(enum) { + /// Nothing has arrived here yet: keep the frozen old cell. + old, + at: Offset, + /// Paint this printable byte in the destination cell's own style, whatever + /// that cell holds — a caret marching over empty space is still a caret. + byte: u8, + churn: u8, + + pub const Offset = struct { cols: i32 = 0, rows: i32 = 0 }; + + pub const settled: CharSource = .{ .at = .{} }; +}; + +pub fn charSource(track: Track, col: u16, row: u16, area: CellArea) CharSource { + const t = track.amount(); + if (t >= 1.0) return .settled; + const w: f32 = @floatFromInt(area.cols); + const h: f32 = @floatFromInt(area.rows); + const c: f32 = @floatFromInt(col); + const r: f32 = @floatFromInt(row); + const remaining = 1.0 - t; + return switch (track.effect) { + .edges => blk: { + const travel = cellsOf(remaining * (w + 1.0)); + break :blk .{ .at = .{ .cols = if (row % 2 == 0) travel else -travel } }; + }, + // Columns rain down, each with its own stable head start, so the pane + // fills from the top and the last glyphs land at the bottom. + .fall => blk: { + const local = staggered(t, cellNoise(track.serial, col, 0) * 0.4); + if (local <= 0.0) break :blk .old; + break :blk .{ .at = .{ .rows = cellsOf((1.0 - local) * (h + 1.0)) } }; + }, + // A vertical ripple travels left to right and its amplitude decays, so + // the pane settles out of a wave instead of a fade. + .wave => .{ .at = .{ + .rows = cellsOf(remaining * @min(4.0, h) * @sin(c * 0.55 - t * 9.0)), + } }, + // A curtain of glyphs marches in from the right, column by column, left + // to right; each column still has a short slide of its own. + .curtain => blk: { + const lead = t * (w + 1.0) - c; + if (lead <= 0.0) break :blk .old; + break :blk .{ .at = .{ .cols = -cellsOf(@max(0.0, 3.0 - lead)) } }; + }, + // Every cell churns through printable ASCII and locks onto its final + // glyph at its own stable threshold: the pane resolves out of noise. + .scramble => blk: { + if (t >= cellNoise(track.serial, col, row) * 0.8) break :blk .settled; + const churn = cellNoise( + track.serial ^ (@as(u32, track.frame) *% 0x27d4_eb2f), + col, + row, + ); + break :blk .{ .churn = @intCast(33 + @min(93, @as(u32, @intFromFloat(churn * 94.0)))) }; + }, + // Reading-order reveal with a caret sitting on the write head. + .typewriter => blk: { + const head = t * w * h; + const index = r * w + c; + if (index + 1.0 <= head) break :blk .settled; + if (index <= head) break :blk .{ .byte = '_' }; + break :blk .old; + }, + // PanelAscii walks its own byte distance per cell, and the geometry + // effects never reach this path at all. + .off, .slide, .zoom, .dissolve, .vertical, .ascii => .settled, + }; +} + +fn cellsOf(distance: f32) i32 { + return @intFromFloat(@round(std.math.clamp(distance, -65535.0, 65535.0))); +} + +/// Remap track progress into one cell's own window. A stagger delays a glyph +/// without making the effect as a whole end after its last frame. +fn staggered(t: f32, delay: f32) f32 { + if (delay >= 1.0) return t; + return (t - delay) / (1.0 - delay); +} + +test "easing presets have exact endpoints and intended shapes" { + inline for (std.enums.values(Easing)) |easing| { + try std.testing.expectEqual(@as(f32, 0), sample(easing, 0)); + try std.testing.expectEqual(@as(f32, 1), sample(easing, 1)); + } + try std.testing.expectEqual(@as(f32, 0.5), sample(.linear, 0.5)); + try std.testing.expect(sample(.in_cubic, 0.5) < sample(.linear, 0.5)); + try std.testing.expect(sample(.out_cubic, 0.5) > sample(.linear, 0.5)); + try std.testing.expect(sample(.out_back, 0.8) > 1.0); + // Slow at both ends, fast through the middle, and symmetric about the + // halfway point: the same curve the integer byte walk reproduces. + try std.testing.expectEqual(@as(f32, 0.5), sample(.smoother, 0.5)); + try std.testing.expect(sample(.smoother, 0.15) < sample(.smooth, 0.15)); + try std.testing.expect(sample(.smoother, 0.85) > sample(.smooth, 0.85)); + try std.testing.expect(sample(.smoother, 0.6) - sample(.smoother, 0.4) > + sample(.linear, 0.6) - sample(.linear, 0.4)); +} + +test "transition progress completes exactly" { + inline for (std.enums.values(Transition)) |effect| { + try std.testing.expectEqual(@as(f32, 1), progress(effect, effect.frames())); + if (effect.frames() > 0) + try std.testing.expectEqual(@as(f32, 1), progress(effect, effect.frames() - 1)); + try std.testing.expectEqual(@as(f32, 1), progress(effect, std.math.maxInt(u16))); + } + try std.testing.expectEqual(@as(f32, 1), progress(.off, 0)); + + const shrinking = lerpBox(.{ .w = 100, .h = 40 }, .{}, sample(.out_back, 0.8)); + try std.testing.expectEqual(@as(f32, 0), shrinking.w); + try std.testing.expectEqual(@as(f32, 0), shrinking.h); + const opening = lerpBox(.{}, .{ .w = 100, .h = 40 }, sample(.out_back, 0.8)); + try std.testing.expect(opening.w > 100); + try std.testing.expect(opening.h > 40); +} + +test "character effects are core-composed and settle on the canonical glyph" { + const box: Box = .{ .x = 4, .y = 2, .w = 20, .h = 6 }; + const area: CellArea = .of(box); + try std.testing.expectEqual(@as(u16, 4), area.x0); + try std.testing.expectEqual(@as(u16, 2), area.y0); + try std.testing.expectEqual(@as(u16, 20), area.cols); + try std.testing.expectEqual(@as(u16, 6), area.rows); + + inline for (std.enums.values(Transition)) |effect| { + if (comptime !effect.composedByCore()) continue; + // Core composition needs the frozen old grid for every glyph which has + // not arrived, so no character effect may animate without it. + try std.testing.expect(effect.needsPreviousGrid()); + if (comptime effect == .ascii) continue; // owns its own per-cell byte walk + + const last: Track = .{ .effect = effect, .frame = effect.frames() - 1, .to = box }; + const first: Track = .{ .effect = effect, .frame = 0, .to = box }; + var moving = false; + var row: u16 = 0; + while (row < area.rows) : (row += 1) { + var col: u16 = 0; + while (col < area.cols) : (col += 1) { + // The last active sample is the exact canonical grid: no cell + // is displaced, churning, or still frozen. + try std.testing.expectEqual(CharSource.settled, charSource(last, col, row, area)); + if (!std.meta.eql(CharSource.settled, charSource(first, col, row, area))) + moving = true; + } + } + try std.testing.expect(moving); + } +} + +test "each character effect moves glyphs along its own axis" { + const box: Box = .{ .w = 30, .h = 8 }; + const area: CellArea = .of(box); + + // Rows alternate which screen edge they come from, and every glyph in a row + // travels as one rigid slide: one offset, no vertical component. + var edges: Track = .{ .effect = .edges, .frame = 2, .to = box }; + const even = charSource(edges, 5, 0, area).at; + const odd = charSource(edges, 5, 1, area).at; + try std.testing.expect(even.cols > 0); + try std.testing.expectEqual(-even.cols, odd.cols); + try std.testing.expectEqual(@as(i32, 0), even.rows); + try std.testing.expectEqual(even, charSource(edges, 17, 0, area).at); + edges.frame = 5; + try std.testing.expect(charSource(edges, 5, 0, area).at.cols < even.cols); + + // Falling columns are vertical only, staggered, and sample from below the + // destination because the new text is still above the pane. + const fall: Track = .{ .effect = .fall, .frame = 4, .to = box }; + var falling = false; + var col: u16 = 0; + while (col < area.cols) : (col += 1) switch (charSource(fall, col, 0, area)) { + .old => {}, + .byte, .churn => return error.FallShouldNotChurn, + .at => |offset| { + try std.testing.expectEqual(@as(i32, 0), offset.cols); + try std.testing.expect(offset.rows >= 0); + if (offset.rows > 0) falling = true; + }, + }; + try std.testing.expect(falling); + + // The wave displaces rows both ways as it travels, and only rows. + const wave: Track = .{ .effect = .wave, .frame = 1, .to = box }; + var above = false; + var below = false; + col = 0; + while (col < area.cols) : (col += 1) { + const offset = charSource(wave, col, 3, area).at; + try std.testing.expectEqual(@as(i32, 0), offset.cols); + if (offset.rows < 0) above = true; + if (offset.rows > 0) below = true; + } + try std.testing.expect(above and below); + + // The curtain has a head: columns behind it hold the old grid, columns the + // head has passed are settled, and the head itself is still sliding. + const curtain: Track = .{ .effect = .curtain, .frame = 5, .to = box }; + try std.testing.expectEqual(CharSource.settled, charSource(curtain, 0, 0, area)); + try std.testing.expectEqual(CharSource{ .old = {} }, charSource(curtain, 29, 0, area)); + var sliding = false; + col = 0; + while (col < area.cols) : (col += 1) switch (charSource(curtain, col, 0, area)) { + .at => |offset| if (offset.cols < 0) { + sliding = true; + }, + .old, .byte, .churn => {}, + }; + try std.testing.expect(sliding); + + var scramble: Track = .{ .effect = .scramble, .frame = 3, .to = box }; + var churning: usize = 0; + var locked: usize = 0; + var changed = false; + col = 0; + while (col < area.cols) : (col += 1) switch (charSource(scramble, col, 0, area)) { + .churn => |byte| { + try std.testing.expect(byte >= ' ' and byte <= '~'); + churning += 1; + scramble.frame = 4; + switch (charSource(scramble, col, 0, area)) { + .churn => |next| changed = changed or next != byte, + .old, .at, .byte => {}, + } + scramble.frame = 3; + }, + .at => locked += 1, + .old, .byte => return error.ScrambleShouldNotFreeze, + }; + try std.testing.expect(churning > 0 and locked > 0 and changed); + + // The typewriter writes in reading order with a caret on its head. + const typewriter: Track = .{ .effect = .typewriter, .frame = 7, .to = box }; + try std.testing.expectEqual(CharSource.settled, charSource(typewriter, 0, 0, area)); + try std.testing.expectEqual( + CharSource{ .old = {} }, + charSource(typewriter, area.cols - 1, area.rows - 1, area), + ); + var carets: usize = 0; + var row: u16 = 0; + while (row < area.rows) : (row += 1) { + col = 0; + while (col < area.cols) : (col += 1) switch (charSource(typewriter, col, row, area)) { + .byte => |byte| { + try std.testing.expectEqual(@as(u8, '_'), byte); + carets += 1; + }, + .old, .at, .churn => {}, + }; + } + try std.testing.expectEqual(@as(usize, 1), carets); +} + +test "opening presets separate geometry and content transitions" { + const target: Box = .{ .x = 30, .y = 2, .w = 20, .h = 8 }; + try std.testing.expectEqual(target, openingBox(.ascii, target, 80)); + try std.testing.expectEqual(@as(f32, 0), openingBox(.zoom, target, 80).w); + try std.testing.expectEqual(@as(f32, 80), openingBox(.slide, target, 80).x); + try std.testing.expectEqual(@as(f32, target.y + target.h), openingBox(.vertical, target, 80).y); + try std.testing.expectEqual(@as(f32, target.y + target.h), closingBox(.vertical, target).y); + + var track: Track = .{ .effect = .slide, .from = target, .to = target }; + try std.testing.expect(track.active()); + track.frame = track.effect.frames(); + try std.testing.expect(!track.active()); + + track = .{ .effect = .dissolve, .frame = 3, .from = .{}, .to = target }; + try std.testing.expectEqual(target, track.visualBox()); + + var closing: Track = .{ + .phase = .closing, + .effect = .vertical, + .from = target, + .to = closingBox(.vertical, target), + }; + try std.testing.expectEqual(target, closing.contentBox()); + closing.frame = 2; + try std.testing.expect(closing.amount() < progress(.vertical, closing.frame)); +} + +test "dissolve has exact stable endpoints" { + for (0..64) |col| { + const x: u16 = @intCast(col); + try std.testing.expect(!dissolveRevealed(42, x, 7, 0)); + try std.testing.expect(dissolveRevealed(42, x, 7, 1)); + if (dissolveRevealed(42, x, 7, 0.25)) + try std.testing.expect(dissolveRevealed(42, x, 7, 0.75)); + } +} + +pub const Animation = struct { + pub const frame_ms: u32 = 16; + pub const frame_ns: u64 = frame_ms * std.time.ns_per_ms; + pub const transition_steps: u16 = 10; + + pub fn Transition(comptime Value: type) type { + return struct { + const Self = @This(); + + from: Value, + to: Value, + displayed: Value, + step: u16 = transition_steps, + + pub fn init(value: Value) Self { + return .{ .from = value, .to = value, .displayed = value }; + } + + pub fn isActive(a: *const Self) bool { + return a.step < transition_steps; + } + + pub fn retarget(a: *Self, target: Value) void { + a.from = a.displayed; + a.to = target; + a.step = if (std.meta.eql(a.from, target)) transition_steps else 0; + if (a.step == transition_steps) a.displayed = target; + } + + pub fn advance(a: *Self) void { + if (!a.isActive()) return; + a.step += 1; + a.displayed = if (a.step == transition_steps) + a.to + else + Value.interpolate(a.from, a.to, a.step, transition_steps); + } + + pub fn snap(a: *Self, value: Value) void { + a.* = init(value); + } + }; + } + + pub fn Immediate(comptime Value: type) type { + return struct { + const Self = @This(); + + displayed: Value, + + pub fn init(value: Value) Self { + return .{ .displayed = value }; + } + + pub fn isActive(_: *const Self) bool { + return false; + } + + pub fn retarget(a: *Self, target: Value) void { + a.displayed = target; + } + + pub fn advance(_: *Self) void {} + + pub fn snap(a: *Self, value: Value) void { + a.displayed = value; + } + }; + } + + pub fn interpolateRgb(from: [3]u8, to: [3]u8, step: u16, steps: u16) [3]u8 { + if (step == 0) return from; + if (step >= steps) return to; + var out: [3]u8 = undefined; + for (&out, from, to) |*dst, a, b| { + const numerator = @as(u32, a) * (steps - step) + @as(u32, b) * step; + dst.* = @intCast((numerator + steps / 2) / steps); + } + return out; + } + + const TestColor = struct { + rgb: [3]u8, + + pub fn interpolate(from: TestColor, to: TestColor, step: u16, steps: u16) TestColor { + return .{ .rgb = interpolateRgb(from.rgb, to.rgb, step, steps) }; + } + }; + + test "Immediate lands where a completed Transition lands" { + const from: TestColor = .{ .rgb = .{ 240, 3, 90 } }; + const to: TestColor = .{ .rgb = .{ 5, 222, 90 } }; + + var faded = Animation.Transition(TestColor).init(from); + faded.retarget(to); + for (0..transition_steps) |_| faded.advance(); + + var instant = Immediate(TestColor).init(from); + try std.testing.expect(!instant.isActive()); + instant.retarget(to); + try std.testing.expectEqual(faded.displayed, instant.displayed); + + // Never active, so a frontend that renders only while something is animating stops immediately + // rather than spending ten frames discovering there is nothing to draw. + try std.testing.expect(!instant.isActive()); + instant.advance(); + try std.testing.expectEqual(to, instant.displayed); + + instant.snap(from); + try std.testing.expectEqual(from, instant.displayed); + } + + test "fixed-step interpolation has exact monotonic endpoints" { + const Tween = Animation.Transition(TestColor); + const from: TestColor = .{ .rgb = .{ 240, 3, 90 } }; + const to: TestColor = .{ .rgb = .{ 5, 222, 90 } }; + var tween = Tween.init(from); + tween.retarget(to); + try std.testing.expectEqual(from, tween.displayed); + + var previous = tween.displayed; + for (0..transition_steps) |_| { + tween.advance(); + try std.testing.expect(tween.displayed.rgb[0] <= previous.rgb[0]); + try std.testing.expect(tween.displayed.rgb[1] >= previous.rgb[1]); + try std.testing.expectEqual(@as(u8, 90), tween.displayed.rgb[2]); + previous = tween.displayed; + } + try std.testing.expect(!tween.isActive()); + try std.testing.expectEqual(to, tween.displayed); + tween.advance(); + try std.testing.expectEqual(to, tween.displayed); + } + + test "retarget starts at the currently displayed value" { + const Tween = Animation.Transition(TestColor); + const first: TestColor = .{ .rgb = .{ 0, 40, 200 } }; + const second: TestColor = .{ .rgb = .{ 200, 140, 0 } }; + const third: TestColor = .{ .rgb = .{ 20, 10, 250 } }; + var tween = Tween.init(first); + tween.retarget(second); + tween.advance(); + tween.advance(); + tween.advance(); + const on_screen = tween.displayed; + + tween.retarget(third); + try std.testing.expectEqual(on_screen, tween.from); + try std.testing.expectEqual(on_screen, tween.displayed); + try std.testing.expect(tween.isActive()); + for (0..transition_steps) |_| tween.advance(); + try std.testing.expectEqual(third, tween.displayed); + } +}; -- cgit v1.3 From e0b5564f04aa0ae6666f29ef29c768d800fbabce Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 13:15:41 -0300 Subject: Step core animation by the shell's clock and sleep to the next wake A shell now answers Host.now, its monotonic clock in ns, and the pump advances core animation to it: one .tick per whole 16 ms frame since core time last stood still. The same animation therefore takes the same time at 60, 120 and 144 Hz, over ssh and after a slow frame (the GUI's re-armed clock ran them ~25% slow at 144 Hz; the tty's post-frame sleep drifted). nextWake() lists every core animation in one place: a frame from now while anything moves, the end of the wait while something only waits (a message lingering, the look-hover delay), null when idle. Shells sleep exactly that long, and a wait is jumped to its end in one step, so an 800 ms linger costs one frame instead of fifty. An overshoot under 1.5 ms after a step is let go: at 60 and 120 Hz every display frame takes exactly one step (a 16 ms frame against a 16.67 ms vsync would otherwise double-step every 24th). The six tick drivers are gone: tty's timer thread only times the wait, interruptibly (a newer, shorter request cuts short a sleep still timing a longer one); the GUI's AnimationClock, web's JS tick bank (pardes_tick now takes rAF's timestamp), the detached server's and the board's ticks; grid mode steps a virtual clock straight to each wake. PARDES_TEST_CLOCK, set by the snapshot harness, gives tty and the detached server the same virtual clock. Every stepped frame is drawn. The old pump never drew the last frame of a fade (a .tick asks for no frame, and the fade was over by the check), so a theme switch stopped at 9/10 of the way until the next input: theme.golden, themesel.golden and the GUI's acme-light scene move to the theme's true colours, and nothing else changes. The frozen previous grid is captured only while a panel transition is chosen, not on every idle frame. --- docs/render-pipeline.md | 2 +- src/Messages.zig | 81 ++- src/Presentation.zig | 13 +- src/animation.zig | 261 +++++----- src/colors.zig | 8 +- src/detached/server.zig | 21 +- src/esp32p4.zig | 15 +- src/exec.zig | 4 + src/gui/gui.zig | 86 +--- src/host_io.zig | 17 + src/macos.zig | 8 +- src/pardes.zig | 151 +++++- src/tty/tty.zig | 108 +++- src/web.zig | 36 +- src/web/app.mjs | 36 +- test/gui-goldens.txt | 6 +- test/gui_golden.py | 4 +- test/pdf.zig | 2 +- test/snapshot.zig | 3 + test/snapshots/theme.golden | 1066 ++++++++++++++++++++-------------------- test/snapshots/themesel.golden | 945 ++++++++++++++++++----------------- test/web_harness.mjs | 16 +- 22 files changed, 1557 insertions(+), 1332 deletions(-) (limited to 'src/animation.zig') diff --git a/docs/render-pipeline.md b/docs/render-pipeline.md index 2a679b8d..590a7e72 100644 --- a/docs/render-pipeline.md +++ b/docs/render-pipeline.md @@ -803,7 +803,7 @@ stripped. No visual change until stage 9 unless stated. |---|---|---| | 0 | GUI capture goldens: a handful of scenes rendered hidden in capture mode, PPM hashes checked in (same machine/driver) | the safety net the GUI stages need; machine- and driver-specific, so a LOCAL gate only, never CI | | 1 | pure moves: render → `src/draw.zig`; Presentation/animation out of layout.zig | no behaviour | -| 2a | clock plumbing: `now_ns` into pump/events, `nextWake`, `advance`, deadlines for linger/hover; all shells pass now and sleep to the wake; six tick drivers deleted; ack moves into pump; capturePrevious only when transitions are on. Animations still compute `frame = floor(elapsed / 16 ms)`, so every curve is identical | pacing is corrected (144 Hz no longer ~25% slow, ssh no longer drifts): a bug fix allowed in this phase. Unit tests step an injected clock by exactly 16 ms, so their numbers do not change. The snapshot harness runs the binary with `--test-clock` (each core-time step is exactly 16.67 ms per tick request, as today), so goldens see the same sequence and `stable` (test/snapshot.zig ~861) is unaffected by wall-time pacing | +| 2a | clock plumbing: `Host.now` (the shell's monotonic ns) and `Pardes.advance(now)`, which runs one `.tick` per whole 16 ms frame since core time last stood still; `nextWake()` lists every core animation and answers the next frame while anything moves, the wait's end while something only waits (message linger, look-hover delay), null when idle; the pump sleeps to it and draws only stepped frames. No shell posts `.tick` any more (tty's timer thread only wakes the wait; GUI's AnimationClock, web's JS tick bank and the detached server's and the board's ticks are gone; grid mode moves its virtual clock straight to the next wake). `acknowledgePanelPresentation` stays in the shells (revisit at stage 8: grid mode acks `&.{}` on purpose). capturePrevious only when transitions are on. `.tick` stays as the one-frame step `advance` and the unit tests use, so every curve and every asserted number is unchanged | pacing is corrected (144 Hz no longer ~25% slow, ssh no longer drifts): a bug fix allowed in this phase. PARDES_TEST_CLOCK (set by the snapshot harness): tty and the detached server answer a virtual clock that a timed-out wait moves exactly to the core's next wake, so goldens replay the same frame sequence on any machine load. Review fixes: tty's wake timer is interruptible (a newer, shorter request cuts short an older sleep); a wait (linger, hover delay) is jumped to its end in one step, never counted against the 240-frame catch-up cap; an overshoot under 1.5 ms after a step is let go, so 60 and 120 Hz take exactly one step per display frame. Known exceptions: the GUI still polls every 16 ms when idle (SDL events, gamepad, fs tick, smooth scroll depend on it; revisit at stage 8); a minimized GUI renders every stepped frame of a running animation (stage 8) | | 2b | (on `fx`, feel-reviewed) Tween/Spring curves, settle rules, OKLab crossfades, new durations from §8.1 | a look change, kept apart from 2a so either can be bisected | | 3 | paint functions take `s: *Surface`; delete the swap hack | mechanical, wide | | 4 | PLACE: Region list built once; renderPane/tags/notices read their rects from it | wait for tag→Text; the refactor is already replacing BOX_H with `pane.tag_rows` and `p.tagTop/bodyTop(pane, r)` — PLACE absorbs those into the region rects | diff --git a/src/Messages.zig b/src/Messages.zig index 97a2f939..7334de95 100644 --- a/src/Messages.zig +++ b/src/Messages.zig @@ -272,11 +272,35 @@ fn advanceLine(p: *const Pardes, life: *Pane.MessageLife) bool { return false; } -/// Is any message line moving (and so owed ticks)? -pub fn messagesAnimating(pane: *const Pane) bool { - if (pane.msg_len > 0 and pane.msg_life.phase != .shown) return true; - for (pane.msg_older[0..pane.msg_older_len]) |line| if (line.life.phase != .shown) return true; - return false; +/// Move every lingering message line `frames` along its linger at once: +/// how `advance` jumps a wait instead of stepping it frame by frame. +pub fn holdMessages(p: *Pardes, frames: u64) void { + const n: u16 = @intCast(@min(frames, std.math.maxInt(u16))); + for (p.panes) |slot| { + const pane = slot orelse continue; + if (pane.msg_len > 0 and pane.msg_life.phase == .lingering) pane.msg_life.frame +|= n; + for (pane.msg_older[0..pane.msg_older_len]) |*line| { + if (line.life.phase == .lingering) line.life.frame +|= n; + } + } +} + +/// Frames until this pane's message lines next change: 1 while a line +/// falls in or dissolves, the rest of its linger while one only waits, null +/// when every line just shows. +pub fn messageWake(p: *const Pardes, pane: *const Pane) ?u64 { + var soonest: ?u64 = null; + const linger = messageFrames(p.settings.message_linger_ms); + for (0..@as(usize, pane.msg_older_len) + 1) |i| { + const life = if (i < pane.msg_older_len) pane.msg_older[i].life else if (pane.msg_len > 0) pane.msg_life else continue; + const frames: u64 = switch (life.phase) { + .shown => continue, + .entering, .leaving => 1, + .lingering => @max(1, linger -| life.frame), + }; + soonest = if (soonest) |n| @min(n, frames) else frames; + } + return soonest; } /// How present a message is (1 = fully) and how far above its row it @@ -286,7 +310,7 @@ pub const MessageMotion = struct { alpha: f32 = 1, slide: f32 = 0 }; /// Animation frames a message timing lasts; at least one. pub fn messageFrames(ms: u16) u16 { - return @intCast(@max(1, (@as(u32, ms) + animation.Animation.frame_ms - 1) / animation.Animation.frame_ms)); + return @intCast(@max(1, (@as(u32, ms) + animation.frame_ms - 1) / animation.frame_ms)); } pub fn messageMotion(p: *const Pardes, life: Pane.MessageLife) MessageMotion { @@ -792,3 +816,48 @@ test "notices stack over the body's top rows without moving them" { for (p.surface.tagLayers()) |*layer| try std.testing.expect(layer.cols == 0 or layer.kind != .notice); } + +test "an idle core wants no wake and a lingering message sleeps to its linger's end" { + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 80, .rows = 24 }); + defer p.deinit(); + while (p.nextEffect()) |_| {} + const frame = animation.frame_ns; + p.advance(1_000_000); + try std.testing.expectEqual(@as(?u64, null), p.nextWake()); + const pane = p.panes[p.active].?; + try std.testing.expect(p.executeBuiltinLine(p.active, "Msg hello")); + // Falling in: a frame from now. + try std.testing.expectEqual(@as(?u64, p.stepped_ns + frame), p.nextWake()); + const fall = messageFrames(p.settings.message_fall_ms); + p.advance(p.stepped_ns + fall * frame); + try std.testing.expectEqual(Pane.MessagePhase.shown, pane.msg_life.phase); + try std.testing.expectEqual(@as(?u64, null), p.nextWake()); + // A key starts the 800 ms linger: the core sleeps to its end, not a + // frame at a time, and the frames it jumps draw nothing. + p.update(.{ .key = .{ .cp = 'x' } }); + try std.testing.expectEqual(Pane.MessagePhase.lingering, pane.msg_life.phase); + const linger = messageFrames(p.settings.message_linger_ms); + const due = p.stepped_ns + linger * frame; + try std.testing.expectEqual(@as(?u64, due), p.nextWake()); + p.needs_frame = false; + p.advance(due - frame); + try std.testing.expectEqual(@as(?u64, due), p.nextWake()); + try std.testing.expectEqual(Pane.MessagePhase.lingering, pane.msg_life.phase); + p.advance(due); + try std.testing.expectEqual(Pane.MessagePhase.leaving, pane.msg_life.phase); + try std.testing.expectEqual(@as(?u64, due + frame), p.nextWake()); + p.advance(due + 10 * std.time.ns_per_s); + try std.testing.expectEqual(@as(usize, 0), pane.msg_len); + try std.testing.expectEqual(@as(?u64, null), p.nextWake()); + + // A long linger ends when it says, jumped in one step, not stretched by + // the cap on catching up with moving frames. + try std.testing.expect(p.executeBuiltinLine(p.active, "MessageLinger 5000")); + try std.testing.expect(p.executeBuiltinLine(p.active, "Msg again")); + p.advance(p.stepped_ns + fall * frame); + p.update(.{ .key = .{ .cp = 'x' } }); + const long_due = p.stepped_ns + messageFrames(5000) * frame; + try std.testing.expectEqual(@as(?u64, long_due), p.nextWake()); + p.advance(long_due); + try std.testing.expectEqual(Pane.MessagePhase.leaving, pane.msg_life.phase); +} diff --git a/src/Presentation.zig b/src/Presentation.zig index fe8317dd..45b196c8 100644 --- a/src/Presentation.zig +++ b/src/Presentation.zig @@ -295,11 +295,18 @@ pub fn acknowledge(self: *Presentation, p: *Pardes, tracks: []const Track) void self.tracks = @splat(null); self.closing_len = 0; self.diff_state = .none; - self.capturePrevious(p.gpa, &p.surface); + // The frozen grid exists for transitions alone: with them off, an + // idle frame copies nothing. + if (p.settings.panel_transition != .off) { + self.capturePrevious(p.gpa, &p.surface); + } else { + self.previous_valid = false; + self.previous_layout = @splat(null); + } } } -fn capturePrevious(self: *Presentation, gpa: std.mem.Allocator, surface: *const pardes.Surface) void { +pub fn capturePrevious(self: *Presentation, gpa: std.mem.Allocator, surface: *const pardes.Surface) void { self.diff_state = .none; const cells = surface.cells; if (cells.len == 0) { @@ -651,6 +658,8 @@ test "presentation submission failure preserves shown geometry until retry is ac defer p.deinit(); var frame: std.heap.ArenaAllocator = .init(std.testing.allocator); defer frame.deinit(); + // The frozen grid is kept only while a transition is chosen. + p.settings.panel_transition = .slide; _ = try p.render(frame.allocator()); p.acknowledgePanelPresentation(&.{}); const shown = p.presentation.shown; diff --git a/src/animation.zig b/src/animation.zig index b190c1a1..d6ef1ec3 100644 --- a/src/animation.zig +++ b/src/animation.zig @@ -588,157 +588,158 @@ test "dissolve has exact stable endpoints" { } } -pub const Animation = struct { - pub const frame_ms: u32 = 16; - pub const frame_ns: u64 = frame_ms * std.time.ns_per_ms; - pub const transition_steps: u16 = 10; - - pub fn Transition(comptime Value: type) type { - return struct { - const Self = @This(); - - from: Value, - to: Value, - displayed: Value, - step: u16 = transition_steps, - - pub fn init(value: Value) Self { - return .{ .from = value, .to = value, .displayed = value }; - } +pub const frame_ms: u32 = 16; +pub const frame_ns: u64 = frame_ms * std.time.ns_per_ms; +pub const transition_steps: u16 = 10; + +/// A displayed value that fades from one target to the next over +/// `transition_steps` frames; the chrome colours are one. +pub fn Fade(comptime Value: type) type { + return struct { + const Self = @This(); + + from: Value, + to: Value, + displayed: Value, + step: u16 = transition_steps, + + pub fn init(value: Value) Self { + return .{ .from = value, .to = value, .displayed = value }; + } - pub fn isActive(a: *const Self) bool { - return a.step < transition_steps; - } + pub fn isActive(a: *const Self) bool { + return a.step < transition_steps; + } - pub fn retarget(a: *Self, target: Value) void { - a.from = a.displayed; - a.to = target; - a.step = if (std.meta.eql(a.from, target)) transition_steps else 0; - if (a.step == transition_steps) a.displayed = target; - } + pub fn retarget(a: *Self, target: Value) void { + a.from = a.displayed; + a.to = target; + a.step = if (std.meta.eql(a.from, target)) transition_steps else 0; + if (a.step == transition_steps) a.displayed = target; + } - pub fn advance(a: *Self) void { - if (!a.isActive()) return; - a.step += 1; - a.displayed = if (a.step == transition_steps) - a.to - else - Value.interpolate(a.from, a.to, a.step, transition_steps); - } + pub fn advance(a: *Self) void { + if (!a.isActive()) return; + a.step += 1; + a.displayed = if (a.step == transition_steps) + a.to + else + Value.interpolate(a.from, a.to, a.step, transition_steps); + } - pub fn snap(a: *Self, value: Value) void { - a.* = init(value); - } - }; - } + pub fn snap(a: *Self, value: Value) void { + a.* = init(value); + } + }; +} - pub fn Immediate(comptime Value: type) type { - return struct { - const Self = @This(); +pub fn Immediate(comptime Value: type) type { + return struct { + const Self = @This(); - displayed: Value, + displayed: Value, - pub fn init(value: Value) Self { - return .{ .displayed = value }; - } + pub fn init(value: Value) Self { + return .{ .displayed = value }; + } - pub fn isActive(_: *const Self) bool { - return false; - } + pub fn isActive(_: *const Self) bool { + return false; + } - pub fn retarget(a: *Self, target: Value) void { - a.displayed = target; - } + pub fn retarget(a: *Self, target: Value) void { + a.displayed = target; + } - pub fn advance(_: *Self) void {} + pub fn advance(_: *Self) void {} - pub fn snap(a: *Self, value: Value) void { - a.displayed = value; - } - }; - } - - pub fn interpolateRgb(from: [3]u8, to: [3]u8, step: u16, steps: u16) [3]u8 { - if (step == 0) return from; - if (step >= steps) return to; - var out: [3]u8 = undefined; - for (&out, from, to) |*dst, a, b| { - const numerator = @as(u32, a) * (steps - step) + @as(u32, b) * step; - dst.* = @intCast((numerator + steps / 2) / steps); + pub fn snap(a: *Self, value: Value) void { + a.displayed = value; } - return out; + }; +} + +pub fn interpolateRgb(from: [3]u8, to: [3]u8, step: u16, steps: u16) [3]u8 { + if (step == 0) return from; + if (step >= steps) return to; + var out: [3]u8 = undefined; + for (&out, from, to) |*dst, a, b| { + const numerator = @as(u32, a) * (steps - step) + @as(u32, b) * step; + dst.* = @intCast((numerator + steps / 2) / steps); } + return out; +} - const TestColor = struct { - rgb: [3]u8, +const TestColor = struct { + rgb: [3]u8, - pub fn interpolate(from: TestColor, to: TestColor, step: u16, steps: u16) TestColor { - return .{ .rgb = interpolateRgb(from.rgb, to.rgb, step, steps) }; - } - }; + pub fn interpolate(from: TestColor, to: TestColor, step: u16, steps: u16) TestColor { + return .{ .rgb = interpolateRgb(from.rgb, to.rgb, step, steps) }; + } +}; - test "Immediate lands where a completed Transition lands" { - const from: TestColor = .{ .rgb = .{ 240, 3, 90 } }; - const to: TestColor = .{ .rgb = .{ 5, 222, 90 } }; +test "Immediate lands where a completed Transition lands" { + const from: TestColor = .{ .rgb = .{ 240, 3, 90 } }; + const to: TestColor = .{ .rgb = .{ 5, 222, 90 } }; - var faded = Animation.Transition(TestColor).init(from); - faded.retarget(to); - for (0..transition_steps) |_| faded.advance(); + var faded = Fade(TestColor).init(from); + faded.retarget(to); + for (0..transition_steps) |_| faded.advance(); - var instant = Immediate(TestColor).init(from); - try std.testing.expect(!instant.isActive()); - instant.retarget(to); - try std.testing.expectEqual(faded.displayed, instant.displayed); + var instant = Immediate(TestColor).init(from); + try std.testing.expect(!instant.isActive()); + instant.retarget(to); + try std.testing.expectEqual(faded.displayed, instant.displayed); - // Never active, so a frontend that renders only while something is animating stops immediately - // rather than spending ten frames discovering there is nothing to draw. - try std.testing.expect(!instant.isActive()); - instant.advance(); - try std.testing.expectEqual(to, instant.displayed); + // Never active, so a frontend that renders only while something is animating stops immediately + // rather than spending ten frames discovering there is nothing to draw. + try std.testing.expect(!instant.isActive()); + instant.advance(); + try std.testing.expectEqual(to, instant.displayed); - instant.snap(from); - try std.testing.expectEqual(from, instant.displayed); - } + instant.snap(from); + try std.testing.expectEqual(from, instant.displayed); +} - test "fixed-step interpolation has exact monotonic endpoints" { - const Tween = Animation.Transition(TestColor); - const from: TestColor = .{ .rgb = .{ 240, 3, 90 } }; - const to: TestColor = .{ .rgb = .{ 5, 222, 90 } }; - var tween = Tween.init(from); - tween.retarget(to); - try std.testing.expectEqual(from, tween.displayed); - - var previous = tween.displayed; - for (0..transition_steps) |_| { - tween.advance(); - try std.testing.expect(tween.displayed.rgb[0] <= previous.rgb[0]); - try std.testing.expect(tween.displayed.rgb[1] >= previous.rgb[1]); - try std.testing.expectEqual(@as(u8, 90), tween.displayed.rgb[2]); - previous = tween.displayed; - } - try std.testing.expect(!tween.isActive()); - try std.testing.expectEqual(to, tween.displayed); - tween.advance(); - try std.testing.expectEqual(to, tween.displayed); - } +test "fixed-step interpolation has exact monotonic endpoints" { + const Tween = Fade(TestColor); + const from: TestColor = .{ .rgb = .{ 240, 3, 90 } }; + const to: TestColor = .{ .rgb = .{ 5, 222, 90 } }; + var tween = Tween.init(from); + tween.retarget(to); + try std.testing.expectEqual(from, tween.displayed); - test "retarget starts at the currently displayed value" { - const Tween = Animation.Transition(TestColor); - const first: TestColor = .{ .rgb = .{ 0, 40, 200 } }; - const second: TestColor = .{ .rgb = .{ 200, 140, 0 } }; - const third: TestColor = .{ .rgb = .{ 20, 10, 250 } }; - var tween = Tween.init(first); - tween.retarget(second); - tween.advance(); - tween.advance(); + var previous = tween.displayed; + for (0..transition_steps) |_| { tween.advance(); - const on_screen = tween.displayed; - - tween.retarget(third); - try std.testing.expectEqual(on_screen, tween.from); - try std.testing.expectEqual(on_screen, tween.displayed); - try std.testing.expect(tween.isActive()); - for (0..transition_steps) |_| tween.advance(); - try std.testing.expectEqual(third, tween.displayed); + try std.testing.expect(tween.displayed.rgb[0] <= previous.rgb[0]); + try std.testing.expect(tween.displayed.rgb[1] >= previous.rgb[1]); + try std.testing.expectEqual(@as(u8, 90), tween.displayed.rgb[2]); + previous = tween.displayed; } -}; + try std.testing.expect(!tween.isActive()); + try std.testing.expectEqual(to, tween.displayed); + tween.advance(); + try std.testing.expectEqual(to, tween.displayed); +} + +test "retarget starts at the currently displayed value" { + const Tween = Fade(TestColor); + const first: TestColor = .{ .rgb = .{ 0, 40, 200 } }; + const second: TestColor = .{ .rgb = .{ 200, 140, 0 } }; + const third: TestColor = .{ .rgb = .{ 20, 10, 250 } }; + var tween = Tween.init(first); + tween.retarget(second); + tween.advance(); + tween.advance(); + tween.advance(); + const on_screen = tween.displayed; + + tween.retarget(third); + try std.testing.expectEqual(on_screen, tween.from); + try std.testing.expectEqual(on_screen, tween.displayed); + try std.testing.expect(tween.isActive()); + for (0..transition_steps) |_| tween.advance(); + try std.testing.expectEqual(third, tween.displayed); +} + diff --git a/src/colors.zig b/src/colors.zig index f814d55d..0065d5ef 100644 --- a/src/colors.zig +++ b/src/colors.zig @@ -186,15 +186,15 @@ pub const ChromeTheme = struct { pub fn interpolate(from: ChromeTheme, to: ChromeTheme, step: u16, steps: u16) ChromeTheme { var out: ChromeTheme = undefined; inline for (@typeInfo(ChromeTheme).@"struct".fields) |field| - @field(out, field.name) = animation.Animation.interpolateRgb(@field(from, field.name), @field(to, field.name), step, steps); + @field(out, field.name) = animation.interpolateRgb(@field(from, field.name), @field(to, field.name), step, steps); return out; } }; pub const ChromeAnimation = if (theme_animation) - animation.Animation.Transition(ChromeTheme) + animation.Fade(ChromeTheme) else - animation.Animation.Immediate(ChromeTheme); + animation.Immediate(ChromeTheme); pub const initial_chrome = ChromeTheme.fromTheme(&themes[0]); @@ -414,7 +414,7 @@ test "runtime theme changes animate chrome and retarget without a jump" { try std.testing.expectEqualStrings("acme", p.theme().name); try std.testing.expectEqual(midflight, p.chromeTheme().*); try std.testing.expect(p.animationActive()); - for (0..animation.Animation.transition_steps) |_| p.update(.tick); + for (0..animation.transition_steps) |_| p.update(.tick); try std.testing.expect(!p.animationActive()); try std.testing.expectEqual(ChromeTheme.fromTheme(p.theme()), p.chromeTheme().*); // Extra ticks are inert at the exact endpoint. diff --git a/src/detached/server.zig b/src/detached/server.zig index 5c0e1487..212c9a3d 100644 --- a/src/detached/server.zig +++ b/src/detached/server.zig @@ -140,6 +140,8 @@ pub const Session = struct { worker_gpa: std.mem.Allocator, io: std.Io, core: *pardes.Pardes, + /// PARDES_TEST_CLOCK's virtual time (host_io.testClock). + test_clock: ?u64 = null, listener: c_int = -1, path_buf: [sun_path_len]u8 = undefined, path_len: usize = 0, @@ -343,8 +345,14 @@ pub const Session = struct { return @ptrCast(@alignCast(ctx.?)); } + fn clockNow(ctx: ?*anyopaque) u64 { + const s = of(ctx); + return s.test_clock orelse host_io.monotonicNs(); + } + const vtable: host_io.Host.VTable = .{ .wait_input = waitInput, + .now = clockNow, .present = present, .poll_frame = pollFrame, .spawn = spawn, @@ -827,8 +835,9 @@ pub const Session = struct { pardes.turn.rest(); nap(if (timeout_ms == 0) 16 else timeout_ms); pardes.turn.wake(); - // An animating frame's wait is the frame: tick it. - if (timeout_ms != 0) s.core.update(.tick); + if (timeout_ms != 0) if (s.test_clock) |*virtual| { + virtual.* = @max(virtual.*, s.core.nextWake() orelse virtual.*); + }; return; } var timeout: c_int = if (timeout_ms == 0) -1 else @intCast(@min(timeout_ms, std.math.maxInt(c_int))); @@ -838,9 +847,10 @@ pub const Session = struct { const ready = libc.poll(&fds, @intCast(n), timeout); pardes.turn.wake(); if (ready > 0) s.dispatch(fds[0..n], src[0..n]); - // Animating, the frame's time is up: advance it. There is no other - // tick in a detached session, and without it an animation never ends. - if (timeout_ms != 0 and monotonicMs() -| now >= timeout_ms) s.core.update(.tick); + // The core's wake ran out; the pump advances it from `now`. + if (timeout_ms != 0 and monotonicMs() -| now >= timeout_ms) if (s.test_clock) |*virtual| { + virtual.* = @max(virtual.*, s.core.nextWake() orelse virtual.*); + }; _ = s.drainCompletions(true); s.expire(monotonicMs()); s.harvest(); @@ -1155,6 +1165,7 @@ pub fn run(init: std.process.Init, opts: pardes.Options, name: []const u8) !void .cols = options.cols, .rows = options.rows, .prompt_rcs = host_io.Shell.prepare(), + .test_clock = host_io.testClock(), }; defer session.core.deinit(); defer session.deinit(); diff --git a/src/esp32p4.zig b/src/esp32p4.zig index 0d532e1e..c583de54 100644 --- a/src/esp32p4.zig +++ b/src/esp32p4.zig @@ -572,10 +572,9 @@ export fn pardes_esp32p4_tick(now_ms: u64) callconv(.c) void { dirty = true; } } - if (c.animationActive()) { - c.update(.tick); - dirty = true; - } + // The core steps its own animations by this clock (`Host.now`). + c.advance(now_ms * std.time.ns_per_ms); + if (c.needs_frame) dirty = true; } /// How long a lone ESC waits for a second byte before it counts as the Escape key. @@ -590,7 +589,7 @@ var esc_held_at: ?u64 = null; export fn pardes_esp32p4_wants_frame() callconv(.c) bool { const c = core orelse return false; - return dirty or c.animationActive(); + return dirty or c.needs_frame; } export fn pardes_esp32p4_render() callconv(.c) u32 { @@ -607,7 +606,11 @@ export fn pardes_esp32p4_quit() callconv(.c) bool { // ------------------------------------------------------------------------------------ the host -const pardes_host: pardes.Host.VTable = .{ .present = present, .gpio_toggle = gpioToggle }; +const pardes_host: pardes.Host.VTable = .{ .now = boardNow, .present = present, .gpio_toggle = gpioToggle }; + +fn boardNow(_: ?*anyopaque) u64 { + return last_now_ms * std.time.ns_per_ms; +} /// The `Gpio` word's one seam to the board. Nothing here knows what a pad is; it forwards, and /// answers false when the firmware brought none, which is what puts "gpio: NoPads" on the message diff --git a/src/exec.zig b/src/exec.zig index b16c600c..ad0f7b9b 100644 --- a/src/exec.zig +++ b/src/exec.zig @@ -502,6 +502,10 @@ pub fn applySettingBuiltin(p: *Pardes, setting: config.Runtime.Setting, arg: ?[] if (p.settings.column_tags != previous_column_tags) { if (p.header_column != null) tagline.exitHeader(p); } + // Idle frames keep no frozen grid while transitions are off; the one + // just drawn is the baseline the first transition starts from. + if (transition_changed and previous_transition == .off) + p.presentation.capturePrevious(p.gpa, &p.surface); if (transition_changed or tagline_metrics_changed or p.settings.column_tags != previous_column_tags) { p.abandonPanelAnimations(); } diff --git a/src/gui/gui.zig b/src/gui/gui.zig index 0737bb63..a1e21dc0 100644 --- a/src/gui/gui.zig +++ b/src/gui/gui.zig @@ -200,55 +200,18 @@ const touch_click_flash_max_frames: u8 = 14; const touch_click_flash_vertices: usize = 1400; const touch_scroll_tick: f32 = 0.02; -const AnimationClock = struct { - next_ns: u64 = 0, - - fn due(clock: *AnimationClock, active: bool, now_ns: u64) bool { - if (!active) { - clock.next_ns = 0; - return false; - } - if (clock.next_ns == 0) { - clock.next_ns = now_ns +| pardes.animation.Animation.frame_ns; - return false; - } - if (now_ns < clock.next_ns) return false; - clock.next_ns = now_ns +| pardes.animation.Animation.frame_ns; - return true; - } -}; - -fn finishPresentedAnimationFrame( - clock: *AnimationClock, - core: *pardes.Pardes, - tracks: []const pardes.animation.Track, - now_ns: u64, -) void { - core.acknowledgePanelPresentation(tracks); - if (clock.due(core.animationActive(), now_ns)) core.update(.tick); -} - -test "GUI animation clock is active-only and cadence gated" { - var clock: AnimationClock = .{}; - try std.testing.expect(!clock.due(false, 100)); - try std.testing.expect(!clock.due(true, 100)); - try std.testing.expect(!clock.due(true, 100 + pardes.animation.Animation.frame_ns - 1)); - try std.testing.expect(clock.due(true, 100 + pardes.animation.Animation.frame_ns)); - try std.testing.expect(!clock.due(false, 100 + 2 * pardes.animation.Animation.frame_ns)); - try std.testing.expectEqual(@as(u64, 0), clock.next_ns); -} - test "a persistent scene presents a new panel's frame zero before advancing it" { const core = try pardes.Pardes.init(std.testing.allocator, .{ .tty_only = true }); defer core.deinit(); var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); defer arena.deinit(); - var clock: AnimationClock = .{}; + const frame = pardes.animation.frame_ns; core.settings.scene_effects.crt = true; + core.advance(100); const scene_frame = try core.render(arena.allocator()); - finishPresentedAnimationFrame(&clock, core, scene_frame.panelTracks(), 100); - try std.testing.expectEqual(@as(u64, 100 + pardes.animation.Animation.frame_ns), clock.next_ns); + core.acknowledgePanelPresentation(scene_frame.panelTracks()); + try std.testing.expectEqual(@as(?u64, 100 + frame), core.nextWake()); core.settings.panel_transition = .slide; core.update(.{ .command = "Newcol" }); @@ -258,12 +221,12 @@ test "a persistent scene presents a new panel's frame zero before advancing it" try std.testing.expect(track_count > 0); for (first.panelTracks()) |track| try std.testing.expectEqual(@as(u16, 0), track.frame); - finishPresentedAnimationFrame( - &clock, - core, - first.panelTracks(), - 100 + pardes.animation.Animation.frame_ns, - ); + core.acknowledgePanelPresentation(first.panelTracks()); + // Just short of a frame is no frame at all. + core.advance(100 + frame - 1); + _ = arena.reset(.retain_capacity); + for ((try core.render(arena.allocator())).panelTracks()) |track| try std.testing.expectEqual(@as(u16, 0), track.frame); + core.advance(100 + frame); _ = arena.reset(.retain_capacity); const second = try core.render(arena.allocator()); try std.testing.expectEqual(track_count, second.panelTracks().len); @@ -3742,7 +3705,6 @@ const Shell = struct { presented: bool = false, skipped_presents: u8 = 0, surface: ?*pardes.Surface = null, - animation_clock: AnimationClock = .{}, saw_event: bool = false, dump_err: ?anyerror = null, retired_shells: [pardes.MAX_PANES]RetiredShell = @splat(.{}), @@ -3832,6 +3794,7 @@ const Shell = struct { const vtable: pardes.Host.VTable = .{ .wait_input = waitInput, + .now = guiNow, .present = present, .post_present = postPresent, .poll_frame = pollFrame, @@ -3857,6 +3820,7 @@ const Shell = struct { const grid_vtable: pardes.Host.VTable = vt: { var v = vtable; v.wait_input = null; + v.now = null; v.poll_frame = gridPollFrame; v.present = gridPresent; v.post_present = gridPostPresent; @@ -4106,20 +4070,16 @@ fn present(ctx: ?*anyopaque, surface: *const pardes.Surface) void { fn postPresent(ctx: ?*anyopaque) void { const s = shellOf(ctx); const g = s.gui orelse return; - if (!s.presented) { - // No image (a minimized window): time still passes, so a lingering - // message or a hover wait runs out instead of redrawing forever. - if (s.animation_clock.due(s.core.animationActive(), c.SDL_GetTicksNS())) s.core.update(.tick); - return; - } + // No image (a minimized window) shows no tracks; time still passes and + // the pump advances it all the same. + if (!s.presented) return; const frame = s.surface orelse return; refreshPresentedPointer(g, s.core); - finishPresentedAnimationFrame( - &s.animation_clock, - s.core, - frame.panelTracks(), - c.SDL_GetTicksNS(), - ); + s.core.acknowledgePanelPresentation(frame.panelTracks()); +} + +fn guiNow(_: ?*anyopaque) u64 { + return c.SDL_GetTicksNS(); } fn gridPollFrame(ctx: ?*anyopaque) void { @@ -4129,8 +4089,10 @@ fn gridPollFrame(ctx: ?*anyopaque) void { if (f.tick().count != 0) s.saw_event = true; } pollCwds(s.core, s.ptys); - if (s.core.animationActive()) { - s.core.update(.tick); + // Grid time is virtual: each poll moves it straight to the core's + // next wake, a frame at a time while anything moves. + if (s.core.nextWake()) |due| { + s.core.advance(due); s.saw_event = true; } } diff --git a/src/host_io.zig b/src/host_io.zig index 6009c00f..f1a4eb98 100644 --- a/src/host_io.zig +++ b/src/host_io.zig @@ -12,6 +12,9 @@ pub const Host = struct { pub const VTable = struct { wait_input: ?*const fn (ctx: ?*anyopaque, timeout_ms: u32) void = null, + /// The shell's monotonic clock in ns: the only time the core sees. + /// Without one, core animation moves only by `.tick` events. + now: ?*const fn (ctx: ?*anyopaque) u64 = null, present: ?*const fn (ctx: ?*anyopaque, surface: *const pardes.Surface) void = null, post_present: ?*const fn (ctx: ?*anyopaque) void = null, poll_frame: ?*const fn (ctx: ?*anyopaque) void = null, @@ -39,6 +42,20 @@ pub const Host = struct { }; }; +/// The shells' monotonic clock in ns, the one `Host.now` answers with. +/// PARDES_TEST_CLOCK makes a shell answer a virtual clock instead, moved +/// only when a timed wait runs out -- straight to the core's next wake -- so +/// the snapshot harness sees the same frames whatever the machine's load. +pub fn monotonicNs() u64 { + var ts: libc.timespec = undefined; + if (libc.clock_gettime(.MONOTONIC, &ts) != 0) return 0; + return @as(u64, @intCast(ts.sec)) * std.time.ns_per_s + @as(u64, @intCast(ts.nsec)); +} + +pub fn testClock() ?u64 { + return if (libc.getenv("PARDES_TEST_CLOCK")) |v| (if (std.mem.span(v).len != 0) 0 else null) else null; +} + pub const Fallback = struct { pub const dump_path = "pardes.dump.zon"; diff --git a/src/macos.zig b/src/macos.zig index 854b24f8..fce0a53e 100644 --- a/src/macos.zig +++ b/src/macos.zig @@ -118,7 +118,7 @@ const scene_frame_hz: u32 = 60; const scene_wrap_ns: u64 = 4096 * std.time.ns_per_s; -const max_tick_catch_up_ns: u64 = 4 * pardes.animation.Animation.frame_ns; +const max_tick_catch_up_ns: u64 = 4 * pardes.animation.frame_ns; const theme_watch_pane: u8 = @intCast(pardes.MAX_PANES); const watch_slot_count = pardes.MAX_PANES + 1; @@ -870,18 +870,18 @@ const TickSpend = struct { elapsed_ns: u64, steps: u32, bank_ns: u64 }; fn spendTickTime(previous_ns: u64, now_ns: u64, bank_ns: u64) TickSpend { const measured = if (previous_ns == 0 or now_ns <= previous_ns) - pardes.animation.Animation.frame_ns + pardes.animation.frame_ns else now_ns - previous_ns; const elapsed = @min(measured, max_tick_catch_up_ns); var bank = bank_ns +| elapsed; var steps: u32 = 0; - while (bank >= pardes.animation.Animation.frame_ns) : (steps += 1) bank -= pardes.animation.Animation.frame_ns; + while (bank >= pardes.animation.frame_ns) : (steps += 1) bank -= pardes.animation.frame_ns; return .{ .elapsed_ns = elapsed, .steps = steps, .bank_ns = bank }; } test "the animation clock spends real time, not callbacks" { - const frame = pardes.animation.Animation.frame_ns; + const frame = pardes.animation.frame_ns; const expectEqual = std.testing.expectEqual; const first = spendTickTime(0, 999 * std.time.ns_per_s, 0); diff --git a/src/pardes.zig b/src/pardes.zig index bfd8a4f6..161a047c 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -3337,9 +3337,9 @@ test "vertical close samples only a canonical baseline containing that pane" { defer p.deinit(); var frame = std.heap.ArenaAllocator.init(std.testing.allocator); defer frame.deinit(); + p.settings.panel_transition = .vertical; const boot = try p.render(frame.allocator()); p.acknowledgePanelPresentation(boot.panelTracks()); - p.settings.panel_transition = .vertical; _ = try p.newShell(1, ""); try std.testing.expect(layout.splitColumn(p, 0, 1, false)); @@ -3360,9 +3360,9 @@ test "vertical close samples only a canonical baseline containing that pane" { defer p.deinit(); var frame = std.heap.ArenaAllocator.init(std.testing.allocator); defer frame.deinit(); + p.settings.panel_transition = .vertical; const boot = try p.render(frame.allocator()); p.acknowledgePanelPresentation(boot.panelTracks()); - p.settings.panel_transition = .vertical; _ = try p.newShell(1, ""); try std.testing.expect(layout.splitColumn(p, 0, 1, false)); @@ -3386,9 +3386,9 @@ test "previous-grid animation refreshes its mask and snaps overlapping layout ch defer p.deinit(); var frame = std.heap.ArenaAllocator.init(std.testing.allocator); defer frame.deinit(); + p.settings.panel_transition = .vertical; const boot = try p.render(frame.allocator()); p.acknowledgePanelPresentation(boot.panelTracks()); - p.settings.panel_transition = .vertical; _ = try p.newShell(1, ""); try std.testing.expect(layout.splitColumn(p, 0, 1, false)); @@ -3875,6 +3875,13 @@ pub const Pardes = struct { /// Set by a shell whose present was skipped (no swapchain image): the /// frame is still owed, and the next pump draws it again. present_skipped: bool = false, + /// Core time. `now_ns` is the shell's monotonic clock as `advance` was + /// last given it; `stepped_ns` is how far animation has been stepped + /// along it, in whole frames of `animation.frame_ns`. Idle, the two are + /// the same instant. + now_ns: u64 = 0, + stepped_ns: u64 = 0, + clock_started: bool = false, /// Builtins announce themselves on the message row. Off while startup /// configuration is applied: those lines are nobody clicking, and should /// not leave the row holding the name of the last one. @@ -4647,7 +4654,17 @@ pub const Pardes = struct { pub fn pump(p: *Pardes, h: Host) !void { p.host = h; const v = h.vtable; - if (v.wait_input) |f| f(h.ctx, if (p.animationActive()) animation.Animation.frame_ms else 0); + if (v.wait_input) |f| { + // Asleep until input or until core animation next needs a + // frame; 0 is no timeout at all. + const timeout: u32 = if (p.nextWake()) |due| wake: { + const now = if (v.now) |clock| clock(h.ctx) else p.now_ns; + const ms = ((due -| now) + std.time.ns_per_ms - 1) / std.time.ns_per_ms; + break :wake @intCast(std.math.clamp(ms, 1, std.math.maxInt(u32))); + } else 0; + f(h.ctx, timeout); + } + if (v.now) |clock| p.advance(clock(h.ctx)); while (p.nextQueued()) |ev| p.update(ev); while (p.nextEffect()) |e| p.perform(e); turn.settle(); @@ -4657,7 +4674,9 @@ pub const Pardes = struct { // Nothing to draw: do not spend a frame, and above all do not block on // the display for one. This is what keeps the frame rate governing // rendering and nothing else. - if (!p.needs_frame and !p.animationActive()) return; + // A shell with a clock draws what `advance` stepped; one without + // steps its own `.tick`s and draws while anything animates. + if (!p.needs_frame and (v.now != null or !p.animationActive())) return; _ = p.frame_arena.reset(.retain_capacity); const surface = try p.render(p.frame_arena.allocator()); if (v.present) |f| f(h.ctx, surface); @@ -5717,18 +5736,85 @@ pub const Pardes = struct { return &p.chrome_animation.displayed; } - /// Whether an idle frontend should schedule another tick. All frame-based - /// core work belongs in this one predicate so TTY and GUI mature it alike. - pub fn animationActive(p: *const Pardes) bool { - if (p.chrome_animation.isActive() or p.look_hover_wait != null) return true; + /// When core animation next needs a frame, on the `stepped_ns` + /// timeline: a frame from now while anything moves, the end of the + /// wait while something only waits (a message lingering, a look-hover + /// delay), null when nothing is animating. Every frame-based animation + /// the core owns is in this one list, which is what lets every shell + /// sleep exactly as long as nothing changes. + pub fn nextWake(p: *const Pardes) ?u64 { + const next = p.stepped_ns + animation.frame_ns; + if (p.chrome_animation.isActive() or p.presentation.animating()) return next; + const scene = p.settings.scene_effects; + if (scene.crt or scene.ripple or scene.glitch) return next; + var frames: ?u64 = null; + if (p.look_hover_wait) |waiting| { + const delay = config.look_preview_delay_frames orelse return next; + frames = @max(1, delay -| waiting.frames); + } for (p.panes) |slot| if (slot) |pane| { - if (Messages.messagesAnimating(pane)) return true; + const wait = Messages.messageWake(p, pane) orelse continue; + frames = if (frames) |soonest| @min(soonest, wait) else wait; }; - const scene = p.settings.scene_effects; - if (scene.crt or scene.ripple or scene.glitch) return true; - return p.presentation.animating(); + return if (frames) |n| p.stepped_ns + n * animation.frame_ns else null; } + /// For shells that only need a yes or no: macOS and the board, which + /// keep their own frame banks. + pub fn animationActive(p: *const Pardes) bool { + return p.nextWake() != null; + } + + /// Bring core animation up to `now_ns`, the shell's monotonic clock: one + /// `.tick` for every whole frame that has passed since it last stood + /// still, so an animation takes as long on a 144 Hz display, over ssh or + /// after a slow frame as it does anywhere. A wait (a message lingering, + /// the look-hover delay) is jumped to its end in one step and drawn + /// once. Idle, time is not banked: the next animation starts from now. + pub fn advance(p: *Pardes, now_ns: u64) void { + // The first time a shell's clock is seen, core time starts there. + const started = p.clock_started; + p.clock_started = true; + p.now_ns = now_ns; + if (!started) p.stepped_ns = now_ns; + const frame = animation.frame_ns; + var moving: u32 = 0; + while (p.nextWake()) |due| { + if (due > now_ns) break; + // Only waiting until `due`: its counters move there at once. + const held = (due - p.stepped_ns) / frame; + if (held > 1) { + Messages.holdMessages(p, held - 1); + if (p.look_hover_wait) |*waiting| waiting.frames +|= @intCast(@min(held - 1, std.math.maxInt(u16))); + p.stepped_ns += (held - 1) * frame; + } + // Back from a stall, the moving frames it missed are not all + // run at once: the rest are stepped from now, at their pace. + if (moving == max_catch_up_frames) { + p.stepped_ns = now_ns; + return; + } + moving += 1; + p.stepped_ns += frame; + p.update(.tick); + // A stepped frame is a frame to draw. (A `.tick` does not ask + // for one itself: the last frame of a fade used never to be + // drawn.) + p.needs_frame = true; + } else { + p.stepped_ns = now_ns; + return; + } + // A 60 Hz display frame is 16.67 ms and ours 16: without this, every + // 24th display frame would take two steps, a hitch in every fade. + // An overshoot this small is let go; a larger one (144 Hz lands 4.8 + // ms past) is kept, so the average rate stays right. + if (moving > 0 and now_ns - p.stepped_ns < vsync_slack_ns) p.stepped_ns = now_ns; + } + + pub const vsync_slack_ns = 1500 * std.time.ns_per_us; + pub const max_catch_up_frames = 240; + pub fn noteHaptic(p: *Pardes, pulse: Haptic) void { if (comptime platform != .macos) return; if (p.haptic == .look) return; @@ -6751,9 +6837,9 @@ test "board heap: transition grids fit the budget and the surface borrows its sn defer p.deinit(); var frame: std.heap.ArenaAllocator = .init(std.testing.allocator); defer frame.deinit(); + p.settings.panel_transition = .vertical; const boot = try p.render(frame.allocator()); p.acknowledgePanelPresentation(boot.panelTracks()); - p.settings.panel_transition = .vertical; _ = try p.newShell(1, ""); try std.testing.expect(layout.splitColumn(p, 0, 1, false)); p.sync(); @@ -6864,3 +6950,40 @@ test "jump history terminal rectangle follows output and clears on reflow" { try std.testing.expect(pane.pointerSelection(pane.sel[0]) == null); try std.testing.expectEqual(.none, pane.sel[0].state); } + +test "core animation keeps wall time at 60, 120 and 144 Hz and across a stall" { + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 80, .rows = 24 }); + defer p.deinit(); + const frame = animation.frame_ns; + // Something that animates for as long as it is on. + p.settings.scene_effects.crt = true; + for ([_]u64{ 60, 120, 144 }) |hz| { + const start: u64 = 5 * std.time.ns_per_s; + p.clock_started = false; + p.advance(start); + var steps: u64 = 0; + var at: u64 = 1; + while (at <= hz) : (at += 1) { + const before = p.stepped_ns; + p.advance(start + at * std.time.ns_per_s / hz); + const taken = (p.stepped_ns - before + frame / 2) / frame; + // Never two steps in one display frame: that is a visible hitch. + try std.testing.expect(taken <= 1); + steps += taken; + } + switch (hz) { + // One step a display frame, every display frame. + 60 => try std.testing.expectEqual(@as(u64, 60), steps), + 120 => try std.testing.expectEqual(@as(u64, 60), steps), + // The frame's own rate: 62 a second, give or take one. + else => try std.testing.expect(steps >= 61 and steps <= 63), + } + } + // A stall of minutes is not minutes of frames. + const before = p.stepped_ns; + p.advance(before + 600 * std.time.ns_per_s); + try std.testing.expectEqual(before + 600 * std.time.ns_per_s, p.stepped_ns); + p.settings.scene_effects.crt = false; + p.advance(p.stepped_ns + frame); + try std.testing.expectEqual(@as(?u64, null), p.nextWake()); +} diff --git a/src/tty/tty.zig b/src/tty/tty.zig index 66e551b1..6083f609 100644 --- a/src/tty/tty.zig +++ b/src/tty/tty.zig @@ -76,7 +76,8 @@ pub const Command = struct { pub var value: union(enum) { nop, quit, - tick, + /// A timed wait ran out: the core's next wake has come. + wake, key_press: vaxis.Key, pty_read: struct { id: usize, gen: u32, bytes: []u8 }, pty_eof: struct { id: usize, gen: u32 }, @@ -728,6 +729,7 @@ fn localSession( .paste_buf = paste_buf, .inotify_fd = file_watch.init(false), .fs = fs, + .test_clock = host_io.testClock(), }; pardes.lsp.setStatusSink(&sh, lspStatusSink); defer { @@ -775,7 +777,7 @@ fn localSession( try startInput(loop, input_cache); defer if (attached.* == null) stopInput(loop); (try std.Thread.spawn(.{}, winchWatch, .{ loop, vx, tty })).detach(); - (try std.Thread.spawn(.{}, tickWatch, .{ io, loop })).detach(); + (try std.Thread.spawn(.{}, wakeWatch, .{ io, loop })).detach(); if (fs) |f| try f.watch(loop, wakeFs); try vx.queryTerminalSend(tty.writer()); @@ -900,6 +902,10 @@ const Shell = struct { check_files: bool = false, in_paste: bool = false, tracks: []const pardes.animation.Track = &.{}, + /// PARDES_TEST_CLOCK's virtual time (host_io.testClock), and where the + /// running-out wait moves it: the core's next wake. + test_clock: ?u64 = null, + wake_to: u64 = 0, fn of(ctx: ?*anyopaque) *Shell { return @ptrCast(@alignCast(ctx.?)); @@ -911,12 +917,14 @@ const Shell = struct { const isolated_vtable: host_io.Host.VTable = .{ .wait_input = waitInput, + .now = clockNow, .present = present, .post_present = postPresent, }; const vtable: host_io.Host.VTable = .{ .wait_input = waitInput, + .now = clockNow, .present = present, .post_present = postPresent, .poll_frame = pollFrame, @@ -939,12 +947,20 @@ const Shell = struct { .pipe = pipe, }; + fn clockNow(ctx: ?*anyopaque) u64 { + const s = of(ctx); + return s.test_clock orelse host_io.monotonicNs(); + } + fn waitInput(ctx: ?*anyopaque, timeout_ms: u32) void { const s = of(ctx); var batch: usize = 0; - // The wait is the 9P connections' turn with the core. Animating, the - // frame's tick arrives through the same queue (`tickWatch`). - if (timeout_ms != 0) tick_request.set(s.io); + // The wait is the 9P connections' turn with the core. A timeout (the + // core's next wake) arrives through the same queue (`wakeWatch`). + if (timeout_ms != 0) { + s.wake_to = s.core.nextWake() orelse 0; + requestWake(s.io, timeout_ms); + } pardes.turn.rest(); const first = s.loop.nextEvent() catch { pardes.turn.wake(); @@ -984,7 +1000,9 @@ const Shell = struct { defer tz_event.end(); switch (event) { .nop => {}, - .tick => core.update(.tick), + .wake => if (s.test_clock) |*virtual| { + virtual.* = @max(virtual.*, s.wake_to); + }, .quit => { core.quit = true; return true; @@ -1523,21 +1541,77 @@ fn winchWatch(loop: *Loop, vx: *vaxis.Vaxis, tty: *vaxis.Tty) void { } } -/// Set by an animating frame's wait: post one `.tick` a frame from now. The -/// tick joins the input queue, so a key or a click that lands inside the -/// frame is handled at once instead of after a blind sleep. -var tick_request: std.Io.Event = .unset; +/// A wait with a timeout: post one `.wake` when it runs out. The wake joins +/// the input queue, so a key or a click that lands first is handled at once +/// instead of after a blind sleep; vaxis's queue has no timed wait. Each +/// request bumps `wake_gen`, which cuts short a sleep still timing an +/// earlier, longer request (an 800 ms linger interrupted by a key that +/// starts a 16 ms fall) and times the new one instead. +var wake_gen: std.atomic.Value(u32) = .init(0); +var wake_ms: std.atomic.Value(u32) = .init(0); + +fn requestWake(io: std.Io, ms: u32) void { + wake_ms.store(ms, .release); + _ = wake_gen.fetchAdd(1, .acq_rel); + io.futexWake(u32, &wake_gen.raw, 1); +} + +fn wakeWatch(io: std.Io, loop: *Loop) void { + var seen = wake_gen.load(.acquire); + while (true) { + // Asleep until a request. + while (wake_gen.load(.acquire) == seen) io.futexWaitUncancelable(u32, &wake_gen.raw, seen); + seen = wake_gen.load(.acquire); + if (timeWake(io, &seen)) loop.postEvent(.wake) catch {}; + } +} -fn tickWatch(io: std.Io, loop: *Loop) void { +/// Sleep out the latest request; true when it ran its course, false when +/// none is pending. A newer request met on the way restarts the timing. +fn timeWake(io: std.Io, seen: *u32) bool { while (true) { - tick_request.waitUncancelable(io); - tick_request.reset(); - var ts: libc.timespec = .{ .sec = 0, .nsec = @as(isize, pardes.animation.Animation.frame_ms) * std.time.ns_per_ms }; - _ = libc.nanosleep(&ts, null); - loop.postEvent(.tick) catch {}; + const ms = wake_ms.load(.acquire); + const deadline = host_io.monotonicNs() + @as(u64, ms) * std.time.ns_per_ms; + while (true) { + const now = host_io.monotonicNs(); + if (now >= deadline) return true; + io.futexWaitTimeout(u32, &wake_gen.raw, seen.*, .{ .duration = .{ + .raw = .fromNanoseconds(@intCast(deadline - now)), + .clock = .awake, + } }) catch {}; + const gen = wake_gen.load(.acquire); + if (gen != seen.*) { + seen.* = gen; + break; + } + } } } +test "a short wake request cuts short a long one still sleeping" { + const io = std.testing.io; + var seen = wake_gen.load(.acquire); + requestWake(io, 800); + seen = wake_gen.load(.acquire); + const Timer = struct { + fn run(timer_io: std.Io, start_seen: u32, done: *std.atomic.Value(u64)) void { + var local = start_seen; + _ = timeWake(timer_io, &local); + done.store(host_io.monotonicNs(), .release); + } + }; + var done: std.atomic.Value(u64) = .init(0); + const start = host_io.monotonicNs(); + const thread = try std.Thread.spawn(.{}, Timer.run, .{ io, seen, &done }); + var pause: std.c.timespec = .{ .sec = 0, .nsec = 50 * std.time.ns_per_ms }; + _ = std.c.nanosleep(&pause, null); + requestWake(io, 16); + thread.join(); + // The 16 ms request lands about 66 ms in: well before the 800 ms one + // would have, however loaded the machine. + try std.testing.expect(done.load(.acquire) - start < 700 * std.time.ns_per_ms); +} + fn wakeFs(ctx: ?*anyopaque) void { const loop: *Loop = @ptrCast(@alignCast(ctx.?)); _ = loop.tryPostEvent(.fs_ready) catch {}; @@ -1822,7 +1896,7 @@ const Attach = struct { fn apply(a: *Attach, event: @TypeOf(Command.value)) ?AttachEnd { switch (event) { - .nop, .tick, .fs_ready, .lsp_done, .lsp_status, .pipe_done, .pty_read, .pty_eof, .files_changed => {}, + .nop, .wake, .fs_ready, .lsp_done, .lsp_status, .pipe_done, .pty_read, .pty_eof, .files_changed => {}, .quit => return .none, .focus_in => {}, .focus_out => return a.send(.pointer_leave), diff --git a/src/web.zig b/src/web.zig index b77d07a6..532e3794 100644 --- a/src/web.zig +++ b/src/web.zig @@ -1,7 +1,7 @@ //! Freestanding WebAssembly boundary for the DOM backend. //! //! JavaScript owns the clock and nothing else: it calls `pardes_tick` (one -//! `Pardes.pump`) and reads the packed cell buffer `present` left behind. The +//! `Pardes.pump`, at requestAnimationFrame's timestamp) and reads the packed cell buffer `present` left behind. The //! host vtable below is only what a page genuinely has — pixels, the //! clipboard, links, downloads. Processes, a real filesystem, a language //! backend and subprocess pipes are left null, so the core answers them itself @@ -106,6 +106,9 @@ const State = struct { frame_len: usize = 0, frame_cols: u16 = 0, frame_rows: u16 = 0, + /// requestAnimationFrame's timestamp as the last tick gave it, in ns: + /// the core's clock (`Host.now`). + now_ns: u64 = 0, fn init(cols: u16, rows: u16) !State { const allocs = pardes.memory.init(gpa); @@ -276,30 +279,19 @@ export fn pardes_pointer_leave() void { s.core.update(.pointer_leave); } -/// One iteration of the core's own loop: queued input, effects, render, -/// present. JS calls it once per animation frame, after spending that frame's -/// animation ticks. -export fn pardes_tick() void { +/// One iteration of the core's own loop: queued input, effects, the frames +/// of animation `timestamp_ms` (requestAnimationFrame's clock) has brought, +/// render, present. JS calls it once per display frame; a call with no +/// timestamp (NaN) leaves the clock where it was. +export fn pardes_tick(timestamp_ms: f64) void { const s = &(state orelse return); + if (std.math.isFinite(timestamp_ms) and timestamp_ms >= 0) + s.now_ns = @intFromFloat(timestamp_ms * std.time.ns_per_ms); s.core.pump(host(s)) catch |err| { last_error = @intFromError(err); }; } -/// Spend one fixed 60 Hz animation step, the only writer of animated time in -/// this host. Separate from the pump for the reason the AppKit shell keeps them -/// separate: if every input pump also advanced the transition, a burst of keys -/// would collapse a ten-frame fade into one display frame. -export fn pardes_animation_tick() void { - const s = &(state orelse return); - s.core.update(.tick); -} - -export fn pardes_animating() u32 { - const s = &(state orelse return 0); - return @intFromBool(s.core.animationActive()); -} - /// The Exit builtin, or the last pane closing. JS stops its frame loop. export fn pardes_should_quit() u32 { const s = &(state orelse return 0); @@ -462,7 +454,13 @@ extern "pardes" fn host_download( bytes_len: usize, ) callconv(.c) void; +fn now(ctx: ?*anyopaque) u64 { + const s: *State = @ptrCast(@alignCast(ctx.?)); + return s.now_ns; +} + const vtable: pardes.Host.VTable = .{ + .now = now, .present = present, .write_file = writeFile, .write_dump = writeDump, diff --git a/src/web/app.mjs b/src/web/app.mjs index 11d8f228..280239d5 100644 --- a/src/web/app.mjs +++ b/src/web/app.mjs @@ -1,7 +1,5 @@ const CELL_SIZE = 20; const ATTR_TAGLINE = 1 << 7; -const ANIMATION_FRAME_MS = 1000 / 60; -const MAX_ANIMATION_TICKS = 4; const DEFAULT_FG = "#cccccc"; const DEFAULT_BG = "#121212"; const decoder = new TextDecoder(); @@ -325,7 +323,6 @@ export class PardesRuntime { this.running = false; this.pointer = null; this.frameHandle = 0; - this.animationClock = { last: null, lag: 0 }; this.bindEvents(); } @@ -338,7 +335,6 @@ export class PardesRuntime { this.wasm.pardes_tick(); this.renderer.render(); this.running = true; - this.animationClock = { last: null, lag: 0 }; this.frameHandle = requestAnimationFrame((timestamp) => this.frame(timestamp)); this.root.focus({ preventScroll: true }); } @@ -355,13 +351,9 @@ export class PardesRuntime { this.resize(); this.root.style.setProperty("--pardes-tagline-font-size", `${this.measuredTaglinePercent}%`); } - // JS owns the animation clock; the pump that follows applies those ticks, - // runs the core's effects and presents one frame however fast the display - // is. Both halves are needed: an idle core still has input and effects. - for (let ticks = animationTicks(this.animationClock, timestamp); ticks > 0; ticks -= 1) { - this.wasm.pardes_animation_tick(); - } - this.wasm.pardes_tick(); + // The core's clock is this timestamp: it steps its own animations + // by it, however fast the display refreshes. + this.wasm.pardes_tick(timestamp); if (this.wasm.pardes_should_quit()) this.running = false; this.renderer.render(); this.frameHandle = requestAnimationFrame((nextTimestamp) => this.frame(nextTimestamp)); @@ -559,28 +551,6 @@ export class PardesRuntime { } } -/// Spend monotonic requestAnimationFrame time as fixed 60 Hz core ticks. A -/// high-refresh display therefore renders repeated states rather than making -/// frame-count transitions run twice as fast. Long suspension is capped so a -/// returning tab cannot execute an unbounded loop before drawing. -export function animationTicks(clock, timestamp) { - if (!Number.isFinite(timestamp)) return 0; - if (clock.last === null || timestamp < clock.last) { - clock.last = timestamp; - clock.lag = 0; - return 0; - } - const elapsed = Math.min(timestamp - clock.last, ANIMATION_FRAME_MS * MAX_ANIMATION_TICKS); - clock.last = timestamp; - clock.lag += elapsed; - const ticks = Math.min(MAX_ANIMATION_TICKS, Math.floor((clock.lag + 1e-7) / ANIMATION_FRAME_MS)); - clock.lag -= ticks * ANIMATION_FRAME_MS; - return ticks; -} - -/// The four things the core asks the page for, as the module's imports. The -/// holder exists because the import object has to be built before the instance -/// whose memory those imports read. export function browserHost() { const host = { wasm: null }; const view = (ptr, len) => new Uint8Array(host.wasm.memory.buffer, ptr, len); diff --git a/test/gui-goldens.txt b/test/gui-goldens.txt index 5531b578..73aa1f86 100644 --- a/test/gui-goldens.txt +++ b/test/gui-goldens.txt @@ -3,8 +3,8 @@ 03-notice 4d59205fe6b89a4589e94c68521257f21b40917c0198ea20248925687663d9f5 04-context-rows-mid-thumb 7129ee318975f858c430b1e610b26ca756d5bb37553338c0dc6caab2290de2a4 05-body-selection 39103f3747aae07a12b5151e7e377430b88b96e504670ac2f2d0272197eb827b -06-tag-focus-selection 2bedec24842109aa2cc302226d53a994ff547791ad54d5781fe48d8eaac179ac -07-prompt-notice 6d6eda3f3a74d3ff048cb0e433e3794ca061e356184ff62a4878395c58482727 +06-tag-focus-selection 6a7f50e292f4ede92df24a817218818b93a01fd15fcf39467cf06c3772077834 +07-prompt-notice d5cdacd74ae6d29834f0b09a607bfce5a2ca8a549c259be50b76a57a2d2d3271 08-wide-glyph-edges 123a182b82a2a8c455291a5c89ccfa28e1f8ce9566172ca404c0b9c71dd33b48 09-collapsed-focus-tint ae66076fdfb0cab9a906b052df257deee5ba91a6274712d52a76d8a7adc5b415 10-tagbottom dd8ba0d3ecabf3420d000b0c9591a57278179cbd2a9060f849d283f3881d1847 @@ -12,5 +12,5 @@ 12-multiline-tag 1b9ccd331814dc76111470f18448b1ca192e1edc3d79ea6eee19a6a78a4b1831 13-image de18edee2ab2e0f2d4bf4162d86e3d8a1852fccfc344830491fa1a359095d4ea 14-theme-ink 6e733d4212373c90b860b2e41102ced8719b07480591b5da3905267c8358db18 -15-theme-acme-light 39f9256b7c8ed5187cd8d2cc3840db274efcecad263e519c4f9c79eb9efea19c +15-theme-acme-light e099dc1a351de8efa63ecd0ec0899636ea31b722a945eb40137a18abd87a82e2 16-debug e97ce74b7a3d4a08f6a529b1f0de111d62e5532c829a6b1633b966dd45218f55 diff --git a/test/gui_golden.py b/test/gui_golden.py index 6586af3a..1252014c 100644 --- a/test/gui_golden.py +++ b/test/gui_golden.py @@ -89,7 +89,9 @@ def run(args): name = 'gui-golden' config = root / 'config/pardes' config.mkdir(parents=True) - (config / 'init').write_text('Shell /bin/sh\nTheme forge\n') + # No linger: a dismissed message is gone, not caught part way, when a + # scene settles. + (config / 'init').write_text('Shell /bin/sh\nTheme forge\nMessageLinger 0\n') source = root / 'shapes.zig' source.write_bytes(ZIG) notes = root / 'notes.txt' diff --git a/test/pdf.zig b/test/pdf.zig index ed5d6908..93418727 100644 --- a/test/pdf.zig +++ b/test/pdf.zig @@ -1400,7 +1400,7 @@ test "PDF tint and tinted theme changes rebuild every visible raster only" { // Chrome animation reads a separate palette. Its ticks must never disturb // the target-theme tint key or ask MuPDF for the same pixels again. try std.testing.expect(p.animationActive()); - for (0..animation.Animation.transition_steps) |_| { + for (0..animation.transition_steps) |_| { p.update(.tick); _ = frame.reset(.retain_capacity); const tick_surface = try p.render(frame.allocator()); diff --git a/test/snapshot.zig b/test/snapshot.zig index bdabb56e..53553657 100644 --- a/test/snapshot.zig +++ b/test/snapshot.zig @@ -579,6 +579,9 @@ fn runScript(arena: std.mem.Allocator, exe_z: [:0]const u8, script_path: []const // views' durations (lsp_zls.zig hideTime) and the transient message row's // time of day (message.zig stamp) _ = setenv("PARDES_NOTIME", "1", 1); + // A virtual core clock: animation moves a frame per timed-out wait, + // whatever the machine's load (host_io.testClock). + _ = setenv("PARDES_TEST_CLOCK", "1", 1); // The protocol client must be HERMETIC under the harness: `.rs` files // speak to the deterministic mock when the build handed one over, and // every other spec is disabled outright — a script that opened a stray diff --git a/test/snapshots/theme.golden b/test/snapshots/theme.golden index 51bb77ff..02750663 100644 --- a/test/snapshots/theme.golden +++ b/test/snapshots/theme.golden @@ -53,178 +53,174 @@ |30: 0-0 d,#608269, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, == snap dusk grid=100x31 cursor=4,5 == style dusk grid=100x31 -|0: 0-50 #c4b7c4,#2b262f, 51-59 #fff0eb,#654053, 60-70 #c4b7c4,#2b262f, 71-99 d,#2b262f, -|1: 0-1 #ffffff,#77795e, 2-2 d,#39303a, 3-34 #d0c3d0,#39303a, 35-49 d,#39303a, 50-51 #ffffff,#77795e, 52-52 d,#2b262f, 53-84 #c4b7c4,#2b262f, 85-99 d,#2b262f, -|2: 0-1 #000000,#e7b491, 2-2 d,#39303a, 3-29 #d0c3d0,#39303a, 30-32 #a7d3c7,#39303a, 33-49 #d0c3d0,#39303a, 50-51 #ffffff,#705667, 52-52 d,#2b262f, 53-79 #c4b7c4,#2b262f, 80-82 #9cc7bb,#2b262f, 83-99 #c4b7c4,#2b262f, -|3: 0-0 d,#89697a, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#89697a, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, -|4: 0-0 d,#89697a, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#89697a, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, -|5: 0-0 d,#89697a, 1-49 d,#39323b, 50-50 d,#89697a, 51-99 d,#39323b, -|6: 0-0 d,#89697a, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#89697a, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, -|7: 0-0 d,#89697a, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#89697a, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, -|8: 0-0 d,#89697a, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#89697a, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, -|9: 0-0 d,#89697a, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#89697a, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, -|10: 0-0 d,#89697a, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#89697a, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, -|11: 0-0 d,#89697a, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#89697a, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, -|12: 0-0 d,#89697a, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#89697a, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, -|13: 0-0 d,#89697a, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#89697a, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, -|14: 0-0 d,#89697a, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#89697a, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, -|15: 0-0 d,#89697a, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#89697a, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, -|16: 0-0 d,#89697a, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#89697a, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, -|17: 0-1 #ffffff,#705667, 2-2 d,#2b262f, 3-29 #c4b7c4,#2b262f, 30-32 #9cc7bb,#2b262f, 33-49 #c4b7c4,#2b262f, 50-50 d,#89697a, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, -|18: 0-0 d,#89697a, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#89697a, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, -|19: 0-0 d,#89697a, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#89697a, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, -|20: 0-0 d,#89697a, 1-49 d,#39323b, 50-50 d,#89697a, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, -|21: 0-0 d,#89697a, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#89697a, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, -|22: 0-0 d,#89697a, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#89697a, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, -|23: 0-0 d,#89697a, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#89697a, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, -|24: 0-0 d,#89697a, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#89697a, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, -|25: 0-0 d,#89697a, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#89697a, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, -|26: 0-0 d,#89697a, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#89697a, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, -|27: 0-0 d,#89697a, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#89697a, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, -|28: 0-0 d,#89697a, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#89697a, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, -|29: 0-0 d,#89697a, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#89697a, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, -|30: 0-0 d,#89697a, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#89697a, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, +|0: 0-50 #c6b6c5,#2c2630, 51-59 #fff0eb,#654053, 60-70 #c6b6c5,#2c2630, 71-99 d,#2c2630, +|1: 0-1 #ffffff,#757a5f, 2-2 d,#3b2f3b, 3-34 #d2c2d1,#3b2f3b, 35-49 d,#3b2f3b, 50-51 #ffffff,#757a5f, 52-52 d,#2c2630, 53-84 #c6b6c5,#2c2630, 85-99 d,#2c2630, +|2: 0-1 #000000,#edb092, 2-2 d,#3b2f3b, 3-29 #d2c2d1,#3b2f3b, 30-32 #a1d5c4,#3b2f3b, 33-49 #d2c2d1,#3b2f3b, 50-51 #ffffff,#745469, 52-52 d,#2c2630, 53-79 #c6b6c5,#2c2630, 80-82 #96c9b8,#2c2630, 83-99 #c6b6c5,#2c2630, +|3: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, +|4: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, +|5: 0-0 d,#8e667c, 1-49 d,#39323b, 50-50 d,#8e667c, 51-99 d,#39323b, +|6: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, +|7: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, +|8: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, +|9: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, +|10: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, +|11: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, +|12: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, +|13: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, +|14: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, +|15: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, +|16: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, +|17: 0-1 #ffffff,#745469, 2-2 d,#2c2630, 3-29 #c6b6c5,#2c2630, 30-32 #96c9b8,#2c2630, 33-49 #c6b6c5,#2c2630, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, +|18: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, +|19: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, +|20: 0-0 d,#8e667c, 1-49 d,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, +|21: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, +|22: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, +|23: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, +|24: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, +|25: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, +|26: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, +|27: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, +|28: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, +|29: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, +|30: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, == snap ink grid=100x31 cursor=4,5 == style ink grid=100x31 -|0: 0-50 #d4dfed,#17222c, 51-59 #ffffff,#1b547c, 60-70 #d4dfed,#17222c, 71-99 d,#17222c, -|1: 0-1 #ffffff,#8c7d5b, 2-2 d,#253341, 3-34 #e0eaf5,#253341, 35-49 d,#253341, 50-51 #ffffff,#8c7d5b, 52-52 d,#17222c, 53-84 #d4dfed,#17222c, 85-99 d,#17222c, -|2: 0-1 #000000,#90d4f4, 2-2 d,#253341, 3-29 #e0eaf5,#253341, 30-32 #f6e8b9,#253341, 33-49 #e0eaf5,#253341, 50-51 #ffffff,#61758c, 52-52 d,#17222c, 53-79 #d4dfed,#17222c, 80-82 #ecdead,#17222c, 83-99 #d4dfed,#17222c, -|3: 0-0 d,#87a1bb, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#87a1bb, 51-51 d,#000000, 52-99 #f3f7fc,#000000, -|4: 0-0 d,#87a1bb, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#87a1bb, 51-51 d,#000000, 52-99 #f3f7fc,#000000, -|5: 0-0 d,#87a1bb, 1-49 d,#000000, 50-50 d,#87a1bb, 51-99 d,#000000, -|6: 0-0 d,#87a1bb, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#87a1bb, 51-51 d,#000000, 52-99 #f3f7fc,#000000, -|7: 0-0 d,#87a1bb, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#87a1bb, 51-51 d,#000000, 52-99 #f3f7fc,#000000, -|8: 0-0 d,#87a1bb, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#87a1bb, 51-51 d,#000000, 52-99 #f3f7fc,#000000, -|9: 0-0 d,#87a1bb, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#87a1bb, 51-51 d,#000000, 52-99 #f3f7fc,#000000, -|10: 0-0 d,#87a1bb, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#87a1bb, 51-51 d,#000000, 52-99 #f3f7fc,#000000, -|11: 0-0 d,#87a1bb, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#87a1bb, 51-51 d,#000000, 52-99 #f3f7fc,#000000, -|12: 0-0 d,#87a1bb, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#87a1bb, 51-51 d,#000000, 52-99 #f3f7fc,#000000, -|13: 0-0 d,#87a1bb, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#87a1bb, 51-51 d,#000000, 52-99 #f3f7fc,#000000, -|14: 0-0 d,#87a1bb, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#87a1bb, 51-51 d,#000000, 52-99 #f3f7fc,#000000, -|15: 0-0 d,#87a1bb, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#87a1bb, 51-51 d,#000000, 52-99 #f3f7fc,#000000, -|16: 0-0 d,#87a1bb, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#87a1bb, 51-51 d,#000000, 52-99 #f3f7fc,#000000, -|17: 0-1 #ffffff,#61758c, 2-2 d,#17222c, 3-29 #d4dfed,#17222c, 30-32 #ecdead,#17222c, 33-49 #d4dfed,#17222c, 50-50 d,#87a1bb, 51-51 d,#000000, 52-99 #f3f7fc,#000000, -|18: 0-0 d,#87a1bb, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#87a1bb, 51-51 d,#000000, 52-99 #f3f7fc,#000000, -|19: 0-0 d,#87a1bb, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#87a1bb, 51-51 d,#000000, 52-99 #f3f7fc,#000000, -|20: 0-0 d,#87a1bb, 1-49 d,#000000, 50-50 d,#87a1bb, 51-51 d,#000000, 52-99 #f3f7fc,#000000, -|21: 0-0 d,#87a1bb, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#87a1bb, 51-51 d,#000000, 52-99 #f3f7fc,#000000, -|22: 0-0 d,#87a1bb, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#87a1bb, 51-51 d,#000000, 52-99 #f3f7fc,#000000, -|23: 0-0 d,#87a1bb, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#87a1bb, 51-51 d,#000000, 52-99 #f3f7fc,#000000, -|24: 0-0 d,#87a1bb, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#87a1bb, 51-51 d,#000000, 52-99 #f3f7fc,#000000, -|25: 0-0 d,#87a1bb, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#87a1bb, 51-51 d,#000000, 52-99 #f3f7fc,#000000, -|26: 0-0 d,#87a1bb, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#87a1bb, 51-51 d,#000000, 52-99 #f3f7fc,#000000, -|27: 0-0 d,#87a1bb, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#87a1bb, 51-51 d,#000000, 52-99 #f3f7fc,#000000, -|28: 0-0 d,#87a1bb, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#87a1bb, 51-51 d,#000000, 52-99 #f3f7fc,#000000, -|29: 0-0 d,#87a1bb, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#87a1bb, 51-51 d,#000000, 52-99 #f3f7fc,#000000, -|30: 0-0 d,#87a1bb, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#87a1bb, 51-51 d,#000000, 52-99 #f3f7fc,#000000, +|0: 0-50 #d5e4f1,#15212c, 51-59 #ffffff,#1b547c, 60-70 #d5e4f1,#15212c, 71-99 d,#15212c, +|1: 0-1 #ffffff,#8f7d5b, 2-2 d,#223342, 3-34 #e1eef9,#223342, 35-49 d,#223342, 50-51 #ffffff,#8f7d5b, 52-52 d,#15212c, 53-84 #d5e4f1,#15212c, 85-99 d,#15212c, +|2: 0-1 #000000,#86d8ff, 2-2 d,#223342, 3-29 #e1eef9,#223342, 30-32 #ffeab8,#223342, 33-49 #e1eef9,#223342, 50-51 #ffffff,#5f7990, 52-52 d,#15212c, 53-79 #d5e4f1,#15212c, 80-82 #f5e0ac,#15212c, 83-99 #d5e4f1,#15212c, +|3: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, +|4: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, +|5: 0-0 d,#86a8c2, 1-49 d,#000000, 50-50 d,#86a8c2, 51-99 d,#000000, +|6: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, +|7: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, +|8: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, +|9: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, +|10: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, +|11: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, +|12: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, +|13: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, +|14: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, +|15: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, +|16: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, +|17: 0-1 #ffffff,#5f7990, 2-2 d,#15212c, 3-29 #d5e4f1,#15212c, 30-32 #f5e0ac,#15212c, 33-49 #d5e4f1,#15212c, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, +|18: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, +|19: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, +|20: 0-0 d,#86a8c2, 1-49 d,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, +|21: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, +|22: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, +|23: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, +|24: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, +|25: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, +|26: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, +|27: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, +|28: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, +|29: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, +|30: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, == snap paper grid=100x31 cursor=4,5 == style paper grid=100x31 -|0: 0-50 #5f6f69,#d3d6d1, 51-59 #203e31,#c5e2d0, 60-70 #5f6f69,#d3d6d1, 71-99 d,#d3d6d1, -|1: 0-1 #000000,#b89e7b, 2-2 d,#c9d3cd, 3-34 #596963,#c9d3cd, 35-49 d,#c9d3cd, 50-51 #000000,#b89e7b, 52-52 d,#d3d6d1, 53-84 #5f6f69,#d3d6d1, 85-99 d,#d3d6d1, -|2: 0-1 #ffffff,#3e7b68, 2-2 d,#c9d3cd, 3-29 #596963,#c9d3cd, 30-32 #785580,#c9d3cd, 33-49 #596963,#c9d3cd, 50-51 #000000,#8aa194, 52-52 d,#d3d6d1, 53-79 #5f6f69,#d3d6d1, 80-82 #7f5b87,#d3d6d1, 83-99 #5f6f69,#d3d6d1, -|3: 0-0 d,#869e90, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869e90, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, -|4: 0-0 d,#869e90, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869e90, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, -|5: 0-0 d,#869e90, 1-49 d,#f5f3ed, 50-50 d,#869e90, 51-99 d,#f5f3ed, -|6: 0-0 d,#869e90, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869e90, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, -|7: 0-0 d,#869e90, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869e90, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, -|8: 0-0 d,#869e90, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869e90, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, -|9: 0-0 d,#869e90, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869e90, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, -|10: 0-0 d,#869e90, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869e90, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, -|11: 0-0 d,#869e90, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869e90, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, -|12: 0-0 d,#869e90, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869e90, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, -|13: 0-0 d,#869e90, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869e90, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, -|14: 0-0 d,#869e90, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869e90, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, -|15: 0-0 d,#869e90, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869e90, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, -|16: 0-0 d,#869e90, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869e90, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, -|17: 0-1 #000000,#8aa194, 2-2 d,#d3d6d1, 3-29 #5f6f69,#d3d6d1, 30-32 #7f5b87,#d3d6d1, 33-49 #5f6f69,#d3d6d1, 50-50 d,#869e90, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, -|18: 0-0 d,#869e90, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869e90, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, -|19: 0-0 d,#869e90, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869e90, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, -|20: 0-0 d,#869e90, 1-49 d,#f5f3ed, 50-50 d,#869e90, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, -|21: 0-0 d,#869e90, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869e90, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, -|22: 0-0 d,#869e90, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869e90, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, -|23: 0-0 d,#869e90, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869e90, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, -|24: 0-0 d,#869e90, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869e90, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, -|25: 0-0 d,#869e90, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869e90, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, -|26: 0-0 d,#869e90, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869e90, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, -|27: 0-0 d,#869e90, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869e90, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, -|28: 0-0 d,#869e90, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869e90, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, -|29: 0-0 d,#869e90, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869e90, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, -|30: 0-0 d,#869e90, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869e90, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, +|0: 0-50 #52625a,#e8eae3, 51-59 #203e31,#c5e2d0, 60-70 #52625a,#e8eae3, 71-99 d,#e8eae3, +|1: 0-1 #000000,#bda27e, 2-2 d,#dce5dc, 3-34 #4a5a52,#dce5dc, 35-49 d,#dce5dc, 50-51 #000000,#bda27e, 52-52 d,#e8eae3, 53-84 #52625a,#e8eae3, 85-99 d,#e8eae3, +|2: 0-1 #ffffff,#367157, 2-2 d,#dce5dc, 3-29 #4a5a52,#dce5dc, 30-32 #69447a,#dce5dc, 33-49 #4a5a52,#dce5dc, 50-51 #000000,#8fa594, 52-52 d,#e8eae3, 53-79 #52625a,#e8eae3, 80-82 #724c83,#e8eae3, 83-99 #52625a,#e8eae3, +|3: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, +|4: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, +|5: 0-0 d,#869d8a, 1-49 d,#f5f3ed, 50-50 d,#869d8a, 51-99 d,#f5f3ed, +|6: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, +|7: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, +|8: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, +|9: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, +|10: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, +|11: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, +|12: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, +|13: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, +|14: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, +|15: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, +|16: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, +|17: 0-1 #000000,#8fa594, 2-2 d,#e8eae3, 3-29 #52625a,#e8eae3, 30-32 #724c83,#e8eae3, 33-49 #52625a,#e8eae3, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, +|18: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, +|19: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, +|20: 0-0 d,#869d8a, 1-49 d,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, +|21: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, +|22: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, +|23: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, +|24: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, +|25: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, +|26: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, +|27: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, +|28: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, +|29: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, +|30: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, == snap daybreak grid=100x31 cursor=4,5 == style daybreak grid=100x31 -|0: 0-50 #2d495e,#edf1f5, 51-59 #10364f,#b9dffb, 60-70 #2d495e,#edf1f5, 71-99 d,#edf1f5, -|1: 0-1 #000000,#b89b7a, 2-2 d,#dfeaf1, 3-34 #254156,#dfeaf1, 35-49 d,#dfeaf1, 50-51 #000000,#b89b7a, 52-52 d,#edf1f5, 53-84 #2d495e,#edf1f5, 85-99 d,#edf1f5, -|2: 0-1 #ffffff,#056392, 2-2 d,#dfeaf1, 3-29 #254156,#dfeaf1, 30-32 #542862,#dfeaf1, 33-49 #254156,#dfeaf1, 50-51 #000000,#7d9fb4, 52-52 d,#edf1f5, 53-79 #2d495e,#edf1f5, 80-82 #5c306a,#edf1f5, 83-99 #2d495e,#edf1f5, -|3: 0-0 d,#69879e, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#69879e, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, -|4: 0-0 d,#69879e, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#69879e, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, -|5: 0-0 d,#69879e, 1-49 d,#ffffff, 50-50 d,#69879e, 51-99 d,#ffffff, -|6: 0-0 d,#69879e, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#69879e, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, -|7: 0-0 d,#69879e, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#69879e, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, -|8: 0-0 d,#69879e, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#69879e, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, -|9: 0-0 d,#69879e, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#69879e, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, -|10: 0-0 d,#69879e, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#69879e, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, -|11: 0-0 d,#69879e, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#69879e, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, -|12: 0-0 d,#69879e, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#69879e, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, -|13: 0-0 d,#69879e, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#69879e, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, -|14: 0-0 d,#69879e, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#69879e, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, -|15: 0-0 d,#69879e, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#69879e, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, -|16: 0-0 d,#69879e, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#69879e, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, -|17: 0-1 #000000,#7d9fb4, 2-2 d,#edf1f5, 3-29 #2d495e,#edf1f5, 30-32 #5c306a,#edf1f5, 33-49 #2d495e,#edf1f5, 50-50 d,#69879e, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, -|18: 0-0 d,#69879e, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#69879e, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, -|19: 0-0 d,#69879e, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#69879e, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, -|20: 0-0 d,#69879e, 1-49 d,#ffffff, 50-50 d,#69879e, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, -|21: 0-0 d,#69879e, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#69879e, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, -|22: 0-0 d,#69879e, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#69879e, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, -|23: 0-0 d,#69879e, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#69879e, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, -|24: 0-0 d,#69879e, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#69879e, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, -|25: 0-0 d,#69879e, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#69879e, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, -|26: 0-0 d,#69879e, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#69879e, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, -|27: 0-0 d,#69879e, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#69879e, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, -|28: 0-0 d,#69879e, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#69879e, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, -|29: 0-0 d,#69879e, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#69879e, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, -|30: 0-0 d,#69879e, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#69879e, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, +|0: 0-50 #29465e,#edf2f7, 51-59 #10364f,#b9dffb, 60-70 #29465e,#edf2f7, 71-99 d,#edf2f7, +|1: 0-1 #000000,#b79a7a, 2-2 d,#dfeaf3, 3-34 #213e56,#dfeaf3, 35-49 d,#dfeaf3, 50-51 #000000,#b79a7a, 52-52 d,#edf2f7, 53-84 #29465e,#edf2f7, 85-99 d,#edf2f7, +|2: 0-1 #ffffff,#006198, 2-2 d,#dfeaf3, 3-29 #213e56,#dfeaf3, 30-32 #52255f,#dfeaf3, 33-49 #213e56,#dfeaf3, 50-51 #000000,#7b9eb7, 52-52 d,#edf2f7, 53-79 #29465e,#edf2f7, 80-82 #5a2d67,#edf2f7, 83-99 #29465e,#edf2f7, +|3: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, +|4: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, +|5: 0-0 d,#6685a0, 1-49 d,#ffffff, 50-50 d,#6685a0, 51-99 d,#ffffff, +|6: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, +|7: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, +|8: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, +|9: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, +|10: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, +|11: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, +|12: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, +|13: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, +|14: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, +|15: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, +|16: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, +|17: 0-1 #000000,#7b9eb7, 2-2 d,#edf2f7, 3-29 #29465e,#edf2f7, 30-32 #5a2d67,#edf2f7, 33-49 #29465e,#edf2f7, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, +|18: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, +|19: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, +|20: 0-0 d,#6685a0, 1-49 d,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, +|21: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, +|22: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, +|23: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, +|24: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, +|25: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, +|26: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, +|27: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, +|28: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, +|29: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, +|30: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, == snap atelier grid=100x31 cursor=4,5 == style atelier grid=100x31 -|0: 0-50 #3b5d56,#e5f0ee, 51-59 #35492d,#edf2a5, 60-70 #3b5d56,#e5f0ee, 71-99 d,#e5f0ee, -|1: 0-1 #000000,#bca380, 2-2 d,#d4e7e1, 3-34 #33554e,#d4e7e1, 35-49 d,#d4e7e1, 50-51 #000000,#bca380, 52-52 d,#e5f0ee, 53-84 #3b5d56,#e5f0ee, 85-99 d,#e5f0ee, -|2: 0-1 #ffffff,#317f70, 2-2 d,#d4e7e1, 3-29 #33554e,#d4e7e1, 30-32 #633c6a,#d4e7e1, 33-49 #33554e,#d4e7e1, 50-51 #000000,#92b7a4, 52-52 d,#e5f0ee, 53-79 #3b5d56,#e5f0ee, 80-82 #6c4473,#e5f0ee, 83-99 #3b5d56,#e5f0ee, -|3: 0-0 d,#99aa7a, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#99aa7a, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, -|4: 0-0 d,#99aa7a, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#99aa7a, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, -|5: 0-0 d,#99aa7a, 1-49 d,#fffdeb, 50-50 d,#99aa7a, 51-99 d,#fffdeb, -|6: 0-0 d,#99aa7a, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#99aa7a, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, -|7: 0-0 d,#99aa7a, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#99aa7a, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, -|8: 0-0 d,#99aa7a, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#99aa7a, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, -|9: 0-0 d,#99aa7a, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#99aa7a, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, -|10: 0-0 d,#99aa7a, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#99aa7a, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, -|11: 0-0 d,#99aa7a, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#99aa7a, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, -|12: 0-0 d,#99aa7a, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#99aa7a, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, -|13: 0-0 d,#99aa7a, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#99aa7a, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, -|14: 0-0 d,#99aa7a, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#99aa7a, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, -|15: 0-0 d,#99aa7a, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#99aa7a, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, -|16: 0-0 d,#99aa7a, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#99aa7a, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, -|17: 0-1 #000000,#92b7a4, 2-2 d,#e5f0ee, 3-29 #3b5d56,#e5f0ee, 30-32 #6c4473,#e5f0ee, 33-49 #3b5d56,#e5f0ee, 50-50 d,#99aa7a, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, -|18: 0-0 d,#99aa7a, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#99aa7a, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, -|19: 0-0 d,#99aa7a, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#99aa7a, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, -|20: 0-0 d,#99aa7a, 1-49 d,#fffdeb, 50-50 d,#99aa7a, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, -|21: 0-0 d,#99aa7a, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#99aa7a, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, -|22: 0-0 d,#99aa7a, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#99aa7a, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, -|23: 0-0 d,#99aa7a, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#99aa7a, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, -|24: 0-0 d,#99aa7a, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#99aa7a, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, -|25: 0-0 d,#99aa7a, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#99aa7a, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, -|26: 0-0 d,#99aa7a, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#99aa7a, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, -|27: 0-0 d,#99aa7a, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#99aa7a, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, -|28: 0-0 d,#99aa7a, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#99aa7a, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, -|29: 0-0 d,#99aa7a, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#99aa7a, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, -|30: 0-0 d,#99aa7a, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#99aa7a, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, -== snap colors-off grid=100x31 cursor=4,5 -|3: ls Colors ls -== style colors-off grid=100x31 |0: 0-50 #3d6055,#e4f0ed, 51-59 #35492d,#edf2a5, 60-70 #3d6055,#e4f0ed, 71-99 d,#e4f0ed, |1: 0-1 #000000,#bca481, 2-2 d,#d3e7df, 3-34 #35584d,#d3e7df, 35-49 d,#d3e7df, 50-51 #000000,#bca481, 52-52 d,#e4f0ed, 53-84 #3d6055,#e4f0ed, 85-99 d,#e4f0ed, |2: 0-1 #ffffff,#36826b, 2-2 d,#d3e7df, 3-29 #35584d,#d3e7df, 30-32 #653f6b,#d3e7df, 33-49 #35584d,#d3e7df, 50-51 #000000,#95baa2, 52-52 d,#e4f0ed, 53-79 #3d6055,#e4f0ed, 80-82 #6e4774,#e4f0ed, 83-99 #3d6055,#e4f0ed, +|3: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, +|4: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, +|5: 0-0 d,#9fae76, 1-49 d,#fffdeb, 50-50 d,#9fae76, 51-99 d,#fffdeb, +|6: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, +|7: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, +|8: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, +|9: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, +|10: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, +|11: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, +|12: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, +|13: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, +|14: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, +|15: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, +|16: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, +|17: 0-1 #000000,#95baa2, 2-2 d,#e4f0ed, 3-29 #3d6055,#e4f0ed, 30-32 #6e4774,#e4f0ed, 33-49 #3d6055,#e4f0ed, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, +|18: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, +|19: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, +|20: 0-0 d,#9fae76, 1-49 d,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, +|21: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, +|22: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, +|23: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, +|24: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, +|25: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, +|26: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, +|27: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, +|28: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, +|29: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, +|30: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, +== snap colors-off grid=100x31 cursor=4,5 +|3: ls Colors ls +== style colors-off grid=100x31 |3: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-3 #293f35,#fffdeb, 4-41 d,#fffdeb, 42-42 d,#e4f0ed, 43-48 #3d6055,#e4f0ed, 49-49 d,#e4f0ed, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-53 #293f35,#fffdeb, 54-99 d,#fffdeb, |4: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-6 #293f35,#fffdeb, 7-49 d,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-56 #293f35,#fffdeb, 57-99 d,#fffdeb, -|5: 0-0 d,#9fae76, 1-49 d,#fffdeb, 50-50 d,#9fae76, 51-99 d,#fffdeb, |6: 0-0 d,#9fae76, 1-49 d,#fffdeb, 50-50 d,#9fae76, 51-99 d,#fffdeb, |7: 0-0 d,#9fae76, 1-49 d,#fffdeb, 50-50 d,#9fae76, 51-99 d,#fffdeb, |8: 0-0 d,#9fae76, 1-49 d,#fffdeb, 50-50 d,#9fae76, 51-99 d,#fffdeb, @@ -253,240 +249,240 @@ == snap forge grid=100x31 cursor=4,5 |3: ls ls == style forge grid=100x31 -|0: 0-50 #b7bfc6,#35393e, 51-59 #e2eef4,#1c5172, 60-70 #b7bfc6,#35393e, 71-99 d,#35393e, -|1: 0-1 #ffffff,#727f6a, 2-2 d,#3e4650, 3-34 #c1c9d0,#3e4650, 35-49 d,#3e4650, 50-51 #ffffff,#727f6a, 52-52 d,#35393e, 53-84 #b7bfc6,#35393e, 85-99 d,#35393e, -|2: 0-1 #000000,#92bde9, 2-2 d,#3e4650, 3-29 #c1c9d0,#3e4650, 30-32 #e9bda1,#3e4650, 33-49 #c1c9d0,#3e4650, 50-51 #ffffff,#5b6d7f, 52-52 d,#35393e, 53-79 #b7bfc6,#35393e, 80-82 #dfb397,#35393e, 83-99 #b7bfc6,#35393e, -|3: 0-0 d,#758592, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#758592, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, -|4: 0-0 d,#758592, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#758592, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, -|5: 0-0 d,#758592, 1-49 d,#0c0c0e, 50-50 d,#758592, 51-99 d,#0c0c0e, -|6: 0-0 d,#758592, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#758592, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, -|7: 0-0 d,#758592, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#758592, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, -|8: 0-0 d,#758592, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#758592, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, -|9: 0-0 d,#758592, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#758592, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, -|10: 0-0 d,#758592, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#758592, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, -|11: 0-0 d,#758592, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#758592, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, -|12: 0-0 d,#758592, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#758592, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, -|13: 0-0 d,#758592, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#758592, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, -|14: 0-0 d,#758592, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#758592, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, -|15: 0-0 d,#758592, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#758592, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, -|16: 0-0 d,#758592, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#758592, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, -|17: 0-1 #ffffff,#5b6d7f, 2-2 d,#35393e, 3-29 #b7bfc6,#35393e, 30-32 #dfb397,#35393e, 33-49 #b7bfc6,#35393e, 50-50 d,#758592, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, -|18: 0-0 d,#758592, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#758592, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, -|19: 0-0 d,#758592, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#758592, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, -|20: 0-0 d,#758592, 1-49 d,#0c0c0e, 50-50 d,#758592, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, -|21: 0-0 d,#758592, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#758592, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, -|22: 0-0 d,#758592, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#758592, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, -|23: 0-0 d,#758592, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#758592, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, -|24: 0-0 d,#758592, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#758592, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, -|25: 0-0 d,#758592, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#758592, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, -|26: 0-0 d,#758592, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#758592, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, -|27: 0-0 d,#758592, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#758592, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, -|28: 0-0 d,#758592, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#758592, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, -|29: 0-0 d,#758592, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#758592, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, -|30: 0-0 d,#758592, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#758592, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, +|0: 0-50 #c5c9d2,#22252b, 51-59 #e2eef4,#1c5172, 60-70 #c5c9d2,#22252b, 71-99 d,#22252b, +|1: 0-1 #ffffff,#6a7b67, 2-2 d,#2d3440, 3-34 #d1d5de,#2d3440, 35-49 d,#2d3440, 50-51 #ffffff,#6a7b67, 52-52 d,#22252b, 53-84 #c5c9d2,#22252b, 85-99 d,#22252b, +|2: 0-1 #000000,#9cc3f7, 2-2 d,#2d3440, 3-29 #d1d5de,#2d3440, 30-32 #f8cba7,#2d3440, 33-49 #d1d5de,#2d3440, 50-51 #ffffff,#55647b, 52-52 d,#22252b, 53-79 #c5c9d2,#22252b, 80-82 #ecbf9b,#22252b, 83-99 #c5c9d2,#22252b, +|3: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, +|4: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, +|5: 0-0 d,#708095, 1-49 d,#0c0c0e, 50-50 d,#708095, 51-99 d,#0c0c0e, +|6: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, +|7: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, +|8: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, +|9: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, +|10: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, +|11: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, +|12: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, +|13: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, +|14: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, +|15: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, +|16: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, +|17: 0-1 #ffffff,#55647b, 2-2 d,#22252b, 3-29 #c5c9d2,#22252b, 30-32 #ecbf9b,#22252b, 33-49 #c5c9d2,#22252b, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, +|18: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, +|19: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, +|20: 0-0 d,#708095, 1-49 d,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, +|21: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, +|22: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, +|23: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, +|24: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, +|25: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, +|26: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, +|27: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, +|28: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, +|29: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, +|30: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, == snap lagoon grid=100x31 cursor=4,5 == style lagoon grid=100x31 -|0: 0-50 #bcd6cc,#28413d, 51-59 #e6f2f3,#325f6c, 60-70 #bcd6cc,#28413d, 71-99 d,#28413d, -|1: 0-1 #ffffff,#957763, 2-2 d,#33544c, 3-34 #c8e2d8,#33544c, 35-49 d,#33544c, 50-51 #ffffff,#957763, 52-52 d,#28413d, 53-84 #bcd6cc,#28413d, 85-99 d,#28413d, -|2: 0-1 #000000,#84cebd, 2-2 d,#33544c, 3-29 #c8e2d8,#33544c, 30-32 #f1cef0,#33544c, 33-49 #c8e2d8,#33544c, 50-51 #ffffff,#548073, 52-52 d,#28413d, 53-79 #bcd6cc,#28413d, 80-82 #e5c2e4,#28413d, 83-99 #bcd6cc,#28413d, -|3: 0-0 d,#6e988c, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e988c, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, -|4: 0-0 d,#6e988c, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e988c, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, -|5: 0-0 d,#6e988c, 1-49 d,#34464c, 50-50 d,#6e988c, 51-99 d,#34464c, -|6: 0-0 d,#6e988c, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e988c, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, -|7: 0-0 d,#6e988c, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e988c, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, -|8: 0-0 d,#6e988c, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e988c, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, -|9: 0-0 d,#6e988c, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e988c, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, -|10: 0-0 d,#6e988c, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e988c, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, -|11: 0-0 d,#6e988c, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e988c, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, -|12: 0-0 d,#6e988c, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e988c, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, -|13: 0-0 d,#6e988c, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e988c, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, -|14: 0-0 d,#6e988c, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e988c, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, -|15: 0-0 d,#6e988c, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e988c, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, -|16: 0-0 d,#6e988c, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e988c, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, -|17: 0-1 #ffffff,#548073, 2-2 d,#28413d, 3-29 #bcd6cc,#28413d, 30-32 #e5c2e4,#28413d, 33-49 #bcd6cc,#28413d, 50-50 d,#6e988c, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, -|18: 0-0 d,#6e988c, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e988c, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, -|19: 0-0 d,#6e988c, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e988c, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, -|20: 0-0 d,#6e988c, 1-49 d,#34464c, 50-50 d,#6e988c, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, -|21: 0-0 d,#6e988c, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e988c, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, -|22: 0-0 d,#6e988c, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e988c, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, -|23: 0-0 d,#6e988c, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e988c, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, -|24: 0-0 d,#6e988c, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e988c, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, -|25: 0-0 d,#6e988c, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e988c, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, -|26: 0-0 d,#6e988c, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e988c, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, -|27: 0-0 d,#6e988c, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e988c, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, -|28: 0-0 d,#6e988c, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e988c, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, -|29: 0-0 d,#6e988c, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e988c, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, -|30: 0-0 d,#6e988c, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e988c, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, +|0: 0-50 #bbd7cb,#29443f, 51-59 #e6f2f3,#325f6c, 60-70 #bbd7cb,#29443f, 71-99 d,#29443f, +|1: 0-1 #ffffff,#9a7662, 2-2 d,#34574d, 3-34 #c7e3d7,#34574d, 35-49 d,#34574d, 50-51 #ffffff,#9a7662, 52-52 d,#29443f, 53-84 #bbd7cb,#29443f, 85-99 d,#29443f, +|2: 0-1 #000000,#81cfb7, 2-2 d,#34574d, 3-29 #c7e3d7,#34574d, 30-32 #f0cef8,#34574d, 33-49 #c7e3d7,#34574d, 50-51 #ffffff,#548372, 52-52 d,#29443f, 53-79 #bbd7cb,#29443f, 80-82 #e4c2ec,#29443f, 83-99 #bbd7cb,#29443f, +|3: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, +|4: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, +|5: 0-0 d,#6e9b8b, 1-49 d,#34464c, 50-50 d,#6e9b8b, 51-99 d,#34464c, +|6: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, +|7: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, +|8: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, +|9: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, +|10: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, +|11: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, +|12: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, +|13: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, +|14: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, +|15: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, +|16: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, +|17: 0-1 #ffffff,#548372, 2-2 d,#29443f, 3-29 #bbd7cb,#29443f, 30-32 #e4c2ec,#29443f, 33-49 #bbd7cb,#29443f, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, +|18: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, +|19: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, +|20: 0-0 d,#6e9b8b, 1-49 d,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, +|21: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, +|22: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, +|23: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, +|24: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, +|25: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, +|26: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, +|27: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, +|28: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, +|29: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, +|30: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, == snap solarium grid=100x31 cursor=4,5 == style solarium grid=100x31 -|0: 0-50 #c6d4be,#144043, 51-59 #eee8d5,#1f5c64, 60-70 #c6d4be,#144043, 71-99 d,#144043, -|1: 0-1 #ffffff,#3e7a94, 2-2 d,#234f50, 3-34 #d2e0ca,#234f50, 35-49 d,#234f50, 50-51 #ffffff,#3e7a94, 52-52 d,#144043, 53-84 #c6d4be,#144043, 85-99 d,#144043, -|2: 0-1 #000000,#d7c36e, 2-2 d,#234f50, 3-29 #d2e0ca,#234f50, 30-32 #f5cded,#234f50, 33-49 #d2e0ca,#234f50, 50-51 #ffffff,#477f76, 52-52 d,#144043, 53-79 #c6d4be,#144043, 80-82 #e9c1e1,#144043, 83-99 #c6d4be,#144043, -|3: 0-0 d,#66978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#66978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, -|4: 0-0 d,#66978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#66978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, -|5: 0-0 d,#66978c, 1-49 d,#173e45, 50-50 d,#66978c, 51-99 d,#173e45, -|6: 0-0 d,#66978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#66978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, -|7: 0-0 d,#66978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#66978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, -|8: 0-0 d,#66978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#66978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, -|9: 0-0 d,#66978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#66978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, -|10: 0-0 d,#66978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#66978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, -|11: 0-0 d,#66978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#66978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, -|12: 0-0 d,#66978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#66978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, -|13: 0-0 d,#66978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#66978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, -|14: 0-0 d,#66978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#66978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, -|15: 0-0 d,#66978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#66978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, -|16: 0-0 d,#66978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#66978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, -|17: 0-1 #ffffff,#477f76, 2-2 d,#144043, 3-29 #c6d4be,#144043, 30-32 #e9c1e1,#144043, 33-49 #c6d4be,#144043, 50-50 d,#66978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, -|18: 0-0 d,#66978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#66978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, -|19: 0-0 d,#66978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#66978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, -|20: 0-0 d,#66978c, 1-49 d,#173e45, 50-50 d,#66978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, -|21: 0-0 d,#66978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#66978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, -|22: 0-0 d,#66978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#66978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, -|23: 0-0 d,#66978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#66978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, -|24: 0-0 d,#66978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#66978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, -|25: 0-0 d,#66978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#66978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, -|26: 0-0 d,#66978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#66978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, -|27: 0-0 d,#66978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#66978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, -|28: 0-0 d,#66978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#66978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, -|29: 0-0 d,#66978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#66978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, -|30: 0-0 d,#66978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#66978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, +|0: 0-50 #c7d4bd,#123f43, 51-59 #eee8d5,#1f5c64, 60-70 #c7d4bd,#123f43, 71-99 d,#123f43, +|1: 0-1 #ffffff,#347a9a, 2-2 d,#214e50, 3-34 #d3e0c9,#214e50, 35-49 d,#214e50, 50-51 #ffffff,#347a9a, 52-52 d,#123f43, 53-84 #c7d4bd,#123f43, 85-99 d,#123f43, +|2: 0-1 #000000,#e1c266, 2-2 d,#214e50, 3-29 #d3e0c9,#214e50, 30-32 #f5cdec,#214e50, 33-49 #d3e0c9,#214e50, 50-51 #ffffff,#467f76, 52-52 d,#123f43, 53-79 #c7d4bd,#123f43, 80-82 #e9c1e0,#123f43, 83-99 #c7d4bd,#123f43, +|3: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, +|4: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, +|5: 0-0 d,#65978c, 1-49 d,#173e45, 50-50 d,#65978c, 51-99 d,#173e45, +|6: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, +|7: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, +|8: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, +|9: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, +|10: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, +|11: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, +|12: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, +|13: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, +|14: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, +|15: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, +|16: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, +|17: 0-1 #ffffff,#467f76, 2-2 d,#123f43, 3-29 #c7d4bd,#123f43, 30-32 #e9c1e0,#123f43, 33-49 #c7d4bd,#123f43, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, +|18: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, +|19: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, +|20: 0-0 d,#65978c, 1-49 d,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, +|21: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, +|22: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, +|23: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, +|24: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, +|25: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, +|26: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, +|27: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, +|28: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, +|29: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, +|30: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, == snap spectrum grid=100x31 cursor=4,5 == style spectrum grid=100x31 -|0: 0-50 #dbd8bb,#373932, 51-59 #fcfcfa,#513f60, 60-70 #dbd8bb,#373932, 71-99 d,#373932, -|1: 0-1 #ffffff,#6a6796, 2-2 d,#494839, 3-34 #e7e4c7,#494839, 35-49 d,#494839, 50-51 #ffffff,#6a6796, 52-52 d,#373932, 53-84 #dbd8bb,#373932, 85-99 d,#373932, -|2: 0-1 #000000,#fcd666, 2-2 d,#494839, 3-29 #e7e4c7,#494839, 30-32 #dae1fd,#494839, 33-49 #e7e4c7,#494839, 50-51 #ffffff,#8a8453, 52-52 d,#373932, 53-79 #dbd8bb,#373932, 80-82 #c9d5fc,#373932, 83-99 #dbd8bb,#373932, -|3: 0-0 d,#8f8e72, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#8f8e72, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, -|4: 0-0 d,#8f8e72, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#8f8e72, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, -|5: 0-0 d,#8f8e72, 1-49 d,#100e11, 50-50 d,#8f8e72, 51-99 d,#100e11, -|6: 0-0 d,#8f8e72, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#8f8e72, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, -|7: 0-0 d,#8f8e72, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#8f8e72, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, -|8: 0-0 d,#8f8e72, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#8f8e72, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, -|9: 0-0 d,#8f8e72, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#8f8e72, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, -|10: 0-0 d,#8f8e72, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#8f8e72, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, -|11: 0-0 d,#8f8e72, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#8f8e72, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, -|12: 0-0 d,#8f8e72, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#8f8e72, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, -|13: 0-0 d,#8f8e72, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#8f8e72, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, -|14: 0-0 d,#8f8e72, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#8f8e72, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, -|15: 0-0 d,#8f8e72, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#8f8e72, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, -|16: 0-0 d,#8f8e72, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#8f8e72, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, -|17: 0-1 #ffffff,#8a8453, 2-2 d,#373932, 3-29 #dbd8bb,#373932, 30-32 #c9d5fc,#373932, 33-49 #dbd8bb,#373932, 50-50 d,#8f8e72, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, -|18: 0-0 d,#8f8e72, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#8f8e72, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, -|19: 0-0 d,#8f8e72, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#8f8e72, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, -|20: 0-0 d,#8f8e72, 1-49 d,#100e11, 50-50 d,#8f8e72, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, -|21: 0-0 d,#8f8e72, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#8f8e72, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, -|22: 0-0 d,#8f8e72, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#8f8e72, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, -|23: 0-0 d,#8f8e72, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#8f8e72, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, -|24: 0-0 d,#8f8e72, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#8f8e72, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, -|25: 0-0 d,#8f8e72, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#8f8e72, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, -|26: 0-0 d,#8f8e72, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#8f8e72, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, -|27: 0-0 d,#8f8e72, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#8f8e72, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, -|28: 0-0 d,#8f8e72, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#8f8e72, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, -|29: 0-0 d,#8f8e72, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#8f8e72, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, -|30: 0-0 d,#8f8e72, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#8f8e72, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, +|0: 0-50 #ddd8bb,#3b3830, 51-59 #fcfcfa,#513f60, 60-70 #ddd8bb,#3b3830, 71-99 d,#3b3830, +|1: 0-1 #ffffff,#706596, 2-2 d,#4d4736, 3-34 #e9e4c7,#4d4736, 35-49 d,#4d4736, 50-51 #ffffff,#706596, 52-52 d,#3b3830, 53-84 #ddd8bb,#3b3830, 85-99 d,#3b3830, +|2: 0-1 #000000,#ffd866, 2-2 d,#4d4736, 3-29 #e9e4c7,#4d4736, 30-32 #d7e3ff,#4d4736, 33-49 #e9e4c7,#4d4736, 50-51 #ffffff,#92844f, 52-52 d,#3b3830, 53-79 #ddd8bb,#3b3830, 80-82 #c5d7ff,#3b3830, 83-99 #ddd8bb,#3b3830, +|3: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, +|4: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, +|5: 0-0 d,#948d6f, 1-49 d,#100e11, 50-50 d,#948d6f, 51-99 d,#100e11, +|6: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, +|7: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, +|8: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, +|9: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, +|10: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, +|11: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, +|12: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, +|13: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, +|14: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, +|15: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, +|16: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, +|17: 0-1 #ffffff,#92844f, 2-2 d,#3b3830, 3-29 #ddd8bb,#3b3830, 30-32 #c5d7ff,#3b3830, 33-49 #ddd8bb,#3b3830, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, +|18: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, +|19: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, +|20: 0-0 d,#948d6f, 1-49 d,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, +|21: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, +|22: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, +|23: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, +|24: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, +|25: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, +|26: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, +|27: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, +|28: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, +|29: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, +|30: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, == snap harvest grid=100x31 cursor=4,5 == style harvest grid=100x31 -|0: 0-50 #d6d8b8,#353b2e, 51-59 #f3f2cc,#4f5834, 60-70 #d6d8b8,#353b2e, 71-99 d,#353b2e, -|1: 0-1 #ffffff,#975948, 2-2 d,#434c38, 3-34 #e2e4c4,#434c38, 35-49 d,#434c38, 50-51 #ffffff,#975948, 52-52 d,#353b2e, 53-84 #d6d8b8,#353b2e, 85-99 d,#353b2e, -|2: 0-1 #000000,#dcc080, 2-2 d,#434c38, 3-29 #e2e4c4,#434c38, 30-32 #c7e7f8,#434c38, 33-49 #e2e4c4,#434c38, 50-51 #ffffff,#7d7f4e, 52-52 d,#353b2e, 53-79 #d6d8b8,#353b2e, 80-82 #bbdbed,#353b2e, 83-99 #d6d8b8,#353b2e, -|3: 0-0 d,#898e60, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#898e60, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, -|4: 0-0 d,#898e60, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#898e60, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, -|5: 0-0 d,#898e60, 1-49 d,#100e0b, 50-50 d,#898e60, 51-99 d,#100e0b, -|6: 0-0 d,#898e60, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#898e60, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, -|7: 0-0 d,#898e60, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#898e60, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, -|8: 0-0 d,#898e60, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#898e60, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, -|9: 0-0 d,#898e60, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#898e60, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, -|10: 0-0 d,#898e60, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#898e60, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, -|11: 0-0 d,#898e60, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#898e60, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, -|12: 0-0 d,#898e60, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#898e60, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, -|13: 0-0 d,#898e60, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#898e60, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, -|14: 0-0 d,#898e60, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#898e60, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, -|15: 0-0 d,#898e60, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#898e60, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, -|16: 0-0 d,#898e60, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#898e60, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, -|17: 0-1 #ffffff,#7d7f4e, 2-2 d,#353b2e, 3-29 #d6d8b8,#353b2e, 30-32 #bbdbed,#353b2e, 33-49 #d6d8b8,#353b2e, 50-50 d,#898e60, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, -|18: 0-0 d,#898e60, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#898e60, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, -|19: 0-0 d,#898e60, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#898e60, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, -|20: 0-0 d,#898e60, 1-49 d,#100e0b, 50-50 d,#898e60, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, -|21: 0-0 d,#898e60, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#898e60, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, -|22: 0-0 d,#898e60, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#898e60, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, -|23: 0-0 d,#898e60, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#898e60, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, -|24: 0-0 d,#898e60, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#898e60, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, -|25: 0-0 d,#898e60, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#898e60, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, -|26: 0-0 d,#898e60, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#898e60, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, -|27: 0-0 d,#898e60, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#898e60, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, -|28: 0-0 d,#898e60, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#898e60, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, -|29: 0-0 d,#898e60, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#898e60, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, -|30: 0-0 d,#898e60, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#898e60, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, +|0: 0-50 #d5d8b8,#343b2e, 51-59 #f3f2cc,#4f5834, 60-70 #d5d8b8,#343b2e, 71-99 d,#343b2e, +|1: 0-1 #ffffff,#9b583f, 2-2 d,#424d38, 3-34 #e1e4c4,#424d38, 35-49 d,#424d38, 50-51 #ffffff,#9b583f, 52-52 d,#343b2e, 53-84 #d5d8b8,#343b2e, 85-99 d,#343b2e, +|2: 0-1 #000000,#d8bd83, 2-2 d,#424d38, 3-29 #e1e4c4,#424d38, 30-32 #c5e7f7,#424d38, 33-49 #e1e4c4,#424d38, 50-51 #ffffff,#7b7e4e, 52-52 d,#343b2e, 53-79 #d5d8b8,#343b2e, 80-82 #badbeb,#343b2e, 83-99 #d5d8b8,#343b2e, +|3: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, +|4: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, +|5: 0-0 d,#888e5e, 1-49 d,#100e0b, 50-50 d,#888e5e, 51-99 d,#100e0b, +|6: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, +|7: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, +|8: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, +|9: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, +|10: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, +|11: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, +|12: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, +|13: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, +|14: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, +|15: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, +|16: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, +|17: 0-1 #ffffff,#7b7e4e, 2-2 d,#343b2e, 3-29 #d5d8b8,#343b2e, 30-32 #badbeb,#343b2e, 33-49 #d5d8b8,#343b2e, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, +|18: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, +|19: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, +|20: 0-0 d,#888e5e, 1-49 d,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, +|21: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, +|22: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, +|23: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, +|24: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, +|25: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, +|26: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, +|27: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, +|28: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, +|29: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, +|30: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, == snap clay grid=100x31 cursor=4,5 == style clay grid=100x31 -|0: 0-50 #d7d6b6,#343f35, 51-59 #fbf1c7,#505c38, 60-70 #d7d6b6,#343f35, 71-99 d,#343f35, -|1: 0-1 #ffffff,#895d61, 2-2 d,#424f3f, 3-34 #e3e2c2,#424f3f, 35-49 d,#424f3f, 50-51 #ffffff,#895d61, 52-52 d,#343f35, 53-84 #d7d6b6,#343f35, 85-99 d,#343f35, -|2: 0-1 #000000,#92c776, 2-2 d,#424f3f, 3-29 #e3e2c2,#424f3f, 30-32 #b7eced,#424f3f, 33-49 #e3e2c2,#424f3f, 50-51 #ffffff,#6a7c4d, 52-52 d,#343f35, 53-79 #d7d6b6,#343f35, 80-82 #ace0e1,#343f35, 83-99 #d7d6b6,#343f35, -|3: 0-0 d,#8c9366, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9366, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, -|4: 0-0 d,#8c9366, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9366, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, -|5: 0-0 d,#8c9366, 1-49 d,#10100e, 50-50 d,#8c9366, 51-99 d,#10100e, -|6: 0-0 d,#8c9366, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9366, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, -|7: 0-0 d,#8c9366, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9366, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, -|8: 0-0 d,#8c9366, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9366, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, -|9: 0-0 d,#8c9366, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9366, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, -|10: 0-0 d,#8c9366, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9366, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, -|11: 0-0 d,#8c9366, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9366, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, -|12: 0-0 d,#8c9366, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9366, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, -|13: 0-0 d,#8c9366, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9366, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, -|14: 0-0 d,#8c9366, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9366, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, -|15: 0-0 d,#8c9366, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9366, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, -|16: 0-0 d,#8c9366, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9366, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, -|17: 0-1 #ffffff,#6a7c4d, 2-2 d,#343f35, 3-29 #d7d6b6,#343f35, 30-32 #ace0e1,#343f35, 33-49 #d7d6b6,#343f35, 50-50 d,#8c9366, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, -|18: 0-0 d,#8c9366, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9366, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, -|19: 0-0 d,#8c9366, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9366, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, -|20: 0-0 d,#8c9366, 1-49 d,#10100e, 50-50 d,#8c9366, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, -|21: 0-0 d,#8c9366, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9366, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, -|22: 0-0 d,#8c9366, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9366, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, -|23: 0-0 d,#8c9366, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9366, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, -|24: 0-0 d,#8c9366, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9366, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, -|25: 0-0 d,#8c9366, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9366, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, -|26: 0-0 d,#8c9366, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9366, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, -|27: 0-0 d,#8c9366, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9366, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, -|28: 0-0 d,#8c9366, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9366, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, -|29: 0-0 d,#8c9366, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9366, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, -|30: 0-0 d,#8c9366, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9366, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, +|0: 0-50 #d7d6b6,#343f36, 51-59 #fbf1c7,#505c38, 60-70 #d7d6b6,#343f36, 71-99 d,#343f36, +|1: 0-1 #ffffff,#875e65, 2-2 d,#424f40, 3-34 #e3e2c2,#424f40, 35-49 d,#424f40, 50-51 #ffffff,#875e65, 52-52 d,#343f36, 53-84 #d7d6b6,#343f36, 85-99 d,#343f36, +|2: 0-1 #000000,#8ac874, 2-2 d,#424f40, 3-29 #e3e2c2,#424f40, 30-32 #b5ecec,#424f40, 33-49 #e3e2c2,#424f40, 50-51 #ffffff,#687c4d, 52-52 d,#343f36, 53-79 #d7d6b6,#343f36, 80-82 #aae0e0,#343f36, 83-99 #d7d6b6,#343f36, +|3: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, +|4: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, +|5: 0-0 d,#8c9467, 1-49 d,#10100e, 50-50 d,#8c9467, 51-99 d,#10100e, +|6: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, +|7: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, +|8: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, +|9: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, +|10: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, +|11: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, +|12: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, +|13: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, +|14: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, +|15: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, +|16: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, +|17: 0-1 #ffffff,#687c4d, 2-2 d,#343f36, 3-29 #d7d6b6,#343f36, 30-32 #aae0e0,#343f36, 33-49 #d7d6b6,#343f36, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, +|18: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, +|19: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, +|20: 0-0 d,#8c9467, 1-49 d,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, +|21: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, +|22: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, +|23: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, +|24: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, +|25: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, +|26: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, +|27: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, +|28: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, +|29: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, +|30: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, == snap forge-black grid=100x31 cursor=4,5 == style forge-black grid=100x31 -|0: 0-50 #c7cacf,#24282c, 51-59 #e2eef4,#1c5172, 60-70 #c7cacf,#24282c, 71-99 d,#24282c, -|1: 0-1 #ffffff,#6d7867, 2-2 d,#2f3740, 3-34 #d3d6db,#2f3740, 35-49 d,#2f3740, 50-51 #ffffff,#6d7867, 52-52 d,#24282c, 53-84 #c7cacf,#24282c, 85-99 d,#24282c, -|2: 0-1 #000000,#9ac4ea, 2-2 d,#2f3740, 3-29 #d3d6db,#2f3740, 30-32 #f1ceae,#2f3740, 33-49 #d3d6db,#2f3740, 50-51 #ffffff,#576676, 52-52 d,#24282c, 53-79 #c7cacf,#24282c, 80-82 #e5c2a2,#24282c, 83-99 #c7cacf,#24282c, -|3: 0-0 d,#738290, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#738290, 51-51 d,#000000, 52-99 #f6f6f6,#000000, -|4: 0-0 d,#738290, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#738290, 51-51 d,#000000, 52-99 #f6f6f6,#000000, -|5: 0-0 d,#738290, 1-49 d,#000000, 50-50 d,#738290, 51-99 d,#000000, -|6: 0-0 d,#738290, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#738290, 51-51 d,#000000, 52-99 #f6f6f6,#000000, -|7: 0-0 d,#738290, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#738290, 51-51 d,#000000, 52-99 #f6f6f6,#000000, -|8: 0-0 d,#738290, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#738290, 51-51 d,#000000, 52-99 #f6f6f6,#000000, -|9: 0-0 d,#738290, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#738290, 51-51 d,#000000, 52-99 #f6f6f6,#000000, -|10: 0-0 d,#738290, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#738290, 51-51 d,#000000, 52-99 #f6f6f6,#000000, -|11: 0-0 d,#738290, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#738290, 51-51 d,#000000, 52-99 #f6f6f6,#000000, -|12: 0-0 d,#738290, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#738290, 51-51 d,#000000, 52-99 #f6f6f6,#000000, -|13: 0-0 d,#738290, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#738290, 51-51 d,#000000, 52-99 #f6f6f6,#000000, -|14: 0-0 d,#738290, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#738290, 51-51 d,#000000, 52-99 #f6f6f6,#000000, -|15: 0-0 d,#738290, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#738290, 51-51 d,#000000, 52-99 #f6f6f6,#000000, -|16: 0-0 d,#738290, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#738290, 51-51 d,#000000, 52-99 #f6f6f6,#000000, -|17: 0-1 #ffffff,#576676, 2-2 d,#24282c, 3-29 #c7cacf,#24282c, 30-32 #e5c2a2,#24282c, 33-49 #c7cacf,#24282c, 50-50 d,#738290, 51-51 d,#000000, 52-99 #f6f6f6,#000000, -|18: 0-0 d,#738290, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#738290, 51-51 d,#000000, 52-99 #f6f6f6,#000000, -|19: 0-0 d,#738290, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#738290, 51-51 d,#000000, 52-99 #f6f6f6,#000000, -|20: 0-0 d,#738290, 1-49 d,#000000, 50-50 d,#738290, 51-51 d,#000000, 52-99 #f6f6f6,#000000, -|21: 0-0 d,#738290, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#738290, 51-51 d,#000000, 52-99 #f6f6f6,#000000, -|22: 0-0 d,#738290, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#738290, 51-51 d,#000000, 52-99 #f6f6f6,#000000, -|23: 0-0 d,#738290, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#738290, 51-51 d,#000000, 52-99 #f6f6f6,#000000, -|24: 0-0 d,#738290, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#738290, 51-51 d,#000000, 52-99 #f6f6f6,#000000, -|25: 0-0 d,#738290, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#738290, 51-51 d,#000000, 52-99 #f6f6f6,#000000, -|26: 0-0 d,#738290, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#738290, 51-51 d,#000000, 52-99 #f6f6f6,#000000, -|27: 0-0 d,#738290, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#738290, 51-51 d,#000000, 52-99 #f6f6f6,#000000, -|28: 0-0 d,#738290, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#738290, 51-51 d,#000000, 52-99 #f6f6f6,#000000, -|29: 0-0 d,#738290, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#738290, 51-51 d,#000000, 52-99 #f6f6f6,#000000, -|30: 0-0 d,#738290, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#738290, 51-51 d,#000000, 52-99 #f6f6f6,#000000, +|0: 0-50 #c5c9d2,#22252b, 51-59 #e2eef4,#1c5172, 60-70 #c5c9d2,#22252b, 71-99 d,#22252b, +|1: 0-1 #ffffff,#6a7b67, 2-2 d,#2d3440, 3-34 #d1d5de,#2d3440, 35-49 d,#2d3440, 50-51 #ffffff,#6a7b67, 52-52 d,#22252b, 53-84 #c5c9d2,#22252b, 85-99 d,#22252b, +|2: 0-1 #000000,#9cc3f7, 2-2 d,#2d3440, 3-29 #d1d5de,#2d3440, 30-32 #f8cba7,#2d3440, 33-49 #d1d5de,#2d3440, 50-51 #ffffff,#55647b, 52-52 d,#22252b, 53-79 #c5c9d2,#22252b, 80-82 #ecbf9b,#22252b, 83-99 #c5c9d2,#22252b, +|3: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, +|4: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, +|5: 0-0 d,#708095, 1-49 d,#000000, 50-50 d,#708095, 51-99 d,#000000, +|6: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, +|7: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, +|8: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, +|9: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, +|10: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, +|11: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, +|12: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, +|13: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, +|14: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, +|15: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, +|16: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, +|17: 0-1 #ffffff,#55647b, 2-2 d,#22252b, 3-29 #c5c9d2,#22252b, 30-32 #ecbf9b,#22252b, 33-49 #c5c9d2,#22252b, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, +|18: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, +|19: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, +|20: 0-0 d,#708095, 1-49 d,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, +|21: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, +|22: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, +|23: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, +|24: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, +|25: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, +|26: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, +|27: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, +|28: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, +|29: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, +|30: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, == snap forge-soft grid=100x31 cursor=4,5 == style forge-soft grid=100x31 -|0: 0-50 #c5c9d2,#292c33, 51-59 #e2eef4,#1c5172, 60-70 #c5c9d2,#292c33, 71-99 d,#292c33, -|1: 0-1 #ffffff,#6d7f6c, 2-2 d,#363d48, 3-34 #d1d5de,#363d48, 35-49 d,#363d48, 50-51 #ffffff,#6d7f6c, 52-52 d,#292c33, 53-84 #c5c9d2,#292c33, 85-99 d,#292c33, -|2: 0-1 #000000,#9cc3f7, 2-2 d,#363d48, 3-29 #d1d5de,#363d48, 30-32 #f8cba7,#363d48, 33-49 #d1d5de,#363d48, 50-51 #ffffff,#55647b, 52-52 d,#292c33, 53-79 #c5c9d2,#292c33, 80-82 #ecbf9b,#292c33, 83-99 #c5c9d2,#292c33, +|0: 0-50 #c5c9d2,#2a2d34, 51-59 #e2eef4,#1c5172, 60-70 #c5c9d2,#2a2d34, 71-99 d,#2a2d34, +|1: 0-1 #ffffff,#6d7f6c, 2-2 d,#373e49, 3-34 #d1d5de,#373e49, 35-49 d,#373e49, 50-51 #ffffff,#6d7f6c, 52-52 d,#2a2d34, 53-84 #c5c9d2,#2a2d34, 85-99 d,#2a2d34, +|2: 0-1 #000000,#9cc3f7, 2-2 d,#373e49, 3-29 #d1d5de,#373e49, 30-32 #f8cba7,#373e49, 33-49 #d1d5de,#373e49, 50-51 #ffffff,#55647b, 52-52 d,#2a2d34, 53-79 #c5c9d2,#2a2d34, 80-82 #ecbf9b,#2a2d34, 83-99 #c5c9d2,#2a2d34, |3: 0-0 d,#708095, 1-1 d,#35383e, 2-49 #bfc3cc,#35383e, 50-50 d,#708095, 51-51 d,#35383e, 52-99 #bfc3cc,#35383e, |4: 0-0 d,#708095, 1-1 d,#35383e, 2-49 #bfc3cc,#35383e, 50-50 d,#708095, 51-51 d,#35383e, 52-99 #bfc3cc,#35383e, |5: 0-0 d,#708095, 1-49 d,#35383e, 50-50 d,#708095, 51-99 d,#35383e, @@ -501,7 +497,7 @@ |14: 0-0 d,#708095, 1-1 d,#35383e, 2-49 #bfc3cc,#35383e, 50-50 d,#708095, 51-51 d,#35383e, 52-99 #bfc3cc,#35383e, |15: 0-0 d,#708095, 1-1 d,#35383e, 2-49 #bfc3cc,#35383e, 50-50 d,#708095, 51-51 d,#35383e, 52-99 #bfc3cc,#35383e, |16: 0-0 d,#708095, 1-1 d,#35383e, 2-49 #bfc3cc,#35383e, 50-50 d,#708095, 51-51 d,#35383e, 52-99 #bfc3cc,#35383e, -|17: 0-1 #ffffff,#55647b, 2-2 d,#292c33, 3-29 #c5c9d2,#292c33, 30-32 #ecbf9b,#292c33, 33-49 #c5c9d2,#292c33, 50-50 d,#708095, 51-51 d,#35383e, 52-99 #bfc3cc,#35383e, +|17: 0-1 #ffffff,#55647b, 2-2 d,#2a2d34, 3-29 #c5c9d2,#2a2d34, 30-32 #ecbf9b,#2a2d34, 33-49 #c5c9d2,#2a2d34, 50-50 d,#708095, 51-51 d,#35383e, 52-99 #bfc3cc,#35383e, |18: 0-0 d,#708095, 1-1 d,#35383e, 2-49 #bfc3cc,#35383e, 50-50 d,#708095, 51-51 d,#35383e, 52-99 #bfc3cc,#35383e, |19: 0-0 d,#708095, 1-1 d,#35383e, 2-49 #bfc3cc,#35383e, 50-50 d,#708095, 51-51 d,#35383e, 52-99 #bfc3cc,#35383e, |20: 0-0 d,#708095, 1-49 d,#35383e, 50-50 d,#708095, 51-51 d,#35383e, 52-99 #bfc3cc,#35383e, @@ -517,166 +513,166 @@ |30: 0-0 d,#708095, 1-1 d,#35383e, 2-49 #bfc3cc,#35383e, 50-50 d,#708095, 51-51 d,#35383e, 52-99 #bfc3cc,#35383e, == snap orchard-black grid=100x31 cursor=4,5 == style orchard-black grid=100x31 -|0: 0-50 #b4c4bb,#1e2926, 51-59 #f5f7ee,#3c614a, 60-70 #b4c4bb,#1e2926, 71-99 d,#1e2926, -|1: 0-1 #ffffff,#857557, 2-2 d,#283730, 3-34 #c0d0c7,#283730, 35-49 d,#283730, 50-51 #ffffff,#857557, 52-52 d,#1e2926, 53-84 #b4c4bb,#1e2926, 85-99 d,#1e2926, -|2: 0-1 #000000,#abd195, 2-2 d,#283730, 3-29 #c0d0c7,#283730, 30-32 #e2bedf,#283730, 33-49 #c0d0c7,#283730, 50-51 #ffffff,#4a665a, 52-52 d,#1e2926, 53-79 #b4c4bb,#1e2926, 80-82 #d6b2d3,#1e2926, 83-99 #b4c4bb,#1e2926, -|3: 0-0 d,#62826d, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#62826d, 51-51 d,#000000, 52-99 #f2f8ef,#000000, -|4: 0-0 d,#62826d, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#62826d, 51-51 d,#000000, 52-99 #f2f8ef,#000000, -|5: 0-0 d,#62826d, 1-49 d,#000000, 50-50 d,#62826d, 51-99 d,#000000, -|6: 0-0 d,#62826d, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#62826d, 51-51 d,#000000, 52-99 #f2f8ef,#000000, -|7: 0-0 d,#62826d, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#62826d, 51-51 d,#000000, 52-99 #f2f8ef,#000000, -|8: 0-0 d,#62826d, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#62826d, 51-51 d,#000000, 52-99 #f2f8ef,#000000, -|9: 0-0 d,#62826d, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#62826d, 51-51 d,#000000, 52-99 #f2f8ef,#000000, -|10: 0-0 d,#62826d, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#62826d, 51-51 d,#000000, 52-99 #f2f8ef,#000000, -|11: 0-0 d,#62826d, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#62826d, 51-51 d,#000000, 52-99 #f2f8ef,#000000, -|12: 0-0 d,#62826d, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#62826d, 51-51 d,#000000, 52-99 #f2f8ef,#000000, -|13: 0-0 d,#62826d, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#62826d, 51-51 d,#000000, 52-99 #f2f8ef,#000000, -|14: 0-0 d,#62826d, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#62826d, 51-51 d,#000000, 52-99 #f2f8ef,#000000, -|15: 0-0 d,#62826d, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#62826d, 51-51 d,#000000, 52-99 #f2f8ef,#000000, -|16: 0-0 d,#62826d, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#62826d, 51-51 d,#000000, 52-99 #f2f8ef,#000000, -|17: 0-1 #ffffff,#4a665a, 2-2 d,#1e2926, 3-29 #b4c4bb,#1e2926, 30-32 #d6b2d3,#1e2926, 33-49 #b4c4bb,#1e2926, 50-50 d,#62826d, 51-51 d,#000000, 52-99 #f2f8ef,#000000, -|18: 0-0 d,#62826d, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#62826d, 51-51 d,#000000, 52-99 #f2f8ef,#000000, -|19: 0-0 d,#62826d, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#62826d, 51-51 d,#000000, 52-99 #f2f8ef,#000000, -|20: 0-0 d,#62826d, 1-49 d,#000000, 50-50 d,#62826d, 51-51 d,#000000, 52-99 #f2f8ef,#000000, -|21: 0-0 d,#62826d, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#62826d, 51-51 d,#000000, 52-99 #f2f8ef,#000000, -|22: 0-0 d,#62826d, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#62826d, 51-51 d,#000000, 52-99 #f2f8ef,#000000, -|23: 0-0 d,#62826d, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#62826d, 51-51 d,#000000, 52-99 #f2f8ef,#000000, -|24: 0-0 d,#62826d, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#62826d, 51-51 d,#000000, 52-99 #f2f8ef,#000000, -|25: 0-0 d,#62826d, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#62826d, 51-51 d,#000000, 52-99 #f2f8ef,#000000, -|26: 0-0 d,#62826d, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#62826d, 51-51 d,#000000, 52-99 #f2f8ef,#000000, -|27: 0-0 d,#62826d, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#62826d, 51-51 d,#000000, 52-99 #f2f8ef,#000000, -|28: 0-0 d,#62826d, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#62826d, 51-51 d,#000000, 52-99 #f2f8ef,#000000, -|29: 0-0 d,#62826d, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#62826d, 51-51 d,#000000, 52-99 #f2f8ef,#000000, -|30: 0-0 d,#62826d, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#62826d, 51-51 d,#000000, 52-99 #f2f8ef,#000000, +|0: 0-50 #b2c3b8,#1d2824, 51-59 #f5f7ee,#3c614a, 60-70 #b2c3b8,#1d2824, 71-99 d,#1d2824, +|1: 0-1 #ffffff,#887455, 2-2 d,#26362d, 3-34 #becfc4,#26362d, 35-49 d,#26362d, 50-51 #ffffff,#887455, 52-52 d,#1d2824, 53-84 #b2c3b8,#1d2824, 85-99 d,#1d2824, +|2: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-29 #becfc4,#26362d, 30-32 #dfbce5,#26362d, 33-49 #becfc4,#26362d, 50-51 #ffffff,#496656, 52-52 d,#1d2824, 53-79 #b2c3b8,#1d2824, 80-82 #d3b0d9,#1d2824, 83-99 #b2c3b8,#1d2824, +|3: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, +|4: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, +|5: 0-0 d,#608269, 1-49 d,#000000, 50-50 d,#608269, 51-99 d,#000000, +|6: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, +|7: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, +|8: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, +|9: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, +|10: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, +|11: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, +|12: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, +|13: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, +|14: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, +|15: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, +|16: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, +|17: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-29 #b2c3b8,#1d2824, 30-32 #d3b0d9,#1d2824, 33-49 #b2c3b8,#1d2824, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, +|18: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, +|19: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, +|20: 0-0 d,#608269, 1-49 d,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, +|21: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, +|22: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, +|23: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, +|24: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, +|25: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, +|26: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, +|27: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, +|28: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, +|29: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, +|30: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, == snap helix grid=100x31 cursor=4,5 == style helix grid=100x31 -|0: 0-50 #979998,#131414, 51-59 #080808,#80a0ff, 60-70 #979998,#131414, 71-99 d,#131414, -|1: 0-1 #ffffff,#837048, 2-2 d,#141615, 3-34 #989a99,#141615, 35-49 d,#141615, 50-51 #ffffff,#837048, 52-52 d,#131414, 53-84 #979998,#131414, 85-99 d,#131414, -|2: 0-1 #ffffff,#6a6d66, 2-2 d,#141615, 3-29 #989a99,#141615, 30-32 #9c989c,#141615, 33-49 #989a99,#141615, 50-51 #ffffff,#2a2c2b, 52-52 d,#131414, 53-79 #979998,#131414, 80-82 #9a979b,#131414, 83-99 #979998,#131414, -|3: 0-0 d,#474a48, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#474a48, 51-51 d,#080808, 52-99 #bdbdbd,#080808, -|4: 0-0 d,#474a48, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#474a48, 51-51 d,#080808, 52-99 #bdbdbd,#080808, -|5: 0-0 d,#474a48, 1-49 d,#080808, 50-50 d,#474a48, 51-99 d,#080808, -|6: 0-0 d,#474a48, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#474a48, 51-51 d,#080808, 52-99 #bdbdbd,#080808, -|7: 0-0 d,#474a48, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#474a48, 51-51 d,#080808, 52-99 #bdbdbd,#080808, -|8: 0-0 d,#474a48, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#474a48, 51-51 d,#080808, 52-99 #bdbdbd,#080808, -|9: 0-0 d,#474a48, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#474a48, 51-51 d,#080808, 52-99 #bdbdbd,#080808, -|10: 0-0 d,#474a48, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#474a48, 51-51 d,#080808, 52-99 #bdbdbd,#080808, -|11: 0-0 d,#474a48, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#474a48, 51-51 d,#080808, 52-99 #bdbdbd,#080808, -|12: 0-0 d,#474a48, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#474a48, 51-51 d,#080808, 52-99 #bdbdbd,#080808, -|13: 0-0 d,#474a48, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#474a48, 51-51 d,#080808, 52-99 #bdbdbd,#080808, -|14: 0-0 d,#474a48, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#474a48, 51-51 d,#080808, 52-99 #bdbdbd,#080808, -|15: 0-0 d,#474a48, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#474a48, 51-51 d,#080808, 52-99 #bdbdbd,#080808, -|16: 0-0 d,#474a48, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#474a48, 51-51 d,#080808, 52-99 #bdbdbd,#080808, -|17: 0-1 #ffffff,#2a2c2b, 2-2 d,#131414, 3-29 #979998,#131414, 30-32 #9a979b,#131414, 33-49 #979998,#131414, 50-50 d,#474a48, 51-51 d,#080808, 52-99 #bdbdbd,#080808, -|18: 0-0 d,#474a48, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#474a48, 51-51 d,#080808, 52-99 #bdbdbd,#080808, -|19: 0-0 d,#474a48, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#474a48, 51-51 d,#080808, 52-99 #bdbdbd,#080808, -|20: 0-0 d,#474a48, 1-49 d,#080808, 50-50 d,#474a48, 51-51 d,#080808, 52-99 #bdbdbd,#080808, -|21: 0-0 d,#474a48, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#474a48, 51-51 d,#080808, 52-99 #bdbdbd,#080808, -|22: 0-0 d,#474a48, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#474a48, 51-51 d,#080808, 52-99 #bdbdbd,#080808, -|23: 0-0 d,#474a48, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#474a48, 51-51 d,#080808, 52-99 #bdbdbd,#080808, -|24: 0-0 d,#474a48, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#474a48, 51-51 d,#080808, 52-99 #bdbdbd,#080808, -|25: 0-0 d,#474a48, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#474a48, 51-51 d,#080808, 52-99 #bdbdbd,#080808, -|26: 0-0 d,#474a48, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#474a48, 51-51 d,#080808, 52-99 #bdbdbd,#080808, -|27: 0-0 d,#474a48, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#474a48, 51-51 d,#080808, 52-99 #bdbdbd,#080808, -|28: 0-0 d,#474a48, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#474a48, 51-51 d,#080808, 52-99 #bdbdbd,#080808, -|29: 0-0 d,#474a48, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#474a48, 51-51 d,#080808, 52-99 #bdbdbd,#080808, -|30: 0-0 d,#474a48, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#474a48, 51-51 d,#080808, 52-99 #bdbdbd,#080808, +|0: 0-50 #949494,#121212, 51-59 #080808,#80a0ff, 60-70 #949494,#121212, 71-99 d,#121212, +|1: 0-1 #ffffff,#826f46, 2-2 d,#121212, 3-34 #949494,#121212, 35-49 d,#121212, 50-51 #ffffff,#826f46, 52-52 d,#121212, 53-84 #949494,#121212, 85-99 d,#121212, +|2: 0-1 #ffffff,#626262, 2-2 d,#121212, 3-49 #949494,#121212, 50-51 #ffffff,#262626, 52-52 d,#121212, 53-99 #949494,#121212, +|3: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, +|4: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, +|5: 0-0 d,#444444, 1-49 d,#080808, 50-50 d,#444444, 51-99 d,#080808, +|6: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, +|7: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, +|8: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, +|9: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, +|10: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, +|11: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, +|12: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, +|13: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, +|14: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, +|15: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, +|16: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, +|17: 0-1 #ffffff,#262626, 2-2 d,#121212, 3-49 #949494,#121212, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, +|18: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, +|19: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, +|20: 0-0 d,#444444, 1-49 d,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, +|21: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, +|22: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, +|23: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, +|24: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, +|25: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, +|26: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, +|27: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, +|28: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, +|29: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, +|30: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, == snap dark grid=100x31 cursor=4,5 == style dark grid=100x31 -|0: 0-50 #969696,#29283a, 51-59 #d8c8a8,#513c17, 60-70 #969696,#29283a, 71-99 d,#29283a, -|1: 0-1 #ffffff,#86794d, 2-2 d,#29283a, 3-34 #969696,#29283a, 35-49 d,#29283a, 50-51 #ffffff,#86794d, 52-52 d,#29283a, 53-84 #969696,#29283a, 85-99 d,#29283a, -|2: 0-1 #ffffff,#6643d3, 2-2 d,#29283a, 3-49 #969696,#29283a, 50-51 #ffffff,#343853, 52-52 d,#29283a, 53-99 #969696,#29283a, -|3: 0-0 d,#515151, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#515151, 51-51 d,d, 52-99 #969696,#2c2a3e, -|4: 0-0 d,#515151, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#515151, 51-51 d,d, 52-99 #969696,#2c2a3e, -|5: 0-0 d,#515151, 1-49 d,d, 50-50 d,#515151, 51-99 d,d, -|6: 0-0 d,#515151, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#515151, 51-51 d,d, 52-99 #969696,#2c2a3e, -|7: 0-0 d,#515151, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#515151, 51-51 d,d, 52-99 #969696,#2c2a3e, -|8: 0-0 d,#515151, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#515151, 51-51 d,d, 52-99 #969696,#2c2a3e, -|9: 0-0 d,#515151, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#515151, 51-51 d,d, 52-99 #969696,#2c2a3e, -|10: 0-0 d,#515151, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#515151, 51-51 d,d, 52-99 #969696,#2c2a3e, -|11: 0-0 d,#515151, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#515151, 51-51 d,d, 52-99 #969696,#2c2a3e, -|12: 0-0 d,#515151, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#515151, 51-51 d,d, 52-99 #969696,#2c2a3e, -|13: 0-0 d,#515151, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#515151, 51-51 d,d, 52-99 #969696,#2c2a3e, -|14: 0-0 d,#515151, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#515151, 51-51 d,d, 52-99 #969696,#2c2a3e, -|15: 0-0 d,#515151, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#515151, 51-51 d,d, 52-99 #969696,#2c2a3e, -|16: 0-0 d,#515151, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#515151, 51-51 d,d, 52-99 #969696,#2c2a3e, -|17: 0-1 #ffffff,#343853, 2-2 d,#29283a, 3-49 #969696,#29283a, 50-50 d,#515151, 51-51 d,d, 52-99 #969696,#2c2a3e, -|18: 0-0 d,#515151, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#515151, 51-51 d,d, 52-99 #969696,#2c2a3e, -|19: 0-0 d,#515151, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#515151, 51-51 d,d, 52-99 #969696,#2c2a3e, -|20: 0-0 d,#515151, 1-49 d,d, 50-50 d,#515151, 51-51 d,d, 52-99 #969696,#2c2a3e, -|21: 0-0 d,#515151, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#515151, 51-51 d,d, 52-99 #969696,#2c2a3e, -|22: 0-0 d,#515151, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#515151, 51-51 d,d, 52-99 #969696,#2c2a3e, -|23: 0-0 d,#515151, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#515151, 51-51 d,d, 52-99 #969696,#2c2a3e, -|24: 0-0 d,#515151, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#515151, 51-51 d,d, 52-99 #969696,#2c2a3e, -|25: 0-0 d,#515151, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#515151, 51-51 d,d, 52-99 #969696,#2c2a3e, -|26: 0-0 d,#515151, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#515151, 51-51 d,d, 52-99 #969696,#2c2a3e, -|27: 0-0 d,#515151, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#515151, 51-51 d,d, 52-99 #969696,#2c2a3e, -|28: 0-0 d,#515151, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#515151, 51-51 d,d, 52-99 #969696,#2c2a3e, -|29: 0-0 d,#515151, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#515151, 51-51 d,d, 52-99 #969696,#2c2a3e, -|30: 0-0 d,#515151, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#515151, 51-51 d,d, 52-99 #969696,#2c2a3e, +|0: 0-50 #969696,#2c2a3e, 51-59 #d8c8a8,#513c17, 60-70 #969696,#2c2a3e, 71-99 d,#2c2a3e, +|1: 0-1 #ffffff,#867a4e, 2-2 d,#2c2a3e, 3-34 #969696,#2c2a3e, 35-49 d,#2c2a3e, 50-51 #ffffff,#867a4e, 52-52 d,#2c2a3e, 53-84 #969696,#2c2a3e, 85-99 d,#2c2a3e, +|2: 0-1 #ffffff,#663fe0, 2-2 d,#2c2a3e, 3-49 #969696,#2c2a3e, 50-51 #ffffff,#353a58, 52-52 d,#2c2a3e, 53-99 #969696,#2c2a3e, +|3: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, +|4: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, +|5: 0-0 d,#525252, 1-49 d,d, 50-50 d,#525252, 51-99 d,d, +|6: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, +|7: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, +|8: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, +|9: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, +|10: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, +|11: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, +|12: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, +|13: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, +|14: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, +|15: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, +|16: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, +|17: 0-1 #ffffff,#353a58, 2-2 d,#2c2a3e, 3-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, +|18: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, +|19: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, +|20: 0-0 d,#525252, 1-49 d,d, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, +|21: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, +|22: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, +|23: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, +|24: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, +|25: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, +|26: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, +|27: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, +|28: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, +|29: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, +|30: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, == snap acme grid=100x31 cursor=4,5 == style acme grid=100x31 -|0: 0-50 #0f0f0f,#d7eaec, 51-59 #000000,#fafa92, 60-70 #0f0f0f,#d7eaec, 71-99 d,#d7eaec, -|1: 0-1 #000000,#bfa37a, 2-2 d,#d7eaec, 3-34 #0f0f0f,#d7eaec, 35-49 d,#d7eaec, 50-51 #000000,#bfa37a, 52-52 d,#d7eaec, 53-84 #0f0f0f,#d7eaec, 85-99 d,#d7eaec, -|2: 0-1 #ffffff,#663fe0, 2-2 d,#d7eaec, 3-49 #0f0f0f,#d7eaec, 50-51 #ffffff,#353a58, 52-52 d,#d7eaec, 53-99 #0f0f0f,#d7eaec, -|3: 0-0 d,#eeeedb, 1-1 d,#ffffea, 2-49 #000000,#ffffea, 50-50 d,#eeeedb, 51-51 d,#ffffea, 52-99 #000000,#ffffea, -|4: 0-0 d,#eeeedb, 1-1 d,#ffffea, 2-49 #000000,#ffffea, 50-50 d,#eeeedb, 51-51 d,#ffffea, 52-99 #000000,#ffffea, -|5: 0-0 d,#eeeedb, 1-49 d,#ffffea, 50-50 d,#eeeedb, 51-99 d,#ffffea, -|6: 0-0 d,#eeeedb, 1-1 d,#ffffea, 2-49 #000000,#ffffea, 50-50 d,#eeeedb, 51-51 d,#ffffea, 52-99 #000000,#ffffea, -|7: 0-0 d,#eeeedb, 1-1 d,#ffffea, 2-49 #000000,#ffffea, 50-50 d,#eeeedb, 51-51 d,#ffffea, 52-99 #000000,#ffffea, -|8: 0-0 d,#eeeedb, 1-1 d,#ffffea, 2-49 #000000,#ffffea, 50-50 d,#eeeedb, 51-51 d,#ffffea, 52-99 #000000,#ffffea, -|9: 0-0 d,#eeeedb, 1-1 d,#ffffea, 2-49 #000000,#ffffea, 50-50 d,#eeeedb, 51-51 d,#ffffea, 52-99 #000000,#ffffea, -|10: 0-0 d,#eeeedb, 1-1 d,#ffffea, 2-49 #000000,#ffffea, 50-50 d,#eeeedb, 51-51 d,#ffffea, 52-99 #000000,#ffffea, -|11: 0-0 d,#eeeedb, 1-1 d,#ffffea, 2-49 #000000,#ffffea, 50-50 d,#eeeedb, 51-51 d,#ffffea, 52-99 #000000,#ffffea, -|12: 0-0 d,#eeeedb, 1-1 d,#ffffea, 2-49 #000000,#ffffea, 50-50 d,#eeeedb, 51-51 d,#ffffea, 52-99 #000000,#ffffea, -|13: 0-0 d,#eeeedb, 1-1 d,#ffffea, 2-49 #000000,#ffffea, 50-50 d,#eeeedb, 51-51 d,#ffffea, 52-99 #000000,#ffffea, -|14: 0-0 d,#eeeedb, 1-1 d,#ffffea, 2-49 #000000,#ffffea, 50-50 d,#eeeedb, 51-51 d,#ffffea, 52-99 #000000,#ffffea, -|15: 0-0 d,#eeeedb, 1-1 d,#ffffea, 2-49 #000000,#ffffea, 50-50 d,#eeeedb, 51-51 d,#ffffea, 52-99 #000000,#ffffea, -|16: 0-0 d,#eeeedb, 1-1 d,#ffffea, 2-49 #000000,#ffffea, 50-50 d,#eeeedb, 51-51 d,#ffffea, 52-99 #000000,#ffffea, -|17: 0-1 #ffffff,#353a58, 2-2 d,#d7eaec, 3-49 #0f0f0f,#d7eaec, 50-50 d,#eeeedb, 51-51 d,#ffffea, 52-99 #000000,#ffffea, -|18: 0-0 d,#eeeedb, 1-1 d,#ffffea, 2-49 #000000,#ffffea, 50-50 d,#eeeedb, 51-51 d,#ffffea, 52-99 #000000,#ffffea, -|19: 0-0 d,#eeeedb, 1-1 d,#ffffea, 2-49 #000000,#ffffea, 50-50 d,#eeeedb, 51-51 d,#ffffea, 52-99 #000000,#ffffea, -|20: 0-0 d,#eeeedb, 1-49 d,#ffffea, 50-50 d,#eeeedb, 51-51 d,#ffffea, 52-99 #000000,#ffffea, -|21: 0-0 d,#eeeedb, 1-1 d,#ffffea, 2-49 #000000,#ffffea, 50-50 d,#eeeedb, 51-51 d,#ffffea, 52-99 #000000,#ffffea, -|22: 0-0 d,#eeeedb, 1-1 d,#ffffea, 2-49 #000000,#ffffea, 50-50 d,#eeeedb, 51-51 d,#ffffea, 52-99 #000000,#ffffea, -|23: 0-0 d,#eeeedb, 1-1 d,#ffffea, 2-49 #000000,#ffffea, 50-50 d,#eeeedb, 51-51 d,#ffffea, 52-99 #000000,#ffffea, -|24: 0-0 d,#eeeedb, 1-1 d,#ffffea, 2-49 #000000,#ffffea, 50-50 d,#eeeedb, 51-51 d,#ffffea, 52-99 #000000,#ffffea, -|25: 0-0 d,#eeeedb, 1-1 d,#ffffea, 2-49 #000000,#ffffea, 50-50 d,#eeeedb, 51-51 d,#ffffea, 52-99 #000000,#ffffea, -|26: 0-0 d,#eeeedb, 1-1 d,#ffffea, 2-49 #000000,#ffffea, 50-50 d,#eeeedb, 51-51 d,#ffffea, 52-99 #000000,#ffffea, -|27: 0-0 d,#eeeedb, 1-1 d,#ffffea, 2-49 #000000,#ffffea, 50-50 d,#eeeedb, 51-51 d,#ffffea, 52-99 #000000,#ffffea, -|28: 0-0 d,#eeeedb, 1-1 d,#ffffea, 2-49 #000000,#ffffea, 50-50 d,#eeeedb, 51-51 d,#ffffea, 52-99 #000000,#ffffea, -|29: 0-0 d,#eeeedb, 1-1 d,#ffffea, 2-49 #000000,#ffffea, 50-50 d,#eeeedb, 51-51 d,#ffffea, 52-99 #000000,#ffffea, -|30: 0-0 d,#eeeedb, 1-1 d,#ffffea, 2-49 #000000,#ffffea, 50-50 d,#eeeedb, 51-51 d,#ffffea, 52-99 #000000,#ffffea, +|0: 0-50 #000000,#eaffff, 51-59 #000000,#fafa92, 60-70 #000000,#eaffff, 71-99 d,#eaffff, +|1: 0-1 #000000,#c5a87f, 2-2 d,#eaffff, 3-34 #000000,#eaffff, 35-49 d,#eaffff, 50-51 #000000,#c5a87f, 52-52 d,#eaffff, 53-84 #000000,#eaffff, 85-99 d,#eaffff, +|2: 0-1 #ffffff,#663fe0, 2-2 d,#eaffff, 3-49 #000000,#eaffff, 50-51 #ffffff,#353a58, 52-52 d,#eaffff, 53-99 #000000,#eaffff, +|3: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, +|4: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, +|5: 0-99 d,#ffffea, +|6: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, +|7: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, +|8: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, +|9: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, +|10: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, +|11: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, +|12: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, +|13: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, +|14: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, +|15: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, +|16: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, +|17: 0-1 #ffffff,#353a58, 2-2 d,#eaffff, 3-49 #000000,#eaffff, 50-51 d,#ffffea, 52-99 #000000,#ffffea, +|18: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, +|19: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, +|20: 0-51 d,#ffffea, 52-99 #000000,#ffffea, +|21: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, +|22: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, +|23: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, +|24: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, +|25: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, +|26: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, +|27: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, +|28: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, +|29: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, +|30: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, == snap generated grid=100x31 cursor=4,5 == style generated grid=100x31 -|0: 0-50 #cacaca,#323c43, 51-59 #e0e0e0,#303840, 60-70 #cacaca,#323c43, 71-99 d,#323c43, -|1: 0-1 #ffffff,#5b8094, 2-2 d,#323c43, 3-34 #cacaca,#323c43, 35-49 d,#323c43, 50-51 #ffffff,#5b8094, 52-52 d,#323c43, 53-84 #cacaca,#323c43, 85-99 d,#323c43, -|2: 0-1 #000000,#f0966d, 2-2 d,#323c43, 3-49 #cacaca,#323c43, 50-51 #ffffff,#313842, 52-52 d,#323c43, 53-99 #cacaca,#323c43, -|3: 0-0 d,#454c51, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#454c51, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|4: 0-0 d,#454c51, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#454c51, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|5: 0-0 d,#454c51, 1-49 d,#161e26, 50-50 d,#454c51, 51-99 d,#161e26, -|6: 0-0 d,#454c51, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#454c51, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|7: 0-0 d,#454c51, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#454c51, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|8: 0-0 d,#454c51, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#454c51, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|9: 0-0 d,#454c51, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#454c51, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|10: 0-0 d,#454c51, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#454c51, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|11: 0-0 d,#454c51, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#454c51, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|12: 0-0 d,#454c51, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#454c51, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|13: 0-0 d,#454c51, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#454c51, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|14: 0-0 d,#454c51, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#454c51, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|15: 0-0 d,#454c51, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#454c51, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|16: 0-0 d,#454c51, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#454c51, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|17: 0-1 #ffffff,#313842, 2-2 d,#323c43, 3-49 #cacaca,#323c43, 50-50 d,#454c51, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|18: 0-0 d,#454c51, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#454c51, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|19: 0-0 d,#454c51, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#454c51, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|20: 0-0 d,#454c51, 1-49 d,#161e26, 50-50 d,#454c51, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|21: 0-0 d,#454c51, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#454c51, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|22: 0-0 d,#454c51, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#454c51, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|23: 0-0 d,#454c51, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#454c51, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|24: 0-0 d,#454c51, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#454c51, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|25: 0-0 d,#454c51, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#454c51, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|26: 0-0 d,#454c51, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#454c51, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|27: 0-0 d,#454c51, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#454c51, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|28: 0-0 d,#454c51, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#454c51, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|29: 0-0 d,#454c51, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#454c51, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|30: 0-0 d,#454c51, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#454c51, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, +|0: 0-50 #e0e0e0,#1e262e, 51-59 #e0e0e0,#303840, 60-70 #e0e0e0,#1e262e, 71-99 d,#1e262e, +|1: 0-1 #ffffff,#4f7b96, 2-2 d,#1e262e, 3-34 #e0e0e0,#1e262e, 35-49 d,#1e262e, 50-51 #ffffff,#4f7b96, 52-52 d,#1e262e, 53-84 #e0e0e0,#1e262e, 85-99 d,#1e262e, +|2: 0-1 #000000,#ffa060, 2-2 d,#1e262e, 3-49 #e0e0e0,#1e262e, 50-51 #ffffff,#303840, 52-52 d,#1e262e, 53-99 #e0e0e0,#1e262e, +|3: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, +|4: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, +|5: 0-0 d,#303840, 1-49 d,#161e26, 50-50 d,#303840, 51-99 d,#161e26, +|6: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, +|7: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, +|8: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, +|9: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, +|10: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, +|11: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, +|12: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, +|13: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, +|14: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, +|15: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, +|16: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, +|17: 0-1 #ffffff,#303840, 2-2 d,#1e262e, 3-49 #e0e0e0,#1e262e, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, +|18: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, +|19: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, +|20: 0-0 d,#303840, 1-49 d,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, +|21: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, +|22: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, +|23: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, +|24: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, +|25: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, +|26: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, +|27: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, +|28: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, +|29: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, +|30: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, diff --git a/test/snapshots/themesel.golden b/test/snapshots/themesel.golden index b3100e27..97d3ed41 100644 --- a/test/snapshots/themesel.golden +++ b/test/snapshots/themesel.golden @@ -83,169 +83,169 @@ |30: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-16 #e5eee5,#0d1410, 17-99 d,#0d1410, == snap step-dusk grid=100x31 cursor=7,20 == style step-dusk grid=100x31 -|0: 0-70 #c4b7c4,#2b262f, 71-99 d,#2b262f, -|1: 0-1 #ffffff,#77795e, 2-2 d,#39303a, 3-34 #d0c3d0,#39303a, 35-99 d,#39303a, -|2: 0-1 #ffffff,#705667, 2-2 d,#2b262f, 3-32 #c4b7c4,#2b262f, 33-37 #9cc7bb,#2b262f, 38-59 #c4b7c4,#2b262f, 60-99 d,#2b262f, -|3: 0-0 d,#89697a, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-12 #c3b6c1,#39323b, 13-99 d,#39323b, -|4: 0-0 d,#89697a, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-12 #c3b6c1,#39323b, 13-99 d,#39323b, -|5: 0-0 d,#89697a, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-12 #c3b6c1,#39323b, 13-99 d,#39323b, -|6: 0-0 d,#89697a, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-12 #c3b6c1,#39323b, 13-99 d,#39323b, -|7: 0-0 d,#39333c, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-12 #c3b6c1,#39323b, 13-99 d,#39323b, -|8: 0-0 d,#39333c, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-12 #c3b6c1,#39323b, 13-99 d,#39323b, -|9: 0-0 d,#39333c, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-12 #c3b6c1,#39323b, 13-99 d,#39323b, -|10: 0-0 d,#39333c, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-12 #c3b6c1,#39323b, 13-99 d,#39323b, -|11: 0-0 d,#39333c, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-12 #c3b6c1,#39323b, 13-99 d,#39323b, -|12: 0-0 d,#39333c, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-13 #c3b6c1,#39323b, 14-99 d,#39323b, -|13: 0-0 d,#39333c, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-13 #c3b6c1,#39323b, 14-99 d,#39323b, -|14: 0-0 d,#39333c, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-13 #c3b6c1,#39323b, 14-99 d,#39323b, -|15: 0-0 d,#39333c, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-13 #c3b6c1,#39323b, 14-99 d,#39323b, -|16: 0-0 d,#39333c, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-13 #c3b6c1,#39323b, 14-99 d,#39323b, -|17: 0-1 #000000,#e7b491, 2-2 d,#39303a, 3-32 #d0c3d0,#39303a, 33-39 #a7d3c7,#39303a, 40-61 #d0c3d0,#39303a, 62-99 d,#39303a, -|18: 0-0 d,#89697a, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-21 #c3b6c1,#39323b, 22-92 d,#39323b, 93-93 d,#2b262f, 94-98 #c4b7c4,#2b262f, 99-99 d,#2b262f, -|19: 0-0 d,#39333c, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-19 #c3b6c1,#39323b, 20-99 d,#39323b, -|20: 0-0 d,#39333c, 1-1 d,#39323b, 2-6 #8a7986,#39323b,b 7-16 #c3b6c1,#39323b, 17-99 d,#39323b, -|21: 0-0 d,#39333c, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-15 #c3b6c1,#39323b, 16-99 d,#39323b, -|22: 0-0 d,#39333c, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-17 #c3b6c1,#39323b, 18-99 d,#39323b, -|23: 0-0 d,#39333c, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-20 #c3b6c1,#39323b, 21-99 d,#39323b, -|24: 0-0 d,#39333c, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-19 #c3b6c1,#39323b, 20-99 d,#39323b, -|25: 0-0 d,#39333c, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-17 #c3b6c1,#39323b, 18-99 d,#39323b, -|26: 0-0 d,#39333c, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-18 #c3b6c1,#39323b, 19-99 d,#39323b, -|27: 0-0 d,#39333c, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-20 #c3b6c1,#39323b, 21-99 d,#39323b, -|28: 0-0 d,#39333c, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-20 #c3b6c1,#39323b, 21-99 d,#39323b, -|29: 0-0 d,#39333c, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-19 #c3b6c1,#39323b, 20-99 d,#39323b, -|30: 0-0 d,#39333c, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-16 #c3b6c1,#39323b, 17-99 d,#39323b, +|0: 0-70 #c6b6c5,#2c2630, 71-99 d,#2c2630, +|1: 0-1 #ffffff,#757a5f, 2-2 d,#3b2f3b, 3-34 #d2c2d1,#3b2f3b, 35-99 d,#3b2f3b, +|2: 0-1 #ffffff,#745469, 2-2 d,#2c2630, 3-32 #c6b6c5,#2c2630, 33-37 #96c9b8,#2c2630, 38-59 #c6b6c5,#2c2630, 60-99 d,#2c2630, +|3: 0-0 d,#8e667c, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-12 #c3b6c1,#39323b, 13-99 d,#39323b, +|4: 0-0 d,#8e667c, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-12 #c3b6c1,#39323b, 13-99 d,#39323b, +|5: 0-0 d,#8e667c, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-12 #c3b6c1,#39323b, 13-99 d,#39323b, +|6: 0-0 d,#8e667c, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-12 #c3b6c1,#39323b, 13-99 d,#39323b, +|7: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-12 #c3b6c1,#39323b, 13-99 d,#39323b, +|8: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-12 #c3b6c1,#39323b, 13-99 d,#39323b, +|9: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-12 #c3b6c1,#39323b, 13-99 d,#39323b, +|10: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-12 #c3b6c1,#39323b, 13-99 d,#39323b, +|11: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-12 #c3b6c1,#39323b, 13-99 d,#39323b, +|12: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-13 #c3b6c1,#39323b, 14-99 d,#39323b, +|13: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-13 #c3b6c1,#39323b, 14-99 d,#39323b, +|14: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-13 #c3b6c1,#39323b, 14-99 d,#39323b, +|15: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-13 #c3b6c1,#39323b, 14-99 d,#39323b, +|16: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-13 #c3b6c1,#39323b, 14-99 d,#39323b, +|17: 0-1 #000000,#edb092, 2-2 d,#3b2f3b, 3-32 #d2c2d1,#3b2f3b, 33-39 #a1d5c4,#3b2f3b, 40-61 #d2c2d1,#3b2f3b, 62-99 d,#3b2f3b, +|18: 0-0 d,#8e667c, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-21 #c3b6c1,#39323b, 22-92 d,#39323b, 93-93 d,#2c2630, 94-98 #c6b6c5,#2c2630, 99-99 d,#2c2630, +|19: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-19 #c3b6c1,#39323b, 20-99 d,#39323b, +|20: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #8a7986,#39323b,b 7-16 #c3b6c1,#39323b, 17-99 d,#39323b, +|21: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-15 #c3b6c1,#39323b, 16-99 d,#39323b, +|22: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-17 #c3b6c1,#39323b, 18-99 d,#39323b, +|23: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-20 #c3b6c1,#39323b, 21-99 d,#39323b, +|24: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-19 #c3b6c1,#39323b, 20-99 d,#39323b, +|25: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-17 #c3b6c1,#39323b, 18-99 d,#39323b, +|26: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-18 #c3b6c1,#39323b, 19-99 d,#39323b, +|27: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-20 #c3b6c1,#39323b, 21-99 d,#39323b, +|28: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-20 #c3b6c1,#39323b, 21-99 d,#39323b, +|29: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-19 #c3b6c1,#39323b, 20-99 d,#39323b, +|30: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-16 #c3b6c1,#39323b, 17-99 d,#39323b, == snap step-atelier grid=100x31 cursor=7,24 == style step-atelier grid=100x31 -|0: 0-70 #4b6960,#d2dcda, 71-99 d,#d2dcda, -|1: 0-1 #000000,#b5a07e, 2-2 d,#c4d5cf, 3-34 #45635a,#c4d5cf, 35-99 d,#c4d5cf, -|2: 0-1 #000000,#92b09c, 2-2 d,#d2dcda, 3-32 #4b6960,#d2dcda, 33-37 #72547b,#d2dcda, 38-59 #4b6960,#d2dcda, 60-99 d,#d2dcda, -|3: 0-0 d,#9da777, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-12 #293f35,#fffdeb, 13-99 d,#fffdeb, -|4: 0-0 d,#9da777, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-12 #293f35,#fffdeb, 13-99 d,#fffdeb, -|5: 0-0 d,#9da777, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-12 #293f35,#fffdeb, 13-99 d,#fffdeb, -|6: 0-0 d,#9da777, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-12 #293f35,#fffdeb, 13-99 d,#fffdeb, -|7: 0-0 d,#dcdac1, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-12 #293f35,#fffdeb, 13-99 d,#fffdeb, -|8: 0-0 d,#dcdac1, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-12 #293f35,#fffdeb, 13-99 d,#fffdeb, -|9: 0-0 d,#dcdac1, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-12 #293f35,#fffdeb, 13-99 d,#fffdeb, -|10: 0-0 d,#dcdac1, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-12 #293f35,#fffdeb, 13-99 d,#fffdeb, -|11: 0-0 d,#dcdac1, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-12 #293f35,#fffdeb, 13-99 d,#fffdeb, -|12: 0-0 d,#dcdac1, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-13 #293f35,#fffdeb, 14-99 d,#fffdeb, -|13: 0-0 d,#dcdac1, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-13 #293f35,#fffdeb, 14-99 d,#fffdeb, -|14: 0-0 d,#dcdac1, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-13 #293f35,#fffdeb, 14-99 d,#fffdeb, -|15: 0-0 d,#dcdac1, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-13 #293f35,#fffdeb, 14-99 d,#fffdeb, -|16: 0-0 d,#dcdac1, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-13 #293f35,#fffdeb, 14-99 d,#fffdeb, -|17: 0-1 #ffffff,#48876f, 2-2 d,#c4d5cf, 3-32 #45635a,#c4d5cf, 33-39 #6b4e74,#c4d5cf, 40-61 #45635a,#c4d5cf, 62-99 d,#c4d5cf, -|18: 0-0 d,#9da777, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-21 #293f35,#fffdeb, 22-92 d,#fffdeb, 93-93 d,#d2dcda, 94-98 #4b6960,#d2dcda, 99-99 d,#d2dcda, -|19: 0-0 d,#dcdac1, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-19 #293f35,#fffdeb, 20-99 d,#fffdeb, -|20: 0-0 d,#dcdac1, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-16 #293f35,#fffdeb, 17-99 d,#fffdeb, -|21: 0-0 d,#dcdac1, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-15 #293f35,#fffdeb, 16-99 d,#fffdeb, -|22: 0-0 d,#dcdac1, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-17 #293f35,#fffdeb, 18-99 d,#fffdeb, -|23: 0-0 d,#dcdac1, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-20 #293f35,#fffdeb, 21-99 d,#fffdeb, -|24: 0-0 d,#dcdac1, 1-1 d,#fffdeb, 2-6 #889078,#fffdeb,b 7-19 #293f35,#fffdeb, 20-99 d,#fffdeb, -|25: 0-0 d,#dcdac1, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-17 #293f35,#fffdeb, 18-99 d,#fffdeb, -|26: 0-0 d,#dcdac1, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-18 #293f35,#fffdeb, 19-99 d,#fffdeb, -|27: 0-0 d,#dcdac1, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-20 #293f35,#fffdeb, 21-99 d,#fffdeb, -|28: 0-0 d,#dcdac1, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-20 #293f35,#fffdeb, 21-99 d,#fffdeb, -|29: 0-0 d,#dcdac1, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-19 #293f35,#fffdeb, 20-99 d,#fffdeb, -|30: 0-0 d,#dcdac1, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-16 #293f35,#fffdeb, 17-99 d,#fffdeb, +|0: 0-70 #3d6055,#e4f0ed, 71-99 d,#e4f0ed, +|1: 0-1 #000000,#bca481, 2-2 d,#d3e7df, 3-34 #35584d,#d3e7df, 35-99 d,#d3e7df, +|2: 0-1 #000000,#95baa2, 2-2 d,#e4f0ed, 3-32 #3d6055,#e4f0ed, 33-37 #6e4774,#e4f0ed, 38-59 #3d6055,#e4f0ed, 60-99 d,#e4f0ed, +|3: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-12 #293f35,#fffdeb, 13-99 d,#fffdeb, +|4: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-12 #293f35,#fffdeb, 13-99 d,#fffdeb, +|5: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-12 #293f35,#fffdeb, 13-99 d,#fffdeb, +|6: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-12 #293f35,#fffdeb, 13-99 d,#fffdeb, +|7: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-12 #293f35,#fffdeb, 13-99 d,#fffdeb, +|8: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-12 #293f35,#fffdeb, 13-99 d,#fffdeb, +|9: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-12 #293f35,#fffdeb, 13-99 d,#fffdeb, +|10: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-12 #293f35,#fffdeb, 13-99 d,#fffdeb, +|11: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-12 #293f35,#fffdeb, 13-99 d,#fffdeb, +|12: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-13 #293f35,#fffdeb, 14-99 d,#fffdeb, +|13: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-13 #293f35,#fffdeb, 14-99 d,#fffdeb, +|14: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-13 #293f35,#fffdeb, 14-99 d,#fffdeb, +|15: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-13 #293f35,#fffdeb, 14-99 d,#fffdeb, +|16: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-13 #293f35,#fffdeb, 14-99 d,#fffdeb, +|17: 0-1 #ffffff,#36826b, 2-2 d,#d3e7df, 3-32 #35584d,#d3e7df, 33-39 #653f6b,#d3e7df, 40-61 #35584d,#d3e7df, 62-99 d,#d3e7df, +|18: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-21 #293f35,#fffdeb, 22-92 d,#fffdeb, 93-93 d,#e4f0ed, 94-98 #3d6055,#e4f0ed, 99-99 d,#e4f0ed, +|19: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-19 #293f35,#fffdeb, 20-99 d,#fffdeb, +|20: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-16 #293f35,#fffdeb, 17-99 d,#fffdeb, +|21: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-15 #293f35,#fffdeb, 16-99 d,#fffdeb, +|22: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-17 #293f35,#fffdeb, 18-99 d,#fffdeb, +|23: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-20 #293f35,#fffdeb, 21-99 d,#fffdeb, +|24: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #889078,#fffdeb,b 7-19 #293f35,#fffdeb, 20-99 d,#fffdeb, +|25: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-17 #293f35,#fffdeb, 18-99 d,#fffdeb, +|26: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-18 #293f35,#fffdeb, 19-99 d,#fffdeb, +|27: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-20 #293f35,#fffdeb, 21-99 d,#fffdeb, +|28: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-20 #293f35,#fffdeb, 21-99 d,#fffdeb, +|29: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-19 #293f35,#fffdeb, 20-99 d,#fffdeb, +|30: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-16 #293f35,#fffdeb, 17-99 d,#fffdeb, == snap step-forge grid=100x31 cursor=7,25 == style step-forge grid=100x31 -|0: 0-70 #b7bfc6,#35393e, 71-99 d,#35393e, -|1: 0-1 #ffffff,#727f6a, 2-2 d,#3e4650, 3-34 #c1c9d0,#3e4650, 35-99 d,#3e4650, -|2: 0-1 #ffffff,#5b6d7f, 2-2 d,#35393e, 3-32 #b7bfc6,#35393e, 33-37 #dfb397,#35393e, 38-59 #b7bfc6,#35393e, 60-99 d,#35393e, -|3: 0-0 d,#758592, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-12 #e5e5e5,#0c0c0e, 13-99 d,#0c0c0e, -|4: 0-0 d,#758592, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-12 #e5e5e5,#0c0c0e, 13-99 d,#0c0c0e, -|5: 0-0 d,#758592, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-12 #e5e5e5,#0c0c0e, 13-99 d,#0c0c0e, -|6: 0-0 d,#758592, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-12 #e5e5e5,#0c0c0e, 13-99 d,#0c0c0e, -|7: 0-0 d,#2f3132, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-12 #e5e5e5,#0c0c0e, 13-99 d,#0c0c0e, -|8: 0-0 d,#2f3132, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-12 #e5e5e5,#0c0c0e, 13-99 d,#0c0c0e, -|9: 0-0 d,#2f3132, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-12 #e5e5e5,#0c0c0e, 13-99 d,#0c0c0e, -|10: 0-0 d,#2f3132, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-12 #e5e5e5,#0c0c0e, 13-99 d,#0c0c0e, -|11: 0-0 d,#2f3132, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-12 #e5e5e5,#0c0c0e, 13-99 d,#0c0c0e, -|12: 0-0 d,#2f3132, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-13 #e5e5e5,#0c0c0e, 14-99 d,#0c0c0e, -|13: 0-0 d,#2f3132, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-13 #e5e5e5,#0c0c0e, 14-99 d,#0c0c0e, -|14: 0-0 d,#2f3132, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-13 #e5e5e5,#0c0c0e, 14-99 d,#0c0c0e, -|15: 0-0 d,#2f3132, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-13 #e5e5e5,#0c0c0e, 14-99 d,#0c0c0e, -|16: 0-0 d,#2f3132, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-13 #e5e5e5,#0c0c0e, 14-99 d,#0c0c0e, -|17: 0-1 #000000,#92bde9, 2-2 d,#3e4650, 3-32 #c1c9d0,#3e4650, 33-39 #e9bda1,#3e4650, 40-61 #c1c9d0,#3e4650, 62-99 d,#3e4650, -|18: 0-0 d,#758592, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-21 #e5e5e5,#0c0c0e, 22-92 d,#0c0c0e, 93-93 d,#35393e, 94-98 #b7bfc6,#35393e, 99-99 d,#35393e, -|19: 0-0 d,#2f3132, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-19 #e5e5e5,#0c0c0e, 20-99 d,#0c0c0e, -|20: 0-0 d,#2f3132, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-16 #e5e5e5,#0c0c0e, 17-99 d,#0c0c0e, -|21: 0-0 d,#2f3132, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-15 #e5e5e5,#0c0c0e, 16-99 d,#0c0c0e, -|22: 0-0 d,#2f3132, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-17 #e5e5e5,#0c0c0e, 18-99 d,#0c0c0e, -|23: 0-0 d,#2f3132, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-20 #e5e5e5,#0c0c0e, 21-99 d,#0c0c0e, -|24: 0-0 d,#2f3132, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-19 #e5e5e5,#0c0c0e, 20-99 d,#0c0c0e, -|25: 0-0 d,#2f3132, 1-1 d,#0c0c0e, 2-6 #6a6d74,#0c0c0e,b 7-17 #e5e5e5,#0c0c0e, 18-99 d,#0c0c0e, -|26: 0-0 d,#2f3132, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-18 #e5e5e5,#0c0c0e, 19-99 d,#0c0c0e, -|27: 0-0 d,#2f3132, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-20 #e5e5e5,#0c0c0e, 21-99 d,#0c0c0e, -|28: 0-0 d,#2f3132, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-20 #e5e5e5,#0c0c0e, 21-99 d,#0c0c0e, -|29: 0-0 d,#2f3132, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-19 #e5e5e5,#0c0c0e, 20-99 d,#0c0c0e, -|30: 0-0 d,#2f3132, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-16 #e5e5e5,#0c0c0e, 17-99 d,#0c0c0e, +|0: 0-70 #c5c9d2,#22252b, 71-99 d,#22252b, +|1: 0-1 #ffffff,#6a7b67, 2-2 d,#2d3440, 3-34 #d1d5de,#2d3440, 35-99 d,#2d3440, +|2: 0-1 #ffffff,#55647b, 2-2 d,#22252b, 3-32 #c5c9d2,#22252b, 33-37 #ecbf9b,#22252b, 38-59 #c5c9d2,#22252b, 60-99 d,#22252b, +|3: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-12 #e5e5e5,#0c0c0e, 13-99 d,#0c0c0e, +|4: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-12 #e5e5e5,#0c0c0e, 13-99 d,#0c0c0e, +|5: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-12 #e5e5e5,#0c0c0e, 13-99 d,#0c0c0e, +|6: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-12 #e5e5e5,#0c0c0e, 13-99 d,#0c0c0e, +|7: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-12 #e5e5e5,#0c0c0e, 13-99 d,#0c0c0e, +|8: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-12 #e5e5e5,#0c0c0e, 13-99 d,#0c0c0e, +|9: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-12 #e5e5e5,#0c0c0e, 13-99 d,#0c0c0e, +|10: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-12 #e5e5e5,#0c0c0e, 13-99 d,#0c0c0e, +|11: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-12 #e5e5e5,#0c0c0e, 13-99 d,#0c0c0e, +|12: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-13 #e5e5e5,#0c0c0e, 14-99 d,#0c0c0e, +|13: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-13 #e5e5e5,#0c0c0e, 14-99 d,#0c0c0e, +|14: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-13 #e5e5e5,#0c0c0e, 14-99 d,#0c0c0e, +|15: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-13 #e5e5e5,#0c0c0e, 14-99 d,#0c0c0e, +|16: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-13 #e5e5e5,#0c0c0e, 14-99 d,#0c0c0e, +|17: 0-1 #000000,#9cc3f7, 2-2 d,#2d3440, 3-32 #d1d5de,#2d3440, 33-39 #f8cba7,#2d3440, 40-61 #d1d5de,#2d3440, 62-99 d,#2d3440, +|18: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-21 #e5e5e5,#0c0c0e, 22-92 d,#0c0c0e, 93-93 d,#22252b, 94-98 #c5c9d2,#22252b, 99-99 d,#22252b, +|19: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-19 #e5e5e5,#0c0c0e, 20-99 d,#0c0c0e, +|20: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-16 #e5e5e5,#0c0c0e, 17-99 d,#0c0c0e, +|21: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-15 #e5e5e5,#0c0c0e, 16-99 d,#0c0c0e, +|22: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-17 #e5e5e5,#0c0c0e, 18-99 d,#0c0c0e, +|23: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-20 #e5e5e5,#0c0c0e, 21-99 d,#0c0c0e, +|24: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-19 #e5e5e5,#0c0c0e, 20-99 d,#0c0c0e, +|25: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #6a6d74,#0c0c0e,b 7-17 #e5e5e5,#0c0c0e, 18-99 d,#0c0c0e, +|26: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-18 #e5e5e5,#0c0c0e, 19-99 d,#0c0c0e, +|27: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-20 #e5e5e5,#0c0c0e, 21-99 d,#0c0c0e, +|28: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-20 #e5e5e5,#0c0c0e, 21-99 d,#0c0c0e, +|29: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-19 #e5e5e5,#0c0c0e, 20-99 d,#0c0c0e, +|30: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-16 #e5e5e5,#0c0c0e, 17-99 d,#0c0c0e, == snap step-lagoon grid=100x31 cursor=7,26 == style step-lagoon grid=100x31 -|0: 0-70 #bcd6cc,#28413d, 71-99 d,#28413d, -|1: 0-1 #ffffff,#957763, 2-2 d,#33544c, 3-34 #c8e2d8,#33544c, 35-99 d,#33544c, -|2: 0-1 #ffffff,#548073, 2-2 d,#28413d, 3-32 #bcd6cc,#28413d, 33-37 #e5c2e4,#28413d, 38-59 #bcd6cc,#28413d, 60-99 d,#28413d, -|3: 0-0 d,#6e988c, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-12 #bac9ce,#34464c, 13-99 d,#34464c, -|4: 0-0 d,#6e988c, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-12 #bac9ce,#34464c, 13-99 d,#34464c, -|5: 0-0 d,#6e988c, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-12 #bac9ce,#34464c, 13-99 d,#34464c, -|6: 0-0 d,#6e988c, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-12 #bac9ce,#34464c, 13-99 d,#34464c, -|7: 0-0 d,#37474b, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-12 #bac9ce,#34464c, 13-99 d,#34464c, -|8: 0-0 d,#37474b, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-12 #bac9ce,#34464c, 13-99 d,#34464c, -|9: 0-0 d,#37474b, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-12 #bac9ce,#34464c, 13-99 d,#34464c, -|10: 0-0 d,#37474b, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-12 #bac9ce,#34464c, 13-99 d,#34464c, -|11: 0-0 d,#37474b, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-12 #bac9ce,#34464c, 13-99 d,#34464c, -|12: 0-0 d,#37474b, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-13 #bac9ce,#34464c, 14-99 d,#34464c, -|13: 0-0 d,#37474b, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-13 #bac9ce,#34464c, 14-99 d,#34464c, -|14: 0-0 d,#37474b, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-13 #bac9ce,#34464c, 14-99 d,#34464c, -|15: 0-0 d,#37474b, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-13 #bac9ce,#34464c, 14-99 d,#34464c, -|16: 0-0 d,#37474b, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-13 #bac9ce,#34464c, 14-99 d,#34464c, -|17: 0-1 #000000,#84cebd, 2-2 d,#33544c, 3-32 #c8e2d8,#33544c, 33-39 #f1cef0,#33544c, 40-61 #c8e2d8,#33544c, 62-99 d,#33544c, -|18: 0-0 d,#6e988c, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-21 #bac9ce,#34464c, 22-92 d,#34464c, 93-93 d,#28413d, 94-98 #bcd6cc,#28413d, 99-99 d,#28413d, -|19: 0-0 d,#37474b, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-19 #bac9ce,#34464c, 20-99 d,#34464c, -|20: 0-0 d,#37474b, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-16 #bac9ce,#34464c, 17-99 d,#34464c, -|21: 0-0 d,#37474b, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-15 #bac9ce,#34464c, 16-99 d,#34464c, -|22: 0-0 d,#37474b, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-17 #bac9ce,#34464c, 18-99 d,#34464c, -|23: 0-0 d,#37474b, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-20 #bac9ce,#34464c, 21-99 d,#34464c, -|24: 0-0 d,#37474b, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-19 #bac9ce,#34464c, 20-99 d,#34464c, -|25: 0-0 d,#37474b, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-17 #bac9ce,#34464c, 18-99 d,#34464c, -|26: 0-0 d,#37474b, 1-1 d,#34464c, 2-6 #83999c,#34464c,b 7-18 #bac9ce,#34464c, 19-99 d,#34464c, -|27: 0-0 d,#37474b, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-20 #bac9ce,#34464c, 21-99 d,#34464c, -|28: 0-0 d,#37474b, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-20 #bac9ce,#34464c, 21-99 d,#34464c, -|29: 0-0 d,#37474b, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-19 #bac9ce,#34464c, 20-99 d,#34464c, -|30: 0-0 d,#37474b, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-16 #bac9ce,#34464c, 17-99 d,#34464c, +|0: 0-70 #bbd7cb,#29443f, 71-99 d,#29443f, +|1: 0-1 #ffffff,#9a7662, 2-2 d,#34574d, 3-34 #c7e3d7,#34574d, 35-99 d,#34574d, +|2: 0-1 #ffffff,#548372, 2-2 d,#29443f, 3-32 #bbd7cb,#29443f, 33-37 #e4c2ec,#29443f, 38-59 #bbd7cb,#29443f, 60-99 d,#29443f, +|3: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-12 #bac9ce,#34464c, 13-99 d,#34464c, +|4: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-12 #bac9ce,#34464c, 13-99 d,#34464c, +|5: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-12 #bac9ce,#34464c, 13-99 d,#34464c, +|6: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-12 #bac9ce,#34464c, 13-99 d,#34464c, +|7: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-12 #bac9ce,#34464c, 13-99 d,#34464c, +|8: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-12 #bac9ce,#34464c, 13-99 d,#34464c, +|9: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-12 #bac9ce,#34464c, 13-99 d,#34464c, +|10: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-12 #bac9ce,#34464c, 13-99 d,#34464c, +|11: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-12 #bac9ce,#34464c, 13-99 d,#34464c, +|12: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-13 #bac9ce,#34464c, 14-99 d,#34464c, +|13: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-13 #bac9ce,#34464c, 14-99 d,#34464c, +|14: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-13 #bac9ce,#34464c, 14-99 d,#34464c, +|15: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-13 #bac9ce,#34464c, 14-99 d,#34464c, +|16: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-13 #bac9ce,#34464c, 14-99 d,#34464c, +|17: 0-1 #000000,#81cfb7, 2-2 d,#34574d, 3-32 #c7e3d7,#34574d, 33-39 #f0cef8,#34574d, 40-61 #c7e3d7,#34574d, 62-99 d,#34574d, +|18: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-21 #bac9ce,#34464c, 22-92 d,#34464c, 93-93 d,#29443f, 94-98 #bbd7cb,#29443f, 99-99 d,#29443f, +|19: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-19 #bac9ce,#34464c, 20-99 d,#34464c, +|20: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-16 #bac9ce,#34464c, 17-99 d,#34464c, +|21: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-15 #bac9ce,#34464c, 16-99 d,#34464c, +|22: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-17 #bac9ce,#34464c, 18-99 d,#34464c, +|23: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-20 #bac9ce,#34464c, 21-99 d,#34464c, +|24: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-19 #bac9ce,#34464c, 20-99 d,#34464c, +|25: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-17 #bac9ce,#34464c, 18-99 d,#34464c, +|26: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #83999c,#34464c,b 7-18 #bac9ce,#34464c, 19-99 d,#34464c, +|27: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-20 #bac9ce,#34464c, 21-99 d,#34464c, +|28: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-20 #bac9ce,#34464c, 21-99 d,#34464c, +|29: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-19 #bac9ce,#34464c, 20-99 d,#34464c, +|30: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-16 #bac9ce,#34464c, 17-99 d,#34464c, == snap step-solarium grid=100x31 cursor=7,27 == style step-solarium grid=100x31 -|0: 0-70 #c6d4be,#144043, 71-99 d,#144043, -|1: 0-1 #ffffff,#3e7a94, 2-2 d,#234f50, 3-34 #d2e0ca,#234f50, 35-99 d,#234f50, -|2: 0-1 #ffffff,#477f76, 2-2 d,#144043, 3-32 #c6d4be,#144043, 33-37 #e9c1e1,#144043, 38-59 #c6d4be,#144043, 60-99 d,#144043, -|3: 0-0 d,#66978c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-12 #a7b7ae,#173e45, 13-99 d,#173e45, -|4: 0-0 d,#66978c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-12 #a7b7ae,#173e45, 13-99 d,#173e45, -|5: 0-0 d,#66978c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-12 #a7b7ae,#173e45, 13-99 d,#173e45, -|6: 0-0 d,#66978c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-12 #a7b7ae,#173e45, 13-99 d,#173e45, -|7: 0-0 d,#1f464c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-12 #a7b7ae,#173e45, 13-99 d,#173e45, -|8: 0-0 d,#1f464c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-12 #a7b7ae,#173e45, 13-99 d,#173e45, -|9: 0-0 d,#1f464c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-12 #a7b7ae,#173e45, 13-99 d,#173e45, -|10: 0-0 d,#1f464c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-12 #a7b7ae,#173e45, 13-99 d,#173e45, -|11: 0-0 d,#1f464c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-12 #a7b7ae,#173e45, 13-99 d,#173e45, -|12: 0-0 d,#1f464c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-13 #a7b7ae,#173e45, 14-99 d,#173e45, -|13: 0-0 d,#1f464c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-13 #a7b7ae,#173e45, 14-99 d,#173e45, -|14: 0-0 d,#1f464c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-13 #a7b7ae,#173e45, 14-99 d,#173e45, -|15: 0-0 d,#1f464c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-13 #a7b7ae,#173e45, 14-99 d,#173e45, -|16: 0-0 d,#1f464c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-13 #a7b7ae,#173e45, 14-99 d,#173e45, -|17: 0-1 #000000,#d7c36e, 2-2 d,#234f50, 3-32 #d2e0ca,#234f50, 33-39 #f5cded,#234f50, 40-61 #d2e0ca,#234f50, 62-99 d,#234f50, -|18: 0-0 d,#66978c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-21 #a7b7ae,#173e45, 22-92 d,#173e45, 93-93 d,#144043, 94-98 #c6d4be,#144043, 99-99 d,#144043, -|19: 0-0 d,#1f464c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-19 #a7b7ae,#173e45, 20-99 d,#173e45, -|20: 0-0 d,#1f464c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-16 #a7b7ae,#173e45, 17-99 d,#173e45, -|21: 0-0 d,#1f464c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-15 #a7b7ae,#173e45, 16-99 d,#173e45, -|22: 0-0 d,#1f464c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-17 #a7b7ae,#173e45, 18-99 d,#173e45, -|23: 0-0 d,#1f464c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-20 #a7b7ae,#173e45, 21-99 d,#173e45, -|24: 0-0 d,#1f464c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-19 #a7b7ae,#173e45, 20-99 d,#173e45, -|25: 0-0 d,#1f464c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-17 #a7b7ae,#173e45, 18-99 d,#173e45, -|26: 0-0 d,#1f464c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-18 #a7b7ae,#173e45, 19-99 d,#173e45, -|27: 0-0 d,#1f464c, 1-1 d,#173e45, 2-6 #6c8684,#173e45,b 7-20 #a7b7ae,#173e45, 21-99 d,#173e45, -|28: 0-0 d,#1f464c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-20 #a7b7ae,#173e45, 21-99 d,#173e45, -|29: 0-0 d,#1f464c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-19 #a7b7ae,#173e45, 20-99 d,#173e45, -|30: 0-0 d,#1f464c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-16 #a7b7ae,#173e45, 17-99 d,#173e45, +|0: 0-70 #c7d4bd,#123f43, 71-99 d,#123f43, +|1: 0-1 #ffffff,#347a9a, 2-2 d,#214e50, 3-34 #d3e0c9,#214e50, 35-99 d,#214e50, +|2: 0-1 #ffffff,#467f76, 2-2 d,#123f43, 3-32 #c7d4bd,#123f43, 33-37 #e9c1e0,#123f43, 38-59 #c7d4bd,#123f43, 60-99 d,#123f43, +|3: 0-0 d,#65978c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-12 #a7b7ae,#173e45, 13-99 d,#173e45, +|4: 0-0 d,#65978c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-12 #a7b7ae,#173e45, 13-99 d,#173e45, +|5: 0-0 d,#65978c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-12 #a7b7ae,#173e45, 13-99 d,#173e45, +|6: 0-0 d,#65978c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-12 #a7b7ae,#173e45, 13-99 d,#173e45, +|7: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-12 #a7b7ae,#173e45, 13-99 d,#173e45, +|8: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-12 #a7b7ae,#173e45, 13-99 d,#173e45, +|9: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-12 #a7b7ae,#173e45, 13-99 d,#173e45, +|10: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-12 #a7b7ae,#173e45, 13-99 d,#173e45, +|11: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-12 #a7b7ae,#173e45, 13-99 d,#173e45, +|12: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-13 #a7b7ae,#173e45, 14-99 d,#173e45, +|13: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-13 #a7b7ae,#173e45, 14-99 d,#173e45, +|14: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-13 #a7b7ae,#173e45, 14-99 d,#173e45, +|15: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-13 #a7b7ae,#173e45, 14-99 d,#173e45, +|16: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-13 #a7b7ae,#173e45, 14-99 d,#173e45, +|17: 0-1 #000000,#e1c266, 2-2 d,#214e50, 3-32 #d3e0c9,#214e50, 33-39 #f5cdec,#214e50, 40-61 #d3e0c9,#214e50, 62-99 d,#214e50, +|18: 0-0 d,#65978c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-21 #a7b7ae,#173e45, 22-92 d,#173e45, 93-93 d,#123f43, 94-98 #c7d4bd,#123f43, 99-99 d,#123f43, +|19: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-19 #a7b7ae,#173e45, 20-99 d,#173e45, +|20: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-16 #a7b7ae,#173e45, 17-99 d,#173e45, +|21: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-15 #a7b7ae,#173e45, 16-99 d,#173e45, +|22: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-17 #a7b7ae,#173e45, 18-99 d,#173e45, +|23: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-20 #a7b7ae,#173e45, 21-99 d,#173e45, +|24: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-19 #a7b7ae,#173e45, 20-99 d,#173e45, +|25: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-17 #a7b7ae,#173e45, 18-99 d,#173e45, +|26: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-18 #a7b7ae,#173e45, 19-99 d,#173e45, +|27: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #6c8684,#173e45,b 7-20 #a7b7ae,#173e45, 21-99 d,#173e45, +|28: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-20 #a7b7ae,#173e45, 21-99 d,#173e45, +|29: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-19 #a7b7ae,#173e45, 20-99 d,#173e45, +|30: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-16 #a7b7ae,#173e45, 17-99 d,#173e45, == snap step-spectrum grid=100x31 cursor=7,27 |18: 2 Theme orchard Theme |19: 3 Theme dusk @@ -261,37 +261,37 @@ |29: 13 Theme clay |30: 14 Theme forge_black == style step-spectrum grid=100x31 -|0: 0-70 #dbd8bb,#373932, 71-99 d,#373932, -|1: 0-1 #ffffff,#6a6796, 2-2 d,#494839, 3-34 #e7e4c7,#494839, 35-99 d,#494839, -|2: 0-1 #ffffff,#8a8453, 2-2 d,#373932, 3-32 #dbd8bb,#373932, 33-37 #c9d5fc,#373932, 38-59 #dbd8bb,#373932, 60-99 d,#373932, -|3: 0-0 d,#8f8e72, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-12 #fcfcfa,#100e11, 13-99 d,#100e11, -|4: 0-0 d,#8f8e72, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-12 #fcfcfa,#100e11, 13-99 d,#100e11, -|5: 0-0 d,#8f8e72, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-12 #fcfcfa,#100e11, 13-99 d,#100e11, -|6: 0-0 d,#8f8e72, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-12 #fcfcfa,#100e11, 13-99 d,#100e11, -|7: 0-0 d,#212127, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-12 #fcfcfa,#100e11, 13-99 d,#100e11, -|8: 0-0 d,#212127, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-12 #fcfcfa,#100e11, 13-99 d,#100e11, -|9: 0-0 d,#212127, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-12 #fcfcfa,#100e11, 13-99 d,#100e11, -|10: 0-0 d,#212127, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-12 #fcfcfa,#100e11, 13-99 d,#100e11, -|11: 0-0 d,#212127, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-12 #fcfcfa,#100e11, 13-99 d,#100e11, -|12: 0-0 d,#212127, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-13 #fcfcfa,#100e11, 14-99 d,#100e11, -|13: 0-0 d,#212127, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-13 #fcfcfa,#100e11, 14-99 d,#100e11, -|14: 0-0 d,#212127, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-13 #fcfcfa,#100e11, 14-99 d,#100e11, -|15: 0-0 d,#212127, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-13 #fcfcfa,#100e11, 14-99 d,#100e11, -|16: 0-0 d,#212127, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-13 #fcfcfa,#100e11, 14-99 d,#100e11, -|17: 0-1 #000000,#fcd666, 2-2 d,#494839, 3-32 #e7e4c7,#494839, 33-39 #dae1fd,#494839, 40-61 #e7e4c7,#494839, 62-99 d,#494839, -|18: 0-0 d,#8f8e72, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-19 #fcfcfa,#100e11, 20-92 d,#100e11, 93-93 d,#373932, 94-98 #dbd8bb,#373932, 99-99 d,#373932, -|19: 0-0 d,#212127, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-16 #fcfcfa,#100e11, 17-99 d,#100e11, -|20: 0-0 d,#212127, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-15 #fcfcfa,#100e11, 16-99 d,#100e11, -|21: 0-0 d,#212127, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-17 #fcfcfa,#100e11, 18-99 d,#100e11, -|22: 0-0 d,#212127, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-20 #fcfcfa,#100e11, 21-99 d,#100e11, -|23: 0-0 d,#212127, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-19 #fcfcfa,#100e11, 20-99 d,#100e11, -|24: 0-0 d,#212127, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-17 #fcfcfa,#100e11, 18-99 d,#100e11, -|25: 0-0 d,#212127, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-18 #fcfcfa,#100e11, 19-99 d,#100e11, -|26: 0-0 d,#212127, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-20 #fcfcfa,#100e11, 21-99 d,#100e11, -|27: 0-0 d,#212127, 1-1 d,#100e11, 2-6 #706973,#100e11,b 7-20 #fcfcfa,#100e11, 21-99 d,#100e11, -|28: 0-0 d,#212127, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-19 #fcfcfa,#100e11, 20-99 d,#100e11, -|29: 0-0 d,#212127, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-16 #fcfcfa,#100e11, 17-99 d,#100e11, -|30: 0-0 d,#212127, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-23 #fcfcfa,#100e11, 24-99 d,#100e11, +|0: 0-70 #ddd8bb,#3b3830, 71-99 d,#3b3830, +|1: 0-1 #ffffff,#706596, 2-2 d,#4d4736, 3-34 #e9e4c7,#4d4736, 35-99 d,#4d4736, +|2: 0-1 #ffffff,#92844f, 2-2 d,#3b3830, 3-32 #ddd8bb,#3b3830, 33-37 #c5d7ff,#3b3830, 38-59 #ddd8bb,#3b3830, 60-99 d,#3b3830, +|3: 0-0 d,#948d6f, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-12 #fcfcfa,#100e11, 13-99 d,#100e11, +|4: 0-0 d,#948d6f, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-12 #fcfcfa,#100e11, 13-99 d,#100e11, +|5: 0-0 d,#948d6f, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-12 #fcfcfa,#100e11, 13-99 d,#100e11, +|6: 0-0 d,#948d6f, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-12 #fcfcfa,#100e11, 13-99 d,#100e11, +|7: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-12 #fcfcfa,#100e11, 13-99 d,#100e11, +|8: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-12 #fcfcfa,#100e11, 13-99 d,#100e11, +|9: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-12 #fcfcfa,#100e11, 13-99 d,#100e11, +|10: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-12 #fcfcfa,#100e11, 13-99 d,#100e11, +|11: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-12 #fcfcfa,#100e11, 13-99 d,#100e11, +|12: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-13 #fcfcfa,#100e11, 14-99 d,#100e11, +|13: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-13 #fcfcfa,#100e11, 14-99 d,#100e11, +|14: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-13 #fcfcfa,#100e11, 14-99 d,#100e11, +|15: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-13 #fcfcfa,#100e11, 14-99 d,#100e11, +|16: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-13 #fcfcfa,#100e11, 14-99 d,#100e11, +|17: 0-1 #000000,#ffd866, 2-2 d,#4d4736, 3-32 #e9e4c7,#4d4736, 33-39 #d7e3ff,#4d4736, 40-61 #e9e4c7,#4d4736, 62-99 d,#4d4736, +|18: 0-0 d,#948d6f, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-19 #fcfcfa,#100e11, 20-92 d,#100e11, 93-93 d,#3b3830, 94-98 #ddd8bb,#3b3830, 99-99 d,#3b3830, +|19: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-16 #fcfcfa,#100e11, 17-99 d,#100e11, +|20: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-15 #fcfcfa,#100e11, 16-99 d,#100e11, +|21: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-17 #fcfcfa,#100e11, 18-99 d,#100e11, +|22: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-20 #fcfcfa,#100e11, 21-99 d,#100e11, +|23: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-19 #fcfcfa,#100e11, 20-99 d,#100e11, +|24: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-17 #fcfcfa,#100e11, 18-99 d,#100e11, +|25: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-18 #fcfcfa,#100e11, 19-99 d,#100e11, +|26: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-20 #fcfcfa,#100e11, 21-99 d,#100e11, +|27: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #706973,#100e11,b 7-20 #fcfcfa,#100e11, 21-99 d,#100e11, +|28: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-19 #fcfcfa,#100e11, 20-99 d,#100e11, +|29: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-16 #fcfcfa,#100e11, 17-99 d,#100e11, +|30: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-23 #fcfcfa,#100e11, 24-99 d,#100e11, == snap step-harvest grid=100x31 cursor=7,27 |18: 3 Theme dusk Theme |19: 4 Theme ink @@ -307,37 +307,37 @@ |29: 14 Theme forge_black |30: 15 Theme forge_soft == style step-harvest grid=100x31 -|0: 0-70 #d6d8b8,#353b2e, 71-99 d,#353b2e, -|1: 0-1 #ffffff,#975948, 2-2 d,#434c38, 3-34 #e2e4c4,#434c38, 35-99 d,#434c38, -|2: 0-1 #ffffff,#7d7f4e, 2-2 d,#353b2e, 3-32 #d6d8b8,#353b2e, 33-37 #bbdbed,#353b2e, 38-59 #d6d8b8,#353b2e, 60-99 d,#353b2e, -|3: 0-0 d,#898e60, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-12 #f3f2cc,#100e0b, 13-99 d,#100e0b, -|4: 0-0 d,#898e60, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-12 #f3f2cc,#100e0b, 13-99 d,#100e0b, -|5: 0-0 d,#898e60, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-12 #f3f2cc,#100e0b, 13-99 d,#100e0b, -|6: 0-0 d,#898e60, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-12 #f3f2cc,#100e0b, 13-99 d,#100e0b, -|7: 0-0 d,#201d18, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-12 #f3f2cc,#100e0b, 13-99 d,#100e0b, -|8: 0-0 d,#201d18, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-12 #f3f2cc,#100e0b, 13-99 d,#100e0b, -|9: 0-0 d,#201d18, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-12 #f3f2cc,#100e0b, 13-99 d,#100e0b, -|10: 0-0 d,#201d18, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-12 #f3f2cc,#100e0b, 13-99 d,#100e0b, -|11: 0-0 d,#201d18, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-12 #f3f2cc,#100e0b, 13-99 d,#100e0b, -|12: 0-0 d,#201d18, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-13 #f3f2cc,#100e0b, 14-99 d,#100e0b, -|13: 0-0 d,#201d18, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-13 #f3f2cc,#100e0b, 14-99 d,#100e0b, -|14: 0-0 d,#201d18, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-13 #f3f2cc,#100e0b, 14-99 d,#100e0b, -|15: 0-0 d,#201d18, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-13 #f3f2cc,#100e0b, 14-99 d,#100e0b, -|16: 0-0 d,#201d18, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-13 #f3f2cc,#100e0b, 14-99 d,#100e0b, -|17: 0-1 #000000,#dcc080, 2-2 d,#434c38, 3-32 #e2e4c4,#434c38, 33-39 #c7e7f8,#434c38, 40-61 #e2e4c4,#434c38, 62-99 d,#434c38, -|18: 0-0 d,#898e60, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-16 #f3f2cc,#100e0b, 17-92 d,#100e0b, 93-93 d,#353b2e, 94-98 #d6d8b8,#353b2e, 99-99 d,#353b2e, -|19: 0-0 d,#201d18, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-15 #f3f2cc,#100e0b, 16-99 d,#100e0b, -|20: 0-0 d,#201d18, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-17 #f3f2cc,#100e0b, 18-99 d,#100e0b, -|21: 0-0 d,#201d18, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-20 #f3f2cc,#100e0b, 21-99 d,#100e0b, -|22: 0-0 d,#201d18, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-19 #f3f2cc,#100e0b, 20-99 d,#100e0b, -|23: 0-0 d,#201d18, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-17 #f3f2cc,#100e0b, 18-99 d,#100e0b, -|24: 0-0 d,#201d18, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-18 #f3f2cc,#100e0b, 19-99 d,#100e0b, -|25: 0-0 d,#201d18, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-20 #f3f2cc,#100e0b, 21-99 d,#100e0b, -|26: 0-0 d,#201d18, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-20 #f3f2cc,#100e0b, 21-99 d,#100e0b, -|27: 0-0 d,#201d18, 1-1 d,#100e0b, 2-6 #726c5e,#100e0b,b 7-19 #f3f2cc,#100e0b, 20-99 d,#100e0b, -|28: 0-0 d,#201d18, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-16 #f3f2cc,#100e0b, 17-99 d,#100e0b, -|29: 0-0 d,#201d18, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-23 #f3f2cc,#100e0b, 24-99 d,#100e0b, -|30: 0-0 d,#201d18, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-22 #f3f2cc,#100e0b, 23-99 d,#100e0b, +|0: 0-70 #d5d8b8,#343b2e, 71-99 d,#343b2e, +|1: 0-1 #ffffff,#9b583f, 2-2 d,#424d38, 3-34 #e1e4c4,#424d38, 35-99 d,#424d38, +|2: 0-1 #ffffff,#7b7e4e, 2-2 d,#343b2e, 3-32 #d5d8b8,#343b2e, 33-37 #badbeb,#343b2e, 38-59 #d5d8b8,#343b2e, 60-99 d,#343b2e, +|3: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-12 #f3f2cc,#100e0b, 13-99 d,#100e0b, +|4: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-12 #f3f2cc,#100e0b, 13-99 d,#100e0b, +|5: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-12 #f3f2cc,#100e0b, 13-99 d,#100e0b, +|6: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-12 #f3f2cc,#100e0b, 13-99 d,#100e0b, +|7: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-12 #f3f2cc,#100e0b, 13-99 d,#100e0b, +|8: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-12 #f3f2cc,#100e0b, 13-99 d,#100e0b, +|9: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-12 #f3f2cc,#100e0b, 13-99 d,#100e0b, +|10: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-12 #f3f2cc,#100e0b, 13-99 d,#100e0b, +|11: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-12 #f3f2cc,#100e0b, 13-99 d,#100e0b, +|12: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-13 #f3f2cc,#100e0b, 14-99 d,#100e0b, +|13: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-13 #f3f2cc,#100e0b, 14-99 d,#100e0b, +|14: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-13 #f3f2cc,#100e0b, 14-99 d,#100e0b, +|15: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-13 #f3f2cc,#100e0b, 14-99 d,#100e0b, +|16: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-13 #f3f2cc,#100e0b, 14-99 d,#100e0b, +|17: 0-1 #000000,#d8bd83, 2-2 d,#424d38, 3-32 #e1e4c4,#424d38, 33-39 #c5e7f7,#424d38, 40-61 #e1e4c4,#424d38, 62-99 d,#424d38, +|18: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-16 #f3f2cc,#100e0b, 17-92 d,#100e0b, 93-93 d,#343b2e, 94-98 #d5d8b8,#343b2e, 99-99 d,#343b2e, +|19: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-15 #f3f2cc,#100e0b, 16-99 d,#100e0b, +|20: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-17 #f3f2cc,#100e0b, 18-99 d,#100e0b, +|21: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-20 #f3f2cc,#100e0b, 21-99 d,#100e0b, +|22: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-19 #f3f2cc,#100e0b, 20-99 d,#100e0b, +|23: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-17 #f3f2cc,#100e0b, 18-99 d,#100e0b, +|24: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-18 #f3f2cc,#100e0b, 19-99 d,#100e0b, +|25: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-20 #f3f2cc,#100e0b, 21-99 d,#100e0b, +|26: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-20 #f3f2cc,#100e0b, 21-99 d,#100e0b, +|27: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #726c5e,#100e0b,b 7-19 #f3f2cc,#100e0b, 20-99 d,#100e0b, +|28: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-16 #f3f2cc,#100e0b, 17-99 d,#100e0b, +|29: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-23 #f3f2cc,#100e0b, 24-99 d,#100e0b, +|30: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-22 #f3f2cc,#100e0b, 23-99 d,#100e0b, == snap step-clay grid=100x31 cursor=7,27 |18: 4 Theme ink Theme |19: 5 Theme paper @@ -353,13 +353,13 @@ |29: 15 Theme forge_soft |30: 16 Theme orchard_black == style step-clay grid=100x31 -|0: 0-70 #d7d6b6,#343f35, 71-99 d,#343f35, -|1: 0-1 #ffffff,#895d61, 2-2 d,#424f3f, 3-34 #e3e2c2,#424f3f, 35-99 d,#424f3f, -|2: 0-1 #ffffff,#6a7c4d, 2-2 d,#343f35, 3-32 #d7d6b6,#343f35, 33-37 #ace0e1,#343f35, 38-59 #d7d6b6,#343f35, 60-99 d,#343f35, -|3: 0-0 d,#8c9366, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-12 #f3e5bc,#10100e, 13-99 d,#10100e, -|4: 0-0 d,#8c9366, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-12 #f3e5bc,#10100e, 13-99 d,#10100e, -|5: 0-0 d,#8c9366, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-12 #f3e5bc,#10100e, 13-99 d,#10100e, -|6: 0-0 d,#8c9366, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-12 #f3e5bc,#10100e, 13-99 d,#10100e, +|0: 0-70 #d7d6b6,#343f36, 71-99 d,#343f36, +|1: 0-1 #ffffff,#875e65, 2-2 d,#424f40, 3-34 #e3e2c2,#424f40, 35-99 d,#424f40, +|2: 0-1 #ffffff,#687c4d, 2-2 d,#343f36, 3-32 #d7d6b6,#343f36, 33-37 #aae0e0,#343f36, 38-59 #d7d6b6,#343f36, 60-99 d,#343f36, +|3: 0-0 d,#8c9467, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-12 #f3e5bc,#10100e, 13-99 d,#10100e, +|4: 0-0 d,#8c9467, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-12 #f3e5bc,#10100e, 13-99 d,#10100e, +|5: 0-0 d,#8c9467, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-12 #f3e5bc,#10100e, 13-99 d,#10100e, +|6: 0-0 d,#8c9467, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-12 #f3e5bc,#10100e, 13-99 d,#10100e, |7: 0-0 d,#222119, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-12 #f3e5bc,#10100e, 13-99 d,#10100e, |8: 0-0 d,#222119, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-12 #f3e5bc,#10100e, 13-99 d,#10100e, |9: 0-0 d,#222119, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-12 #f3e5bc,#10100e, 13-99 d,#10100e, @@ -370,8 +370,8 @@ |14: 0-0 d,#222119, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-13 #f3e5bc,#10100e, 14-99 d,#10100e, |15: 0-0 d,#222119, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-13 #f3e5bc,#10100e, 14-99 d,#10100e, |16: 0-0 d,#222119, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-13 #f3e5bc,#10100e, 14-99 d,#10100e, -|17: 0-1 #000000,#92c776, 2-2 d,#424f3f, 3-32 #e3e2c2,#424f3f, 33-39 #b7eced,#424f3f, 40-61 #e3e2c2,#424f3f, 62-99 d,#424f3f, -|18: 0-0 d,#8c9366, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-15 #f3e5bc,#10100e, 16-92 d,#10100e, 93-93 d,#343f35, 94-98 #d7d6b6,#343f35, 99-99 d,#343f35, +|17: 0-1 #000000,#8ac874, 2-2 d,#424f40, 3-32 #e3e2c2,#424f40, 33-39 #b5ecec,#424f40, 40-61 #e3e2c2,#424f40, 62-99 d,#424f40, +|18: 0-0 d,#8c9467, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-15 #f3e5bc,#10100e, 16-92 d,#10100e, 93-93 d,#343f36, 94-98 #d7d6b6,#343f36, 99-99 d,#343f36, |19: 0-0 d,#222119, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-17 #f3e5bc,#10100e, 18-99 d,#10100e, |20: 0-0 d,#222119, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-20 #f3e5bc,#10100e, 21-99 d,#10100e, |21: 0-0 d,#222119, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-19 #f3e5bc,#10100e, 20-99 d,#10100e, @@ -399,37 +399,37 @@ |29: 16 Theme orchard_black |30: 17 == style step-forge-black grid=100x31 -|0: 0-70 #c7cacf,#24282c, 71-99 d,#24282c, -|1: 0-1 #ffffff,#6d7867, 2-2 d,#2f3740, 3-34 #d3d6db,#2f3740, 35-99 d,#2f3740, -|2: 0-1 #ffffff,#576676, 2-2 d,#24282c, 3-32 #c7cacf,#24282c, 33-37 #e5c2a2,#24282c, 38-59 #c7cacf,#24282c, 60-99 d,#24282c, -|3: 0-0 d,#738290, 1-1 d,#000000, 2-6 #62656c,#000000, 7-12 #f6f6f6,#000000, 13-99 d,#000000, -|4: 0-0 d,#738290, 1-1 d,#000000, 2-6 #62656c,#000000, 7-12 #f6f6f6,#000000, 13-99 d,#000000, -|5: 0-0 d,#738290, 1-1 d,#000000, 2-6 #62656c,#000000, 7-12 #f6f6f6,#000000, 13-99 d,#000000, -|6: 0-0 d,#738290, 1-1 d,#000000, 2-6 #62656c,#000000, 7-12 #f6f6f6,#000000, 13-99 d,#000000, -|7: 0-0 d,#15171a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-12 #f6f6f6,#000000, 13-99 d,#000000, -|8: 0-0 d,#15171a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-12 #f6f6f6,#000000, 13-99 d,#000000, -|9: 0-0 d,#15171a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-12 #f6f6f6,#000000, 13-99 d,#000000, -|10: 0-0 d,#15171a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-12 #f6f6f6,#000000, 13-99 d,#000000, -|11: 0-0 d,#15171a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-12 #f6f6f6,#000000, 13-99 d,#000000, -|12: 0-0 d,#15171a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-13 #f6f6f6,#000000, 14-99 d,#000000, -|13: 0-0 d,#15171a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-13 #f6f6f6,#000000, 14-99 d,#000000, -|14: 0-0 d,#15171a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-13 #f6f6f6,#000000, 14-99 d,#000000, -|15: 0-0 d,#15171a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-13 #f6f6f6,#000000, 14-99 d,#000000, -|16: 0-0 d,#15171a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-13 #f6f6f6,#000000, 14-99 d,#000000, -|17: 0-1 #000000,#9ac4ea, 2-2 d,#2f3740, 3-32 #d3d6db,#2f3740, 33-39 #f1ceae,#2f3740, 40-61 #d3d6db,#2f3740, 62-99 d,#2f3740, -|18: 0-0 d,#738290, 1-1 d,#000000, 2-6 #62656c,#000000, 7-17 #f6f6f6,#000000, 18-92 d,#000000, 93-93 d,#24282c, 94-98 #c7cacf,#24282c, 99-99 d,#24282c, -|19: 0-0 d,#15171a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-20 #f6f6f6,#000000, 21-99 d,#000000, -|20: 0-0 d,#15171a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-19 #f6f6f6,#000000, 20-99 d,#000000, -|21: 0-0 d,#15171a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-17 #f6f6f6,#000000, 18-99 d,#000000, -|22: 0-0 d,#15171a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-18 #f6f6f6,#000000, 19-99 d,#000000, -|23: 0-0 d,#15171a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-20 #f6f6f6,#000000, 21-99 d,#000000, -|24: 0-0 d,#15171a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-20 #f6f6f6,#000000, 21-99 d,#000000, -|25: 0-0 d,#15171a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-19 #f6f6f6,#000000, 20-99 d,#000000, -|26: 0-0 d,#15171a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-16 #f6f6f6,#000000, 17-99 d,#000000, -|27: 0-0 d,#15171a, 1-1 d,#000000, 2-6 #6a6d74,#000000,b 7-23 #f6f6f6,#000000, 24-99 d,#000000, -|28: 0-0 d,#15171a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-22 #f6f6f6,#000000, 23-99 d,#000000, -|29: 0-0 d,#15171a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-25 #f6f6f6,#000000, 26-99 d,#000000, -|30: 0-0 d,#15171a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-99 d,#000000, +|0: 0-70 #c5c9d2,#22252b, 71-99 d,#22252b, +|1: 0-1 #ffffff,#6a7b67, 2-2 d,#2d3440, 3-34 #d1d5de,#2d3440, 35-99 d,#2d3440, +|2: 0-1 #ffffff,#55647b, 2-2 d,#22252b, 3-32 #c5c9d2,#22252b, 33-37 #ecbf9b,#22252b, 38-59 #c5c9d2,#22252b, 60-99 d,#22252b, +|3: 0-0 d,#708095, 1-1 d,#000000, 2-6 #62656c,#000000, 7-12 #f6f6f6,#000000, 13-99 d,#000000, +|4: 0-0 d,#708095, 1-1 d,#000000, 2-6 #62656c,#000000, 7-12 #f6f6f6,#000000, 13-99 d,#000000, +|5: 0-0 d,#708095, 1-1 d,#000000, 2-6 #62656c,#000000, 7-12 #f6f6f6,#000000, 13-99 d,#000000, +|6: 0-0 d,#708095, 1-1 d,#000000, 2-6 #62656c,#000000, 7-12 #f6f6f6,#000000, 13-99 d,#000000, +|7: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-12 #f6f6f6,#000000, 13-99 d,#000000, +|8: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-12 #f6f6f6,#000000, 13-99 d,#000000, +|9: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-12 #f6f6f6,#000000, 13-99 d,#000000, +|10: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-12 #f6f6f6,#000000, 13-99 d,#000000, +|11: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-12 #f6f6f6,#000000, 13-99 d,#000000, +|12: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-13 #f6f6f6,#000000, 14-99 d,#000000, +|13: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-13 #f6f6f6,#000000, 14-99 d,#000000, +|14: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-13 #f6f6f6,#000000, 14-99 d,#000000, +|15: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-13 #f6f6f6,#000000, 14-99 d,#000000, +|16: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-13 #f6f6f6,#000000, 14-99 d,#000000, +|17: 0-1 #000000,#9cc3f7, 2-2 d,#2d3440, 3-32 #d1d5de,#2d3440, 33-39 #f8cba7,#2d3440, 40-61 #d1d5de,#2d3440, 62-99 d,#2d3440, +|18: 0-0 d,#708095, 1-1 d,#000000, 2-6 #62656c,#000000, 7-17 #f6f6f6,#000000, 18-92 d,#000000, 93-93 d,#22252b, 94-98 #c5c9d2,#22252b, 99-99 d,#22252b, +|19: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-20 #f6f6f6,#000000, 21-99 d,#000000, +|20: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-19 #f6f6f6,#000000, 20-99 d,#000000, +|21: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-17 #f6f6f6,#000000, 18-99 d,#000000, +|22: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-18 #f6f6f6,#000000, 19-99 d,#000000, +|23: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-20 #f6f6f6,#000000, 21-99 d,#000000, +|24: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-20 #f6f6f6,#000000, 21-99 d,#000000, +|25: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-19 #f6f6f6,#000000, 20-99 d,#000000, +|26: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-16 #f6f6f6,#000000, 17-99 d,#000000, +|27: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #6a6d74,#000000,b 7-23 #f6f6f6,#000000, 24-99 d,#000000, +|28: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-22 #f6f6f6,#000000, 23-99 d,#000000, +|29: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-25 #f6f6f6,#000000, 26-99 d,#000000, +|30: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-99 d,#000000, == snap step-forge-soft grid=100x31 cursor=7,27 |18: 6 Theme daybreak Theme |19: 7 Theme atelier @@ -445,37 +445,37 @@ |29: 17 |30: 18 # Legacy and imported themes == style step-forge-soft grid=100x31 -|0: 0-70 #c5c9d2,#292c33, 71-99 d,#292c33, -|1: 0-1 #ffffff,#6d7f6c, 2-2 d,#363d48, 3-34 #d1d5de,#363d48, 35-99 d,#363d48, -|2: 0-1 #ffffff,#55647b, 2-2 d,#292c33, 3-32 #c5c9d2,#292c33, 33-37 #ecbf9b,#292c33, 38-59 #c5c9d2,#292c33, 60-99 d,#292c33, +|0: 0-70 #c5c9d2,#2a2d34, 71-99 d,#2a2d34, +|1: 0-1 #ffffff,#6d7f6c, 2-2 d,#373e49, 3-34 #d1d5de,#373e49, 35-99 d,#373e49, +|2: 0-1 #ffffff,#55647b, 2-2 d,#2a2d34, 3-32 #c5c9d2,#2a2d34, 33-37 #ecbf9b,#2a2d34, 38-59 #c5c9d2,#2a2d34, 60-99 d,#2a2d34, |3: 0-0 d,#708095, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-12 #bfc3cc,#35383e, 13-99 d,#35383e, |4: 0-0 d,#708095, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-12 #bfc3cc,#35383e, 13-99 d,#35383e, |5: 0-0 d,#708095, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-12 #bfc3cc,#35383e, 13-99 d,#35383e, |6: 0-0 d,#708095, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-12 #bfc3cc,#35383e, 13-99 d,#35383e, -|7: 0-0 d,#393c43, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-12 #bfc3cc,#35383e, 13-99 d,#35383e, -|8: 0-0 d,#393c43, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-12 #bfc3cc,#35383e, 13-99 d,#35383e, -|9: 0-0 d,#393c43, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-12 #bfc3cc,#35383e, 13-99 d,#35383e, -|10: 0-0 d,#393c43, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-12 #bfc3cc,#35383e, 13-99 d,#35383e, -|11: 0-0 d,#393c43, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-12 #bfc3cc,#35383e, 13-99 d,#35383e, -|12: 0-0 d,#393c43, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-13 #bfc3cc,#35383e, 14-99 d,#35383e, -|13: 0-0 d,#393c43, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-13 #bfc3cc,#35383e, 14-99 d,#35383e, -|14: 0-0 d,#393c43, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-13 #bfc3cc,#35383e, 14-99 d,#35383e, -|15: 0-0 d,#393c43, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-13 #bfc3cc,#35383e, 14-99 d,#35383e, -|16: 0-0 d,#393c43, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-13 #bfc3cc,#35383e, 14-99 d,#35383e, -|17: 0-1 #000000,#9cc3f7, 2-2 d,#363d48, 3-32 #d1d5de,#363d48, 33-39 #f8cba7,#363d48, 40-61 #d1d5de,#363d48, 62-99 d,#363d48, -|18: 0-0 d,#708095, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-20 #bfc3cc,#35383e, 21-92 d,#35383e, 93-93 d,#292c33, 94-98 #c5c9d2,#292c33, 99-99 d,#292c33, -|19: 0-0 d,#393c43, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-19 #bfc3cc,#35383e, 20-99 d,#35383e, -|20: 0-0 d,#393c43, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-17 #bfc3cc,#35383e, 18-99 d,#35383e, -|21: 0-0 d,#393c43, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-18 #bfc3cc,#35383e, 19-99 d,#35383e, -|22: 0-0 d,#393c43, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-20 #bfc3cc,#35383e, 21-99 d,#35383e, -|23: 0-0 d,#393c43, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-20 #bfc3cc,#35383e, 21-99 d,#35383e, -|24: 0-0 d,#393c43, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-19 #bfc3cc,#35383e, 20-99 d,#35383e, -|25: 0-0 d,#393c43, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-16 #bfc3cc,#35383e, 17-99 d,#35383e, -|26: 0-0 d,#393c43, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-23 #bfc3cc,#35383e, 24-99 d,#35383e, -|27: 0-0 d,#393c43, 1-1 d,#35383e, 2-6 #8e9199,#35383e,b 7-22 #bfc3cc,#35383e, 23-99 d,#35383e, -|28: 0-0 d,#393c43, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-25 #bfc3cc,#35383e, 26-99 d,#35383e, -|29: 0-0 d,#393c43, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-99 d,#35383e, -|30: 0-0 d,#393c43, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-34 #bfc3cc,#35383e, 35-99 d,#35383e, +|7: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-12 #bfc3cc,#35383e, 13-99 d,#35383e, +|8: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-12 #bfc3cc,#35383e, 13-99 d,#35383e, +|9: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-12 #bfc3cc,#35383e, 13-99 d,#35383e, +|10: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-12 #bfc3cc,#35383e, 13-99 d,#35383e, +|11: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-12 #bfc3cc,#35383e, 13-99 d,#35383e, +|12: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-13 #bfc3cc,#35383e, 14-99 d,#35383e, +|13: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-13 #bfc3cc,#35383e, 14-99 d,#35383e, +|14: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-13 #bfc3cc,#35383e, 14-99 d,#35383e, +|15: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-13 #bfc3cc,#35383e, 14-99 d,#35383e, +|16: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-13 #bfc3cc,#35383e, 14-99 d,#35383e, +|17: 0-1 #000000,#9cc3f7, 2-2 d,#373e49, 3-32 #d1d5de,#373e49, 33-39 #f8cba7,#373e49, 40-61 #d1d5de,#373e49, 62-99 d,#373e49, +|18: 0-0 d,#708095, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-20 #bfc3cc,#35383e, 21-92 d,#35383e, 93-93 d,#2a2d34, 94-98 #c5c9d2,#2a2d34, 99-99 d,#2a2d34, +|19: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-19 #bfc3cc,#35383e, 20-99 d,#35383e, +|20: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-17 #bfc3cc,#35383e, 18-99 d,#35383e, +|21: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-18 #bfc3cc,#35383e, 19-99 d,#35383e, +|22: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-20 #bfc3cc,#35383e, 21-99 d,#35383e, +|23: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-20 #bfc3cc,#35383e, 21-99 d,#35383e, +|24: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-19 #bfc3cc,#35383e, 20-99 d,#35383e, +|25: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-16 #bfc3cc,#35383e, 17-99 d,#35383e, +|26: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-23 #bfc3cc,#35383e, 24-99 d,#35383e, +|27: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #8e9199,#35383e,b 7-22 #bfc3cc,#35383e, 23-99 d,#35383e, +|28: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-25 #bfc3cc,#35383e, 26-99 d,#35383e, +|29: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-99 d,#35383e, +|30: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-34 #bfc3cc,#35383e, 35-99 d,#35383e, == snap step-orchard-black grid=100x31 cursor=7,27 |18: 7 Theme atelier Theme |19: 8 Theme forge @@ -491,37 +491,37 @@ |29: 18 # Legacy and imported themes |30: 19 Theme helix == style step-orchard-black grid=100x31 -|0: 0-70 #b4c4bb,#1e2926, 71-99 d,#1e2926, -|1: 0-1 #ffffff,#857557, 2-2 d,#283730, 3-34 #c0d0c7,#283730, 35-99 d,#283730, -|2: 0-1 #ffffff,#4a665a, 2-2 d,#1e2926, 3-32 #b4c4bb,#1e2926, 33-37 #d6b2d3,#1e2926, 38-59 #b4c4bb,#1e2926, 60-99 d,#1e2926, -|3: 0-0 d,#62826d, 1-1 d,#000000, 2-6 #59675c,#000000, 7-12 #f2f8ef,#000000, 13-99 d,#000000, -|4: 0-0 d,#62826d, 1-1 d,#000000, 2-6 #59675c,#000000, 7-12 #f2f8ef,#000000, 13-99 d,#000000, -|5: 0-0 d,#62826d, 1-1 d,#000000, 2-6 #59675c,#000000, 7-12 #f2f8ef,#000000, 13-99 d,#000000, -|6: 0-0 d,#62826d, 1-1 d,#000000, 2-6 #59675c,#000000, 7-12 #f2f8ef,#000000, 13-99 d,#000000, -|7: 0-0 d,#151c18, 1-1 d,#000000, 2-6 #59675c,#000000, 7-12 #f2f8ef,#000000, 13-99 d,#000000, -|8: 0-0 d,#151c18, 1-1 d,#000000, 2-6 #59675c,#000000, 7-12 #f2f8ef,#000000, 13-99 d,#000000, -|9: 0-0 d,#151c18, 1-1 d,#000000, 2-6 #59675c,#000000, 7-12 #f2f8ef,#000000, 13-99 d,#000000, -|10: 0-0 d,#151c18, 1-1 d,#000000, 2-6 #59675c,#000000, 7-12 #f2f8ef,#000000, 13-99 d,#000000, -|11: 0-0 d,#151c18, 1-1 d,#000000, 2-6 #59675c,#000000, 7-12 #f2f8ef,#000000, 13-99 d,#000000, -|12: 0-0 d,#151c18, 1-1 d,#000000, 2-6 #59675c,#000000, 7-13 #f2f8ef,#000000, 14-99 d,#000000, -|13: 0-0 d,#151c18, 1-1 d,#000000, 2-6 #59675c,#000000, 7-13 #f2f8ef,#000000, 14-99 d,#000000, -|14: 0-0 d,#151c18, 1-1 d,#000000, 2-6 #59675c,#000000, 7-13 #f2f8ef,#000000, 14-99 d,#000000, -|15: 0-0 d,#151c18, 1-1 d,#000000, 2-6 #59675c,#000000, 7-13 #f2f8ef,#000000, 14-99 d,#000000, -|16: 0-0 d,#151c18, 1-1 d,#000000, 2-6 #59675c,#000000, 7-13 #f2f8ef,#000000, 14-99 d,#000000, -|17: 0-1 #000000,#abd195, 2-2 d,#283730, 3-32 #c0d0c7,#283730, 33-39 #e2bedf,#283730, 40-61 #c0d0c7,#283730, 62-99 d,#283730, -|18: 0-0 d,#62826d, 1-1 d,#000000, 2-6 #59675c,#000000, 7-19 #f2f8ef,#000000, 20-92 d,#000000, 93-93 d,#1e2926, 94-98 #b4c4bb,#1e2926, 99-99 d,#1e2926, -|19: 0-0 d,#151c18, 1-1 d,#000000, 2-6 #59675c,#000000, 7-17 #f2f8ef,#000000, 18-99 d,#000000, -|20: 0-0 d,#151c18, 1-1 d,#000000, 2-6 #59675c,#000000, 7-18 #f2f8ef,#000000, 19-99 d,#000000, -|21: 0-0 d,#151c18, 1-1 d,#000000, 2-6 #59675c,#000000, 7-20 #f2f8ef,#000000, 21-99 d,#000000, -|22: 0-0 d,#151c18, 1-1 d,#000000, 2-6 #59675c,#000000, 7-20 #f2f8ef,#000000, 21-99 d,#000000, -|23: 0-0 d,#151c18, 1-1 d,#000000, 2-6 #59675c,#000000, 7-19 #f2f8ef,#000000, 20-99 d,#000000, -|24: 0-0 d,#151c18, 1-1 d,#000000, 2-6 #59675c,#000000, 7-16 #f2f8ef,#000000, 17-99 d,#000000, -|25: 0-0 d,#151c18, 1-1 d,#000000, 2-6 #59675c,#000000, 7-23 #f2f8ef,#000000, 24-99 d,#000000, -|26: 0-0 d,#151c18, 1-1 d,#000000, 2-6 #59675c,#000000, 7-22 #f2f8ef,#000000, 23-99 d,#000000, -|27: 0-0 d,#151c18, 1-1 d,#000000, 2-6 #616f64,#000000,b 7-25 #f2f8ef,#000000, 26-99 d,#000000, -|28: 0-0 d,#151c18, 1-1 d,#000000, 2-6 #59675c,#000000, 7-99 d,#000000, -|29: 0-0 d,#151c18, 1-1 d,#000000, 2-6 #59675c,#000000, 7-34 #f2f8ef,#000000, 35-99 d,#000000, -|30: 0-0 d,#151c18, 1-1 d,#000000, 2-6 #59675c,#000000, 7-17 #f2f8ef,#000000, 18-99 d,#000000, +|0: 0-70 #b2c3b8,#1d2824, 71-99 d,#1d2824, +|1: 0-1 #ffffff,#887455, 2-2 d,#26362d, 3-34 #becfc4,#26362d, 35-99 d,#26362d, +|2: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-32 #b2c3b8,#1d2824, 33-37 #d3b0d9,#1d2824, 38-59 #b2c3b8,#1d2824, 60-99 d,#1d2824, +|3: 0-0 d,#608269, 1-1 d,#000000, 2-6 #59675c,#000000, 7-12 #f2f8ef,#000000, 13-99 d,#000000, +|4: 0-0 d,#608269, 1-1 d,#000000, 2-6 #59675c,#000000, 7-12 #f2f8ef,#000000, 13-99 d,#000000, +|5: 0-0 d,#608269, 1-1 d,#000000, 2-6 #59675c,#000000, 7-12 #f2f8ef,#000000, 13-99 d,#000000, +|6: 0-0 d,#608269, 1-1 d,#000000, 2-6 #59675c,#000000, 7-12 #f2f8ef,#000000, 13-99 d,#000000, +|7: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-12 #f2f8ef,#000000, 13-99 d,#000000, +|8: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-12 #f2f8ef,#000000, 13-99 d,#000000, +|9: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-12 #f2f8ef,#000000, 13-99 d,#000000, +|10: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-12 #f2f8ef,#000000, 13-99 d,#000000, +|11: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-12 #f2f8ef,#000000, 13-99 d,#000000, +|12: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-13 #f2f8ef,#000000, 14-99 d,#000000, +|13: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-13 #f2f8ef,#000000, 14-99 d,#000000, +|14: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-13 #f2f8ef,#000000, 14-99 d,#000000, +|15: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-13 #f2f8ef,#000000, 14-99 d,#000000, +|16: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-13 #f2f8ef,#000000, 14-99 d,#000000, +|17: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-32 #becfc4,#26362d, 33-39 #dfbce5,#26362d, 40-61 #becfc4,#26362d, 62-99 d,#26362d, +|18: 0-0 d,#608269, 1-1 d,#000000, 2-6 #59675c,#000000, 7-19 #f2f8ef,#000000, 20-92 d,#000000, 93-93 d,#1d2824, 94-98 #b2c3b8,#1d2824, 99-99 d,#1d2824, +|19: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-17 #f2f8ef,#000000, 18-99 d,#000000, +|20: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-18 #f2f8ef,#000000, 19-99 d,#000000, +|21: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-20 #f2f8ef,#000000, 21-99 d,#000000, +|22: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-20 #f2f8ef,#000000, 21-99 d,#000000, +|23: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-19 #f2f8ef,#000000, 20-99 d,#000000, +|24: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-16 #f2f8ef,#000000, 17-99 d,#000000, +|25: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-23 #f2f8ef,#000000, 24-99 d,#000000, +|26: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-22 #f2f8ef,#000000, 23-99 d,#000000, +|27: 0-0 d,#111813, 1-1 d,#000000, 2-6 #616f64,#000000,b 7-25 #f2f8ef,#000000, 26-99 d,#000000, +|28: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-99 d,#000000, +|29: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-34 #f2f8ef,#000000, 35-99 d,#000000, +|30: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-17 #f2f8ef,#000000, 18-99 d,#000000, == snap step-helix grid=100x31 cursor=7,27 |18: 10 Theme solarium Theme |19: 11 Theme spectrum @@ -537,37 +537,37 @@ |29: 21 Theme acme |30: 22 Theme acid == style step-helix grid=100x31 -|0: 0-70 #979998,#131414, 71-99 d,#131414, -|1: 0-1 #ffffff,#837048, 2-2 d,#141615, 3-34 #989a99,#141615, 35-99 d,#141615, -|2: 0-1 #ffffff,#2a2c2b, 2-2 d,#131414, 3-32 #979998,#131414, 33-37 #9a979b,#131414, 38-59 #979998,#131414, 60-99 d,#131414, -|3: 0-0 d,#474a48, 1-1 d,#080808, 2-6 #626262,#080808, 7-12 #bdbdbd,#080808, 13-99 d,#080808, -|4: 0-0 d,#474a48, 1-1 d,#080808, 2-6 #626262,#080808, 7-12 #bdbdbd,#080808, 13-99 d,#080808, -|5: 0-0 d,#474a48, 1-1 d,#080808, 2-6 #626262,#080808, 7-12 #bdbdbd,#080808, 13-99 d,#080808, -|6: 0-0 d,#474a48, 1-1 d,#080808, 2-6 #626262,#080808, 7-12 #bdbdbd,#080808, 13-99 d,#080808, -|7: 0-0 d,#121312, 1-1 d,#080808, 2-6 #626262,#080808, 7-12 #bdbdbd,#080808, 13-99 d,#080808, -|8: 0-0 d,#121312, 1-1 d,#080808, 2-6 #626262,#080808, 7-12 #bdbdbd,#080808, 13-99 d,#080808, -|9: 0-0 d,#121312, 1-1 d,#080808, 2-6 #626262,#080808, 7-12 #bdbdbd,#080808, 13-99 d,#080808, -|10: 0-0 d,#121312, 1-1 d,#080808, 2-6 #626262,#080808, 7-12 #bdbdbd,#080808, 13-99 d,#080808, -|11: 0-0 d,#121312, 1-1 d,#080808, 2-6 #626262,#080808, 7-12 #bdbdbd,#080808, 13-99 d,#080808, -|12: 0-0 d,#121312, 1-1 d,#080808, 2-6 #626262,#080808, 7-13 #bdbdbd,#080808, 14-99 d,#080808, -|13: 0-0 d,#121312, 1-1 d,#080808, 2-6 #626262,#080808, 7-13 #bdbdbd,#080808, 14-99 d,#080808, -|14: 0-0 d,#121312, 1-1 d,#080808, 2-6 #626262,#080808, 7-13 #bdbdbd,#080808, 14-99 d,#080808, -|15: 0-0 d,#121312, 1-1 d,#080808, 2-6 #626262,#080808, 7-13 #bdbdbd,#080808, 14-99 d,#080808, -|16: 0-0 d,#121312, 1-1 d,#080808, 2-6 #626262,#080808, 7-13 #bdbdbd,#080808, 14-99 d,#080808, -|17: 0-1 #ffffff,#6a6d66, 2-2 d,#141615, 3-32 #989a99,#141615, 33-39 #9c989c,#141615, 40-61 #989a99,#141615, 62-99 d,#141615, -|18: 0-0 d,#474a48, 1-1 d,#080808, 2-6 #626262,#080808, 7-20 #bdbdbd,#080808, 21-92 d,#080808, 93-93 d,#131414, 94-98 #979998,#131414, 99-99 d,#131414, -|19: 0-0 d,#121312, 1-1 d,#080808, 2-6 #626262,#080808, 7-20 #bdbdbd,#080808, 21-99 d,#080808, -|20: 0-0 d,#121312, 1-1 d,#080808, 2-6 #626262,#080808, 7-19 #bdbdbd,#080808, 20-99 d,#080808, -|21: 0-0 d,#121312, 1-1 d,#080808, 2-6 #626262,#080808, 7-16 #bdbdbd,#080808, 17-99 d,#080808, -|22: 0-0 d,#121312, 1-1 d,#080808, 2-6 #626262,#080808, 7-23 #bdbdbd,#080808, 24-99 d,#080808, -|23: 0-0 d,#121312, 1-1 d,#080808, 2-6 #626262,#080808, 7-22 #bdbdbd,#080808, 23-99 d,#080808, -|24: 0-0 d,#121312, 1-1 d,#080808, 2-6 #626262,#080808, 7-25 #bdbdbd,#080808, 26-99 d,#080808, -|25: 0-0 d,#121312, 1-1 d,#080808, 2-6 #626262,#080808, 7-99 d,#080808, -|26: 0-0 d,#121312, 1-1 d,#080808, 2-6 #626262,#080808, 7-34 #bdbdbd,#080808, 35-99 d,#080808, -|27: 0-0 d,#121312, 1-1 d,#080808, 2-6 #626262,#080808,b 7-17 #bdbdbd,#080808, 18-99 d,#080808, -|28: 0-0 d,#121312, 1-1 d,#080808, 2-6 #626262,#080808, 7-16 #bdbdbd,#080808, 17-99 d,#080808, -|29: 0-0 d,#121312, 1-1 d,#080808, 2-6 #626262,#080808, 7-16 #bdbdbd,#080808, 17-99 d,#080808, -|30: 0-0 d,#121312, 1-1 d,#080808, 2-6 #626262,#080808, 7-16 #bdbdbd,#080808, 17-99 d,#080808, +|0: 0-70 #949494,#121212, 71-99 d,#121212, +|1: 0-1 #ffffff,#826f46, 2-2 d,#121212, 3-34 #949494,#121212, 35-99 d,#121212, +|2: 0-1 #ffffff,#262626, 2-2 d,#121212, 3-59 #949494,#121212, 60-99 d,#121212, +|3: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-12 #bdbdbd,#080808, 13-99 d,#080808, +|4: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-12 #bdbdbd,#080808, 13-99 d,#080808, +|5: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-12 #bdbdbd,#080808, 13-99 d,#080808, +|6: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-12 #bdbdbd,#080808, 13-99 d,#080808, +|7: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-12 #bdbdbd,#080808, 13-99 d,#080808, +|8: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-12 #bdbdbd,#080808, 13-99 d,#080808, +|9: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-12 #bdbdbd,#080808, 13-99 d,#080808, +|10: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-12 #bdbdbd,#080808, 13-99 d,#080808, +|11: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-12 #bdbdbd,#080808, 13-99 d,#080808, +|12: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-13 #bdbdbd,#080808, 14-99 d,#080808, +|13: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-13 #bdbdbd,#080808, 14-99 d,#080808, +|14: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-13 #bdbdbd,#080808, 14-99 d,#080808, +|15: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-13 #bdbdbd,#080808, 14-99 d,#080808, +|16: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-13 #bdbdbd,#080808, 14-99 d,#080808, +|17: 0-1 #ffffff,#626262, 2-2 d,#121212, 3-61 #949494,#121212, 62-99 d,#121212, +|18: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-20 #bdbdbd,#080808, 21-92 d,#080808, 93-93 d,#121212, 94-98 #949494,#121212, 99-99 d,#121212, +|19: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-20 #bdbdbd,#080808, 21-99 d,#080808, +|20: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-19 #bdbdbd,#080808, 20-99 d,#080808, +|21: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-16 #bdbdbd,#080808, 17-99 d,#080808, +|22: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-23 #bdbdbd,#080808, 24-99 d,#080808, +|23: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-22 #bdbdbd,#080808, 23-99 d,#080808, +|24: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-25 #bdbdbd,#080808, 26-99 d,#080808, +|25: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-99 d,#080808, +|26: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-34 #bdbdbd,#080808, 35-99 d,#080808, +|27: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808,b 7-17 #bdbdbd,#080808, 18-99 d,#080808, +|28: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-16 #bdbdbd,#080808, 17-99 d,#080808, +|29: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-16 #bdbdbd,#080808, 17-99 d,#080808, +|30: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-16 #bdbdbd,#080808, 17-99 d,#080808, == snap step-dark grid=100x31 cursor=7,27 |18: 11 Theme spectrum Theme |19: 12 Theme harvest @@ -583,37 +583,37 @@ |29: 22 Theme acid |30: 23 Theme acme_helix == style step-dark grid=100x31 -|0: 0-70 #969696,#29283a, 71-99 d,#29283a, -|1: 0-1 #ffffff,#86794d, 2-2 d,#29283a, 3-34 #969696,#29283a, 35-99 d,#29283a, -|2: 0-1 #ffffff,#343853, 2-2 d,#29283a, 3-59 #969696,#29283a, 60-99 d,#29283a, -|3: 0-0 d,#515151, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, -|4: 0-0 d,#515151, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, -|5: 0-0 d,#515151, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, -|6: 0-0 d,#515151, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, -|7: 0-0 d,#282828, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, -|8: 0-0 d,#282828, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, -|9: 0-0 d,#282828, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, -|10: 0-0 d,#282828, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, -|11: 0-0 d,#282828, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, -|12: 0-0 d,#282828, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, -|13: 0-0 d,#282828, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, -|14: 0-0 d,#282828, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, -|15: 0-0 d,#282828, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, -|16: 0-0 d,#282828, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, -|17: 0-1 #ffffff,#6643d3, 2-2 d,#29283a, 3-61 #969696,#29283a, 62-99 d,#29283a, -|18: 0-0 d,#515151, 1-1 d,d, 2-6 #5a5a5a,d, 7-92 d,d, 93-93 d,#29283a, 94-98 #969696,#29283a, 99-99 d,#29283a, -|19: 0-0 d,#282828, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, -|20: 0-0 d,#282828, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, -|21: 0-0 d,#282828, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, -|22: 0-0 d,#282828, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, -|23: 0-0 d,#282828, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, -|24: 0-0 d,#282828, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, -|25: 0-0 d,#282828, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, -|26: 0-0 d,#282828, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, -|27: 0-0 d,#282828, 1-1 d,d, 2-6 #5a5a5a,d,b 7-99 d,d, -|28: 0-0 d,#282828, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, -|29: 0-0 d,#282828, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, -|30: 0-0 d,#282828, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, +|0: 0-70 #969696,#2c2a3e, 71-99 d,#2c2a3e, +|1: 0-1 #ffffff,#867a4e, 2-2 d,#2c2a3e, 3-34 #969696,#2c2a3e, 35-99 d,#2c2a3e, +|2: 0-1 #ffffff,#353a58, 2-2 d,#2c2a3e, 3-59 #969696,#2c2a3e, 60-99 d,#2c2a3e, +|3: 0-0 d,#525252, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, +|4: 0-0 d,#525252, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, +|5: 0-0 d,#525252, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, +|6: 0-0 d,#525252, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, +|7: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, +|8: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, +|9: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, +|10: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, +|11: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, +|12: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, +|13: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, +|14: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, +|15: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, +|16: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, +|17: 0-1 #ffffff,#663fe0, 2-2 d,#2c2a3e, 3-61 #969696,#2c2a3e, 62-99 d,#2c2a3e, +|18: 0-0 d,#525252, 1-1 d,d, 2-6 #5a5a5a,d, 7-92 d,d, 93-93 d,#2c2a3e, 94-98 #969696,#2c2a3e, 99-99 d,#2c2a3e, +|19: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, +|20: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, +|21: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, +|22: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, +|23: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, +|24: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, +|25: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, +|26: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, +|27: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d,b 7-99 d,d, +|28: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, +|29: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, +|30: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, == snap step-acme grid=100x31 cursor=7,27 |18: 12 Theme harvest Theme |19: 13 Theme clay @@ -629,37 +629,37 @@ |29: 23 Theme acme_helix |30: 24 Theme adwaita_dark == style step-acme grid=100x31 -|0: 0-70 #0f0f0f,#d7eaec, 71-99 d,#d7eaec, -|1: 0-1 #000000,#bfa37a, 2-2 d,#d7eaec, 3-34 #0f0f0f,#d7eaec, 35-99 d,#d7eaec, -|2: 0-1 #ffffff,#353a58, 2-2 d,#d7eaec, 3-59 #0f0f0f,#d7eaec, 60-99 d,#d7eaec, -|3: 0-0 d,#eeeedb, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, -|4: 0-0 d,#eeeedb, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, -|5: 0-0 d,#eeeedb, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, -|6: 0-0 d,#eeeedb, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, -|7: 0-0 d,#8e8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, -|8: 0-0 d,#8e8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, -|9: 0-0 d,#8e8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, -|10: 0-0 d,#8e8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, -|11: 0-0 d,#8e8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, -|12: 0-0 d,#8e8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, -|13: 0-0 d,#8e8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, -|14: 0-0 d,#8e8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, -|15: 0-0 d,#8e8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, -|16: 0-0 d,#8e8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, -|17: 0-1 #ffffff,#663fe0, 2-2 d,#d7eaec, 3-61 #0f0f0f,#d7eaec, 62-99 d,#d7eaec, -|18: 0-0 d,#eeeedb, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-19 #000000,#ffffea, 20-92 d,#ffffea, 93-93 d,#d7eaec, 94-98 #0f0f0f,#d7eaec, 99-99 d,#d7eaec, -|19: 0-0 d,#8e8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-16 #000000,#ffffea, 17-99 d,#ffffea, -|20: 0-0 d,#8e8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-23 #000000,#ffffea, 24-99 d,#ffffea, -|21: 0-0 d,#8e8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-22 #000000,#ffffea, 23-99 d,#ffffea, -|22: 0-0 d,#8e8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-25 #000000,#ffffea, 26-99 d,#ffffea, -|23: 0-0 d,#8e8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-99 d,#ffffea, -|24: 0-0 d,#8e8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-34 #000000,#ffffea, 35-99 d,#ffffea, -|25: 0-0 d,#8e8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-17 #000000,#ffffea, 18-99 d,#ffffea, -|26: 0-0 d,#8e8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-16 #000000,#ffffea, 17-99 d,#ffffea, -|27: 0-0 d,#8e8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea,b 7-16 #000000,#ffffea, 17-99 d,#ffffea, -|28: 0-0 d,#8e8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-16 #000000,#ffffea, 17-99 d,#ffffea, -|29: 0-0 d,#8e8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-22 #000000,#ffffea, 23-99 d,#ffffea, -|30: 0-0 d,#8e8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-24 #000000,#ffffea, 25-99 d,#ffffea, +|0: 0-70 #000000,#eaffff, 71-99 d,#eaffff, +|1: 0-1 #000000,#c5a87f, 2-2 d,#eaffff, 3-34 #000000,#eaffff, 35-99 d,#eaffff, +|2: 0-1 #ffffff,#353a58, 2-2 d,#eaffff, 3-59 #000000,#eaffff, 60-99 d,#eaffff, +|3: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, +|4: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, +|5: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, +|6: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, +|7: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, +|8: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, +|9: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, +|10: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, +|11: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, +|12: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, +|13: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, +|14: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, +|15: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, +|16: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, +|17: 0-1 #ffffff,#663fe0, 2-2 d,#eaffff, 3-61 #000000,#eaffff, 62-99 d,#eaffff, +|18: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-19 #000000,#ffffea, 20-92 d,#ffffea, 93-93 d,#eaffff, 94-98 #000000,#eaffff, 99-99 d,#eaffff, +|19: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-16 #000000,#ffffea, 17-99 d,#ffffea, +|20: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-23 #000000,#ffffea, 24-99 d,#ffffea, +|21: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-22 #000000,#ffffea, 23-99 d,#ffffea, +|22: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-25 #000000,#ffffea, 26-99 d,#ffffea, +|23: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-99 d,#ffffea, +|24: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-34 #000000,#ffffea, 35-99 d,#ffffea, +|25: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-17 #000000,#ffffea, 18-99 d,#ffffea, +|26: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-16 #000000,#ffffea, 17-99 d,#ffffea, +|27: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea,b 7-16 #000000,#ffffea, 17-99 d,#ffffea, +|28: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-16 #000000,#ffffea, 17-99 d,#ffffea, +|29: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-22 #000000,#ffffea, 23-99 d,#ffffea, +|30: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-24 #000000,#ffffea, 25-99 d,#ffffea, == snap step-generated grid=100x31 cursor=7,27 |18: 13 Theme clay Theme |19: 14 Theme forge_black @@ -675,70 +675,70 @@ |29: 24 Theme adwaita_dark |30: 25 Theme adwaita_light == style step-generated grid=100x31 -|0: 0-70 #cacaca,#323c43, 71-99 d,#323c43, -|1: 0-1 #ffffff,#5b8094, 2-2 d,#323c43, 3-34 #cacaca,#323c43, 35-99 d,#323c43, -|2: 0-1 #ffffff,#313842, 2-2 d,#323c43, 3-59 #cacaca,#323c43, 60-99 d,#323c43, -|3: 0-0 d,#454c51, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-12 #e0e0e0,#161e26, 13-99 d,#161e26, -|4: 0-0 d,#454c51, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-12 #e0e0e0,#161e26, 13-99 d,#161e26, -|5: 0-0 d,#454c51, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-12 #e0e0e0,#161e26, 13-99 d,#161e26, -|6: 0-0 d,#454c51, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-12 #e0e0e0,#161e26, 13-99 d,#161e26, -|7: 0-0 d,#2a3231, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-12 #e0e0e0,#161e26, 13-99 d,#161e26, -|8: 0-0 d,#2a3231, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-12 #e0e0e0,#161e26, 13-99 d,#161e26, -|9: 0-0 d,#2a3231, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-12 #e0e0e0,#161e26, 13-99 d,#161e26, -|10: 0-0 d,#2a3231, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-12 #e0e0e0,#161e26, 13-99 d,#161e26, -|11: 0-0 d,#2a3231, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-12 #e0e0e0,#161e26, 13-99 d,#161e26, -|12: 0-0 d,#2a3231, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-13 #e0e0e0,#161e26, 14-99 d,#161e26, -|13: 0-0 d,#2a3231, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-13 #e0e0e0,#161e26, 14-99 d,#161e26, -|14: 0-0 d,#2a3231, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-13 #e0e0e0,#161e26, 14-99 d,#161e26, -|15: 0-0 d,#2a3231, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-13 #e0e0e0,#161e26, 14-99 d,#161e26, -|16: 0-0 d,#2a3231, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-13 #e0e0e0,#161e26, 14-99 d,#161e26, -|17: 0-1 #000000,#f0966d, 2-2 d,#323c43, 3-61 #cacaca,#323c43, 62-99 d,#323c43, -|18: 0-0 d,#454c51, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-16 #e0e0e0,#161e26, 17-92 d,#161e26, 93-93 d,#323c43, 94-98 #cacaca,#323c43, 99-99 d,#323c43, -|19: 0-0 d,#2a3231, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-23 #e0e0e0,#161e26, 24-99 d,#161e26, -|20: 0-0 d,#2a3231, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-22 #e0e0e0,#161e26, 23-99 d,#161e26, -|21: 0-0 d,#2a3231, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-25 #e0e0e0,#161e26, 26-99 d,#161e26, -|22: 0-0 d,#2a3231, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-99 d,#161e26, -|23: 0-0 d,#2a3231, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-34 #e0e0e0,#161e26, 35-99 d,#161e26, -|24: 0-0 d,#2a3231, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-17 #e0e0e0,#161e26, 18-99 d,#161e26, -|25: 0-0 d,#2a3231, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-16 #e0e0e0,#161e26, 17-99 d,#161e26, -|26: 0-0 d,#2a3231, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-16 #e0e0e0,#161e26, 17-99 d,#161e26, -|27: 0-0 d,#2a3231, 1-1 d,#161e26, 2-6 #303840,#161e26,b 7-16 #e0e0e0,#161e26, 17-99 d,#161e26, -|28: 0-0 d,#2a3231, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-22 #e0e0e0,#161e26, 23-99 d,#161e26, -|29: 0-0 d,#2a3231, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-24 #e0e0e0,#161e26, 25-99 d,#161e26, -|30: 0-0 d,#2a3231, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-25 #e0e0e0,#161e26, 26-99 d,#161e26, +|0: 0-70 #e0e0e0,#1e262e, 71-99 d,#1e262e, +|1: 0-1 #ffffff,#4f7b96, 2-2 d,#1e262e, 3-34 #e0e0e0,#1e262e, 35-99 d,#1e262e, +|2: 0-1 #ffffff,#303840, 2-2 d,#1e262e, 3-59 #e0e0e0,#1e262e, 60-99 d,#1e262e, +|3: 0-0 d,#303840, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-12 #e0e0e0,#161e26, 13-99 d,#161e26, +|4: 0-0 d,#303840, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-12 #e0e0e0,#161e26, 13-99 d,#161e26, +|5: 0-0 d,#303840, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-12 #e0e0e0,#161e26, 13-99 d,#161e26, +|6: 0-0 d,#303840, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-12 #e0e0e0,#161e26, 13-99 d,#161e26, +|7: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-12 #e0e0e0,#161e26, 13-99 d,#161e26, +|8: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-12 #e0e0e0,#161e26, 13-99 d,#161e26, +|9: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-12 #e0e0e0,#161e26, 13-99 d,#161e26, +|10: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-12 #e0e0e0,#161e26, 13-99 d,#161e26, +|11: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-12 #e0e0e0,#161e26, 13-99 d,#161e26, +|12: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-13 #e0e0e0,#161e26, 14-99 d,#161e26, +|13: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-13 #e0e0e0,#161e26, 14-99 d,#161e26, +|14: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-13 #e0e0e0,#161e26, 14-99 d,#161e26, +|15: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-13 #e0e0e0,#161e26, 14-99 d,#161e26, +|16: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-13 #e0e0e0,#161e26, 14-99 d,#161e26, +|17: 0-1 #000000,#ffa060, 2-2 d,#1e262e, 3-61 #e0e0e0,#1e262e, 62-99 d,#1e262e, +|18: 0-0 d,#303840, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-16 #e0e0e0,#161e26, 17-92 d,#161e26, 93-93 d,#1e262e, 94-98 #e0e0e0,#1e262e, 99-99 d,#1e262e, +|19: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-23 #e0e0e0,#161e26, 24-99 d,#161e26, +|20: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-22 #e0e0e0,#161e26, 23-99 d,#161e26, +|21: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-25 #e0e0e0,#161e26, 26-99 d,#161e26, +|22: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-99 d,#161e26, +|23: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-34 #e0e0e0,#161e26, 35-99 d,#161e26, +|24: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-17 #e0e0e0,#161e26, 18-99 d,#161e26, +|25: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-16 #e0e0e0,#161e26, 17-99 d,#161e26, +|26: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-16 #e0e0e0,#161e26, 17-99 d,#161e26, +|27: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26,b 7-16 #e0e0e0,#161e26, 17-99 d,#161e26, +|28: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-22 #e0e0e0,#161e26, 23-99 d,#161e26, +|29: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-24 #e0e0e0,#161e26, 25-99 d,#161e26, +|30: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-25 #e0e0e0,#161e26, 26-99 d,#161e26, == snap step-back grid=100x31 cursor=7,26 == style step-back grid=100x31 -|0: 0-70 #161616,#d6e9ea, 71-99 d,#d6e9ea, -|1: 0-1 #000000,#b9a481, 2-2 d,#d6e9ea, 3-34 #161616,#d6e9ea, 35-99 d,#d6e9ea, -|2: 0-1 #ffffff,#353a56, 2-2 d,#d6e9ea, 3-59 #161616,#d6e9ea, 60-99 d,#d6e9ea, -|3: 0-0 d,#eaebd9, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, -|4: 0-0 d,#eaebd9, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, -|5: 0-0 d,#eaebd9, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, -|6: 0-0 d,#eaebd9, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, -|7: 0-0 d,#8d8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, -|8: 0-0 d,#8d8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, -|9: 0-0 d,#8d8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, -|10: 0-0 d,#8d8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, -|11: 0-0 d,#8d8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, -|12: 0-0 d,#8d8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, -|13: 0-0 d,#8d8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, -|14: 0-0 d,#8d8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, -|15: 0-0 d,#8d8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, -|16: 0-0 d,#8d8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, -|17: 0-1 #ffffff,#7549d3, 2-2 d,#d6e9ea, 3-61 #161616,#d6e9ea, 62-99 d,#d6e9ea, -|18: 0-0 d,#eaebd9, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-16 #000000,#ffffea, 17-92 d,#ffffea, 93-93 d,#d6e9ea, 94-98 #161616,#d6e9ea, 99-99 d,#d6e9ea, -|19: 0-0 d,#8d8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-23 #000000,#ffffea, 24-99 d,#ffffea, -|20: 0-0 d,#8d8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-22 #000000,#ffffea, 23-99 d,#ffffea, -|21: 0-0 d,#8d8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-25 #000000,#ffffea, 26-99 d,#ffffea, -|22: 0-0 d,#8d8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-99 d,#ffffea, -|23: 0-0 d,#8d8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-34 #000000,#ffffea, 35-99 d,#ffffea, -|24: 0-0 d,#8d8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-17 #000000,#ffffea, 18-99 d,#ffffea, -|25: 0-0 d,#8d8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-16 #000000,#ffffea, 17-99 d,#ffffea, -|26: 0-0 d,#8d8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea,b 7-16 #000000,#ffffea, 17-99 d,#ffffea, -|27: 0-0 d,#8d8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-16 #000000,#ffffea, 17-99 d,#ffffea, -|28: 0-0 d,#8d8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-22 #000000,#ffffea, 23-99 d,#ffffea, -|29: 0-0 d,#8d8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-24 #000000,#ffffea, 25-99 d,#ffffea, -|30: 0-0 d,#8d8e49, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-25 #000000,#ffffea, 26-99 d,#ffffea, +|0: 0-70 #000000,#eaffff, 71-99 d,#eaffff, +|1: 0-1 #000000,#c5a87f, 2-2 d,#eaffff, 3-34 #000000,#eaffff, 35-99 d,#eaffff, +|2: 0-1 #ffffff,#353a58, 2-2 d,#eaffff, 3-59 #000000,#eaffff, 60-99 d,#eaffff, +|3: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, +|4: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, +|5: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, +|6: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, +|7: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, +|8: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, +|9: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, +|10: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, +|11: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, +|12: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, +|13: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, +|14: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, +|15: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, +|16: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, +|17: 0-1 #ffffff,#663fe0, 2-2 d,#eaffff, 3-61 #000000,#eaffff, 62-99 d,#eaffff, +|18: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-16 #000000,#ffffea, 17-92 d,#ffffea, 93-93 d,#eaffff, 94-98 #000000,#eaffff, 99-99 d,#eaffff, +|19: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-23 #000000,#ffffea, 24-99 d,#ffffea, +|20: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-22 #000000,#ffffea, 23-99 d,#ffffea, +|21: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-25 #000000,#ffffea, 26-99 d,#ffffea, +|22: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-99 d,#ffffea, +|23: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-34 #000000,#ffffea, 35-99 d,#ffffea, +|24: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-17 #000000,#ffffea, 18-99 d,#ffffea, +|25: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-16 #000000,#ffffea, 17-99 d,#ffffea, +|26: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea,b 7-16 #000000,#ffffea, 17-99 d,#ffffea, +|27: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-16 #000000,#ffffea, 17-99 d,#ffffea, +|28: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-22 #000000,#ffffea, 23-99 d,#ffffea, +|29: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-24 #000000,#ffffea, 25-99 d,#ffffea, +|30: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-25 #000000,#ffffea, 26-99 d,#ffffea, == snap tail grid=100x31 cursor=7,29 |18: 235 Theme wolf_alabaster_dark_bg |19: 236 Theme wolf_alabaster_dark_mono @@ -769,13 +769,8 @@ |29: 27 line 27 |30: 28 line 28 == style picked grid=100x31 -|0: 0-70 #000000,#eaffff, 71-99 d,#eaffff, -|1: 0-1 #000000,#c5a87f, 2-2 d,#eaffff, 3-34 #000000,#eaffff, 35-99 d,#eaffff, |2: 0-1 #ffffff,#663fe0, 2-2 d,#eaffff, 3-59 #000000,#eaffff, 60-99 d,#eaffff, |3: 0-1 d,#ffffea, 2-6 #999980,#ffffea,b 7-12 #000000,#ffffea, 13-99 d,#ffffea, -|4: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, -|5: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, -|6: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, |7: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, |8: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, |9: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, diff --git a/test/web_harness.mjs b/test/web_harness.mjs index 90dd9a19..402e41f4 100644 --- a/test/web_harness.mjs +++ b/test/web_harness.mjs @@ -17,7 +17,7 @@ async function main() { const webDir = path.resolve(process.argv[2]); const wasmBytes = await fs.readFile(path.join(webDir, "pardes.wasm")); const module = await WebAssembly.compile(wasmBytes); - const { DomRenderer, animationTicks, browserHost } = await import(pathToFileURL(path.join(webDir, "app.mjs"))); + const { DomRenderer, browserHost } = await import(pathToFileURL(path.join(webDir, "app.mjs"))); assert.deepEqual( [...new Set(WebAssembly.Module.imports(module).map((entry) => entry.module))], ["pardes"], @@ -40,7 +40,7 @@ async function main() { for (const name of [ "memory", "pardes_init", "pardes_frame", "pardes_frame_ptr", "pardes_key", "pardes_mouse", "pardes_pointer_leave", "pardes_pointer_shape", "pardes_gui_tagline_font_percent", - "pardes_paste", "pardes_tick", "pardes_animation_tick", "pardes_animating", + "pardes_paste", "pardes_tick", "pardes_should_quit", "pardes_deinit", ]) assert.ok(wasm[name], `missing wasm export ${name}`); @@ -85,18 +85,6 @@ async function main() { "DOM did not use the build-time tagline percentage", ); - for (const hz of [60, 120, 144]) { - const clock = { last: null, lag: 0 }; - let ticks = 0; - for (let frame = 0; frame <= hz; frame += 1) { - ticks += animationTicks(clock, frame * 1000 / hz); - } - assert.equal(ticks, 60, `${hz} Hz rAF did not produce 60 core ticks per second`); - } - const stalled = { last: null, lag: 0 }; - assert.equal(animationTicks(stalled, 0), 0); - assert.equal(animationTicks(stalled, 60_000), 4, "suspended tab catch-up was not bounded"); - const firstCell = renderer.cells[0]; const second = draw(); assert.equal(renderer.cells[0], firstCell, "stable frames should reuse DOM nodes"); -- cgit v1.3 From c6a5aaf5c4a020eaefe24943ecb6cf9d4eb26836 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 15:57:22 -0300 Subject: Post chain: Shadertoy passes compiled by glslc; Crt, Ripple and Glitch rewritten as Shadertoy files Stage 9 of docs/render-pipeline.md. src/gui/Post.zig runs the chain over the finished frame: each pass reads the one before, ping-pong textures between, the last writes the window; an empty chain is the direct path. The prefix (shaders/post/prefix.glsl) is ghostty's uniform block, name for name and offset for offset (tested against a copy of its Uniforms, the selection colours by their GLSL names), at SDL GPU's bindings, y down. A user's file (Shader ) is compiled by glslc with compileGlsl's own flags on a thread of its own, taken in at the next loop step; errors name the file's own lines; a failed compile keeps the last good pipeline; glslc missing is said once. The bundled passes compile with the build, through the same prefix. Crt, Ripple and Glitch are Shadertoy files, rewritten: no barrel (input is identity, tested), everything in linear light and dithered, keyed to iTime. scene_effects, crt.zig and crt.frag.glsl are gone; the core keeps no clock for the chain. ShaderAnimation off|on|always drives redraw level A: the chain alone over the retained frame (34 us CPU a redraw, measured). Shared files touched: pardes.zig (nextWake loses the scene line, disableSceneEffects empties the chain, two tests), builtins.zig (one switch arm), ninep/ctl.zig (two switch arms), macos.zig (flags from the chain), gui.zig. Not touched: Messages.zig, mouse.zig, detached/*, host_io, tty. --- build.zig | 28 +- docs/render-pipeline.md | 28 +- shaders/crt.frag.glsl | 175 ----------- shaders/crt.vert.glsl | 9 - shaders/post.vert.glsl | 10 + shaders/post/crt.glsl | 79 +++++ shaders/post/glitch.glsl | 60 ++++ shaders/post/prefix.glsl | 67 ++++ shaders/post/ripple.glsl | 49 +++ shaders/prebuilt/crt.frag.glsl | 175 ----------- shaders/prebuilt/crt.frag.spv | Bin 13832 -> 0 bytes shaders/prebuilt/crt.vert.glsl | 9 - shaders/prebuilt/crt.vert.spv | Bin 1208 -> 0 bytes shaders/prebuilt/post-crt.frag.glsl | 146 +++++++++ shaders/prebuilt/post-crt.frag.spv | Bin 0 -> 9480 bytes shaders/prebuilt/post-glitch.frag.glsl | 127 ++++++++ shaders/prebuilt/post-glitch.frag.spv | Bin 0 -> 8308 bytes shaders/prebuilt/post-ripple.frag.glsl | 116 +++++++ shaders/prebuilt/post-ripple.frag.spv | Bin 0 -> 7180 bytes shaders/prebuilt/post.vert.glsl | 10 + shaders/prebuilt/post.vert.spv | Bin 0 -> 1208 bytes src/animation.zig | 6 - src/builtins.zig | 2 +- src/config.zig | 150 ++++++++- src/effect_sources.zig | 23 +- src/gui/Post.zig | 559 +++++++++++++++++++++++++++++++++ src/gui/crt.zig | 101 ------ src/gui/gui.zig | 338 ++++++++++---------- src/macos.zig | 23 +- src/ninep/ctl.zig | 4 +- src/pardes.zig | 28 +- 31 files changed, 1611 insertions(+), 711 deletions(-) delete mode 100644 shaders/crt.frag.glsl delete mode 100644 shaders/crt.vert.glsl create mode 100644 shaders/post.vert.glsl create mode 100644 shaders/post/crt.glsl create mode 100644 shaders/post/glitch.glsl create mode 100644 shaders/post/prefix.glsl create mode 100644 shaders/post/ripple.glsl delete mode 100644 shaders/prebuilt/crt.frag.glsl delete mode 100644 shaders/prebuilt/crt.frag.spv delete mode 100644 shaders/prebuilt/crt.vert.glsl delete mode 100644 shaders/prebuilt/crt.vert.spv create mode 100644 shaders/prebuilt/post-crt.frag.glsl create mode 100644 shaders/prebuilt/post-crt.frag.spv create mode 100644 shaders/prebuilt/post-glitch.frag.glsl create mode 100644 shaders/prebuilt/post-glitch.frag.spv create mode 100644 shaders/prebuilt/post-ripple.frag.glsl create mode 100644 shaders/prebuilt/post-ripple.frag.spv create mode 100644 shaders/prebuilt/post.vert.glsl create mode 100644 shaders/prebuilt/post.vert.spv create mode 100644 src/gui/Post.zig delete mode 100644 src/gui/crt.zig (limited to 'src/animation.zig') diff --git a/build.zig b/build.zig index 803f773d..c1117593 100644 --- a/build.zig +++ b/build.zig @@ -50,7 +50,11 @@ const gui_shaders = [_][]const u8{ "decor.frag", "overlay.vert", "overlay.frag", "image.vert", "image.frag", - "crt.vert", "crt.frag", + "post.vert", + // The bundled post passes: shaders/post/.glsl behind the Shadertoy + // prefix (shaderSource). + "post-crt.frag", "post-ripple.frag", + "post-glitch.frag", }; pub fn build(b: *std.Build) void { @@ -161,7 +165,7 @@ pub fn build(b: *std.Build) void { for (gui_shaders) |name| { update_shaders.addCopyFileToSource(compileGlsl(b, name), b.fmt("shaders/prebuilt/{s}.spv", .{name})); update_shaders.addCopyFileToSource( - b.path(b.fmt("shaders/{s}.glsl", .{name})), + shaderSource(b, name), b.fmt("shaders/prebuilt/{s}.glsl", .{name}), ); } @@ -691,7 +695,7 @@ pub fn build(b: *std.Build) void { mod.addOptions("pardes_isolation", if (isolated_mod == mod) iso_on else iso_off); if (shell == .gui) for (gui_shaders) |name| mod.addAnonymousImport( b.fmt("effect-source-{s}.glsl", .{name}), - .{ .root_source_file = b.path(b.fmt("{s}/{s}.glsl", .{ gui_effect_source_dir, name })) }, + .{ .root_source_file = if (prebuilt_shaders) b.path(b.fmt("{s}/{s}.glsl", .{ gui_effect_source_dir, name })) else shaderSource(b, name) }, ); if (shell == .macos) mod.addAnonymousImport("effect-source-crt.ci.metal", .{ .root_source_file = b.path("shaders/crt.ci.metal"), @@ -756,6 +760,8 @@ pub fn build(b: *std.Build) void { gm.addAnonymousImport("AdwaitaMono-Regular.ttf", .{ .root_source_file = b.path("assets/AdwaitaMono-Regular.ttf"), }); + // Put before a user's Shadertoy file when the gui compiles it. + gm.addAnonymousImport("post-prefix.glsl", .{ .root_source_file = b.path("shaders/post/prefix.glsl") }); for (gui_shaders) |name| gm.addAnonymousImport(b.fmt("{s}.spv", .{name}), .{ .root_source_file = if (prebuilt_shaders) b.path(b.fmt("shaders/prebuilt/{s}.spv", .{name})) @@ -1444,7 +1450,6 @@ pub fn build(b: *std.Build) void { .{ .path = "src/esp32p4/input_rescue.zig", .libc = false }, .{ .path = "src/fonts.zig", .libc = true }, .{ .path = "src/c_heap.zig", .libc = false }, - .{ .path = "src/gui/crt.zig", .libc = false }, }) |entry| { const tests = b.addTest(.{ .root_module = b.createModule(.{ @@ -1626,10 +1631,23 @@ fn reindexed(b: *std.Build, archive: std.Build.LazyPath, index: usize) std.Build // glslc shaders/.glsl -fshader-stage= -o .spv, // returned as a LazyPath for @embedFile. The stage is the name's own suffix. +/// The GLSL a shader is compiled from: shaders/.glsl, or for a bundled +/// post pass (post-.frag) shaders/post/.glsl behind the Shadertoy +/// prefix, the one the gui puts before a user's file at run time. +fn shaderSource(b: *std.Build, name: []const u8) std.Build.LazyPath { + if (!std.mem.startsWith(u8, name, "post-")) return b.path(b.fmt("shaders/{s}.glsl", .{name})); + const joined = b.addSystemCommand(&.{"cat"}); + joined.addFileArg(b.path("shaders/post/prefix.glsl")); + joined.addFileArg(b.path(b.fmt("shaders/post/{s}.glsl", .{name["post-".len .. name.len - ".frag".len]}))); + return joined.captureStdOut(.{}); +} + +/// The one glslc invocation every shader is compiled with, at build time here +/// and at run time for a user's post pass (src/gui/Post.zig). fn compileGlsl(b: *std.Build, name: []const u8) std.Build.LazyPath { const stage = if (std.mem.endsWith(u8, name, ".vert")) "vertex" else "fragment"; const cmd = b.addSystemCommand(&.{ "glslc", b.fmt("-fshader-stage={s}", .{stage}) }); - cmd.addFileArg(b.path(b.fmt("shaders/{s}.glsl", .{name}))); + cmd.addFileArg(shaderSource(b, name)); cmd.addArg("-o"); return cmd.addOutputFileArg(b.fmt("{s}.spv", .{name})); } diff --git a/docs/render-pipeline.md b/docs/render-pipeline.md index d25323b6..254187a3 100644 --- a/docs/render-pipeline.md +++ b/docs/render-pipeline.md @@ -491,21 +491,27 @@ Mirror ghostty 1.3.2 (`zig-pkg/ghostty-*/src/renderer/shadertoy.zig`, ≈3 cells, at the corners of a 4K screen, where the grips and rails are. `crt.zig` is deleted. If the user insists on barrel in the bundled CRT, keep `crt.zig`'s inverse keyed to "bundled crt active". -- **Compile**: GLSL → SPIR-V at runtime with glslang (ghostty's `pkg/glslang`, - the same vendored package in zig-pkg, GUI build only, behind `-Dshadertoy`). - Compiled on load and on file change (file_watch), never on the frame path; - the last good pipeline stays on error; the error is a pane message. glslang is - C++ with its own allocator: a recorded exception to the "Zig allocator hooks - in all C deps" policy. +- **Compile** (decision 3): a user's file is compiled by spawning `glslc` with + the build's own invocation (`-fshader-stage=fragment -o -`, SPIR-V on + stdout), on a thread of its own when the file joins the chain, never on the + frame path; the result is taken in at the next loop step. The prefix ends in + `#line 1`, so glslc's errors count the file's own lines, and they name the + file, not the temporary source. A failed compile keeps the file's last good + pipeline and says glslc's first line as a message; glslc missing is said + once, and only files stay off (the bundled passes are compiled with the + build, through the same prefix). No file watching: `Shader ` twice + (out, then in) compiles it again. - **Animation mode**: `ShaderAnimation off|on|always` (ghostty's `custom-shader-animation`): off = redraw only when content changes; on = continuous while the window is focused; always = continuous. Continuous means the shell redraws its retained Surface (3.1), never a core tick or render. - **Existing crt/ripple/glitch** become bundled Shadertoy files in - `shaders/post/` selected by the same builtins; `scene_effects` and - `crt.frag.glsl`/`crt.zig` are deleted. The bundled CRT is rewritten to - quality (6.1); ripple and glitch are dropped unless the feel review keeps a - rewritten version. + `shaders/post/` selected by the same builtins (decision 2: kept, polished); + `scene_effects` and `crt.frag.glsl`/`crt.zig` are deleted. The chain is + `Crt`, `Ripple`, `Glitch` and `Shader ` in the order they were put in + (`Shader off` empties it of files), `ShaderAnimation off|on|always` + (default on). The core keeps no clock for it: level A redraws are the + shell's. - macOS (out of scope) would need spirv-cross → MSL, as ghostty does. Region filters (e.g. "tint/blur one pane") are NOT built. If one is ever needed: @@ -811,7 +817,7 @@ stripped. No visual change until stage 9 unless stated. | 6 | body layer joined the same way (paint once, copy visible rows) | riskiest join; A/B the old double paint in a temporary test with tag_bottom on and off, then delete it | | 7 | Layer merge (TagLayer + BodyLayer), wire v8, web accessors | after the other agent lands; touches mouse hit paths; breaks the macOS shell's layer ABI (accepted: macOS build ignored for now); done: one `Layer` (src/Layer.zig) with `rows` (0 = no layer) and a `cursor{x, y}`; a tag of N rows is ONE layer of N grid rows (`tagHit` answers the row as `line`, `bodyHit` keeps its meaning), so the per-line layer bases are gone. Wire v8 ships rows, cursor y and the region list in the one bump; v7 and v9 peers are refused in both directions (tests). web: `tag_layer_value` 11 = rows, 12 = cursor y, and app.mjs lays every row. macOS: its Zig side compiles against `Layer`, but pardes.h still sees one row per tag layer (a taller tag shows its first row there) | | 8 | GUI draws from regions: role tiers with track groups in tiers 2-3, page cover, hard-edged snapped decor for rules/rails/grips (pane chrome in the pane's tier), per-instance clip; delete inference functions, `transient_on` and `mark_hover` (breaks macOS glass hover; accepted, macOS ignored for now) | stage-0 PPM goldens byte-identical (possible only because hard decor is snapped and not anti-aliased); pane chrome now also shows during transitions, which is the one allowed visible delta, listed Done: the frame is drawn in groups (makeGroups): tier 0, one group per track in paint order (tiers 2 and 3), tier 4 as two groups (the notices; then the guides and the debug box, which the core paints over them), tier 5 (bar cursors), each drawing cells, images, decor. Decor is every rule, rail, thumb, grip mark, spine, the workspace and column rules, the bottom band, notice rules and bar cursors: whole-pixel rects drawn by `decor.frag` over `ui.vert` as cell instances (colour in the ground, coverage in fg.r, blended like the overlay), so each carries its track's transition and clip; a closing pane's comes from the last frame's regions (`Surface.previous_regions`). Only `solid` exists yet: `decor.vert` and the other kinds (§5.1) come with the first effect that needs them. Deleted: taglineBaseRgb, topbarPaneBorderHeight, bottomTaglinePresent, frameChromeBg, cellBackgroundIs and the rail inference, paneGripCell's scan, transient_on, PaintPlan; one cover map (coverFrame: layer, grip and offset, focus, anchor, floating) is marked from layers and regions once a frame. New regions: `column` (spines, anchors, the focused column's tint), `guide`, `debug`; `Surface.chrome` is the palette, on the wire in v8 (not yet shipped, so no bump), and an attached GUI draws the same chrome (test). Per-instance clip (`CellInstance.clip_*`, 104 → 120 bytes an instance, ~15% more upload a frame) replaces the vertical transition's scissor. While any pane moves, opens or closes, notices stay in their own panes' groups, under whatever slides over them; tier 4 holds them only when nothing moves. Goldens: 01-12 byte-identical; 13-17 differ only in pixels past the grid (with a picture on screen the image pass left the scissor at the grid's size, cutting every rule end, rail foot and band that runs into the leftover pixels; they now run to the edge as in every scene without one); 16-debug shows the debug box (it was drawn from the grid under the source's context-row layer, so the GUI never showed it there); 18-mid-transition is new (virtual clock, PanelSlide Newcol with the picture, frame 6 of 12: chrome moves with its pane). Other deltas, outside the goldens: a guide over a tag or a context-row body now shows; with WindowOpacity < 100 a layer's bar cursor is ink like the grid's; an attached GUI gains the focus tint, notice rules, spines and the theme's page and caret colours. Deferrals fixed: the GUI sleeps when idle (SDL and queue events wake it; a smooth scroll, a gamepad, the test feed, a shell's kill deadline, a present to retry still poll), and a minimized or occluded window sleeps through animation. Tracy, `gui frame build`, 200x60, terminal output plus scrolling, ReleaseFast, two interleaved runs of ~180 frames: before 992/1015 µs median, after 989/987 µs | -| 9 | post chain: glslang, Shadertoy prefix, ping-pong, ShaderAnimation, redraw levels A/B (§5.5); bundled CRT without barrel; delete scene_effects/crt.zig/crt.frag | first visual change (the CRT look) | +| 9 | post chain: glslang, Shadertoy prefix, ping-pong, ShaderAnimation, redraw levels A/B (§5.5); bundled CRT without barrel; delete scene_effects/crt.zig/crt.frag | first visual change (the CRT look) Done: src/gui/Post.zig, shaders/post/ (prefix, crt, ripple, glitch), post.vert. glslc (decision 3) with compileGlsl's flags, off the frame path; ghostty's Uniforms matched offset for offset by a test against a copy, and the prefix's block checked name by name; ghostty's test_shadertoy_crt and _focus compile, _invalid fails with glslc's words. Level A measured (Tracy, 200x60, Crt, idle 10 s): 215 chain-only redraws at 34 µs median CPU each, 6 core frames (957 µs) in the same time; the core's clock stays idle. Input is identity (test: the corner click with Crt on). The three bundled passes were rewritten: Crt without barrel or tube edge, scanlines and mask that average to one, dithered vignette; Ripple as rings in pixels, eased in, lit on their slopes in linear light, dithered; Glitch as short eased bursts of torn bands with an RGB split, keyed to iTime. Before/after stills for the user's judgement. | | 10+ | `fx` bookmark: G1–G3 → feel review → G4 bundled → G5 lapis theme → P2s; tty T1–T2 → feel review → removals of audited effects (after user decision) → T3–T5 | each effect its own change, default off | Tests to add: fixed-clock animation tests (Tween/Spring closed form, retarget diff --git a/shaders/crt.frag.glsl b/shaders/crt.frag.glsl deleted file mode 100644 index b0b0b93b..00000000 --- a/shaders/crt.frag.glsl +++ /dev/null @@ -1,175 +0,0 @@ -#version 450 - -// The Crt builtin's post-process. Screen point cc shows the scene at -// uv' = cc*(1 + BARREL*r2), and src/gui/crt.zig maps mouse input through that -// same transform — keep them equal or clicks drift near the edges. - -layout(set = 2, binding = 0) uniform sampler2D u_scene; -layout(std140, set = 3, binding = 0) uniform CrtUniforms { - float time_seconds; - float frame; - vec2 viewport; - // x = CRT, y = ripple, z = glitch. One scene pass can combine them. - vec4 effects; -} u_crt; - -layout(location = 0) out vec4 o_col; - -const float TAU = 6.28318530718; -const float BARREL = 0.016; // MUST match src/gui/crt.zig barrel -const float EDGE_SOFTNESS_PX = 3.5; -const float CHROMA_PX = 0.28; // radial R/B separation in physical pixels at r2=1 -const float BLOOM_RADIUS_PX = 1.75; -const float BLOOM_THRESHOLD = 0.60; // linear-light channel peak -const float BLOOM_SOFTNESS = 0.25; -const float BLOOM_STRENGTH = 0.045; -const float SCAN_PERIOD_PX = 3.0; -const float SCAN_STRENGTH = 0.055; -const float MASK_PERIOD_PX = 3.0; -const float MASK_STRENGTH = 0.015; -const float VIGNETTE_STRENGTH = 0.11; // 11% only at the extreme corners -const float HUM_STRENGTH = 0.0025; -const float NOISE_STRENGTH = 0.0020; -const float EXPOSURE = 1.012; - -vec3 srgbToLinear(vec3 c) { - vec3 low = c / 12.92; - vec3 high = pow((c + 0.055) / 1.055, vec3(2.4)); - return mix(high, low, lessThanEqual(c, vec3(0.04045))); -} - -vec3 linearToSrgb(vec3 c) { - c = max(c, vec3(0.0)); - vec3 low = c * 12.92; - vec3 high = 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055; - return mix(high, low, lessThanEqual(c, vec3(0.0031308))); -} - -vec3 sceneLinear(vec2 uv) { - return srgbToLinear(texture(u_scene, uv).rgb); -} - -vec3 brightPass(vec2 uv) { - vec3 c = sceneLinear(uv); - float peak = max(c.r, max(c.g, c.b)); - return c * smoothstep(BLOOM_THRESHOLD, BLOOM_THRESHOLD + BLOOM_SOFTNESS, peak); -} - -float noiseAt(vec2 p) { - vec3 q = fract(vec3(p.xyx) * 0.1031); - q += dot(q, q.yzx + 33.33); - return fract((q.x + q.y) * q.z); -} - -// The non-CRT scene effects deliberately distort the source coordinates, -// rather than remapping the final colors. Themes retain their palette and the -// effects remain composable with the linear-light CRT treatment below. -vec2 sceneUv(vec2 uv) { - if (u_crt.effects.y > 0.5) { - vec2 centered = uv - 0.5; - float radius = length(centered); - float envelope = 1.0 - smoothstep(0.04, 0.72, radius); - float wave = sin(radius * 42.0 - u_crt.time_seconds * 5.2); - uv += centered / max(radius, 0.00001) * wave * envelope * 0.0032; - } - if (u_crt.effects.z > 0.5) { - float band = floor(uv.y * 96.0); - float seed = noiseAt(vec2(band, floor(u_crt.time_seconds * 13.0))); - float tear = smoothstep(0.955, 1.0, seed); - uv.x += (noiseAt(vec2(seed, band)) - 0.5) * tear * 0.020; - } - return uv; -} - -// The scene, ALPHA INCLUDED: with a see-through ground the grid arrives -// premultiplied and partly empty, and a pass that dropped .a would turn the -// desktop behind a `*_transparent` theme into opaque black the moment Crt, -// Ripple or Glitch came on. -vec4 sceneSrgb(vec2 uv) { - vec2 warped = sceneUv(uv); - vec4 center = texture(u_scene, warped); - if (u_crt.effects.z <= 0.5) return center; - - float band = floor(warped.y * 96.0); - float seed = noiseAt(vec2(band, floor(u_crt.time_seconds * 13.0))); - float tear = smoothstep(0.955, 1.0, seed); - vec2 chroma = vec2((0.35 + 1.25 * tear) / u_crt.viewport.x, 0.0); - return vec4( - texture(u_scene, warped + chroma).r, - center.g, - texture(u_scene, warped - chroma).b, - center.a - ); -} - -vec3 effectSceneLinear(vec2 uv) { - return srgbToLinear(sceneSrgb(uv).rgb); -} - -void main() { - vec2 size = u_crt.viewport; - vec2 texel = 1.0 / size; - vec2 cc = gl_FragCoord.xy / size * 2.0 - 1.0; - float r2 = dot(cc, cc); - vec2 uv = cc * (1.0 + BARREL * r2 * u_crt.effects.x) * 0.5 + 0.5; - - // Ripple and glitch are useful without the CRT. They only move samples; - // skip every tube-specific light/mask/exposure adjustment. - if (u_crt.effects.x <= 0.5) { - // The scene texture is already sRGB. Avoid a needless transfer-curve - // round trip so a coordinate-only effect preserves theme bytes. - vec4 s = sceneSrgb(uv); - o_col = vec4(clamp(s.rgb, 0.0, 1.0), clamp(s.a, 0.0, 1.0)); - return; - } - - vec3 col = vec3(0.0); - // Outside the tube nothing is drawn, so nothing is covered either: the - // old opaque black there would have punched a rectangle out of the - // compositor's backdrop. - float alpha = 0.0; - float edge = min(min(uv.x, uv.y), min(1.0 - uv.x, 1.0 - uv.y)); - if (edge > 0.0) { - vec3 center = effectSceneLinear(uv); - vec2 chroma = cc * (CHROMA_PX * r2) * texel; - // Each radial sample follows the same ripple/glitch path from its own - // pre-warp coordinate. This is also how the Metal kernel composes the - // effects; sampling a once-warped center here made combined effects - // diverge between the two GUI backends. - col = vec3( - effectSceneLinear(uv + chroma).r, - center.g, - effectSceneLinear(uv - chroma).b - ); - - vec2 bloom_px = texel * BLOOM_RADIUS_PX; - vec3 bloom = brightPass(uv + vec2(bloom_px.x, 0.0)) + - brightPass(uv - vec2(bloom_px.x, 0.0)) + - brightPass(uv + vec2(0.0, bloom_px.y)) + - brightPass(uv - vec2(0.0, bloom_px.y)); - col += bloom * (0.25 * BLOOM_STRENGTH); - - // The mask has unit mean and the scan loss stays small; neither needs - // the old global lift that remapped every theme toward white. - float scan_wave = 0.5 + 0.5 * cos(gl_FragCoord.y * TAU / SCAN_PERIOD_PX); - col *= 1.0 - SCAN_STRENGTH * scan_wave * scan_wave; - vec3 mask = 1.0 + MASK_STRENGTH * cos( - gl_FragCoord.x * TAU / MASK_PERIOD_PX + vec3(0.0, TAU / 3.0, 2.0 * TAU / 3.0) - ); - col *= mask; - - vec2 edge_px = min(uv, 1.0 - uv) * size; - float tube = smoothstep(0.0, EDGE_SOFTNESS_PX, min(edge_px.x, edge_px.y)); - float vignette = 1.0 - VIGNETTE_STRENGTH * pow(clamp(r2 * 0.5, 0.0, 1.0), 1.6); - float hum = 1.0 + HUM_STRENGTH * sin((uv.y * 1.35 - u_crt.time_seconds * 0.11) * TAU); - col *= tube * vignette * hum * EXPOSURE; - // The tube's edge falloff dims coverage as well as colour; everything - // else here is a light adjustment and leaves it alone. - alpha = sceneSrgb(uv).a * tube; - - float luma = dot(col, vec3(0.2126, 0.7152, 0.0722)); - vec2 noise_seed = gl_FragCoord.xy + vec2(u_crt.frame * 17.0, u_crt.frame * 59.0); - col += (noiseAt(noise_seed) - 0.5) * NOISE_STRENGTH * (0.15 + 0.85 * clamp(luma, 0.0, 1.0)); - } - o_col = vec4(linearToSrgb(clamp(col, 0.0, 1.0)), clamp(alpha, 0.0, 1.0)); -} diff --git a/shaders/crt.vert.glsl b/shaders/crt.vert.glsl deleted file mode 100644 index 4e15fcdf..00000000 --- a/shaders/crt.vert.glsl +++ /dev/null @@ -1,9 +0,0 @@ -#version 450 - -// CRT post-process pass: one fullscreen triangle from gl_VertexIndex, no -// vertex buffer. The fragment shader derives uv from gl_FragCoord. - -void main() { - vec2 p = vec2(float((gl_VertexIndex << 1) & 2), float(gl_VertexIndex & 2)); - gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0); -} diff --git a/shaders/post.vert.glsl b/shaders/post.vert.glsl new file mode 100644 index 00000000..86739a65 --- /dev/null +++ b/shaders/post.vert.glsl @@ -0,0 +1,10 @@ +#version 450 + +// A post pass (docs/render-pipeline.md §6): one fullscreen triangle from +// gl_VertexIndex, no vertex buffer. The Shadertoy fragment shader works from +// gl_FragCoord, y down, as its fragCoord. + +void main() { + vec2 p = vec2(float((gl_VertexIndex << 1) & 2), float(gl_VertexIndex & 2)); + gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0); +} diff --git a/shaders/post/crt.glsl b/shaders/post/crt.glsl new file mode 100644 index 00000000..16a5cdd9 --- /dev/null +++ b/shaders/post/crt.glsl @@ -0,0 +1,79 @@ +// Crt: a slot-mask tube's light, with nothing that moves a pixel. There is +// no barrel: every click lands where it is drawn, rails and grips at the +// window's edges included. What is left is light: a beam profile for the +// scanlines, an aperture mask, a soft bloom of the brightest ink, a faint +// vignette and the hum of a live tube. Everything is worked out in linear +// light and dithered back into 8 bits, so the vignette never bands. It is a +// Shadertoy file: pardes puts its prefix before it (shaders/post/prefix.glsl). + +const float TAU = 6.28318530718; +const float CHROMA_PX = 0.35; // red and blue apart, at the corners +const float BLOOM_RADIUS_PX = 1.75; +const float BLOOM_THRESHOLD = 0.60; // linear-light channel peak +const float BLOOM_SOFTNESS = 0.25; +const float BLOOM_STRENGTH = 0.05; +const float SCAN_PERIOD_PX = 3.0; +const float SCAN_STRENGTH = 0.07; +const float MASK_PERIOD_PX = 3.0; +const float MASK_STRENGTH = 0.018; +const float VIGNETTE_STRENGTH = 0.12; // at the far corners only +const float HUM_STRENGTH = 0.004; +const float HUM_HZ = 0.11; +const float EXPOSURE = 1.015; + +vec3 toLinear(vec3 c) { + return mix(pow((c + 0.055) / 1.055, vec3(2.4)), c / 12.92, lessThanEqual(c, vec3(0.04045))); +} + +vec3 toSrgb(vec3 c) { + c = max(c, vec3(0.0)); + return mix(1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, c * 12.92, lessThanEqual(c, vec3(0.0031308))); +} + +vec3 scene(vec2 uv) { + return toLinear(texture(iChannel0, uv).rgb); +} + +vec3 brightPass(vec2 uv) { + vec3 c = scene(uv); + float peak = max(c.r, max(c.g, c.b)); + return c * smoothstep(BLOOM_THRESHOLD, BLOOM_THRESHOLD + BLOOM_SOFTNESS, peak); +} + +// Interleaved gradient noise: a dither with no visible pattern. +float ign(vec2 p) { + return fract(52.9829189 * fract(dot(p, vec2(0.06711056, 0.00583715)))); +} + +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 size = iResolution.xy; + vec2 texel = 1.0 / size; + vec2 uv = fragCoord / size; + vec2 cc = uv * 2.0 - 1.0; + float r2 = dot(cc, cc); + + // The beam converges least at the corners: red and blue drift apart + // there, by a third of a pixel. + vec2 chroma = cc * (CHROMA_PX * r2 * 0.5) * texel; + vec3 col = vec3(scene(uv + chroma).r, scene(uv).g, scene(uv - chroma).b); + + vec2 b = texel * BLOOM_RADIUS_PX; + vec3 bloom = brightPass(uv + vec2(b.x, 0.0)) + brightPass(uv - vec2(b.x, 0.0)) + + brightPass(uv + vec2(0.0, b.y)) + brightPass(uv - vec2(0.0, b.y)); + col += bloom * (0.25 * BLOOM_STRENGTH); + + // A beam is brightest on its line and falls off between lines; the + // mask is three phosphor stripes a triad. Both average to one, so the + // theme keeps its brightness. + float line = 0.5 + 0.5 * cos(fragCoord.y * TAU / SCAN_PERIOD_PX); + col *= 1.0 - SCAN_STRENGTH * (line * line - 0.375) / 0.625; + col *= 1.0 + MASK_STRENGTH * cos(fragCoord.x * TAU / MASK_PERIOD_PX + vec3(0.0, TAU / 3.0, 2.0 * TAU / 3.0)); + + float vignette = 1.0 - VIGNETTE_STRENGTH * pow(clamp(r2 * 0.5, 0.0, 1.0), 1.6); + float hum = 1.0 + HUM_STRENGTH * sin((uv.y * 1.35 - iTime * HUM_HZ) * TAU); + col *= vignette * hum * EXPOSURE; + + vec3 srgb = toSrgb(col); + srgb += (ign(fragCoord + float(iFrame % 64) * vec2(5.588238, 5.588238)) - 0.5) / 255.0; + fragColor = vec4(clamp(srgb, 0.0, 1.0), texture(iChannel0, uv).a); +} diff --git a/shaders/post/glitch.glsl b/shaders/post/glitch.glsl new file mode 100644 index 00000000..ff9ebcb8 --- /dev/null +++ b/shaders/post/glitch.glsl @@ -0,0 +1,60 @@ +// Glitch: now and then the signal slips. Every couple of seconds a short +// burst tears a few horizontal bands sideways and pulls their red and blue +// apart; the bands are sized in pixels and the burst eases in and out over +// a fraction of a second, so it reads as one event rather than a flicker, +// and it looks the same at 60 or 144 Hz because every step is keyed to +// iTime, not to frames. Between bursts the page is left exactly as it is. +// A torn band's brightness dips in linear light. It is a Shadertoy file: +// pardes puts its prefix before it. + +const float PERIOD = 2.4; // seconds between bursts, on average +const float BURST = 0.28; // seconds a burst lasts +const float STEP_HZ = 24.0; // how often a burst re-tears +const float SHIFT_PX = 14.0; +const float SPLIT_PX = 2.5; +const float DIP = 0.12; + +float hash(vec2 p) { + vec3 q = fract(vec3(p.xyx) * 0.1031); + q += dot(q, q.yzx + 33.33); + return fract((q.x + q.y) * q.z); +} + +vec3 toLinear(vec3 c) { + return mix(pow((c + 0.055) / 1.055, vec3(2.4)), c / 12.92, lessThanEqual(c, vec3(0.04045))); +} + +vec3 toSrgb(vec3 c) { + c = max(c, vec3(0.0)); + return mix(1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, c * 12.92, lessThanEqual(c, vec3(0.0031308))); +} + +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 size = iResolution.xy; + vec2 uv = fragCoord / size; + // Which burst window this is, when in it the burst starts, and how far + // into the burst we are: 0 outside one. + float window = floor(iTime / PERIOD); + float start = window * PERIOD + hash(vec2(window, 7.0)) * (PERIOD - BURST); + float t = (iTime - start) / BURST; + float live = step(0.0, t) * step(t, 1.0); + float envelope = live * sin(3.14159265 * clamp(t, 0.0, 1.0)); + if (envelope <= 0.0) { + fragColor = texture(iChannel0, uv); + return; + } + // Bands of 6 to 40 px, re-cut at STEP_HZ during the burst; about one + // band in four tears. + float tick = floor(iTime * STEP_HZ); + float band_px = mix(6.0, 40.0, hash(vec2(tick, 3.0))); + float band = floor(fragCoord.y / band_px); + float roll = hash(vec2(band, tick)); + float tear = smoothstep(0.72, 0.9, roll) * envelope; + float shift = (hash(vec2(tick, band + 11.0)) - 0.5) * 2.0 * SHIFT_PX * tear; + vec2 at = vec2(fragCoord.x + shift, fragCoord.y); + vec2 split = vec2(SPLIT_PX * tear, 0.0); + vec4 center = texture(iChannel0, at / size); + vec3 col = vec3(texture(iChannel0, (at + split) / size).r, center.g, texture(iChannel0, (at - split) / size).b); + col = toSrgb(toLinear(col) * (1.0 - DIP * tear)); + fragColor = vec4(col, center.a); +} diff --git a/shaders/post/prefix.glsl b/shaders/post/prefix.glsl new file mode 100644 index 00000000..efa49483 --- /dev/null +++ b/shaders/post/prefix.glsl @@ -0,0 +1,67 @@ +#version 450 +// pardes's Shadertoy prefix (docs/render-pipeline.md §6), put before every +// post pass: a bundled one in shaders/post/ at build time, a user's file at +// run time. The uniform names, types and std140 order are ghostty 1.3.2's +// (src/renderer/shaders/shadertoy_prefix.glsl), so a shader written for +// ghostty runs here; only the bindings are SDL GPU's (a fragment shader's +// textures in set 2, its uniform blocks in set 3). Y is down everywhere, as +// on ghostty's Metal renderer: fragCoord, iChannel0's uv and the cursor +// rectangles share one top-left space, and iCurrentCursor is (left x, +// BOTTOM-edge y, width, height) in framebuffer pixels. + +layout(set = 3, binding = 0, std140) uniform Globals { + uniform vec3 iResolution; + uniform float iTime; + uniform float iTimeDelta; + uniform float iFrameRate; + uniform int iFrame; + uniform float iChannelTime[4]; + uniform vec3 iChannelResolution[4]; + uniform vec4 iMouse; + uniform vec4 iDate; + uniform float iSampleRate; + uniform vec4 iCurrentCursor; + uniform vec4 iPreviousCursor; + uniform vec4 iCurrentCursorColor; + uniform vec4 iPreviousCursorColor; + uniform int iCurrentCursorStyle; + uniform int iPreviousCursorStyle; + uniform int iCursorVisible; + uniform float iTimeCursorChange; + uniform float iTimeFocus; + uniform int iFocus; + uniform vec3 iPalette[256]; + uniform vec3 iBackgroundColor; + uniform vec3 iForegroundColor; + uniform vec3 iCursorColor; + uniform vec3 iCursorText; + uniform vec3 iSelectionForegroundColor; + uniform vec3 iSelectionBackgroundColor; +}; + +// pardes's own: 1 when the window is opaque, so a pass cannot punch holes +// in it through its alpha. +layout(set = 3, binding = 1, std140) uniform PardesPost { + float pardesOpaque; +}; + +#define CURSORSTYLE_BLOCK 0 +#define CURSORSTYLE_BLOCK_HOLLOW 1 +#define CURSORSTYLE_BAR 2 +#define CURSORSTYLE_UNDERLINE 3 +#define CURSORSTYLE_LOCK 4 + +layout(set = 2, binding = 0) uniform sampler2D iChannel0; + +layout(location = 0) out vec4 _fragColor; + +#define texture2D texture + +void mainImage(out vec4 fragColor, in vec2 fragCoord); +void main() { + mainImage(_fragColor, gl_FragCoord.xy); + if (pardesOpaque > 0.5) _fragColor.a = 1.0; +} + +// The shader's own lines count from 1, so glslc's errors name them. +#line 1 diff --git a/shaders/post/ripple.glsl b/shaders/post/ripple.glsl new file mode 100644 index 00000000..ffecbf70 --- /dev/null +++ b/shaders/post/ripple.glsl @@ -0,0 +1,49 @@ +// Ripple: the page as a still pool a slow swell crosses. Rings travel out +// from the middle of the window at a fixed speed in pixels, so they look +// the same at any size and frame rate; the displacement is at most a couple +// of pixels, so text stays readable, and it eases in over its first half +// second instead of snapping on. Each ring catches the light a little on its +// rising side, in linear light, and the result is dithered so the shading +// never bands. It is a Shadertoy file: pardes puts its prefix before it. + +const float TAU = 6.28318530718; +const float WAVELENGTH_PX = 150.0; +const float SPEED_PX = 55.0; // outward, per second +const float AMPLITUDE_PX = 2.2; +const float SHADE = 0.035; // light on a slope, linear +const float EASE_IN = 0.5; // seconds + +vec3 toLinear(vec3 c) { + return mix(pow((c + 0.055) / 1.055, vec3(2.4)), c / 12.92, lessThanEqual(c, vec3(0.04045))); +} + +vec3 toSrgb(vec3 c) { + c = max(c, vec3(0.0)); + return mix(1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, c * 12.92, lessThanEqual(c, vec3(0.0031308))); +} + +float ign(vec2 p) { + return fract(52.9829189 * fract(dot(p, vec2(0.06711056, 0.00583715)))); +} + +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 size = iResolution.xy; + vec2 center = 0.5 * size; + vec2 offset = fragCoord - center; + float radius = length(offset); + float reach = 0.5 * length(size); + // Strongest a third of the way out, fading to nothing at the middle + // and at the edges, so the window's frame never wobbles. + float envelope = smoothstep(0.0, 0.18, radius / reach) * (1.0 - smoothstep(0.35, 0.95, radius / reach)); + float ease = smoothstep(0.0, EASE_IN, iTime); + float phase = (radius - iTime * SPEED_PX) * TAU / WAVELENGTH_PX; + float height = sin(phase); + float slope = cos(phase); + vec2 dir = offset / max(radius, 1e-3); + vec2 moved = fragCoord + dir * height * AMPLITUDE_PX * envelope * ease; + + vec4 s = texture(iChannel0, moved / size); + vec3 col = toLinear(s.rgb) * (1.0 + SHADE * slope * envelope * ease); + vec3 srgb = toSrgb(col) + (ign(fragCoord + float(iFrame % 64) * 5.588238) - 0.5) / 255.0; + fragColor = vec4(clamp(srgb, 0.0, 1.0), s.a); +} diff --git a/shaders/prebuilt/crt.frag.glsl b/shaders/prebuilt/crt.frag.glsl deleted file mode 100644 index b0b0b93b..00000000 --- a/shaders/prebuilt/crt.frag.glsl +++ /dev/null @@ -1,175 +0,0 @@ -#version 450 - -// The Crt builtin's post-process. Screen point cc shows the scene at -// uv' = cc*(1 + BARREL*r2), and src/gui/crt.zig maps mouse input through that -// same transform — keep them equal or clicks drift near the edges. - -layout(set = 2, binding = 0) uniform sampler2D u_scene; -layout(std140, set = 3, binding = 0) uniform CrtUniforms { - float time_seconds; - float frame; - vec2 viewport; - // x = CRT, y = ripple, z = glitch. One scene pass can combine them. - vec4 effects; -} u_crt; - -layout(location = 0) out vec4 o_col; - -const float TAU = 6.28318530718; -const float BARREL = 0.016; // MUST match src/gui/crt.zig barrel -const float EDGE_SOFTNESS_PX = 3.5; -const float CHROMA_PX = 0.28; // radial R/B separation in physical pixels at r2=1 -const float BLOOM_RADIUS_PX = 1.75; -const float BLOOM_THRESHOLD = 0.60; // linear-light channel peak -const float BLOOM_SOFTNESS = 0.25; -const float BLOOM_STRENGTH = 0.045; -const float SCAN_PERIOD_PX = 3.0; -const float SCAN_STRENGTH = 0.055; -const float MASK_PERIOD_PX = 3.0; -const float MASK_STRENGTH = 0.015; -const float VIGNETTE_STRENGTH = 0.11; // 11% only at the extreme corners -const float HUM_STRENGTH = 0.0025; -const float NOISE_STRENGTH = 0.0020; -const float EXPOSURE = 1.012; - -vec3 srgbToLinear(vec3 c) { - vec3 low = c / 12.92; - vec3 high = pow((c + 0.055) / 1.055, vec3(2.4)); - return mix(high, low, lessThanEqual(c, vec3(0.04045))); -} - -vec3 linearToSrgb(vec3 c) { - c = max(c, vec3(0.0)); - vec3 low = c * 12.92; - vec3 high = 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055; - return mix(high, low, lessThanEqual(c, vec3(0.0031308))); -} - -vec3 sceneLinear(vec2 uv) { - return srgbToLinear(texture(u_scene, uv).rgb); -} - -vec3 brightPass(vec2 uv) { - vec3 c = sceneLinear(uv); - float peak = max(c.r, max(c.g, c.b)); - return c * smoothstep(BLOOM_THRESHOLD, BLOOM_THRESHOLD + BLOOM_SOFTNESS, peak); -} - -float noiseAt(vec2 p) { - vec3 q = fract(vec3(p.xyx) * 0.1031); - q += dot(q, q.yzx + 33.33); - return fract((q.x + q.y) * q.z); -} - -// The non-CRT scene effects deliberately distort the source coordinates, -// rather than remapping the final colors. Themes retain their palette and the -// effects remain composable with the linear-light CRT treatment below. -vec2 sceneUv(vec2 uv) { - if (u_crt.effects.y > 0.5) { - vec2 centered = uv - 0.5; - float radius = length(centered); - float envelope = 1.0 - smoothstep(0.04, 0.72, radius); - float wave = sin(radius * 42.0 - u_crt.time_seconds * 5.2); - uv += centered / max(radius, 0.00001) * wave * envelope * 0.0032; - } - if (u_crt.effects.z > 0.5) { - float band = floor(uv.y * 96.0); - float seed = noiseAt(vec2(band, floor(u_crt.time_seconds * 13.0))); - float tear = smoothstep(0.955, 1.0, seed); - uv.x += (noiseAt(vec2(seed, band)) - 0.5) * tear * 0.020; - } - return uv; -} - -// The scene, ALPHA INCLUDED: with a see-through ground the grid arrives -// premultiplied and partly empty, and a pass that dropped .a would turn the -// desktop behind a `*_transparent` theme into opaque black the moment Crt, -// Ripple or Glitch came on. -vec4 sceneSrgb(vec2 uv) { - vec2 warped = sceneUv(uv); - vec4 center = texture(u_scene, warped); - if (u_crt.effects.z <= 0.5) return center; - - float band = floor(warped.y * 96.0); - float seed = noiseAt(vec2(band, floor(u_crt.time_seconds * 13.0))); - float tear = smoothstep(0.955, 1.0, seed); - vec2 chroma = vec2((0.35 + 1.25 * tear) / u_crt.viewport.x, 0.0); - return vec4( - texture(u_scene, warped + chroma).r, - center.g, - texture(u_scene, warped - chroma).b, - center.a - ); -} - -vec3 effectSceneLinear(vec2 uv) { - return srgbToLinear(sceneSrgb(uv).rgb); -} - -void main() { - vec2 size = u_crt.viewport; - vec2 texel = 1.0 / size; - vec2 cc = gl_FragCoord.xy / size * 2.0 - 1.0; - float r2 = dot(cc, cc); - vec2 uv = cc * (1.0 + BARREL * r2 * u_crt.effects.x) * 0.5 + 0.5; - - // Ripple and glitch are useful without the CRT. They only move samples; - // skip every tube-specific light/mask/exposure adjustment. - if (u_crt.effects.x <= 0.5) { - // The scene texture is already sRGB. Avoid a needless transfer-curve - // round trip so a coordinate-only effect preserves theme bytes. - vec4 s = sceneSrgb(uv); - o_col = vec4(clamp(s.rgb, 0.0, 1.0), clamp(s.a, 0.0, 1.0)); - return; - } - - vec3 col = vec3(0.0); - // Outside the tube nothing is drawn, so nothing is covered either: the - // old opaque black there would have punched a rectangle out of the - // compositor's backdrop. - float alpha = 0.0; - float edge = min(min(uv.x, uv.y), min(1.0 - uv.x, 1.0 - uv.y)); - if (edge > 0.0) { - vec3 center = effectSceneLinear(uv); - vec2 chroma = cc * (CHROMA_PX * r2) * texel; - // Each radial sample follows the same ripple/glitch path from its own - // pre-warp coordinate. This is also how the Metal kernel composes the - // effects; sampling a once-warped center here made combined effects - // diverge between the two GUI backends. - col = vec3( - effectSceneLinear(uv + chroma).r, - center.g, - effectSceneLinear(uv - chroma).b - ); - - vec2 bloom_px = texel * BLOOM_RADIUS_PX; - vec3 bloom = brightPass(uv + vec2(bloom_px.x, 0.0)) + - brightPass(uv - vec2(bloom_px.x, 0.0)) + - brightPass(uv + vec2(0.0, bloom_px.y)) + - brightPass(uv - vec2(0.0, bloom_px.y)); - col += bloom * (0.25 * BLOOM_STRENGTH); - - // The mask has unit mean and the scan loss stays small; neither needs - // the old global lift that remapped every theme toward white. - float scan_wave = 0.5 + 0.5 * cos(gl_FragCoord.y * TAU / SCAN_PERIOD_PX); - col *= 1.0 - SCAN_STRENGTH * scan_wave * scan_wave; - vec3 mask = 1.0 + MASK_STRENGTH * cos( - gl_FragCoord.x * TAU / MASK_PERIOD_PX + vec3(0.0, TAU / 3.0, 2.0 * TAU / 3.0) - ); - col *= mask; - - vec2 edge_px = min(uv, 1.0 - uv) * size; - float tube = smoothstep(0.0, EDGE_SOFTNESS_PX, min(edge_px.x, edge_px.y)); - float vignette = 1.0 - VIGNETTE_STRENGTH * pow(clamp(r2 * 0.5, 0.0, 1.0), 1.6); - float hum = 1.0 + HUM_STRENGTH * sin((uv.y * 1.35 - u_crt.time_seconds * 0.11) * TAU); - col *= tube * vignette * hum * EXPOSURE; - // The tube's edge falloff dims coverage as well as colour; everything - // else here is a light adjustment and leaves it alone. - alpha = sceneSrgb(uv).a * tube; - - float luma = dot(col, vec3(0.2126, 0.7152, 0.0722)); - vec2 noise_seed = gl_FragCoord.xy + vec2(u_crt.frame * 17.0, u_crt.frame * 59.0); - col += (noiseAt(noise_seed) - 0.5) * NOISE_STRENGTH * (0.15 + 0.85 * clamp(luma, 0.0, 1.0)); - } - o_col = vec4(linearToSrgb(clamp(col, 0.0, 1.0)), clamp(alpha, 0.0, 1.0)); -} diff --git a/shaders/prebuilt/crt.frag.spv b/shaders/prebuilt/crt.frag.spv deleted file mode 100644 index f0e14627..00000000 Binary files a/shaders/prebuilt/crt.frag.spv and /dev/null differ diff --git a/shaders/prebuilt/crt.vert.glsl b/shaders/prebuilt/crt.vert.glsl deleted file mode 100644 index 4e15fcdf..00000000 --- a/shaders/prebuilt/crt.vert.glsl +++ /dev/null @@ -1,9 +0,0 @@ -#version 450 - -// CRT post-process pass: one fullscreen triangle from gl_VertexIndex, no -// vertex buffer. The fragment shader derives uv from gl_FragCoord. - -void main() { - vec2 p = vec2(float((gl_VertexIndex << 1) & 2), float(gl_VertexIndex & 2)); - gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0); -} diff --git a/shaders/prebuilt/crt.vert.spv b/shaders/prebuilt/crt.vert.spv deleted file mode 100644 index 8a75ebb3..00000000 Binary files a/shaders/prebuilt/crt.vert.spv and /dev/null differ diff --git a/shaders/prebuilt/post-crt.frag.glsl b/shaders/prebuilt/post-crt.frag.glsl new file mode 100644 index 00000000..11b95272 --- /dev/null +++ b/shaders/prebuilt/post-crt.frag.glsl @@ -0,0 +1,146 @@ +#version 450 +// pardes's Shadertoy prefix (docs/render-pipeline.md §6), put before every +// post pass: a bundled one in shaders/post/ at build time, a user's file at +// run time. The uniform names, types and std140 order are ghostty 1.3.2's +// (src/renderer/shaders/shadertoy_prefix.glsl), so a shader written for +// ghostty runs here; only the bindings are SDL GPU's (a fragment shader's +// textures in set 2, its uniform blocks in set 3). Y is down everywhere, as +// on ghostty's Metal renderer: fragCoord, iChannel0's uv and the cursor +// rectangles share one top-left space, and iCurrentCursor is (left x, +// BOTTOM-edge y, width, height) in framebuffer pixels. + +layout(set = 3, binding = 0, std140) uniform Globals { + uniform vec3 iResolution; + uniform float iTime; + uniform float iTimeDelta; + uniform float iFrameRate; + uniform int iFrame; + uniform float iChannelTime[4]; + uniform vec3 iChannelResolution[4]; + uniform vec4 iMouse; + uniform vec4 iDate; + uniform float iSampleRate; + uniform vec4 iCurrentCursor; + uniform vec4 iPreviousCursor; + uniform vec4 iCurrentCursorColor; + uniform vec4 iPreviousCursorColor; + uniform int iCurrentCursorStyle; + uniform int iPreviousCursorStyle; + uniform int iCursorVisible; + uniform float iTimeCursorChange; + uniform float iTimeFocus; + uniform int iFocus; + uniform vec3 iPalette[256]; + uniform vec3 iBackgroundColor; + uniform vec3 iForegroundColor; + uniform vec3 iCursorColor; + uniform vec3 iCursorText; + uniform vec3 iSelectionForegroundColor; + uniform vec3 iSelectionBackgroundColor; +}; + +// pardes's own: 1 when the window is opaque, so a pass cannot punch holes +// in it through its alpha. +layout(set = 3, binding = 1, std140) uniform PardesPost { + float pardesOpaque; +}; + +#define CURSORSTYLE_BLOCK 0 +#define CURSORSTYLE_BLOCK_HOLLOW 1 +#define CURSORSTYLE_BAR 2 +#define CURSORSTYLE_UNDERLINE 3 +#define CURSORSTYLE_LOCK 4 + +layout(set = 2, binding = 0) uniform sampler2D iChannel0; + +layout(location = 0) out vec4 _fragColor; + +#define texture2D texture + +void mainImage(out vec4 fragColor, in vec2 fragCoord); +void main() { + mainImage(_fragColor, gl_FragCoord.xy); + if (pardesOpaque > 0.5) _fragColor.a = 1.0; +} + +// The shader's own lines count from 1, so glslc's errors name them. +#line 1 +// Crt: a slot-mask tube's light, with nothing that moves a pixel. There is +// no barrel: every click lands where it is drawn, rails and grips at the +// window's edges included. What is left is light: a beam profile for the +// scanlines, an aperture mask, a soft bloom of the brightest ink, a faint +// vignette and the hum of a live tube. Everything is worked out in linear +// light and dithered back into 8 bits, so the vignette never bands. It is a +// Shadertoy file: pardes puts its prefix before it (shaders/post/prefix.glsl). + +const float TAU = 6.28318530718; +const float CHROMA_PX = 0.35; // red and blue apart, at the corners +const float BLOOM_RADIUS_PX = 1.75; +const float BLOOM_THRESHOLD = 0.60; // linear-light channel peak +const float BLOOM_SOFTNESS = 0.25; +const float BLOOM_STRENGTH = 0.05; +const float SCAN_PERIOD_PX = 3.0; +const float SCAN_STRENGTH = 0.07; +const float MASK_PERIOD_PX = 3.0; +const float MASK_STRENGTH = 0.018; +const float VIGNETTE_STRENGTH = 0.12; // at the far corners only +const float HUM_STRENGTH = 0.004; +const float HUM_HZ = 0.11; +const float EXPOSURE = 1.015; + +vec3 toLinear(vec3 c) { + return mix(pow((c + 0.055) / 1.055, vec3(2.4)), c / 12.92, lessThanEqual(c, vec3(0.04045))); +} + +vec3 toSrgb(vec3 c) { + c = max(c, vec3(0.0)); + return mix(1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, c * 12.92, lessThanEqual(c, vec3(0.0031308))); +} + +vec3 scene(vec2 uv) { + return toLinear(texture(iChannel0, uv).rgb); +} + +vec3 brightPass(vec2 uv) { + vec3 c = scene(uv); + float peak = max(c.r, max(c.g, c.b)); + return c * smoothstep(BLOOM_THRESHOLD, BLOOM_THRESHOLD + BLOOM_SOFTNESS, peak); +} + +// Interleaved gradient noise: a dither with no visible pattern. +float ign(vec2 p) { + return fract(52.9829189 * fract(dot(p, vec2(0.06711056, 0.00583715)))); +} + +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 size = iResolution.xy; + vec2 texel = 1.0 / size; + vec2 uv = fragCoord / size; + vec2 cc = uv * 2.0 - 1.0; + float r2 = dot(cc, cc); + + // The beam converges least at the corners: red and blue drift apart + // there, by a third of a pixel. + vec2 chroma = cc * (CHROMA_PX * r2 * 0.5) * texel; + vec3 col = vec3(scene(uv + chroma).r, scene(uv).g, scene(uv - chroma).b); + + vec2 b = texel * BLOOM_RADIUS_PX; + vec3 bloom = brightPass(uv + vec2(b.x, 0.0)) + brightPass(uv - vec2(b.x, 0.0)) + + brightPass(uv + vec2(0.0, b.y)) + brightPass(uv - vec2(0.0, b.y)); + col += bloom * (0.25 * BLOOM_STRENGTH); + + // A beam is brightest on its line and falls off between lines; the + // mask is three phosphor stripes a triad. Both average to one, so the + // theme keeps its brightness. + float line = 0.5 + 0.5 * cos(fragCoord.y * TAU / SCAN_PERIOD_PX); + col *= 1.0 - SCAN_STRENGTH * (line * line - 0.375) / 0.625; + col *= 1.0 + MASK_STRENGTH * cos(fragCoord.x * TAU / MASK_PERIOD_PX + vec3(0.0, TAU / 3.0, 2.0 * TAU / 3.0)); + + float vignette = 1.0 - VIGNETTE_STRENGTH * pow(clamp(r2 * 0.5, 0.0, 1.0), 1.6); + float hum = 1.0 + HUM_STRENGTH * sin((uv.y * 1.35 - iTime * HUM_HZ) * TAU); + col *= vignette * hum * EXPOSURE; + + vec3 srgb = toSrgb(col); + srgb += (ign(fragCoord + float(iFrame % 64) * vec2(5.588238, 5.588238)) - 0.5) / 255.0; + fragColor = vec4(clamp(srgb, 0.0, 1.0), texture(iChannel0, uv).a); +} diff --git a/shaders/prebuilt/post-crt.frag.spv b/shaders/prebuilt/post-crt.frag.spv new file mode 100644 index 00000000..e4d98138 Binary files /dev/null and b/shaders/prebuilt/post-crt.frag.spv differ diff --git a/shaders/prebuilt/post-glitch.frag.glsl b/shaders/prebuilt/post-glitch.frag.glsl new file mode 100644 index 00000000..46538452 --- /dev/null +++ b/shaders/prebuilt/post-glitch.frag.glsl @@ -0,0 +1,127 @@ +#version 450 +// pardes's Shadertoy prefix (docs/render-pipeline.md §6), put before every +// post pass: a bundled one in shaders/post/ at build time, a user's file at +// run time. The uniform names, types and std140 order are ghostty 1.3.2's +// (src/renderer/shaders/shadertoy_prefix.glsl), so a shader written for +// ghostty runs here; only the bindings are SDL GPU's (a fragment shader's +// textures in set 2, its uniform blocks in set 3). Y is down everywhere, as +// on ghostty's Metal renderer: fragCoord, iChannel0's uv and the cursor +// rectangles share one top-left space, and iCurrentCursor is (left x, +// BOTTOM-edge y, width, height) in framebuffer pixels. + +layout(set = 3, binding = 0, std140) uniform Globals { + uniform vec3 iResolution; + uniform float iTime; + uniform float iTimeDelta; + uniform float iFrameRate; + uniform int iFrame; + uniform float iChannelTime[4]; + uniform vec3 iChannelResolution[4]; + uniform vec4 iMouse; + uniform vec4 iDate; + uniform float iSampleRate; + uniform vec4 iCurrentCursor; + uniform vec4 iPreviousCursor; + uniform vec4 iCurrentCursorColor; + uniform vec4 iPreviousCursorColor; + uniform int iCurrentCursorStyle; + uniform int iPreviousCursorStyle; + uniform int iCursorVisible; + uniform float iTimeCursorChange; + uniform float iTimeFocus; + uniform int iFocus; + uniform vec3 iPalette[256]; + uniform vec3 iBackgroundColor; + uniform vec3 iForegroundColor; + uniform vec3 iCursorColor; + uniform vec3 iCursorText; + uniform vec3 iSelectionForegroundColor; + uniform vec3 iSelectionBackgroundColor; +}; + +// pardes's own: 1 when the window is opaque, so a pass cannot punch holes +// in it through its alpha. +layout(set = 3, binding = 1, std140) uniform PardesPost { + float pardesOpaque; +}; + +#define CURSORSTYLE_BLOCK 0 +#define CURSORSTYLE_BLOCK_HOLLOW 1 +#define CURSORSTYLE_BAR 2 +#define CURSORSTYLE_UNDERLINE 3 +#define CURSORSTYLE_LOCK 4 + +layout(set = 2, binding = 0) uniform sampler2D iChannel0; + +layout(location = 0) out vec4 _fragColor; + +#define texture2D texture + +void mainImage(out vec4 fragColor, in vec2 fragCoord); +void main() { + mainImage(_fragColor, gl_FragCoord.xy); + if (pardesOpaque > 0.5) _fragColor.a = 1.0; +} + +// The shader's own lines count from 1, so glslc's errors name them. +#line 1 +// Glitch: now and then the signal slips. Every couple of seconds a short +// burst tears a few horizontal bands sideways and pulls their red and blue +// apart; the bands are sized in pixels and the burst eases in and out over +// a fraction of a second, so it reads as one event rather than a flicker, +// and it looks the same at 60 or 144 Hz because every step is keyed to +// iTime, not to frames. Between bursts the page is left exactly as it is. +// A torn band's brightness dips in linear light. It is a Shadertoy file: +// pardes puts its prefix before it. + +const float PERIOD = 2.4; // seconds between bursts, on average +const float BURST = 0.28; // seconds a burst lasts +const float STEP_HZ = 24.0; // how often a burst re-tears +const float SHIFT_PX = 14.0; +const float SPLIT_PX = 2.5; +const float DIP = 0.12; + +float hash(vec2 p) { + vec3 q = fract(vec3(p.xyx) * 0.1031); + q += dot(q, q.yzx + 33.33); + return fract((q.x + q.y) * q.z); +} + +vec3 toLinear(vec3 c) { + return mix(pow((c + 0.055) / 1.055, vec3(2.4)), c / 12.92, lessThanEqual(c, vec3(0.04045))); +} + +vec3 toSrgb(vec3 c) { + c = max(c, vec3(0.0)); + return mix(1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, c * 12.92, lessThanEqual(c, vec3(0.0031308))); +} + +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 size = iResolution.xy; + vec2 uv = fragCoord / size; + // Which burst window this is, when in it the burst starts, and how far + // into the burst we are: 0 outside one. + float window = floor(iTime / PERIOD); + float start = window * PERIOD + hash(vec2(window, 7.0)) * (PERIOD - BURST); + float t = (iTime - start) / BURST; + float live = step(0.0, t) * step(t, 1.0); + float envelope = live * sin(3.14159265 * clamp(t, 0.0, 1.0)); + if (envelope <= 0.0) { + fragColor = texture(iChannel0, uv); + return; + } + // Bands of 6 to 40 px, re-cut at STEP_HZ during the burst; about one + // band in four tears. + float tick = floor(iTime * STEP_HZ); + float band_px = mix(6.0, 40.0, hash(vec2(tick, 3.0))); + float band = floor(fragCoord.y / band_px); + float roll = hash(vec2(band, tick)); + float tear = smoothstep(0.72, 0.9, roll) * envelope; + float shift = (hash(vec2(tick, band + 11.0)) - 0.5) * 2.0 * SHIFT_PX * tear; + vec2 at = vec2(fragCoord.x + shift, fragCoord.y); + vec2 split = vec2(SPLIT_PX * tear, 0.0); + vec4 center = texture(iChannel0, at / size); + vec3 col = vec3(texture(iChannel0, (at + split) / size).r, center.g, texture(iChannel0, (at - split) / size).b); + col = toSrgb(toLinear(col) * (1.0 - DIP * tear)); + fragColor = vec4(col, center.a); +} diff --git a/shaders/prebuilt/post-glitch.frag.spv b/shaders/prebuilt/post-glitch.frag.spv new file mode 100644 index 00000000..8a224958 Binary files /dev/null and b/shaders/prebuilt/post-glitch.frag.spv differ diff --git a/shaders/prebuilt/post-ripple.frag.glsl b/shaders/prebuilt/post-ripple.frag.glsl new file mode 100644 index 00000000..28059def --- /dev/null +++ b/shaders/prebuilt/post-ripple.frag.glsl @@ -0,0 +1,116 @@ +#version 450 +// pardes's Shadertoy prefix (docs/render-pipeline.md §6), put before every +// post pass: a bundled one in shaders/post/ at build time, a user's file at +// run time. The uniform names, types and std140 order are ghostty 1.3.2's +// (src/renderer/shaders/shadertoy_prefix.glsl), so a shader written for +// ghostty runs here; only the bindings are SDL GPU's (a fragment shader's +// textures in set 2, its uniform blocks in set 3). Y is down everywhere, as +// on ghostty's Metal renderer: fragCoord, iChannel0's uv and the cursor +// rectangles share one top-left space, and iCurrentCursor is (left x, +// BOTTOM-edge y, width, height) in framebuffer pixels. + +layout(set = 3, binding = 0, std140) uniform Globals { + uniform vec3 iResolution; + uniform float iTime; + uniform float iTimeDelta; + uniform float iFrameRate; + uniform int iFrame; + uniform float iChannelTime[4]; + uniform vec3 iChannelResolution[4]; + uniform vec4 iMouse; + uniform vec4 iDate; + uniform float iSampleRate; + uniform vec4 iCurrentCursor; + uniform vec4 iPreviousCursor; + uniform vec4 iCurrentCursorColor; + uniform vec4 iPreviousCursorColor; + uniform int iCurrentCursorStyle; + uniform int iPreviousCursorStyle; + uniform int iCursorVisible; + uniform float iTimeCursorChange; + uniform float iTimeFocus; + uniform int iFocus; + uniform vec3 iPalette[256]; + uniform vec3 iBackgroundColor; + uniform vec3 iForegroundColor; + uniform vec3 iCursorColor; + uniform vec3 iCursorText; + uniform vec3 iSelectionForegroundColor; + uniform vec3 iSelectionBackgroundColor; +}; + +// pardes's own: 1 when the window is opaque, so a pass cannot punch holes +// in it through its alpha. +layout(set = 3, binding = 1, std140) uniform PardesPost { + float pardesOpaque; +}; + +#define CURSORSTYLE_BLOCK 0 +#define CURSORSTYLE_BLOCK_HOLLOW 1 +#define CURSORSTYLE_BAR 2 +#define CURSORSTYLE_UNDERLINE 3 +#define CURSORSTYLE_LOCK 4 + +layout(set = 2, binding = 0) uniform sampler2D iChannel0; + +layout(location = 0) out vec4 _fragColor; + +#define texture2D texture + +void mainImage(out vec4 fragColor, in vec2 fragCoord); +void main() { + mainImage(_fragColor, gl_FragCoord.xy); + if (pardesOpaque > 0.5) _fragColor.a = 1.0; +} + +// The shader's own lines count from 1, so glslc's errors name them. +#line 1 +// Ripple: the page as a still pool a slow swell crosses. Rings travel out +// from the middle of the window at a fixed speed in pixels, so they look +// the same at any size and frame rate; the displacement is at most a couple +// of pixels, so text stays readable, and it eases in over its first half +// second instead of snapping on. Each ring catches the light a little on its +// rising side, in linear light, and the result is dithered so the shading +// never bands. It is a Shadertoy file: pardes puts its prefix before it. + +const float TAU = 6.28318530718; +const float WAVELENGTH_PX = 150.0; +const float SPEED_PX = 55.0; // outward, per second +const float AMPLITUDE_PX = 2.2; +const float SHADE = 0.035; // light on a slope, linear +const float EASE_IN = 0.5; // seconds + +vec3 toLinear(vec3 c) { + return mix(pow((c + 0.055) / 1.055, vec3(2.4)), c / 12.92, lessThanEqual(c, vec3(0.04045))); +} + +vec3 toSrgb(vec3 c) { + c = max(c, vec3(0.0)); + return mix(1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, c * 12.92, lessThanEqual(c, vec3(0.0031308))); +} + +float ign(vec2 p) { + return fract(52.9829189 * fract(dot(p, vec2(0.06711056, 0.00583715)))); +} + +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 size = iResolution.xy; + vec2 center = 0.5 * size; + vec2 offset = fragCoord - center; + float radius = length(offset); + float reach = 0.5 * length(size); + // Strongest a third of the way out, fading to nothing at the middle + // and at the edges, so the window's frame never wobbles. + float envelope = smoothstep(0.0, 0.18, radius / reach) * (1.0 - smoothstep(0.35, 0.95, radius / reach)); + float ease = smoothstep(0.0, EASE_IN, iTime); + float phase = (radius - iTime * SPEED_PX) * TAU / WAVELENGTH_PX; + float height = sin(phase); + float slope = cos(phase); + vec2 dir = offset / max(radius, 1e-3); + vec2 moved = fragCoord + dir * height * AMPLITUDE_PX * envelope * ease; + + vec4 s = texture(iChannel0, moved / size); + vec3 col = toLinear(s.rgb) * (1.0 + SHADE * slope * envelope * ease); + vec3 srgb = toSrgb(col) + (ign(fragCoord + float(iFrame % 64) * 5.588238) - 0.5) / 255.0; + fragColor = vec4(clamp(srgb, 0.0, 1.0), s.a); +} diff --git a/shaders/prebuilt/post-ripple.frag.spv b/shaders/prebuilt/post-ripple.frag.spv new file mode 100644 index 00000000..1a105a33 Binary files /dev/null and b/shaders/prebuilt/post-ripple.frag.spv differ diff --git a/shaders/prebuilt/post.vert.glsl b/shaders/prebuilt/post.vert.glsl new file mode 100644 index 00000000..86739a65 --- /dev/null +++ b/shaders/prebuilt/post.vert.glsl @@ -0,0 +1,10 @@ +#version 450 + +// A post pass (docs/render-pipeline.md §6): one fullscreen triangle from +// gl_VertexIndex, no vertex buffer. The Shadertoy fragment shader works from +// gl_FragCoord, y down, as its fragCoord. + +void main() { + vec2 p = vec2(float((gl_VertexIndex << 1) & 2), float(gl_VertexIndex & 2)); + gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0); +} diff --git a/shaders/prebuilt/post.vert.spv b/shaders/prebuilt/post.vert.spv new file mode 100644 index 00000000..8a75ebb3 Binary files /dev/null and b/shaders/prebuilt/post.vert.spv differ diff --git a/src/animation.zig b/src/animation.zig index d6ef1ec3..948a6679 100644 --- a/src/animation.zig +++ b/src/animation.zig @@ -77,12 +77,6 @@ pub const Transition = enum(u8) { } }; -pub const SceneEffect = struct { - crt: bool = false, - ripple: bool = false, - glitch: bool = false, -}; - pub const Phase = enum(u8) { opening = 0, moving = 1, diff --git a/src/builtins.zig b/src/builtins.zig index 6bfc9b26..1864086f 100644 --- a/src/builtins.zig +++ b/src/builtins.zig @@ -156,7 +156,7 @@ pub const registry = struct { if (@intFromEnum(b) == i) return switch (setting.action) { // a switch flips bare, and DumpDir bare is the default .toggle, .transition, .scene, .dump_dir => false, - .shell, .theme, .font, .tagline_size, .window_opacity, .window_blur, .message_ms => true, + .shell, .theme, .font, .tagline_size, .window_opacity, .window_blur, .message_ms, .shader, .shader_animation => true, }; unreachable; } diff --git a/src/config.zig b/src/config.zig index 122c0e75..1d4043ab 100644 --- a/src/config.zig +++ b/src/config.zig @@ -131,6 +131,8 @@ pub const leader_path = paths: { table.set(.Crt, "tr"); table.set(.Ripple, "tR"); table.set(.Glitch, "tg"); + table.set(.Shader, null); + table.set(.ShaderAnimation, null); } if (builtins.EffectCode.enabled) table.set(.EffectCode, null); if (builtins.TreeContext.enabled) table.set(.TreeContext, null); @@ -611,7 +613,64 @@ pub const Runtime = struct { } = .{}, panel_transition: animation.Transition = .off, - scene_effects: animation.SceneEffect = .{}, + /// The post chain (docs/render-pipeline.md §6), in the order it runs. + post: Post = .{}, + /// When the chain redraws on its own, past the frames it post-processes: + /// never, while the window has the focus, or always (ghostty's + /// custom-shader-animation). + shader_animation: ShaderAnimation = .on, + + pub const ShaderAnimation = enum { off, on, always }; + + /// Each pass a bundled effect (Crt, Ripple, Glitch) or a Shadertoy file. + pub const Post = struct { + pub const max = 8; + pub const Entry = struct { + scene: ?Scene = null, + path: Text(limits.host_path_cap) = .{}, + }; + entries: [max]Entry = @splat(.{}), + len: u8 = 0, + /// Bumped on every change: a shell compiles the chain again when it + /// moves. + generation: u32 = 0, + + pub fn list(post: *const Post) []const Entry { + return post.entries[0..post.len]; + } + + pub fn has(post: *const Post, scene: Scene) bool { + for (post.list()) |entry| if (entry.scene == scene) return true; + return false; + } + + /// Puts the pass in the chain, at its end, or takes it out; `want` + /// null flips it. False when the chain is full or the path too long. + pub fn set(post: *Post, scene: ?Scene, path: []const u8, want: ?bool) bool { + const at: ?usize = for (post.list(), 0..) |entry, i| { + if (entry.scene == scene and std.mem.eql(u8, entry.path.get(), path)) break i; + } else null; + const on = want orelse (at == null); + if (on == (at != null)) return true; + post.generation +%= 1; + if (at) |i| { + std.mem.copyForwards(Entry, post.entries[i .. post.len - 1], post.entries[i + 1 .. post.len]); + post.len -= 1; + return true; + } + if (post.len == max) return false; + post.entries[post.len] = .{ .scene = scene }; + if (!post.entries[post.len].path.set(path)) return false; + post.len += 1; + return true; + } + + pub fn clear(post: *Post) void { + if (post.len == 0) return; + post.len = 0; + post.generation +%= 1; + } + }; pub fn toggleTransition(state: *Runtime, effect: animation.Transition) void { std.debug.assert(effect != .off); @@ -690,7 +749,8 @@ pub const Runtime = struct { pub const MessageTime = enum { linger, fall, dissolve }; pub const Toggle = enum { colors, wrap, tree_context_tag_style, tag_bottom, debug, focus_tint, column_tags, syntax_bold, verbose, message_animation, ligatures }; - pub const Scene = std.meta.FieldEnum(animation.SceneEffect); + /// The bundled post passes, each a Shadertoy file in shaders/post/. + pub const Scene = enum { crt, ripple, glitch }; pub const Action = union(enum) { toggle: Toggle, @@ -704,6 +764,10 @@ pub const Runtime = struct { dump_dir, transition: animation.Transition, scene: Scene, + /// A Shadertoy file into the post chain or out of it; `off` empties + /// it of files. + shader, + shader_animation, }; pub const Setting = struct { @@ -756,6 +820,8 @@ pub const Runtime = struct { .{ .word = "Crt", .action = .{ .scene = .crt }, .availability = .scene_shaders }, .{ .word = "Ripple", .action = .{ .scene = .ripple }, .availability = .scene_shaders }, .{ .word = "Glitch", .action = .{ .scene = .glitch }, .availability = .scene_shaders }, + .{ .word = "Shader", .action = .shader, .availability = .scene_shaders }, + .{ .word = "ShaderAnimation", .action = .shader_animation, .availability = .scene_shaders }, }; pub fn find(name: []const u8) ?Setting { @@ -789,7 +855,7 @@ pub const Runtime = struct { @compileError("window blur must use the window-blur capability"), .transition => if (setting.availability != .panel_transitions) @compileError("panel effects must use the panel-transition capability"), - .scene => if (setting.availability != .scene_shaders) + .scene, .shader, .shader_animation => if (setting.availability != .scene_shaders) @compileError("scene effects must use the scene-shader capability"), // A toggle is plain state any shell can hold, except one for // what only some shells draw: that one exists where they do. @@ -815,9 +881,9 @@ pub const Runtime = struct { for (std.enums.values(Scene)) |effect| { if (actionCount(.{ .scene = effect }) != 1) @compileError("scene effect must occur exactly once: " ++ @tagName(effect)); - if (@FieldType(animation.SceneEffect, @tagName(effect)) != bool) - @compileError("scene effect fields must be booleans: " ++ @tagName(effect)); } + if (actionCount(.shader) != 1 or actionCount(.shader_animation) != 1) + @compileError("Shader and ShaderAnimation must each occur exactly once"); } pub fn apply(state: *Runtime, setting: Setting, argument: ?[]const u8) bool { @@ -884,12 +950,24 @@ pub const Runtime = struct { }, .scene => |effect| { const want = switchedTo(argument) catch return false; - switch (effect) { - inline else => |field| { - const value = &@field(state.scene_effects, @tagName(field)); - value.* = want orelse !value.*; - }, + return state.post.set(effect, "", want); + }, + .shader => { + const path = std.mem.trim(u8, argument orelse return false, " \t\r\n"); + if (path.len == 0) return false; + if (std.mem.eql(u8, path, "off")) { + var i: usize = 0; + while (i < state.post.len) { + const entry = state.post.entries[i]; + if (entry.scene == null) _ = state.post.set(null, entry.path.get(), false) else i += 1; + } + return true; } + return state.post.set(null, path, null); + }, + .shader_animation => { + const text = std.mem.trim(u8, argument orelse return false, " \t\r\n"); + state.shader_animation = std.meta.stringToEnum(ShaderAnimation, text) orelse return false; }, .theme, .font => return false, } @@ -1041,11 +1119,24 @@ pub const Runtime = struct { try out.print("{s} unsupported\n", .{setting.word}); continue; } - const enabled = switch (effect) { - inline else => |field| @field(state.scene_effects, @tagName(field)), + try out.print("{s} {s}\n", .{ setting.word, onOff(state.post.has(effect)) }); + }, + .shader => { + if (!setting.enabled(context.capabilities)) { + try out.print("{s} unsupported\n", .{setting.word}); + continue; + } + var files: usize = 0; + for (state.post.list()) |entry| if (entry.scene == null) { + try out.print("{s} {s}\n", .{ setting.word, entry.path.get() }); + files += 1; }; - try out.print("{s} {s}\n", .{ setting.word, onOff(enabled) }); + if (files == 0) try out.print("{s} off\n", .{setting.word}); }, + .shader_animation => if (setting.enabled(context.capabilities)) + try out.print("{s} {s}\n", .{ setting.word, @tagName(state.shader_animation) }) + else + try out.print("{s} unsupported\n", .{setting.word}), }; if (context.startup_config_path) |path| @@ -1092,9 +1183,7 @@ pub const Runtime = struct { const scene = findAction(.{ .scene = std.enums.values(Scene)[0] }).?; try std.testing.expect(apply(&state, scene, "on")); try std.testing.expect(apply(&state, scene, "on")); - switch (std.enums.values(Scene)[0]) { - inline else => |f| try std.testing.expect(@field(state.scene_effects, @tagName(f))), - } + try std.testing.expect(state.post.has(std.enums.values(Scene)[0])); } test "a toggle flips when bare and sets when told on or off" { @@ -1131,7 +1220,30 @@ pub const Runtime = struct { try std.testing.expect(apply(&state, find("PanelAscii").?, null)); try std.testing.expectEqual(animation.Transition.off, state.panel_transition); try std.testing.expect(apply(&state, find("Crt").?, null)); - try std.testing.expect(state.scene_effects.crt); + try std.testing.expect(state.post.has(.crt)); + } + + test "the post chain runs in the order its passes were put in, and Shader off empties it of files" { + var state: Runtime = .{}; + try std.testing.expect(apply(&state, find("Shader").?, " ~/a.glsl\n")); + try std.testing.expect(apply(&state, find("Glitch").?, "on")); + try std.testing.expect(apply(&state, find("Shader").?, "/b.glsl")); + const chain = state.post.list(); + try std.testing.expectEqual(@as(usize, 3), chain.len); + try std.testing.expectEqualStrings("~/a.glsl", chain[0].path.get()); + try std.testing.expectEqual(Scene.glitch, chain[1].scene.?); + try std.testing.expectEqualStrings("/b.glsl", chain[2].path.get()); + const moved = state.post.generation; + // A file named again leaves; the others keep their order. + try std.testing.expect(apply(&state, find("Shader").?, "~/a.glsl")); + try std.testing.expect(state.post.generation != moved); + try std.testing.expectEqual(Scene.glitch, state.post.list()[0].scene.?); + try std.testing.expect(apply(&state, find("Shader").?, "off")); + try std.testing.expectEqual(@as(usize, 1), state.post.len); + try std.testing.expect(!apply(&state, find("Shader").?, null)); + try std.testing.expect(apply(&state, find("ShaderAnimation").?, "always")); + try std.testing.expectEqual(ShaderAnimation.always, state.shader_animation); + try std.testing.expect(!apply(&state, find("ShaderAnimation").?, "sometimes")); } test "tagline size validates before mutating live state" { @@ -1252,6 +1364,8 @@ pub const Runtime = struct { .window_blur => "40", .message_ms => "1500", .dump_dir => "~/dumps", + .shader => "~/crt.glsl", + .shader_animation => "always", else => null, }; try std.testing.expect(apply(&state, setting, argument)); @@ -1302,6 +1416,8 @@ pub const Runtime = struct { .ripple => "Ripple on\n", .glitch => "Glitch on\n", }, + .shader => "Shader ~/crt.glsl\n", + .shader_animation => "ShaderAnimation always\n", .theme, .font => unreachable, }; try std.testing.expect(std.mem.indexOf(u8, report, expected) != null); diff --git a/src/effect_sources.zig b/src/effect_sources.zig index 08680860..6ce11160 100644 --- a/src/effect_sources.zig +++ b/src/effect_sources.zig @@ -11,8 +11,11 @@ const ui_vertex_path = shader_dir ++ "ui.vert.glsl"; const ui_fragment_path = shader_dir ++ "ui.frag.glsl"; const image_vertex_path = shader_dir ++ "image.vert.glsl"; const image_fragment_path = shader_dir ++ "image.frag.glsl"; -const scene_vertex_path = shader_dir ++ "crt.vert.glsl"; -const scene_fragment_path = shader_dir ++ "crt.frag.glsl"; +const post_vertex_path = shader_dir ++ "post.vert.glsl"; +// The bundled post passes as compiled: the prefix and the pass together. +const post_crt_path = shader_dir ++ "post-crt.frag.glsl"; +const post_ripple_path = shader_dir ++ "post-ripple.frag.glsl"; +const post_glitch_path = shader_dir ++ "post-glitch.frag.glsl"; pub const Backend = @TypeOf(build_config.platform); pub const backend: Backend = build_config.platform; @@ -45,13 +48,15 @@ pub const files = switch (backend) { }, .gui => [_]filesystem.Source{ .{ .path = "src/gui/gui.zig", .contents = @embedFile("gui/gui.zig") }, - .{ .path = "src/gui/crt.zig", .contents = @embedFile("gui/crt.zig") }, + .{ .path = "src/gui/Post.zig", .contents = @embedFile("gui/Post.zig") }, .{ .path = ui_vertex_path, .contents = @embedFile("effect-source-ui.vert.glsl") }, .{ .path = ui_fragment_path, .contents = @embedFile("effect-source-ui.frag.glsl") }, .{ .path = image_vertex_path, .contents = @embedFile("effect-source-image.vert.glsl") }, .{ .path = image_fragment_path, .contents = @embedFile("effect-source-image.frag.glsl") }, - .{ .path = scene_vertex_path, .contents = @embedFile("effect-source-crt.vert.glsl") }, - .{ .path = scene_fragment_path, .contents = @embedFile("effect-source-crt.frag.glsl") }, + .{ .path = post_vertex_path, .contents = @embedFile("effect-source-post.vert.glsl") }, + .{ .path = post_crt_path, .contents = @embedFile("effect-source-post-crt.frag.glsl") }, + .{ .path = post_ripple_path, .contents = @embedFile("effect-source-post-ripple.frag.glsl") }, + .{ .path = post_glitch_path, .contents = @embedFile("effect-source-post-glitch.frag.glsl") }, }, .macos => [_]filesystem.Source{ .{ .path = "src/macos/Sources/PardesView.swift", .contents = @embedFile("macos/Sources/PardesView.swift") }, @@ -69,8 +74,12 @@ pub fn forSetting(setting: config.Runtime.Setting) ?[]const []const u8 { .macos => &.{ "src/layout.zig", "src/pardes.zig", "src/macos/Sources/PardesView.swift", "src/macos/Sources/ScenePostprocessor.swift", "shaders/crt.ci.metal" }, .web, .esp32p4 => null, }, - .scene => switch (backend) { - .gui => &.{ "src/gui/crt.zig", "src/gui/gui.zig", scene_vertex_path, scene_fragment_path }, + .scene => |effect| switch (backend) { + .gui => switch (effect) { + .crt => &.{ "src/gui/Post.zig", "src/gui/gui.zig", post_vertex_path, post_crt_path }, + .ripple => &.{ "src/gui/Post.zig", "src/gui/gui.zig", post_vertex_path, post_ripple_path }, + .glitch => &.{ "src/gui/Post.zig", "src/gui/gui.zig", post_vertex_path, post_glitch_path }, + }, .macos => &.{ "src/macos/Sources/PardesView.swift", "src/macos/Sources/ScenePostprocessor.swift", "shaders/crt.ci.metal" }, .tty, .web, .esp32p4 => null, }, diff --git a/src/gui/Post.zig b/src/gui/Post.zig new file mode 100644 index 00000000..d92623d7 --- /dev/null +++ b/src/gui/Post.zig @@ -0,0 +1,559 @@ +//! The post chain (docs/render-pipeline.md §6): Shadertoy passes over the +//! finished frame, each reading the one before it (the frame first) and the +//! last one writing the window. A pass is bundled (Crt, Ripple, Glitch: +//! shaders/post/, compiled with the build) or a user's file (Shader ), +//! compiled when it joins the chain by the build's own glslc invocation on a +//! thread of its own, never on the frame path. A file whose compile fails +//! keeps its last good pipeline, and glslc missing is said once. +//! +//! The uniforms are ghostty's (Uniforms), so a shader written for ghostty runs +//! here. Their clock is the shell's and moves only when the chain draws: +//! with a frame of the core's, or on its own while ShaderAnimation asks for +//! it (redraw level A, §5.5: the chain alone, over the retained frame). +const std = @import("std"); +const libc = std.c; +const ghostty_vt = @import("ghostty-vt"); +const pardes = @import("../pardes.zig"); +const filesystem = @import("../fs.zig"); +const host_io = @import("../host_io.zig"); +const gui = @import("gui.zig"); +const c = gui.c; + +const Post = @This(); +const Scene = pardes.config.Runtime.Scene; +const Chain = pardes.config.Runtime.Post; +const Pipeline = c.SDL_GPUGraphicsPipeline; +pub const max = Chain.max; + +const prefix = @embedFile("post-prefix.glsl"); +const vert_spv = @embedFile("post.vert.spv"); +const bundled_spv = std.EnumArray(Scene, []const u8).init(.{ + .crt = @embedFile("post-crt.frag.spv"), + .ripple = @embedFile("post-ripple.frag.spv"), + .glitch = @embedFile("post-glitch.frag.spv"), +}); + +/// ghostty's Shadertoy uniform block, member for member in the GLSL block's +/// order and std140 layout (shaders/post/prefix.glsl). Filled by the GLSL +/// names: ghostty's own Zig mirror calls the second-to-last slot +/// selection_background, so there the two selection colours arrive swapped +/// (docs §6, open point 1); here each is what its GLSL name says. +pub const Uniforms = extern struct { + resolution: [3]f32 align(16) = .{ 0, 0, 1 }, + time: f32 align(4) = 0, + time_delta: f32 align(4) = 0, + frame_rate: f32 align(4) = 60, + frame: i32 align(4) = 0, + channel_time: [4][4]f32 align(16) = @splat(@splat(0)), + channel_resolution: [4][4]f32 align(16) = @splat(@splat(0)), + mouse: [4]f32 align(16) = @splat(0), + date: [4]f32 align(16) = @splat(0), + sample_rate: f32 align(4) = 44100, + current_cursor: [4]f32 align(16) = @splat(0), + previous_cursor: [4]f32 align(16) = @splat(0), + current_cursor_color: [4]f32 align(16) = @splat(0), + previous_cursor_color: [4]f32 align(16) = @splat(0), + current_cursor_style: i32 align(4) = 0, + previous_cursor_style: i32 align(4) = 0, + cursor_visible: i32 align(4) = 0, + cursor_change_time: f32 align(4) = 0, + time_focus: f32 align(4) = 0, + focus: i32 align(4) = 1, + palette: [256][4]f32 align(16) = @splat(@splat(0)), + background_color: [4]f32 align(16) = @splat(0), + foreground_color: [4]f32 align(16) = @splat(0), + cursor_color: [4]f32 align(16) = @splat(0), + cursor_text: [4]f32 align(16) = @splat(0), + selection_foreground_color: [4]f32 align(16) = @splat(0), + selection_background_color: [4]f32 align(16) = @splat(0), +}; + +pub const Pass = struct { + scene: ?Scene = null, + /// A user's file, owned; empty for a bundled pass. + path: []u8 = &.{}, + pipeline: ?*Pipeline = null, +}; + +/// One compile of a chain's new files, on its own thread. +const Job = struct { + const Result = union(enum) { none, spirv: []u8, failed: []u8, missing }; + io: std.Io, + gpa: std.mem.Allocator, + paths: [max][]u8 = @splat(&.{}), + results: [max]Result = @splat(.none), + len: usize = 0, + done: std.atomic.Value(bool) = .init(false), + thread: std.Thread = undefined, +}; + +passes: [max]Pass = @splat(.{}), +len: usize = 0, +/// The chain generation `passes` follow; null before the first. +generation: ?u32 = null, +bundled: std.EnumArray(Scene, ?*Pipeline) = .initFill(null), +job: ?*Job = null, +/// Said once: without glslc, files never compile. +missing_said: bool = false, +ping: [2]?*c.SDL_GPUTexture = .{ null, null }, +ping_w: u32 = 0, +ping_h: u32 = 0, +uniforms: Uniforms = .{}, +started_ns: ?u64 = null, +last_ns: u64 = 0, + +/// Brings the passes to the chain the core holds, and takes in a finished +/// compile. Called each loop step, off the frame path. +pub fn sync(post: *Post, gpa: std.mem.Allocator, io: std.Io, device: *c.SDL_GPUDevice, format: c.SDL_GPUTextureFormat, core: *pardes.Pardes) void { + if (post.job) |job| { + if (!job.done.load(.acquire)) return; + job.thread.join(); + post.job = null; + post.collect(gpa, device, format, core, job); + } + const chain = &core.settings.post; + if (post.generation == chain.generation) return; + post.generation = chain.generation; + var next: [max]Pass = @splat(.{}); + for (chain.list(), 0..) |entry, i| { + if (entry.scene) |scene| { + if (post.bundled.get(scene) == null) post.bundled.set(scene, makePipeline(device, format, bundled_spv.get(scene)) catch null); + next[i] = .{ .scene = scene, .pipeline = post.bundled.get(scene) }; + continue; + } + // A file already in the chain keeps its pipeline and its path. + for (post.passes[0..post.len]) |*pass| { + if (pass.scene != null or !std.mem.eql(u8, pass.path, entry.path.get())) continue; + next[i] = pass.*; + pass.* = .{}; + break; + } else next[i] = .{ .path = gpa.dupe(u8, entry.path.get()) catch &.{} }; + } + for (post.passes[0..post.len]) |pass| { + if (pass.scene == null) if (pass.pipeline) |pipeline| c.SDL_ReleaseGPUGraphicsPipeline(device, pipeline); + gpa.free(pass.path); + } + post.passes = next; + post.len = chain.len; + // An emptied chain's clock starts again with the next pass put in it. + if (post.len == 0) { + post.started_ns = null; + post.last_ns = 0; + post.uniforms.frame = 0; + } + // The new files compile together, on a thread of their own. + const job = gpa.create(Job) catch return; + job.* = .{ .io = io, .gpa = gpa }; + for (post.passes[0..post.len]) |pass| { + if (pass.scene != null or pass.pipeline != null or pass.path.len == 0) continue; + job.paths[job.len] = gpa.dupe(u8, pass.path) catch continue; + job.len += 1; + } + if (job.len == 0) return gpa.destroy(job); + job.thread = std.Thread.spawn(.{}, compile, .{job}) catch { + for (job.paths[0..job.len]) |path| gpa.free(path); + return gpa.destroy(job); + }; + post.job = job; +} + +fn collect(post: *Post, gpa: std.mem.Allocator, device: *c.SDL_GPUDevice, format: c.SDL_GPUTextureFormat, core: *pardes.Pardes, job: *Job) void { + defer gpa.destroy(job); + for (job.paths[0..job.len], job.results[0..job.len]) |path, result| { + defer gpa.free(path); + const pass = for (post.passes[0..post.len]) |*pass| { + if (pass.scene == null and std.mem.eql(u8, pass.path, path)) break pass; + } else null; + var buf: [512]u8 = undefined; + switch (result) { + .none => {}, + .missing => if (!post.missing_said) { + post.missing_said = true; + core.setMessage(core.active, "Shader: glslc is not on PATH, so Shadertoy files do not run (Crt, Ripple and Glitch do)"); + }, + .failed => |text| { + defer gpa.free(text); + // glslc's first line says where: in the file's own lines. + const first = std.mem.sliceTo(text, '\n'); + core.setMessage(core.active, std.fmt.bufPrint(&buf, "Shader {s}: {s}", .{ path, first[0..@min(first.len, 400)] }) catch "Shader: compile failed"); + }, + .spirv => |spirv| { + defer gpa.free(spirv); + const target = pass orelse continue; + const made = makePipeline(device, format, spirv) catch { + core.setMessage(core.active, std.fmt.bufPrint(&buf, "Shader {s}: the GPU refused it", .{path}) catch "Shader: the GPU refused it"); + continue; + }; + if (target.pipeline) |old| c.SDL_ReleaseGPUGraphicsPipeline(device, old); + target.pipeline = made; + }, + } + } +} + +/// The compile thread: each file behind the prefix, through glslc exactly as +/// the build calls it (build.zig compileGlsl), its SPIR-V on stdout. +fn compile(job: *Job) void { + defer job.done.store(true, .release); + defer { + var event = std.mem.zeroes(c.SDL_Event); + event.type = c.SDL_EVENT_USER; + _ = c.SDL_PushEvent(&event); + } + for (job.paths[0..job.len], 0..) |path, i| job.results[i] = compileOne(job.gpa, job.io, path, i); +} + +fn compileOne(gpa: std.mem.Allocator, io: std.Io, path: []const u8, index: usize) Job.Result { + var home_buf: [4096]u8 = undefined; + const expanded = if (std.mem.startsWith(u8, path, "~/")) if (libc.getenv("HOME")) |home| + std.fmt.bufPrint(&home_buf, "{s}/{s}", .{ std.mem.span(home), path[2..] }) catch path + else + path else path; + const body = filesystem.readFile(gpa, expanded) catch |err| + return .{ .failed = std.fmt.allocPrint(gpa, "cannot read it ({t})", .{err}) catch return .none }; + defer gpa.free(body); + const dir = if (libc.getenv("XDG_RUNTIME_DIR")) |d| std.mem.span(d) else "/tmp"; + var source_buf: [4096]u8 = undefined; + const source = std.fmt.bufPrintSentinel(&source_buf, "{s}/pardes-post-{d}-{d}.frag.glsl", .{ dir, libc.getpid(), index }, 0) catch return .none; + defer _ = libc.unlink(source); + { + const fd = libc.open(source, .{ .ACCMODE = .WRONLY, .CREAT = true, .TRUNC = true }, @as(libc.mode_t, 0o600)); + if (fd < 0) return .{ .failed = gpa.dupe(u8, "cannot write its source for glslc") catch return .none }; + defer _ = libc.close(fd); + if (!host_io.writeFd(fd, prefix) or !host_io.writeFd(fd, body)) + return .{ .failed = gpa.dupe(u8, "cannot write its source for glslc") catch return .none }; + } + const run = std.process.run(gpa, io, .{ + .argv = &.{ "glslc", "-fshader-stage=fragment", source, "-o", "-" }, + .stdout_limit = .limited(16 << 20), + .stderr_limit = .limited(64 << 10), + }) catch |err| return switch (err) { + error.FileNotFound => .missing, + else => .{ .failed = std.fmt.allocPrint(gpa, "glslc did not run ({t})", .{err}) catch return .none }, + }; + const clean = switch (run.term) { + .exited => |code| code == 0, + else => false, + }; + if (clean) { + gpa.free(run.stderr); + return .{ .spirv = run.stdout }; + } + gpa.free(run.stdout); + // glslc names the temporary source; the file is what a person knows. + defer gpa.free(run.stderr); + const named = std.mem.replaceOwned(u8, gpa, run.stderr, source, path) catch return .{ .failed = gpa.dupe(u8, "glslc failed") catch return .none }; + return .{ .failed = named }; +} + +fn makePipeline(device: *c.SDL_GPUDevice, format: c.SDL_GPUTextureFormat, frag: []const u8) !*Pipeline { + const vs = try gui.makeShader(device, vert_spv, c.SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); + defer c.SDL_ReleaseGPUShader(device, vs); + const fs = try gui.makeShader(device, frag, c.SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 2); + defer c.SDL_ReleaseGPUShader(device, fs); + var target = c.SDL_GPUColorTargetDescription{ .format = format, .blend_state = std.mem.zeroes(c.SDL_GPUColorTargetBlendState) }; + var info = std.mem.zeroes(c.SDL_GPUGraphicsPipelineCreateInfo); + info.vertex_shader = vs; + info.fragment_shader = fs; + info.primitive_type = c.SDL_GPU_PRIMITIVETYPE_TRIANGLELIST; + info.rasterizer_state.fill_mode = c.SDL_GPU_FILLMODE_FILL; + info.rasterizer_state.cull_mode = c.SDL_GPU_CULLMODE_NONE; + info.multisample_state.sample_count = c.SDL_GPU_SAMPLECOUNT_1; + info.target_info = .{ .color_target_descriptions = &target, .num_color_targets = 1 }; + return c.SDL_CreateGPUGraphicsPipeline(device, &info) orelse error.GpuCreate; +} + +/// A pass is ready to draw. +pub fn ready(post: *const Post) bool { + for (post.passes[0..post.len]) |pass| if (pass.pipeline != null) return true; + return false; +} + +/// The chain redraws on its own (level A) as ShaderAnimation says. +pub fn animating(post: *const Post, mode: pardes.config.Runtime.ShaderAnimation) bool { + return post.ready() and switch (mode) { + .off => false, + .on => post.uniforms.focus != 0, + .always => true, + }; +} + +/// What a frame of the core's tells the passes: where its cursor is drawn +/// and in what, and the theme's colours. +pub const Frame = struct { + pub const Cursor = struct { x0: f32, y0: f32, x1: f32, y1: f32, rgb: [3]u8, text: [3]u8, bar: bool }; + cursor: ?Cursor, + background: [3]u8, + foreground: [3]u8, + selection_foreground: [3]u8, + selection_background: [3]u8, +}; + +fn rgba(rgb: [3]u8) [4]f32 { + return .{ @as(f32, @floatFromInt(rgb[0])) / 255, @as(f32, @floatFromInt(rgb[1])) / 255, @as(f32, @floatFromInt(rgb[2])) / 255, 1 }; +} + +/// Takes in a frame of the core's. The cursor's rectangle is (left, BOTTOM +/// edge, width, height) in framebuffer pixels, y down; a move or a new +/// colour makes the old one the previous and stamps the change. +pub fn describe(post: *Post, frame: Frame) void { + const u = &post.uniforms; + u.background_color = rgba(frame.background); + u.foreground_color = rgba(frame.foreground); + u.selection_foreground_color = rgba(frame.selection_foreground); + u.selection_background_color = rgba(frame.selection_background); + if (u.palette[1][3] == 0) for (&u.palette, 0..) |*slot, i| { + const color = ghostty_vt.color.default[i]; + slot.* = rgba(.{ color.r, color.g, color.b }); + }; + u.cursor_visible = @intFromBool(frame.cursor != null); + const cursor = frame.cursor orelse return; + const rect: [4]f32 = .{ cursor.x0, cursor.y1, cursor.x1 - cursor.x0, cursor.y1 - cursor.y0 }; + const style: i32 = if (cursor.bar) 2 else 0; + const color = rgba(cursor.rgb); + u.cursor_color = color; + u.cursor_text = rgba(cursor.text); + if (std.meta.eql(rect, u.current_cursor) and std.meta.eql(color, u.current_cursor_color) and style == u.current_cursor_style) return; + u.previous_cursor = u.current_cursor; + u.previous_cursor_color = u.current_cursor_color; + u.previous_cursor_style = u.current_cursor_style; + u.current_cursor = rect; + u.current_cursor_color = color; + u.current_cursor_style = style; + u.cursor_change_time = u.time; +} + +/// The window gained or lost the focus. +pub fn focus(post: *Post, focused: bool) void { + if (focused and post.uniforms.focus == 0) post.uniforms.time_focus = post.uniforms.time; + post.uniforms.focus = @intFromBool(focused); +} + +/// The pointer, in framebuffer pixels: Shadertoy's iMouse, xy where it is, +/// zw where the last press was, negative once released. +pub fn mouse(post: *Post, x: f32, y: f32, press: ?bool) void { + const m = &post.uniforms.mouse; + m[0] = x; + m[1] = y; + if (press) |down| { + if (down) { + m[2] = x; + m[3] = y; + } else { + m[2] = -@abs(m[2]); + m[3] = -@abs(m[3]); + } + } +} + +/// Runs the passes over `source`, the finished frame, into `target`: each +/// reads the one before, the ones between write two ping-pong textures that +/// start cleared, and iTime and iFrame move by this one draw. +pub fn draw(post: *Post, device: *c.SDL_GPUDevice, cmd: *c.SDL_GPUCommandBuffer, sampler: *c.SDL_GPUSampler, format: c.SDL_GPUTextureFormat, source: *c.SDL_GPUTexture, target: *c.SDL_GPUTexture, w: u32, h: u32, now_ns: u64, opaque_window: bool) !void { + var live: usize = 0; + for (post.passes[0..post.len]) |pass| live += @intFromBool(pass.pipeline != null); + if (live == 0) return; + if (live > 1 and (post.ping[0] == null or post.ping_w != w or post.ping_h != h)) { + for (&post.ping) |*texture| { + if (texture.*) |old| c.SDL_ReleaseGPUTexture(device, old); + var info = std.mem.zeroes(c.SDL_GPUTextureCreateInfo); + info.type = c.SDL_GPU_TEXTURETYPE_2D; + info.format = format; + info.usage = c.SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | c.SDL_GPU_TEXTUREUSAGE_SAMPLER; + info.width = w; + info.height = h; + info.layer_count_or_depth = 1; + info.num_levels = 1; + info.sample_count = c.SDL_GPU_SAMPLECOUNT_1; + texture.* = c.SDL_CreateGPUTexture(device, &info) orelse return error.GpuCreate; + } + post.ping_w = w; + post.ping_h = h; + } + const u = &post.uniforms; + const started = post.started_ns orelse now_ns; + post.started_ns = started; + const elapsed = @as(f64, @floatFromInt(now_ns -| started)) / std.time.ns_per_s; + const delta: f32 = if (post.last_ns == 0) 0 else @floatCast(@as(f64, @floatFromInt(now_ns -| post.last_ns)) / std.time.ns_per_s); + post.last_ns = now_ns; + u.time = @floatCast(elapsed); + u.time_delta = delta; + u.frame_rate = if (delta > 0) 1 / delta else 60; + u.resolution = .{ @floatFromInt(w), @floatFromInt(h), 1 }; + u.channel_resolution[0] = .{ @floatFromInt(w), @floatFromInt(h), 1, 0 }; + // UTC: the year, the month from 0, the day, the second of the day. + var wall: libc.timespec = undefined; + if (libc.clock_gettime(.REALTIME, &wall) != 0) wall.sec = 0; + const epoch: std.time.epoch.EpochSeconds = .{ .secs = @intCast(@max(0, wall.sec)) }; + const day = epoch.getEpochDay().calculateYearDay(); + const month = day.calculateMonthDay(); + u.date = .{ @floatFromInt(day.year), @floatFromInt(month.month.numeric() - 1), @floatFromInt(month.day_index + 1), @floatFromInt(epoch.getDaySeconds().secs) }; + const own: [4]f32 = .{ @floatFromInt(@intFromBool(opaque_window)), 0, 0, 0 }; + + var input = source; + var index: usize = 0; + for (post.passes[0..post.len]) |pass| { + const pipeline = pass.pipeline orelse continue; + index += 1; + const last = index == live; + const output = if (last) target else post.ping[index % 2].?; + var info = std.mem.zeroes(c.SDL_GPUColorTargetInfo); + info.texture = output; + info.load_op = if (last) c.SDL_GPU_LOADOP_DONT_CARE else c.SDL_GPU_LOADOP_CLEAR; + info.store_op = c.SDL_GPU_STOREOP_STORE; + const pass_rp = c.SDL_BeginGPURenderPass(cmd, &info, 1, null); + c.SDL_BindGPUGraphicsPipeline(pass_rp, pipeline); + const binding = c.SDL_GPUTextureSamplerBinding{ .texture = input, .sampler = sampler }; + c.SDL_BindGPUFragmentSamplers(pass_rp, 0, &binding, 1); + c.SDL_PushGPUFragmentUniformData(cmd, 0, u, @sizeOf(Uniforms)); + c.SDL_PushGPUFragmentUniformData(cmd, 1, &own, @sizeOf(@TypeOf(own))); + c.SDL_DrawGPUPrimitives(pass_rp, 3, 1, 0, 0); + c.SDL_EndGPURenderPass(pass_rp); + input = output; + } + u.frame +%= 1; +} + +pub fn deinit(post: *Post, gpa: std.mem.Allocator, device: *c.SDL_GPUDevice) void { + if (post.job) |job| { + job.thread.join(); + for (job.paths[0..job.len], job.results[0..job.len]) |path, result| { + gpa.free(path); + switch (result) { + .spirv, .failed => |bytes| gpa.free(bytes), + .none, .missing => {}, + } + } + gpa.destroy(job); + } + for (post.passes[0..post.len]) |pass| { + if (pass.scene == null) if (pass.pipeline) |pipeline| c.SDL_ReleaseGPUGraphicsPipeline(device, pipeline); + gpa.free(pass.path); + } + for (post.bundled.values) |pipeline| if (pipeline) |p| c.SDL_ReleaseGPUGraphicsPipeline(device, p); + for (post.ping) |texture| if (texture) |t| c.SDL_ReleaseGPUTexture(device, t); +} + +/// ghostty 1.3.2's Uniforms (src/renderer/shadertoy.zig), copied: the offsets +/// ours must match. Its last two names are swapped against its own GLSL. +const GhosttyUniforms = extern struct { + resolution: [3]f32 align(16), + time: f32 align(4), + time_delta: f32 align(4), + frame_rate: f32 align(4), + frame: i32 align(4), + channel_time: [4][4]f32 align(16), + channel_resolution: [4][4]f32 align(16), + mouse: [4]f32 align(16), + date: [4]f32 align(16), + sample_rate: f32 align(4), + current_cursor: [4]f32 align(16), + previous_cursor: [4]f32 align(16), + current_cursor_color: [4]f32 align(16), + previous_cursor_color: [4]f32 align(16), + current_cursor_style: i32 align(4), + previous_cursor_style: i32 align(4), + cursor_visible: i32 align(4), + cursor_change_time: f32 align(4), + time_focus: f32 align(4), + focus: i32 align(4), + palette: [256][4]f32 align(16), + background_color: [4]f32 align(16), + foreground_color: [4]f32 align(16), + cursor_color: [4]f32 align(16), + cursor_text: [4]f32 align(16), + selection_background_color: [4]f32 align(16), + selection_foreground_color: [4]f32 align(16), +}; + +test "the uniform block is ghostty's, offset for offset, and the prefix names it in that order" { + const ours = @typeInfo(Uniforms).@"struct".fields; + const theirs = @typeInfo(GhosttyUniforms).@"struct".fields; + try std.testing.expectEqual(theirs.len, ours.len); + try std.testing.expectEqual(@sizeOf(GhosttyUniforms), @sizeOf(Uniforms)); + inline for (ours, theirs) |a, b| { + try std.testing.expectEqual(@offsetOf(GhosttyUniforms, b.name), @offsetOf(Uniforms, a.name)); + try std.testing.expectEqual(b.type, a.type); + } + // By NAME: iSelectionForegroundColor sits where ghostty's Zig says + // selection_background, which is where its GLSL puts the foreground. + try std.testing.expectEqual(@offsetOf(GhosttyUniforms, "selection_background_color"), @offsetOf(Uniforms, "selection_foreground_color")); + // The prefix's block, member by member, in ghostty's GLSL names and types. + const glsl = [_]struct { []const u8, []const u8 }{ + .{ "vec3", "iResolution" }, .{ "float", "iTime" }, .{ "float", "iTimeDelta" }, + .{ "float", "iFrameRate" }, .{ "int", "iFrame" }, .{ "float", "iChannelTime[4]" }, + .{ "vec3", "iChannelResolution[4]" }, .{ "vec4", "iMouse" }, .{ "vec4", "iDate" }, + .{ "float", "iSampleRate" }, .{ "vec4", "iCurrentCursor" }, .{ "vec4", "iPreviousCursor" }, + .{ "vec4", "iCurrentCursorColor" }, .{ "vec4", "iPreviousCursorColor" }, .{ "int", "iCurrentCursorStyle" }, + .{ "int", "iPreviousCursorStyle" }, .{ "int", "iCursorVisible" }, .{ "float", "iTimeCursorChange" }, + .{ "float", "iTimeFocus" }, .{ "int", "iFocus" }, .{ "vec3", "iPalette[256]" }, + .{ "vec3", "iBackgroundColor" }, .{ "vec3", "iForegroundColor" }, .{ "vec3", "iCursorColor" }, + .{ "vec3", "iCursorText" }, .{ "vec3", "iSelectionForegroundColor" }, .{ "vec3", "iSelectionBackgroundColor" }, + }; + try std.testing.expectEqual(ours.len, glsl.len); + const block = prefix[std.mem.indexOf(u8, prefix, "uniform Globals {").?..]; + var lines = std.mem.tokenizeScalar(u8, block[0..std.mem.indexOfScalar(u8, block, '}').?], '\n'); + _ = lines.next(); + for (glsl) |member| { + var words = std.mem.tokenizeAny(u8, lines.next().?, " ;"); + try std.testing.expectEqualStrings("uniform", words.next().?); + try std.testing.expectEqualStrings(member[0], words.next().?); + try std.testing.expectEqualStrings(member[1], words.next().?); + } + try std.testing.expect(lines.next() == null); +} + +test "a moved cursor becomes the previous one, stamped with the time of the move" { + var post: Post = .{}; + const frame: Frame = .{ .cursor = .{ .x0 = 10, .y0 = 20, .x1 = 11, .y1 = 40, .rgb = .{ 255, 0, 0 }, .text = .{ 0, 0, 0 }, .bar = true }, .background = .{ 0, 0, 0 }, .foreground = .{ 255, 255, 255 }, .selection_foreground = .{ 1, 2, 3 }, .selection_background = .{ 4, 5, 6 } }; + post.uniforms.time = 1.5; + post.describe(frame); + // Left, BOTTOM edge, width, height, in framebuffer pixels, y down. + try std.testing.expectEqual([4]f32{ 10, 40, 1, 20 }, post.uniforms.current_cursor); + try std.testing.expectEqual(@as(i32, 2), post.uniforms.current_cursor_style); + try std.testing.expectEqual(@as(f32, 1.5), post.uniforms.cursor_change_time); + post.uniforms.time = 2; + post.describe(frame); + try std.testing.expectEqual(@as(f32, 1.5), post.uniforms.cursor_change_time); + var moved = frame; + moved.cursor.?.x0 = 20; + moved.cursor.?.x1 = 21; + post.describe(moved); + try std.testing.expectEqual([4]f32{ 10, 40, 1, 20 }, post.uniforms.previous_cursor); + try std.testing.expectEqual(@as(f32, 2), post.uniforms.cursor_change_time); + try std.testing.expectEqual([4]f32{ 1.0 / 255.0, 2.0 / 255.0, 3.0 / 255.0, 1 }, post.uniforms.selection_foreground_color); +} + +test "ghostty's test shaders compile behind the prefix, and its invalid one fails with glslc's words" { + const gpa = std.testing.allocator; + const io = std.testing.io; + // ghostty's own test shaders, from its package in zig-pkg. + var pkg = std.Io.Dir.cwd().openDir(io, "zig-pkg", .{ .iterate = true }) catch return error.SkipZigTest; + defer pkg.close(io); + var it = pkg.iterate(); + const ghostty = while (it.next(io) catch null) |entry| { + if (std.mem.startsWith(u8, entry.name, "ghostty-")) break entry.name; + } else return error.SkipZigTest; + var buf: [512]u8 = undefined; + for ([_][]const u8{ "crt", "focus", "invalid" }, 0..) |name, i| { + const path = try std.fmt.bufPrint(&buf, "zig-pkg/{s}/src/renderer/shaders/test_shadertoy_{s}.glsl", .{ ghostty, name }); + const result = compileOne(gpa, io, path, i); + switch (result) { + .missing => return error.SkipZigTest, // no glslc here + .spirv => |spirv| { + defer gpa.free(spirv); + try std.testing.expect(!std.mem.eql(u8, name, "invalid")); + // SPIR-V's magic number, little-endian. + try std.testing.expectEqual(@as(u32, 0x07230203), std.mem.readInt(u32, spirv[0..4], .little)); + }, + .failed => |text| { + defer gpa.free(text); + try std.testing.expectEqualStrings("invalid", name); + // Named by the file, not by the temporary source glslc read. + try std.testing.expect(std.mem.indexOf(u8, text, path) != null); + try std.testing.expect(std.mem.indexOf(u8, text, "pardes-post-") == null); + }, + .none => return error.TestUnexpectedResult, + } + } +} diff --git a/src/gui/crt.zig b/src/gui/crt.zig deleted file mode 100644 index 2a5ea7e4..00000000 --- a/src/gui/crt.zig +++ /dev/null @@ -1,101 +0,0 @@ -//! The Crt builtin's screen geometry, shared between the gui shell's input -//! mapping and its shader: crt.frag.glsl shows the scene point -//! uv' = cc*(1 + barrel*r2) at screen point cc, so a physical mouse position -//! must pass through the SAME forward transform before it becomes a cell. -//! Pure std, so the unit-test step can pin it against the shader formula. - -const std = @import("std"); - -/// MUST match BARREL in shaders/crt.frag.glsl. -pub const barrel: f32 = 0.016; - -pub const Point = struct { x: f32, y: f32 }; -pub const Effects = struct { crt: bool = false, ripple: bool = false, glitch: bool = false }; -pub const Frame = struct { effects: Effects = .{}, time_seconds: f32 = 0 }; - -fn fract(x: f32) f32 { - return x - @floor(x); -} - -fn smoothstep(lo: f32, hi: f32, value: f32) f32 { - const t = std.math.clamp((value - lo) / (hi - lo), 0, 1); - return t * t * (3 - 2 * t); -} - -/// Match `sceneUv` in crt.frag.glsl. This is a forward screen→sample mapping: -/// a physical pointer is sent to the logical scene pixel displayed under it. -pub fn mapScene(x: f32, y: f32, w: f32, h: f32, effects: Effects, time_seconds: f32) ?Point { - const safe_w = @max(1, w); - const safe_h = @max(1, h); - const ccx = x / safe_w * 2.0 - 1.0; - const ccy = y / safe_h * 2.0 - 1.0; - const s = 1.0 + barrel * (ccx * ccx + ccy * ccy) * @as(f32, @floatFromInt(@intFromBool(effects.crt))); - var u = ccx * s * 0.5 + 0.5; - var v = ccy * s * 0.5 + 0.5; - // The CRT shader paints no scene when this barrel-mapped coordinate lies - // on or beyond the tube edge. There is no logical edge cell under those - // black pixels, so input must be inert there rather than clamped. - if (effects.crt and (u <= 0 or u >= 1 or v <= 0 or v >= 1)) return null; - if (effects.ripple) { - const cx = u - 0.5; - const cy = v - 0.5; - const radius = @sqrt(cx * cx + cy * cy); - const envelope = 1.0 - smoothstep(0.04, 0.72, radius); - const wave = @sin(radius * 42.0 - time_seconds * 5.2); - const len = @max(radius, 0.00001); - u += cx / len * wave * envelope * 0.0032; - v += cy / len * wave * envelope * 0.0032; - } - if (effects.glitch) { - const band = @floor(v * 96.0); - const tick = @floor(time_seconds * 13.0); - const seed = noiseAt(band, tick); - const tear = smoothstep(0.955, 1.0, seed); - u += (noiseAt(seed, band) - 0.5) * tear * 0.020; - } - return .{ - .x = std.math.clamp(u * safe_w, 0, safe_w - 1), - .y = std.math.clamp(v * safe_h, 0, safe_h - 1), - }; -} - -fn noiseAt(x: f32, y: f32) f32 { - var qx = fract(x * 0.1031); - var qy = fract(y * 0.1031); - var qz = fract(x * 0.1031); - const d = qx * (qy + 33.33) + qy * (qz + 33.33) + qz * (qx + 33.33); - qx += d; - qy += d; - qz += d; - return fract((qx + qy) * qz); -} - -test "map matches the shader's barrel formula" { - // center is a fixed point - const center = mapScene(500, 250, 1000, 500, .{ .crt = true }, 0).?; - try std.testing.expectApproxEqAbs(@as(f32, 500), center.x, 0.001); - try std.testing.expectApproxEqAbs(@as(f32, 250), center.y, 0.001); - // Corners barrel beyond the tube and are black/inert in the shader. - try std.testing.expect(mapScene(0, 0, 1000, 500, .{ .crt = true }, 0) == null); - try std.testing.expect(mapScene(1000, 500, 1000, 500, .{ .crt = true }, 0) == null); - // (100,100) of 1000x500: cc=(-0.8,-0.6), r2=1.0, s=1.016 -> - // cc'=(-0.8128,-0.6096) -> (93.6, 97.6) - const near_edge = mapScene(100, 100, 1000, 500, .{ .crt = true }, 0).?; - try std.testing.expectApproxEqAbs(@as(f32, 93.6), near_edge.x, 0.01); - try std.testing.expectApproxEqAbs(@as(f32, 97.6), near_edge.y, 0.01); - // (900,250): cc=(0.8,0), r2=0.64, s=1.01024 -> x'=904.096 - const side = mapScene(900, 250, 1000, 500, .{ .crt = true }, 0).?; - try std.testing.expectApproxEqAbs(@as(f32, 904.096), side.x, 0.01); -} - -test "scene mapping composes ripple and deterministic glitch" { - const plain = mapScene(300, 200, 800, 600, .{}, 2.5).?; - try std.testing.expectApproxEqAbs(@as(f32, 300), plain.x, 0.001); - try std.testing.expectApproxEqAbs(@as(f32, 200), plain.y, 0.001); - const ripple = mapScene(300, 200, 800, 600, .{ .ripple = true }, 2.5).?; - try std.testing.expect(ripple.x != plain.x or ripple.y != plain.y); - try std.testing.expectEqual( - mapScene(300, 200, 800, 600, .{ .glitch = true }, 2.5).?, - mapScene(300, 200, 800, 600, .{ .glitch = true }, 2.5).?, - ); -} diff --git a/src/gui/gui.zig b/src/gui/gui.zig index 2cde5edf..ab1875b7 100644 --- a/src/gui/gui.zig +++ b/src/gui/gui.zig @@ -12,7 +12,7 @@ const look = @import("../look.zig"); const message = pardes.Messages.Message; const file_watch = @import("../file_watch.zig"); const deck = @import("deck.zig"); -const crt = @import("crt.zig"); +const Post = @import("Post.zig"); const pet = @import("pet.zig"); const fonts = @import("../fonts.zig"); // the Font builtin's half of the seam const selection_pipe = @import("../selection_pipe.zig"); @@ -43,8 +43,6 @@ const overlay_vert_spv = @embedFile("overlay.vert.spv"); const overlay_frag_spv = @embedFile("overlay.frag.spv"); const image_vert_spv = @embedFile("image.vert.spv"); const image_frag_spv = @embedFile("image.frag.spv"); -const crt_vert_spv = @embedFile("crt.vert.spv"); -const crt_frag_spv = @embedFile("crt.frag.spv"); const bg_default = [3]u8{ 18, 18, 18 }; const fg_default = [3]u8{ 204, 204, 204 }; @@ -202,18 +200,17 @@ const touch_click_flash_max_frames: u8 = 14; const touch_click_flash_vertices: usize = 1400; const touch_scroll_tick: f32 = 0.02; -test "a persistent scene presents a new panel's frame zero before advancing it" { +test "a new panel's frame zero is presented before it advances" { const core = try pardes.Pardes.init(std.testing.allocator, .{ .tty_only = true }); defer core.deinit(); var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); defer arena.deinit(); const frame = pardes.animation.frame_ns; - core.settings.scene_effects.crt = true; core.advance(100); - const scene_frame = try core.render(arena.allocator()); - core.acknowledgePanelPresentation(scene_frame.panelTracks()); - try std.testing.expectEqual(@as(?u64, 100 + frame), core.nextWake()); + const idle = try core.render(arena.allocator()); + core.acknowledgePanelPresentation(idle.panelTracks()); + try std.testing.expectEqual(@as(?u64, null), core.nextWake()); core.settings.panel_transition = .slide; core.update(.{ .command = "Newcol" }); @@ -235,10 +232,9 @@ test "a persistent scene presents a new panel's frame zero before advancing it" for (second.panelTracks()) |track| try std.testing.expectEqual(@as(u16, 1), track.frame); } -// Per pane: background, rule, two grip strokes, two square rails, and the -// rule under each of its notice chips. -const pane_chrome_vertices: usize = 6 * (6 + pardes.Pane.Notices.max); -const max_overlay_vertices: usize = 24 + pet.max_vertices + pardes.MAX_COLS * 6 + pardes.MAX_PANES * pane_chrome_vertices + touch_click_flash_vertices + max_touch_points * (1100 + max_touch_trail_points * overlay_circle_vertices); +// The triangle overlay holds only the pet and the touch view: the chrome is +// decor. +const max_overlay_vertices: usize = pet.max_vertices + touch_click_flash_vertices + max_touch_points * (1100 + max_touch_trail_points * overlay_circle_vertices); const font_px_step: f32 = 2.0; const font_px_min: f32 = 8.0; @@ -1765,7 +1761,6 @@ const Gui = struct { decor_pipeline: *c.SDL_GPUGraphicsPipeline, overlay_pipeline: *c.SDL_GPUGraphicsPipeline, image_pipeline: *c.SDL_GPUGraphicsPipeline, - crt_pipeline: *c.SDL_GPUGraphicsPipeline, atlas_tex: *c.SDL_GPUTexture, atlas_sampler: *c.SDL_GPUSampler, linear_sampler: *c.SDL_GPUSampler, @@ -1838,7 +1833,8 @@ const Gui = struct { scene_tex_h: u32 = 0, scene_failures: u8 = 0, scene_target_failed: bool = false, - presented_scene: crt.Frame = .{}, + /// The post chain, run over the frame in scene_tex. + post: Post = .{}, pointer_present: bool = false, arrow_cursor: ?*c.SDL_Cursor = null, link_cursor: ?*c.SDL_Cursor = null, @@ -1977,7 +1973,9 @@ fn windowCells(g: *const Gui) GridCells { }; } -fn windowPointToPixels(geometry: WindowGeometry, x: f32, y: f32) crt.Point { +const Point = struct { x: f32, y: f32 }; + +fn windowPointToPixels(geometry: WindowGeometry, x: f32, y: f32) Point { return .{ .x = x * geometry.pixel_w / geometry.window_w, .y = y * geometry.pixel_h / geometry.window_h, @@ -1991,7 +1989,7 @@ test "high-density window coordinates map once to render pixels" { .pixel_w = 1120, .pixel_h = 720, }; - try std.testing.expectEqual(crt.Point{ .x = 700, .y = 240 }, windowPointToPixels(geometry, 350, 120)); + try std.testing.expectEqual(Point{ .x = 700, .y = 240 }, windowPointToPixels(geometry, 350, 120)); } fn fixedCellLayout(g: *const Gui) CellLayout { @@ -2397,7 +2395,6 @@ fn runNative(init: std.process.Init, opts_in: pardes.Options, attach: ?[]const u const decor_pipeline = try makePipeline(device, swapchain_format, decor_frag_spv, 0, backgroundLayerBlend()); const overlay_pipeline = try makeOverlayPipeline(device, swapchain_format); const image_pipeline = try makeImagePipeline(device, swapchain_format); - const crt_pipeline = try makeCrtPipeline(device, swapchain_format); const overlay_buffer_size: u32 = @intCast(max_overlay_vertices * @sizeOf(OverlayVertex)); var ovb_info = c.SDL_GPUBufferCreateInfo{ .usage = c.SDL_GPU_BUFFERUSAGE_VERTEX, .size = overlay_buffer_size, .props = 0 }; @@ -2427,7 +2424,6 @@ fn runNative(init: std.process.Init, opts_in: pardes.Options, attach: ?[]const u .decor_pipeline = decor_pipeline, .overlay_pipeline = overlay_pipeline, .image_pipeline = image_pipeline, - .crt_pipeline = crt_pipeline, .atlas_tex = atlas_tex, .atlas_sampler = atlas_sampler, .linear_sampler = linear_sampler, @@ -2464,7 +2460,6 @@ fn runNative(init: std.process.Init, opts_in: pardes.Options, attach: ?[]const u c.SDL_ReleaseGPUGraphicsPipeline(device, g.pipeline); c.SDL_ReleaseGPUGraphicsPipeline(device, g.decor_pipeline); c.SDL_ReleaseGPUGraphicsPipeline(device, g.overlay_pipeline); - c.SDL_ReleaseGPUGraphicsPipeline(device, g.crt_pipeline); c.SDL_ReleaseGPUTexture(device, g.atlas_tex); c.SDL_ReleaseGPUSampler(device, g.atlas_sampler); c.SDL_ReleaseGPUSampler(device, g.linear_sampler); @@ -2490,6 +2485,7 @@ fn runNative(init: std.process.Init, opts_in: pardes.Options, attach: ?[]const u g.prepared_images.deinit(gpa); g.cover.deinit(gpa); g.decor.deinit(gpa); + g.post.deinit(gpa, device); } defer c.SDL_ReleaseGPUTransferBuffer(device, g.image_vxfer); defer c.SDL_ReleaseGPUBuffer(device, g.image_vbuf); @@ -2796,7 +2792,6 @@ fn paintAttached(g: *Gui, gpa: std.mem.Allocator, client: *detached_client.Clien gpa, null, &surface, - .{}, false, ) catch |err| blk: { log.err("render: {t}", .{err}); @@ -3065,23 +3060,14 @@ const StdinFeed = struct { f.fill = len - seq_start; break; }; - if (parseFinger(body[0..end])) |finger_in| { - var finger = finger_in; + if (parseFinger(body[0..end])) |finger| { if (g) |gp| { var wpw: c_int = 0; var wph: c_int = 0; _ = c.SDL_GetWindowSizeInPixels(gp.window, &wpw, &wph); const win_w: f32 = @floatFromInt(@max(wpw, 1)); const win_h: f32 = @floatFromInt(@max(wph, 1)); - if (mapScenePoint(gp, finger.x * win_w, finger.y * win_h, win_w, win_h)) |m| { - finger.x = m.x / win_w; - finger.y = m.y / win_h; - handleFinger(&gp.touch, in, finger, win_w, win_h, @floatFromInt(gp.cell_w), @floatFromInt(gp.cell_h), @floatFromInt(gp.tagline_width)); - } else { - finger.kind = .cancel; - handleFinger(&gp.touch, in, finger, win_w, win_h, @floatFromInt(gp.cell_w), @floatFromInt(gp.cell_h), @floatFromInt(gp.tagline_width)); - in.post(.pointer_leave); - } + handleFinger(&gp.touch, in, finger, win_w, win_h, @floatFromInt(gp.cell_w), @floatFromInt(gp.cell_h), @floatFromInt(gp.tagline_width)); } else if (in.core) |core| { handleFinger(&grid_touch, in, finger, @floatFromInt(core.screen_w), @floatFromInt(core.screen_h), 1, 1, 1); } @@ -3288,6 +3274,20 @@ const Input = struct { }; fn dispatch(g: *Gui, in: *Input, sev: *const c.SDL_Event) void { + // What the post chain sees of the pointer and the focus. + switch (sev.type) { + c.SDL_EVENT_MOUSE_MOTION => { + const at = windowPointToPixels(windowGeometry(g.window), sev.motion.x, sev.motion.y); + g.post.mouse(at.x, at.y, null); + }, + c.SDL_EVENT_MOUSE_BUTTON_DOWN, c.SDL_EVENT_MOUSE_BUTTON_UP => { + const at = windowPointToPixels(windowGeometry(g.window), sev.button.x, sev.button.y); + g.post.mouse(at.x, at.y, sev.button.down); + }, + c.SDL_EVENT_WINDOW_FOCUS_GAINED => g.post.focus(true), + c.SDL_EVENT_WINDOW_FOCUS_LOST => g.post.focus(false), + else => {}, + } switch (sev.type) { c.SDL_EVENT_QUIT, c.SDL_EVENT_WINDOW_CLOSE_REQUESTED => in.close(), c.SDL_EVENT_WINDOW_MOUSE_LEAVE => { @@ -3460,7 +3460,7 @@ fn dispatch(g: *Gui, in: *Input, sev: *const c.SDL_Event) void { }, c.SDL_EVENT_FINGER_DOWN, c.SDL_EVENT_FINGER_MOTION, c.SDL_EVENT_FINGER_UP, c.SDL_EVENT_FINGER_CANCELED => { const f = sev.tfinger; - var finger: Finger = .{ + const finger: Finger = .{ .kind = switch (sev.type) { c.SDL_EVENT_FINGER_DOWN => .down, c.SDL_EVENT_FINGER_MOTION => .motion, @@ -3479,15 +3479,7 @@ fn dispatch(g: *Gui, in: *Input, sev: *const c.SDL_Event) void { const win_h: f32 = @floatFromInt(@max(ph, 1)); const fcw: f32 = @floatFromInt(g.cell_w); const fch: f32 = @floatFromInt(g.cell_h); - if (mapScenePoint(g, finger.x * win_w, finger.y * win_h, win_w, win_h)) |m| { - finger.x = m.x / win_w; - finger.y = m.y / win_h; - handleFinger(&g.touch, in, finger, win_w, win_h, fcw, fch, @floatFromInt(g.tagline_width)); - } else { - finger.kind = .cancel; - handleFinger(&g.touch, in, finger, win_w, win_h, fcw, fch, @floatFromInt(g.tagline_width)); - in.post(.pointer_leave); - } + handleFinger(&g.touch, in, finger, win_w, win_h, fcw, fch, @floatFromInt(g.tagline_width)); }, c.SDL_EVENT_PINCH_BEGIN, c.SDL_EVENT_PINCH_UPDATE => in.post(.{ .pinch = sev.pinch.scale }), c.SDL_EVENT_GAMEPAD_ADDED => { @@ -3747,14 +3739,30 @@ test "tagline pointer mapping retains physical grips and carries logical text co try std.testing.expectEqual(pardes.TagKind.workspace, topbar.tag_hit.?.kind); } -fn mapScenePoint(g: *const Gui, x: f32, y: f32, w: f32, h: f32) ?crt.Point { - return crt.mapScene(x, y, w, h, g.presented_scene.effects, g.presented_scene.time_seconds); -} - +test "a click at the window's corner lands in the corner cell with a post chain on" { + const core = try pardes.Pardes.init(std.testing.allocator, .{ .cols = 80, .rows = 24 }); + defer core.deinit(); + try std.testing.expect(core.settings.post.set(.crt, "", true)); + var g: Gui = undefined; + g.cell_w = 10; + g.cell_h = 20; + g.tagline_width = 6; + g.tagline_height = 12; + const geometry: WindowGeometry = .{ .window_w = 400, .window_h = 240, .pixel_w = 800, .pixel_h = 480 }; + const last = mouseCellWithGeometry(&g, core, 399.9, 239.9, geometry).?; + try std.testing.expectEqual(@as(u16, 79), last.col); + try std.testing.expectEqual(@as(u16, 23), last.row); + const first = mouseCellWithGeometry(&g, core, 0, 0, geometry).?; + try std.testing.expectEqual(@as(u16, 0), first.col); + try std.testing.expectEqual(@as(u16, 0), first.row); +} + +/// Where a window point lands: the cell drawn under it. Input is identity: +/// no post pass moves a pixel (the bundled Crt has no barrel), so a click +/// is exactly where it looks. fn mouseCellWithGeometry(g: *const Gui, core: ?*const pardes.Pardes, x: f32, y: f32, geometry: WindowGeometry) ?MouseCell { const physical = windowPointToPixels(geometry, x, y); - const mapped = mapScenePoint(g, physical.x, physical.y, geometry.pixel_w, geometry.pixel_h) orelse return null; - return gridCellAtPixels(g, core, mapped.x, mapped.y); + return gridCellAtPixels(g, core, physical.x, physical.y); } fn mouseCell(g: *const Gui, core: ?*const pardes.Pardes, x: f32, y: f32) ?MouseCell { @@ -4064,6 +4072,7 @@ fn waitInput(ctx: ?*anyopaque, timeout_ms: u32) void { // shell's queue raises for a terminal's output, a completion, a file // system request. What the shell moves or times itself polls every // frame: a smooth scroll, a gamepad's sticks, the test stdin feed, + // a post chain that moves on its own (ShaderAnimation), // a retired shell waiting to be killed, a present to retry. A // minimized window sleeps through the core's animation too: nothing // shows it, and it is caught up at once when the window is back. @@ -4072,7 +4081,8 @@ fn waitInput(ctx: ?*anyopaque, timeout_ms: u32) void { } else for (s.ptys) |slot| { if (slot) |pt| if (pt.kill_at != 0) break true; } else false; - const polls = g.scroll_lag != 0 or g.scroll_delta != 0 or g.gamepad != null or s.test_mode or core.needs_frame or retiring; + const polls = g.scroll_lag != 0 or g.scroll_delta != 0 or g.gamepad != null or s.test_mode or core.needs_frame or retiring or + g.post.animating(core.settings.shader_animation); const minimized = !s.test_mode and c.SDL_GetWindowFlags(g.window) & (c.SDL_WINDOW_MINIMIZED | c.SDL_WINDOW_OCCLUDED) != 0; // A latency trace is fed on stdin, whose poll below paces the loop: // waiting here too would make every tick cost two frames. @@ -4157,6 +4167,10 @@ fn pollFrame(ctx: ?*anyopaque) void { const geom = windowCells(g); if (updateCoreResize(core, geom.cols, geom.rows, g.cell_w, g.cell_h, g.tagline_width, g.tagline_height)) resetScroll(g); stepScroll(g, core, s.gpa); + g.post.sync(s.gpa, s.io, g.device, g.swapchain_format, core); + // Between the core's frames, a chain that moves on its own redraws alone. + if (g.post.animating(core.settings.shader_animation) and !core.needs_frame and s.presented and + c.SDL_GetTicksNS() -| g.post.last_ns >= pardes.animation.frame_ns) redrawPost(g, s.gpa); } fn finishWindowOpacity(core: *pardes.Pardes, applied: *u8, failure: ?[]const u8) void { @@ -4216,15 +4230,13 @@ fn present(ctx: ?*anyopaque, surface: *const pardes.Surface) void { frame_started_ns = monoNs(); const frame = @constCast(surface); s.surface = frame; - const scene_requested = core.settings.scene_effects.crt or - core.settings.scene_effects.ripple or core.settings.scene_effects.glitch; + const scene_requested = g.post.ready(); if (!scene_requested) g.scene_failures = 0; s.presented = renderFrame( g, s.gpa, core, frame, - core.settings.scene_effects, core.settings.debug, ) catch |err| blk: { log.err("render: {t}", .{err}); @@ -5190,7 +5202,6 @@ fn renderFrame( gpa: std.mem.Allocator, core: ?*const pardes.Pardes, surface: *pardes.Surface, - scene_effects: pardes.animation.SceneEffect, debug_on: bool, ) !bool { if (surface.pointer_shape != g.pointer_shape) { @@ -5205,48 +5216,19 @@ fn renderFrame( defer if (!command_consumed) { _ = c.SDL_SubmitGPUCommandBuffer(cmd); }; - var sw: u32 = 0; - var sh: u32 = 0; - var target: *c.SDL_GPUTexture = undefined; - if (g.capture) { - sw = @as(u32, surface.cols) * g.cell_w + g.capture_pad; - sh = @as(u32, surface.rows) * g.cell_h + g.capture_pad; - if (sw == 0 or sh == 0) { - command_consumed = true; - _ = c.SDL_SubmitGPUCommandBuffer(cmd); - return false; - } - try ensureCaptureTexture(g, sw, sh); - target = g.capture_tex.?; - } else if (g.soft_present) { - var pw: c_int = 0; - var ph: c_int = 0; - if (!c.SDL_GetWindowSizeInPixels(g.window, &pw, &ph) or pw <= 0 or ph <= 0) { - command_consumed = true; - _ = c.SDL_SubmitGPUCommandBuffer(cmd); - return false; - } - sw = @intCast(pw); - sh = @intCast(ph); - try ensureCaptureTexture(g, sw, sh); - target = g.capture_tex.?; - } else { - var swap_tex: ?*c.SDL_GPUTexture = null; - if (!c.SDL_AcquireGPUSwapchainTexture(cmd, g.window, &swap_tex, &sw, &sh)) { - command_consumed = true; - _ = c.SDL_SubmitGPUCommandBuffer(cmd); - return false; - } - target = swap_tex orelse { // swapchain busy: skip the frame - command_consumed = true; - _ = c.SDL_SubmitGPUCommandBuffer(cmd); - return false; - }; - } + const frame_target = try acquireTarget(g, cmd, @as(u32, surface.cols) * g.cell_w + g.capture_pad, @as(u32, surface.rows) * g.cell_h + g.capture_pad) orelse { + command_consumed = true; + _ = c.SDL_SubmitGPUCommandBuffer(cmd); + return false; + }; + const target = frame_target.texture; + const sw = frame_target.w; + const sh = frame_target.h; const win_w: f32 = @floatFromInt(sw); const win_h: f32 = @floatFromInt(sh); - var scene_on = scene_effects.crt or scene_effects.ripple or scene_effects.glitch; + // The post chain runs over a local frame, from the scene texture. + var scene_on = core != null and g.post.ready(); if (scene_on) ensureSceneTexture(g, sw, sh) catch { g.scene_target_failed = true; scene_on = false; @@ -5559,64 +5541,87 @@ fn renderFrame( drawOverlayGpu(g, cmd, rp, overlay_count); c.SDL_EndGPURenderPass(rp); - var rendered_scene: crt.Frame = .{}; if (scene_on) { - var crt_target = std.mem.zeroes(c.SDL_GPUColorTargetInfo); - crt_target.texture = target; - crt_target.load_op = c.SDL_GPU_LOADOP_DONT_CARE; - crt_target.store_op = c.SDL_GPU_STOREOP_STORE; - const crt_pass = c.SDL_BeginGPURenderPass(cmd, &crt_target, 1, null); - c.SDL_BindGPUGraphicsPipeline(crt_pass, g.crt_pipeline); - const samp_binding = c.SDL_GPUTextureSamplerBinding{ .texture = g.scene_tex.?, .sampler = g.linear_sampler }; - c.SDL_BindGPUFragmentSamplers(crt_pass, 0, &samp_binding, 1); - const now_ns = c.SDL_GetTicksNS(); - const time_wrap_ns: u64 = 4096 * std.time.ns_per_s; - const time_seconds: f32 = @floatCast( - @as(f64, @floatFromInt(now_ns % time_wrap_ns)) / @as(f64, std.time.ns_per_s), - ); - rendered_scene = .{ - .effects = .{ - .crt = scene_effects.crt, - .ripple = scene_effects.ripple, - .glitch = scene_effects.glitch, - }, - .time_seconds = time_seconds, - }; - const crt_uniforms = [8]f32{ - time_seconds, - @floatFromInt((now_ns / (std.time.ns_per_s / 60)) % 65_536), - @floatFromInt(sw), - @floatFromInt(sh), - @floatFromInt(@intFromBool(scene_effects.crt)), - @floatFromInt(@intFromBool(scene_effects.ripple)), - @floatFromInt(@intFromBool(scene_effects.glitch)), - 0, + if (core) |p| { + var boxes: [max_cursors]CursorBox = undefined; + const cursors = if (cursors_shown) cursorBoxes(g, surface, layout, win_h, &boxes) else 0; + g.post.describe(.{ + .cursor = if (cursors > 0) boxes[0] else null, + .background = chrome.page orelse bg_default, + .foreground = chrome.fg orelse fg_default, + .selection_foreground = p.theme().sel_fg, + .selection_background = p.theme().sel_bg, + }); + } + const chain_zone = tracy.zone(@src(), "post chain"); + defer chain_zone.end(); + g.post.draw(g.device, cmd, g.linear_sampler, g.swapchain_format, g.scene_tex.?, target, sw, sh, c.SDL_GetTicksNS(), background_opacity >= 1) catch { + g.scene_target_failed = true; }; - c.SDL_PushGPUFragmentUniformData(cmd, 0, &crt_uniforms, @sizeOf(@TypeOf(crt_uniforms))); - c.SDL_DrawGPUPrimitives(crt_pass, 3, 1, 0, 0); - c.SDL_EndGPURenderPass(crt_pass); } + command_consumed = true; + const shown = try finishFrame(g, gpa, cmd, frame_target, if (groups.tracks != 0) groups.items[1].track.?.frame else null); + if (shown and groups.tracks == 0) rememberPresentedImages(g, gpa, surface); + return shown; +} + +const Target = struct { texture: *c.SDL_GPUTexture, w: u32, h: u32 }; + +/// What this frame draws into: the capture texture in test mode (the grid's +/// size plus PARDES_TEST_PAD) or with a soft presenter, else the swapchain's +/// image; null when there is none to be had this time. +fn acquireTarget(g: *Gui, cmd: *c.SDL_GPUCommandBuffer, capture_w: u32, capture_h: u32) !?Target { if (g.capture) { - command_consumed = true; - try captureFrame(g, gpa, cmd, target, sw, sh, if (groups.tracks != 0) groups.items[1].track.?.frame else null); - g.presented_scene = rendered_scene; - if (groups.tracks == 0) rememberPresentedImages(g, gpa, surface); + if (capture_w == 0 or capture_h == 0) return null; + try ensureCaptureTexture(g, capture_w, capture_h); + return .{ .texture = g.capture_tex.?, .w = capture_w, .h = capture_h }; + } + if (g.soft_present) { + var pw: c_int = 0; + var ph: c_int = 0; + if (!c.SDL_GetWindowSizeInPixels(g.window, &pw, &ph) or pw <= 0 or ph <= 0) return null; + try ensureCaptureTexture(g, @intCast(pw), @intCast(ph)); + return .{ .texture = g.capture_tex.?, .w = @intCast(pw), .h = @intCast(ph) }; + } + var swap_tex: ?*c.SDL_GPUTexture = null; + var sw: u32 = 0; + var sh: u32 = 0; + if (!c.SDL_AcquireGPUSwapchainTexture(cmd, g.window, &swap_tex, &sw, &sh)) return null; + // A busy swapchain skips the frame. + return .{ .texture = swap_tex orelse return null, .w = sw, .h = sh }; +} + +/// Submits the frame: captured, handed to the soft presenter, or presented. +fn finishFrame(g: *Gui, gpa: std.mem.Allocator, cmd: *c.SDL_GPUCommandBuffer, target: Target, transition: ?u16) !bool { + if (g.capture) { + try captureFrame(g, gpa, cmd, target.texture, target.w, target.h, transition); return true; } if (g.soft_present) { - command_consumed = true; - try softPresentFrame(g, cmd, target, sw, sh); - g.presented_scene = rendered_scene; - if (groups.tracks == 0) rememberPresentedImages(g, gpa, surface); + try softPresentFrame(g, cmd, target.texture, target.w, target.h); return true; } - command_consumed = true; - const submitted = c.SDL_SubmitGPUCommandBuffer(cmd); - if (submitted) { - g.presented_scene = rendered_scene; - if (groups.tracks == 0) rememberPresentedImages(g, gpa, surface); + return c.SDL_SubmitGPUCommandBuffer(cmd); +} + +/// Redraw level A (docs §5.5): the post chain alone, over the frame the core +/// last drew, for a pass that moves on its own. No core render, no cell. +fn redrawPost(g: *Gui, gpa: std.mem.Allocator) void { + const scene = g.scene_tex orelse return; + const cmd = c.SDL_AcquireGPUCommandBuffer(g.device) orelse return; + const target = (acquireTarget(g, cmd, g.scene_tex_w, g.scene_tex_h) catch null) orelse { + _ = c.SDL_SubmitGPUCommandBuffer(cmd); + return; + }; + // A window the retained frame no longer fits waits for the core's. + if (target.w != g.scene_tex_w or target.h != g.scene_tex_h) { + _ = c.SDL_SubmitGPUCommandBuffer(cmd); + return; } - return submitted; + const zone = tracy.zone(@src(), "post chain redraw"); + g.post.draw(g.device, cmd, g.linear_sampler, g.swapchain_format, scene, target.texture, target.w, target.h, c.SDL_GetTicksNS(), backgroundOpacity(g.applied_window_opacity) >= 1) catch {}; + zone.end(); + _ = finishFrame(g, gpa, cmd, target, null) catch {}; } const ResolvedCell = struct { @@ -7016,25 +7021,7 @@ fn makeImagePipeline(device: *c.SDL_GPUDevice, color_format: c.SDL_GPUTextureFor return c.SDL_CreateGPUGraphicsPipeline(device, &info) orelse error.GpuCreate; } -fn makeCrtPipeline(device: *c.SDL_GPUDevice, color_format: c.SDL_GPUTextureFormat) !*c.SDL_GPUGraphicsPipeline { - const vs = try makeShader(device, crt_vert_spv, c.SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); - defer c.SDL_ReleaseGPUShader(device, vs); - const fs = try makeShader(device, crt_frag_spv, c.SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); - defer c.SDL_ReleaseGPUShader(device, fs); - - var col_desc = c.SDL_GPUColorTargetDescription{ .format = color_format, .blend_state = std.mem.zeroes(c.SDL_GPUColorTargetBlendState) }; - var info = std.mem.zeroes(c.SDL_GPUGraphicsPipelineCreateInfo); - info.vertex_shader = vs; - info.fragment_shader = fs; - info.primitive_type = c.SDL_GPU_PRIMITIVETYPE_TRIANGLELIST; - info.rasterizer_state.fill_mode = c.SDL_GPU_FILLMODE_FILL; - info.rasterizer_state.cull_mode = c.SDL_GPU_CULLMODE_NONE; - info.multisample_state.sample_count = c.SDL_GPU_SAMPLECOUNT_1; - info.target_info = .{ .color_target_descriptions = &col_desc, .num_color_targets = 1 }; - return c.SDL_CreateGPUGraphicsPipeline(device, &info) orelse error.GpuCreate; -} - -fn makeShader(device: *c.SDL_GPUDevice, code: []const u8, stage: c.SDL_GPUShaderStage, n_samplers: u32, n_uniform_buffers: u32) !*c.SDL_GPUShader { +pub fn makeShader(device: *c.SDL_GPUDevice, code: []const u8, stage: c.SDL_GPUShaderStage, n_samplers: u32, n_uniform_buffers: u32) !*c.SDL_GPUShader { var info = std.mem.zeroes(c.SDL_GPUShaderCreateInfo); info.code = code.ptr; info.code_size = code.len; @@ -7519,25 +7506,32 @@ test "decor is read from the regions, each pane's in its own group" { try std.testing.expectEqual(@as(u32, 0), groups.items[groups.ink].decor_count); } -/// The bar cursors: the grid's where no layer has the cell, and each layer's -/// at its own pitch. -fn cursorDecor(g: *Gui, gpa: std.mem.Allocator, surface: *const pardes.Surface, layout: CellLayout, win_w: f32, win_h: f32) !void { +const CursorBox = Post.Frame.Cursor; +const max_cursors = 1 + pardes.MAX_PANES + pardes.MAX_TAG_LAYERS; + +/// Every cursor on the frame where it is drawn, in window pixels: the grid's +/// where no layer has its cell, and each layer's at that layer's pitch. A bar +/// is an eighth of its cell wide, a block the whole cell. +fn cursorBoxes(g: *const Gui, surface: *const pardes.Surface, layout: CellLayout, win_h: f32, out: *[max_cursors]CursorBox) usize { const chrome = &surface.chrome; + const page = chrome.page orelse bg_default; + var n: usize = 0; if (surface.cursor) |cursor| { - if (cursor.bar and cursor.x < surface.cols and cursor.y < surface.rows and bodyLayerAt(surface, cursor.x, cursor.y) == null and tagLayerIn(surface.tagLayers(), cursor.x, cursor.y) == null) { + if (cursor.x < surface.cols and cursor.y < surface.rows and bodyLayerAt(surface, cursor.x, cursor.y) == null and tagLayerIn(surface.tagLayers(), cursor.x, cursor.y) == null) { const cell = &surface.cells[@as(usize, cursor.y) * surface.cols + cursor.x]; const role = cellFontRole(cell); const width: f32 = if (role == .tagline) @floatFromInt(g.tagline_width) else layout.w; const height = std.math.clamp(if (role == .tagline) @as(f32, @floatFromInt(g.tagline_height)) else layout.h, 1.0, layout.h); const x0 = @as(f32, @floatFromInt(cursor.x)) * layout.w; const y0 = @as(f32, @floatFromInt(cursor.y)) * layout.h + @as(f32, @floatFromInt(taglineBandOffset(cursor.y, win_h, @intFromFloat(layout.h), @intFromFloat(height)))); - try addDecor(g, gpa, x0, y0, x0 + @max(1.0, width / 8.0), y0 + height, cursorColor(chrome, cell, role), 1, null, win_w, win_h); + out[n] = .{ .x0 = x0, .y0 = y0, .x1 = x0 + if (cursor.bar) @max(1.0, width / 8.0) else width, .y1 = y0 + height, .rgb = cursorColor(chrome, cell, role), .text = page, .bar = cursor.bar }; + n += 1; } } for (surface.bodyLayers()) |*layer| { if (layer.rows == 0) continue; const cursor = layer.cursor orelse continue; - if (!cursor.bar or cursor.y >= layer.rows or cursor.x >= layer.cols) continue; + if (cursor.y >= layer.rows or cursor.x >= layer.cols) continue; const left = @as(f32, @floatFromInt(layer.viewport.x)) * layout.w; const bottom = @as(f32, @floatFromInt(layer.viewport.y + layer.viewport.h)) * layout.h; const top = layer.rowTop(cursor.y, layout.h, @floatFromInt(g.tagline_height)); @@ -7547,12 +7541,13 @@ fn cursorDecor(g: *Gui, gpa: std.mem.Allocator, surface: *const pardes.Surface, const width: f32 = if (role == .tagline) @floatFromInt(g.tagline_width) else layout.w; const x = left + @as(f32, @floatFromInt(cursor.x)) * width; const cell = &layer.cells[@as(usize, cursor.y) * layer.cols + cursor.x]; - try addDecor(g, gpa, x, top, x + @max(1, width / 8), @min(bottom, top + height), cursorColor(chrome, cell, role), 1, null, win_w, win_h); + out[n] = .{ .x0 = x, .y0 = top, .x1 = x + if (cursor.bar) @max(1, width / 8) else width, .y1 = @min(bottom, top + height), .rgb = cursorColor(chrome, cell, role), .text = page, .bar = cursor.bar }; + n += 1; } for (surface.tagLayers()) |*layer| { if (layer.rows == 0) continue; const cursor = layer.cursor orelse continue; - if (!cursor.bar or cursor.x >= layer.cols or cursor.y >= layer.rows) continue; + if (cursor.x >= layer.cols or cursor.y >= layer.rows) continue; const width: f32 = @floatFromInt(g.tagline_width); const left = @as(f32, @floatFromInt(layer.viewport.x)) * layout.w; const right = left + @as(f32, @floatFromInt(layer.viewport.w)) * layout.w; @@ -7560,8 +7555,17 @@ fn cursorDecor(g: *Gui, gpa: std.mem.Allocator, surface: *const pardes.Surface, if (x >= right) continue; const y = layer.viewport.y + cursor.y; const top = @as(f32, @floatFromInt(y)) * layout.h + @as(f32, @floatFromInt(taglineBandOffset(y, win_h, g.cell_h, g.tagline_height))); - try addDecor(g, gpa, x, top, @min(right, x + @max(1, width / 8)), top + @as(f32, @floatFromInt(g.tagline_height)), cursorColor(chrome, &layer.cells[@as(usize, cursor.y) * layer.cols + cursor.x], .tagline), 1, null, win_w, win_h); + out[n] = .{ .x0 = x, .y0 = top, .x1 = @min(right, x + if (cursor.bar) @max(1, width / 8) else width), .y1 = top + @as(f32, @floatFromInt(g.tagline_height)), .rgb = cursorColor(chrome, &layer.cells[@as(usize, cursor.y) * layer.cols + cursor.x], .tagline), .text = page, .bar = cursor.bar }; + n += 1; } + return n; +} + +/// The bar cursors as decor; a block cursor is its cell's own colours. +fn cursorDecor(g: *Gui, gpa: std.mem.Allocator, surface: *const pardes.Surface, layout: CellLayout, win_w: f32, win_h: f32) !void { + var boxes: [max_cursors]CursorBox = undefined; + for (boxes[0..cursorBoxes(g, surface, layout, win_h, &boxes)]) |box| + if (box.bar) try addDecor(g, gpa, box.x0, box.y0, box.x1, box.y1, box.rgb, 1, null, win_w, win_h); } /// The triangle overlay: the workspace pet and the touch debug view, over diff --git a/src/macos.zig b/src/macos.zig index 4d8f2883..0bad86ee 100644 --- a/src/macos.zig +++ b/src/macos.zig @@ -850,16 +850,18 @@ export fn pardes_should_quit() bool { return st.core.quit; } -fn encodeSceneEffects(effects: animation.SceneEffect) u32 { +// The Metal shell keeps its own CRT, ripple and glitch kernel: the bundled +// passes of the GUI's post chain, as flags. +fn encodeSceneEffects(post: *const pardes.config.Runtime.Post) u32 { var flags: u32 = 0; - if (effects.crt) flags |= scene_flag_crt; - if (effects.ripple) flags |= scene_flag_ripple; - if (effects.glitch) flags |= scene_flag_glitch; + if (post.has(.crt)) flags |= scene_flag_crt; + if (post.has(.ripple)) flags |= scene_flag_ripple; + if (post.has(.glitch)) flags |= scene_flag_glitch; return flags; } fn currentSceneFlags(st: *const State) u32 { - return encodeSceneEffects(st.core.settings.scene_effects); + return encodeSceneEffects(&st.core.settings.post); } fn advanceSceneClock(st: *State, elapsed_ns: u64) void { @@ -2912,10 +2914,13 @@ test "pardes.h matches the Zig boundary" { test "scene effect flags and display clock are compact and independent" { const expectEqual = std.testing.expectEqual; - try expectEqual(@as(u32, 0), encodeSceneEffects(.{})); - try expectEqual(scene_flag_crt, encodeSceneEffects(.{ .crt = true })); - try expectEqual(scene_flag_ripple, encodeSceneEffects(.{ .ripple = true })); - try expectEqual(scene_flag_glitch, encodeSceneEffects(.{ .glitch = true })); + var post: pardes.config.Runtime.Post = .{}; + try expectEqual(@as(u32, 0), encodeSceneEffects(&post)); + _ = post.set(.crt, "", true); + try expectEqual(scene_flag_crt, encodeSceneEffects(&post)); + _ = post.set(.ripple, "", true); + _ = post.set(.glitch, "", true); + try expectEqual(scene_flag_crt | scene_flag_ripple | scene_flag_glitch, encodeSceneEffects(&post)); try expectEqual( scene_flag_crt | scene_flag_ripple | scene_flag_glitch, encodeSceneEffects(.{ .crt = true, .ripple = true, .glitch = true }), diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index c1f78bab..ed10dd70 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -263,7 +263,9 @@ fn writeSettings(p: *Pardes, w: *std.Io.Writer) !void { // Bare, it names the default directory. .dump_dir => if (s.dump_dir.len > 0) try w.print("{s} {s}\n", .{ word, s.dump_dir.get() }) else try w.print("{s}\n", .{word}), .transition => |effect| try w.print("{s} {s}\n", .{ word, if (s.panel_transition == effect) "on" else "off" }), - .scene => |effect| try w.print("{s} {s}\n", .{ word, if (@field(s.scene_effects, @tagName(effect))) "on" else "off" }), + .scene => |effect| try w.print("{s} {s}\n", .{ word, if (s.post.has(effect)) "on" else "off" }), + .shader => for (s.post.list()) |entry| if (entry.scene == null) try w.print("{s} {s}\n", .{ word, entry.path.get() }), + .shader_animation => try w.print("{s} {s}\n", .{ word, @tagName(s.shader_animation) }), } } try w.writeAll("LocationsConfig "); diff --git a/src/pardes.zig b/src/pardes.zig index 616747fa..5fa3307b 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -584,18 +584,10 @@ test "startup config runs builtin lines in order and isolates bad lines" { }; } -test "scene effects keep the idle frontend animation clock active" { +test "a post chain keeps no core clock running: the shell animates it" { const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true }); defer p.deinit(); - try std.testing.expect(!p.animationActive()); - - p.settings.scene_effects.crt = true; - try std.testing.expect(p.animationActive()); - p.settings.scene_effects = .{ .ripple = true }; - try std.testing.expect(p.animationActive()); - p.settings.scene_effects = .{ .glitch = true }; - try std.testing.expect(p.animationActive()); - p.settings.scene_effects = .{}; + try std.testing.expect(p.settings.post.set(.crt, "", true)); try std.testing.expect(!p.animationActive()); } @@ -5443,10 +5435,10 @@ pub const Pardes = struct { look.cancelLookHover(p); } - /// A native scene pass proved unavailable. Config reads these exact bits, - /// and the idle-clock predicate stops immediately once they are clear. + /// A native post pass proved unavailable: the chain is emptied, which + /// Config then reports. pub fn disableSceneEffects(p: *Pardes) void { - p.settings.scene_effects = .{}; + p.settings.post.clear(); } // ---- doc panes ---- @@ -5744,8 +5736,6 @@ pub const Pardes = struct { pub fn nextWake(p: *const Pardes) ?u64 { const next = p.stepped_ns + animation.frame_ns; if (p.chrome_animation.isActive() or p.presentation.animating()) return next; - const scene = p.settings.scene_effects; - if (scene.crt or scene.ripple or scene.glitch) return next; var frames: ?u64 = null; if (p.look_hover_wait) |waiting| { const delay = config.look_preview_delay_frames orelse return next; @@ -6953,8 +6943,8 @@ test "core animation keeps wall time at 60, 120 and 144 Hz and across a stall" { const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 80, .rows = 24 }); defer p.deinit(); const frame = animation.frame_ns; - // Something that animates for as long as it is on. - p.settings.scene_effects.crt = true; + // Something that animates for as long as the test wants: the theme + // fade, held at its start before every step. for ([_]u64{ 60, 120, 144 }) |hz| { const start: u64 = 5 * std.time.ns_per_s; p.clock_started = false; @@ -6963,6 +6953,7 @@ test "core animation keeps wall time at 60, 120 and 144 Hz and across a stall" { var at: u64 = 1; while (at <= hz) : (at += 1) { const before = p.stepped_ns; + p.chrome_animation.step = 0; p.advance(start + at * std.time.ns_per_s / hz); const taken = (p.stepped_ns - before + frame / 2) / frame; // Never two steps in one display frame: that is a visible hitch. @@ -6979,9 +6970,10 @@ test "core animation keeps wall time at 60, 120 and 144 Hz and across a stall" { } // A stall of minutes is not minutes of frames. const before = p.stepped_ns; + p.chrome_animation.step = 0; p.advance(before + 600 * std.time.ns_per_s); try std.testing.expectEqual(before + 600 * std.time.ns_per_s, p.stepped_ns); - p.settings.scene_effects.crt = false; + p.chrome_animation.step = animation.transition_steps; p.advance(p.stepped_ns + frame); try std.testing.expectEqual(@as(?u64, null), p.nextWake()); } -- cgit v1.3