unmagic-color 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "degrees"
4
+
5
+ module Unmagic
6
+ class Color
7
+ module Units
8
+ class Degrees
9
+ # Represents a gradient direction as a from/to tuple of Degrees.
10
+ #
11
+ # Direction defines a gradient's angle by specifying where it starts (from)
12
+ # and where it ends (to). Supports CSS-style direction strings with flexible
13
+ # parsing that infers missing components.
14
+ #
15
+ # ## Supported Formats
16
+ #
17
+ # - Full directions: `"from left to right"`, `"from bottom left to top right"`
18
+ # - Implicit from: `"to top"` → infers `"from bottom to top"`
19
+ # - Implicit to: `"from bottom"` → infers `"from bottom to top"`
20
+ # - Without "from": `"left to right"` → `"from left to right"`
21
+ # - Mixed formats: `"from 45deg to top right"`, `"from south to 90deg"`, `"from 45° to 90°"`
22
+ # - Hash: `{ from: "north", to: "south" }`
23
+ #
24
+ # @example Parse direction strings
25
+ # Direction.parse("from left to right")
26
+ # Direction.parse("to top") # Infers from bottom
27
+ # Direction.parse("from bottom") # Infers to top
28
+ # Direction.parse("left to right") # Implicit "from"
29
+ # Direction.parse("from 45deg to 90deg") # Numeric degrees
30
+ # Direction.parse("from 45° to 90°") # With ° symbol
31
+ # Direction.parse("from south to top right") # Mixed
32
+ #
33
+ # @example Build from various inputs
34
+ # Direction.build("from left to right")
35
+ # Direction.build(from: "north", to: "south")
36
+ # Direction.build(from: 45, to: 90)
37
+ #
38
+ # @example Direct construction
39
+ # direction = Direction.new(from: Degrees::LEFT, to: Degrees::RIGHT)
40
+ # direction.from.value #=> 270.0
41
+ # direction.to.value #=> 90.0
42
+ #
43
+ # @example Constants
44
+ # Direction::LEFT_TO_RIGHT
45
+ # Direction::BOTTOM_LEFT_TO_TOP_RIGHT
46
+ #
47
+ # @example String output
48
+ # Direction::LEFT_TO_RIGHT.to_s #=> "from left to right"
49
+ # Direction::LEFT_TO_RIGHT.to_css #=> "from left to right"
50
+ class Direction
51
+ attr_reader :from, :to
52
+
53
+ class << self
54
+ # All predefined direction constants
55
+ #
56
+ # @return [Array<Direction>] All constant directions
57
+ def all
58
+ all_constants
59
+ end
60
+
61
+ private
62
+
63
+ # Array of all predefined direction constants
64
+ #
65
+ # @return [Array<Direction>] All constant directions
66
+ def all_constants
67
+ @all_constants ||= [
68
+ BOTTOM_TO_TOP,
69
+ LEFT_TO_RIGHT,
70
+ TOP_TO_BOTTOM,
71
+ RIGHT_TO_LEFT,
72
+ BOTTOM_LEFT_TO_TOP_RIGHT,
73
+ TOP_LEFT_TO_BOTTOM_RIGHT,
74
+ TOP_RIGHT_TO_BOTTOM_LEFT,
75
+ BOTTOM_RIGHT_TO_TOP_LEFT,
76
+ ]
77
+ end
78
+
79
+ public
80
+
81
+ # Check if a string looks like a direction keyword.
82
+ #
83
+ # @param input [String] The string to check
84
+ # @return [Boolean] true if the string appears to be a direction keyword
85
+ def matches?(input)
86
+ return false unless input.is_a?(::String)
87
+
88
+ normalized = input.strip.downcase
89
+
90
+ # Check if it contains "from" or "to" keywords
91
+ normalized.start_with?("to ") || normalized.start_with?("from ") || normalized.include?(" to ")
92
+ end
93
+
94
+ # Build a Direction from various input formats.
95
+ #
96
+ # @param input [String, Direction, Hash] Direction string, instance, or hash with :from and :to keys
97
+ # @return [Direction] Direction instance
98
+ def build(input)
99
+ return input if input.is_a?(Direction)
100
+
101
+ if input.is_a?(::Hash)
102
+ raise Degrees::ParseError, "Hash must have :from and :to keys" unless input.key?(:from) && input.key?(:to)
103
+
104
+ from_degree = Degrees.build(input[:from])
105
+ to_degree = Degrees.build(input[:to])
106
+ return new(from: from_degree, to: to_degree)
107
+ end
108
+
109
+ parse(input)
110
+ end
111
+
112
+ # Parse a direction string into a Direction instance.
113
+ #
114
+ # Supports mixed formats like:
115
+ # - "from 275deg to 45deg"
116
+ # - "from south to 90"
117
+ # - "from north to top right"
118
+ # - "to top" (infers from as opposite)
119
+ # - "from bottom" (infers to as opposite)
120
+ #
121
+ # @param input [String] Direction string
122
+ # @return [Direction] Parsed direction
123
+ # @raise [Degrees::ParseError] If direction is invalid
124
+ def parse(input)
125
+ # Normalize: strip, downcase, and collapse whitespace
126
+ normalized = input.strip.downcase.gsub(/\s+/, " ")
127
+
128
+ # Remove "from " prefix if present, then split on "to "
129
+ parts = normalized.delete_prefix("from ").split("to ", 2).map(&:strip)
130
+ left = parts[0]
131
+ right = parts[1]
132
+
133
+ if left.empty? && right
134
+ # Only right side specified: "to top"
135
+ to_degree = Degrees.build(right)
136
+ from_degree = to_degree.opposite
137
+ elsif right.nil?
138
+ # Only left side specified: "from bottom"
139
+ from_degree = Degrees.build(left)
140
+ to_degree = from_degree.opposite
141
+ else
142
+ # Both sides specified: "left to right" or "from left to right"
143
+ from_degree = Degrees.build(left)
144
+ to_degree = Degrees.build(right)
145
+ end
146
+
147
+ new(from: from_degree, to: to_degree)
148
+ end
149
+ end
150
+
151
+ # Create a new Direction instance.
152
+ #
153
+ # @param from [Degrees] Starting degree
154
+ # @param to [Degrees] Ending degree
155
+ def initialize(from:, to:)
156
+ @from = from
157
+ @to = to
158
+ end
159
+
160
+ # Convert to CSS string format.
161
+ #
162
+ # @return [String] CSS direction string (e.g., "from left to right")
163
+ def to_css
164
+ from_str = @from.name || @from.to_css
165
+ to_str = @to.name || @to.to_css
166
+ "from #{from_str} to #{to_str}"
167
+ end
168
+
169
+ # Convert to string representation.
170
+ #
171
+ # @return [String] Canonical string format that can be parsed back
172
+ def to_s
173
+ from_str = @from.name || @from.to_s
174
+ to_str = @to.name || @to.to_s
175
+ "from #{from_str} to #{to_str}"
176
+ end
177
+
178
+ # Check equality.
179
+ #
180
+ # @param other [Object] Value to compare
181
+ # @return [Boolean] true if from and to are equal
182
+ def ==(other)
183
+ other.is_a?(Direction) && @from == other.from && @to == other.to
184
+ end
185
+
186
+ # Predefined direction constants
187
+ BOTTOM_TO_TOP = new(from: Degrees::BOTTOM, to: Degrees::TOP).freeze
188
+ # Left to right direction (horizontal)
189
+ LEFT_TO_RIGHT = new(from: Degrees::LEFT, to: Degrees::RIGHT).freeze
190
+ # Top to bottom direction (vertical, default)
191
+ TOP_TO_BOTTOM = new(from: Degrees::TOP, to: Degrees::BOTTOM).freeze
192
+ # Right to left direction (horizontal)
193
+ RIGHT_TO_LEFT = new(from: Degrees::RIGHT, to: Degrees::LEFT).freeze
194
+ # Bottom-left to top-right diagonal direction
195
+ BOTTOM_LEFT_TO_TOP_RIGHT = new(from: Degrees::BOTTOM_LEFT, to: Degrees::TOP_RIGHT).freeze
196
+ # Top-left to bottom-right diagonal direction
197
+ TOP_LEFT_TO_BOTTOM_RIGHT = new(from: Degrees::TOP_LEFT, to: Degrees::BOTTOM_RIGHT).freeze
198
+ # Top-right to bottom-left diagonal direction
199
+ TOP_RIGHT_TO_BOTTOM_LEFT = new(from: Degrees::TOP_RIGHT, to: Degrees::BOTTOM_LEFT).freeze
200
+ # Bottom-right to top-left diagonal direction
201
+ BOTTOM_RIGHT_TO_TOP_LEFT = new(from: Degrees::BOTTOM_RIGHT, to: Degrees::TOP_LEFT).freeze
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
@@ -7,19 +7,19 @@ module Unmagic
7
7
  # Handles both direct percentage values and ratio calculations.
8
8
  #
9
9
  # @example Direct percentage value
10
- # percentage = Percentage.new(75.5)
10
+ # percentage = Percentage.build(75.5)
11
11
  # percentage.to_s
12
12
  # #=> "75.5%"
13
13
  # percentage.value
14
14
  # #=> 75.5
15
15
  #
16
16
  # @example Calculated from ratio
17
- # percentage = Percentage.new(50, 100)
17
+ # percentage = Percentage.build(50, 100)
18
18
  # percentage.to_s
19
19
  # #=> "50.0%"
20
20
  #
21
21
  # @example Progress tracking
22
- # percentage = Percentage.new(current_item, total_items)
22
+ # percentage = Percentage.build(current_item, total_items)
23
23
  # percentage.to_s
24
24
  # #=> "25.0%"
25
25
  class Percentage
@@ -29,24 +29,130 @@ module Unmagic
29
29
 
30
30
  # Create a new percentage
31
31
  #
32
- # @param args [Array<Numeric>] Either a single percentage value (0-100) or numerator and denominator
33
- def initialize(*args)
34
- case args.length
35
- when 1
36
- @value = args[0].to_f
37
- when 2
38
- numerator, denominator = args
39
- @value = if denominator.to_f.zero?
40
- 0.0
32
+ # @param value [Numeric] The percentage value (0-100)
33
+ def initialize(value:)
34
+ @value = value.to_f.clamp(0.0, 100.0)
35
+ end
36
+
37
+ class << self
38
+ # Build a percentage from various input types.
39
+ #
40
+ # Handles both single value and numerator/denominator ratio inputs.
41
+ #
42
+ # @param args [Array<Numeric>] Either a single percentage value (0-100) or numerator and denominator
43
+ # @option kwargs [Numeric] :value The percentage value (0-100)
44
+ # @return [Percentage, nil] The created percentage or nil if passed nil
45
+ #
46
+ # @example Single value
47
+ # Percentage.build(75.5)
48
+ # #=> Percentage with value 75.5
49
+ #
50
+ # @example Ratio
51
+ # Percentage.build(50, 100)
52
+ # #=> Percentage with value 50.0
53
+ #
54
+ # @example Keyword argument
55
+ # Percentage.build(value: 75.5)
56
+ # #=> Percentage with value 75.5
57
+ #
58
+ # @example Pass through existing instance
59
+ # existing = Percentage.new(value: 50)
60
+ # Percentage.build(existing)
61
+ # #=> Returns the same instance
62
+ def build(*args, **kwargs)
63
+ return new(**kwargs) if kwargs.any?
64
+
65
+ case args.length
66
+ when 1
67
+ input = args[0]
68
+ return if input.nil?
69
+ return input if input.is_a?(self)
70
+
71
+ # Handle strings by parsing
72
+ return parse(input) if input.is_a?(::String)
73
+
74
+ # Handle Rational
75
+ if input.is_a?(Rational)
76
+ return new(value: input.to_f * 100)
77
+ end
78
+
79
+ # Handle numeric values
80
+ # If value is <= 1.0, treat as ratio (multiply by 100)
81
+ # If value is > 1.0, treat as literal percentage value
82
+ value = input.to_f
83
+ if value <= 1.0
84
+ new(value: value * 100)
85
+ else
86
+ new(value: value)
87
+ end
88
+ when 2
89
+ numerator, denominator = args
90
+ value = if denominator.to_f.zero?
91
+ 0.0
92
+ else
93
+ (numerator.to_f / denominator.to_f * 100.0)
94
+ end
95
+ new(value: value)
41
96
  else
42
- (numerator.to_f / denominator.to_f * 100.0)
97
+ raise ArgumentError, "wrong number of arguments (given #{args.length}, expected 1..2)"
43
98
  end
44
- else
45
- raise ArgumentError, "wrong number of arguments (given #{args.length}, expected 1..2)"
46
99
  end
47
100
 
48
- # Clamp to valid percentage range
49
- @value = @value.clamp(0.0, 100.0)
101
+ # Parse a percentage from string format.
102
+ #
103
+ # Handles multiple formats:
104
+ # - Explicit percentage: "50%" → 50.0
105
+ # - Fraction notation: "10/100" → 10.0
106
+ # - Bare decimal ≤ 1.0: "0.5" → 50.0 (treated as ratio)
107
+ # - Bare decimal > 1.0: "75" → 75.0 (treated as literal percentage)
108
+ #
109
+ # @param input [String] The string to parse
110
+ # @return [Percentage] The parsed percentage
111
+ # @raise [ArgumentError] If input is not a string or format is invalid
112
+ #
113
+ # @example Parse explicit percentage
114
+ # Percentage.parse("23.5%")
115
+ # #=> Percentage with value 23.5
116
+ #
117
+ # @example Parse ratio
118
+ # Percentage.parse("0.5")
119
+ # #=> Percentage with value 50.0
120
+ #
121
+ # @example Parse fraction
122
+ # Percentage.parse("1/4")
123
+ # #=> Percentage with value 25.0
124
+ def parse(input)
125
+ raise ArgumentError, "Input must be a string" unless input.is_a?(::String)
126
+
127
+ input = input.strip
128
+
129
+ # Handle explicit percentage format
130
+ if input.end_with?("%")
131
+ value = input.chomp("%").to_f
132
+ return new(value: value)
133
+ end
134
+
135
+ # Handle fraction notation
136
+ if input.include?("/")
137
+ parts = input.split("/").map(&:strip)
138
+ raise ArgumentError, "Invalid fraction format" unless parts.length == 2
139
+
140
+ numerator = parts[0].to_f
141
+ denominator = parts[1].to_f
142
+ return build(numerator, denominator)
143
+ end
144
+
145
+ # Handle bare numeric values
146
+ value = input.to_f
147
+
148
+ # If value is <= 1.0, treat as ratio (multiply by 100)
149
+ # If value is > 1.0, treat as literal percentage value
150
+ if value <= 1.0
151
+ new(value: value * 100)
152
+ else
153
+ new(value: value)
154
+ end
155
+ end
50
156
  end
51
157
 
52
158
  # Format as percentage string with configurable decimal places
@@ -102,9 +208,9 @@ module Unmagic
102
208
  def +(other)
103
209
  case other
104
210
  when Percentage
105
- Percentage.new([value + other.value, 100.0].min)
211
+ Percentage.new(value: [value + other.value, 100.0].min)
106
212
  when Numeric
107
- Percentage.new([value + other.to_f, 100.0].min)
213
+ Percentage.new(value: [value + other.to_f, 100.0].min)
108
214
  else
109
215
  raise TypeError, "can't add #{other.class} to Percentage"
110
216
  end
@@ -117,9 +223,9 @@ module Unmagic
117
223
  def -(other)
118
224
  case other
119
225
  when Percentage
120
- Percentage.new([value - other.value, 0.0].max)
226
+ Percentage.new(value: [value - other.value, 0.0].max)
121
227
  when Numeric
122
- Percentage.new([value - other.to_f, 0.0].max)
228
+ Percentage.new(value: [value - other.to_f, 0.0].max)
123
229
  else
124
230
  raise TypeError, "can't subtract #{other.class} from Percentage"
125
231
  end
@@ -129,7 +235,7 @@ module Unmagic
129
235
  #
130
236
  # @return [Percentage] New percentage with absolute value
131
237
  def abs
132
- self.class.new(@value.abs)
238
+ self.class.new(value: @value.abs)
133
239
  end
134
240
 
135
241
  # Check if percentage is zero
@@ -138,6 +244,28 @@ module Unmagic
138
244
  def zero?
139
245
  @value.zero?
140
246
  end
247
+
248
+ # Pretty print format for debugging
249
+ #
250
+ # @param pp [PP] The pretty printer
251
+ # @return [void]
252
+ def pretty_print(pp)
253
+ pp.group(1, "#<#{self.class}(", ")>") do
254
+ pp.text("\"#{self}\"")
255
+ end
256
+ end
257
+ end
258
+
259
+ # Constructor-style method for creating Percentage instances
260
+ #
261
+ # Handles both string parsing and numeric building.
262
+ #
263
+ # @param args [Array] Arguments to pass to Percentage.build
264
+ # @option kwargs [Numeric] :value The percentage value (0-100)
265
+ # @return [Percentage, nil] The created percentage
266
+ def Percentage(*args, **kwargs) # rubocop:disable Naming/MethodName
267
+ Percentage.build(*args, **kwargs)
141
268
  end
269
+ module_function :Percentage
142
270
  end
143
271
  end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unmagic
4
+ class Color
5
+ # Current version of the Unmagic::Color gem
6
+ VERSION = "0.2.1"
7
+ end
8
+ end
data/lib/unmagic/color.rb CHANGED
@@ -37,18 +37,38 @@ module Unmagic
37
37
  # darker = color.darken(0.1)
38
38
  # mixed = color.blend(other_color, 0.5)
39
39
  class Color
40
+ # Base error class for color-related errors.
40
41
  # @private
41
42
  class Error < StandardError; end
43
+
44
+ # Error raised when a color string cannot be parsed.
42
45
  # @private
43
46
  class ParseError < Error; end
44
47
 
48
+ # Path to the data directory containing color databases.
49
+ # @api private
50
+ DATA_PATH = File.join(__dir__, "..", "..", "data")
51
+
52
+ require_relative "color/version"
45
53
  require_relative "color/rgb"
46
54
  require_relative "color/rgb/hex"
47
55
  require_relative "color/rgb/named"
56
+ require_relative "color/rgb/ansi"
48
57
  require_relative "color/hsl"
49
58
  require_relative "color/oklch"
50
59
  require_relative "color/string/hash_function"
51
60
  require_relative "color/util/percentage"
61
+ require_relative "color/units/degrees"
62
+ require_relative "color/gradient"
63
+ require_relative "color/gradient/stop"
64
+ require_relative "color/gradient/bitmap"
65
+ require_relative "color/gradient/base"
66
+ require_relative "color/rgb/gradient/linear"
67
+ require_relative "color/hsl/gradient/linear"
68
+ require_relative "color/oklch/gradient/linear"
69
+ require_relative "color/harmony"
70
+
71
+ include Harmony
52
72
 
53
73
  class << self
54
74
  # Parse a color string into the appropriate color space object.
@@ -92,6 +112,8 @@ module Unmagic
92
112
  HSL.parse(input)
93
113
  elsif input.start_with?("oklch")
94
114
  OKLCH.parse(input)
115
+ elsif input.match?(/\A\d+(?:;\d+)*\z/) && RGB::ANSI.valid?(input)
116
+ RGB::ANSI.parse(input)
95
117
  elsif RGB::Named.valid?(input)
96
118
  RGB::Named.parse(input)
97
119
  else
@@ -124,7 +146,10 @@ module Unmagic
124
146
  super(value: value.to_i.clamp(0, 255))
125
147
  end
126
148
 
149
+ # @return [Integer] Component value as integer
127
150
  def to_i = value
151
+
152
+ # @return [Float] Component value as float
128
153
  def to_f = value.to_f
129
154
 
130
155
  # @param other [Component, Numeric] Value to compare
@@ -182,7 +207,10 @@ module Unmagic
182
207
  super(value: value.to_f % 360)
183
208
  end
184
209
 
210
+ # @return [Float] Hue value as float
185
211
  def to_f = value
212
+
213
+ # @return [Float] Hue value in degrees
186
214
  def degrees = value
187
215
 
188
216
  # @param other [Hue, Numeric] Value to compare
@@ -281,6 +309,53 @@ module Unmagic
281
309
  # Lightness percentage (0-100%)
282
310
  class Lightness < Unmagic::Util::Percentage; end
283
311
 
312
+ # Alpha channel for transparency. Stored internally as percentage (0-100%)
313
+ # where 100% is fully opaque and 0% is fully transparent. Use to_css to
314
+ # output as ratio (0.0-1.0) for CSS formats.
315
+ #
316
+ # Inherits parse method from Percentage which handles:
317
+ # - Explicit percentage: "50%" → Alpha with value 50.0
318
+ # - Fraction notation: "1/2" → Alpha with value 50.0
319
+ # - Bare decimal ≤ 1.0: "0.5" → Alpha with value 50.0 (treated as CSS ratio)
320
+ # - Bare decimal > 1.0: "75" → Alpha with value 75.0
321
+ #
322
+ # @example Parse CSS ratio
323
+ # alpha = Unmagic::Color::Alpha.parse("0.5")
324
+ # alpha.value
325
+ # #=> 50.0
326
+ #
327
+ # @example Parse percentage
328
+ # alpha = Unmagic::Color::Alpha.parse("50%")
329
+ # alpha.value
330
+ # #=> 50.0
331
+ class Alpha < Unmagic::Util::Percentage
332
+ # Default alpha value (fully opaque)
333
+ DEFAULT = new(value: 100).freeze
334
+
335
+ # Convert to CSS output format (as ratio 0.0-1.0, not percentage).
336
+ #
337
+ # Returns "1" for fully opaque, "0" for fully transparent, and decimal
338
+ # values for semi-transparent colors (e.g., "0.5", "0.75").
339
+ #
340
+ # @return [String] The alpha value as a CSS ratio string
341
+ #
342
+ # @example Fully opaque
343
+ # Unmagic::Color::Alpha.new(value: 100).to_css
344
+ # #=> "1"
345
+ #
346
+ # @example Semi-transparent
347
+ # Unmagic::Color::Alpha.new(value: 50).to_css
348
+ # #=> "0.5"
349
+ #
350
+ # @example Fully transparent
351
+ # Unmagic::Color::Alpha.new(value: 0).to_css
352
+ # #=> "0"
353
+ def to_css
354
+ ratio = to_ratio
355
+ ratio == 1.0 ? "1" : ratio.to_s.sub(/\.?0+$/, "")
356
+ end
357
+ end
358
+
284
359
  # Convert this color to RGB color space.
285
360
  #
286
361
  # RGB represents colors as a combination of Red, Green, and Blue light,
@@ -311,6 +386,26 @@ module Unmagic
311
386
  raise NotImplementedError
312
387
  end
313
388
 
389
+ # Convert this color to an ANSI SGR color code.
390
+ #
391
+ # Returns an ANSI Select Graphic Rendition (SGR) parameter string that can be used
392
+ # in terminal output. The returned string does not include the escape sequence prefix
393
+ # (\x1b[) or the trailing 'm'.
394
+ #
395
+ # @param layer [Symbol] Whether to generate foreground (:foreground) or background (:background) code
396
+ # @return [String] ANSI SGR code like "31" or "38;2;255;0;0"
397
+ #
398
+ # @example Output red text
399
+ # color = Unmagic::Color.parse("red")
400
+ # puts "\x1b[#{color.to_ansi}mHello\x1b[0m"
401
+ #
402
+ # @example Set background color
403
+ # color = Unmagic::Color.parse("#336699")
404
+ # puts "\x1b[#{color.to_ansi(layer: :background)}mText\x1b[0m"
405
+ def to_ansi(layer: :foreground)
406
+ raise NotImplementedError
407
+ end
408
+
314
409
  # Calculate the perceptual luminance of this color.
315
410
  #
316
411
  # Luminance represents how bright the color appears to the human eye,
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: unmagic-color
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keith Pitt
@@ -17,16 +17,36 @@ executables: []
17
17
  extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
+ - CHANGELOG.md
20
21
  - README.md
21
- - data/rgb.txt
22
+ - data/css.jsonc
23
+ - data/css.txt
24
+ - data/x11.jsonc
25
+ - data/x11.txt
22
26
  - lib/unmagic/color.rb
27
+ - lib/unmagic/color/console/banner.rb
28
+ - lib/unmagic/color/console/card.rb
29
+ - lib/unmagic/color/console/help.rb
30
+ - lib/unmagic/color/console/highlighter.rb
31
+ - lib/unmagic/color/gradient.rb
32
+ - lib/unmagic/color/gradient/base.rb
33
+ - lib/unmagic/color/gradient/bitmap.rb
34
+ - lib/unmagic/color/gradient/stop.rb
35
+ - lib/unmagic/color/harmony.rb
23
36
  - lib/unmagic/color/hsl.rb
37
+ - lib/unmagic/color/hsl/gradient/linear.rb
24
38
  - lib/unmagic/color/oklch.rb
39
+ - lib/unmagic/color/oklch/gradient/linear.rb
25
40
  - lib/unmagic/color/rgb.rb
41
+ - lib/unmagic/color/rgb/ansi.rb
42
+ - lib/unmagic/color/rgb/gradient/linear.rb
26
43
  - lib/unmagic/color/rgb/hex.rb
27
44
  - lib/unmagic/color/rgb/named.rb
28
45
  - lib/unmagic/color/string/hash_function.rb
46
+ - lib/unmagic/color/units/degrees.rb
47
+ - lib/unmagic/color/units/direction.rb
29
48
  - lib/unmagic/color/util/percentage.rb
49
+ - lib/unmagic/color/version.rb
30
50
  - lib/unmagic_color.rb
31
51
  homepage: https://github.com/unreasonable-magic/unmagic-color
32
52
  licenses:
@@ -49,7 +69,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
49
69
  - !ruby/object:Gem::Version
50
70
  version: '0'
51
71
  requirements: []
52
- rubygems_version: 3.6.9
72
+ rubygems_version: 4.0.3
53
73
  specification_version: 4
54
74
  summary: Comprehensive color manipulation library
55
75
  test_files: []