plumb 0.0.16 → 0.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +34 -0
- data/lib/plumb/array_class.rb +1 -1
- data/lib/plumb/attributes.rb +3 -1
- data/lib/plumb/composable.rb +12 -0
- data/lib/plumb/hash_class.rb +16 -10
- data/lib/plumb/interface_class.rb +29 -1
- data/lib/plumb/or.rb +7 -2
- data/lib/plumb/version.rb +1 -1
- metadata +2 -3
- data/docs/styles.css +0 -540
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c718a272ada163dfc399bd4a6ae0353ac95b99210ac5450e5adf5e77881ed2d5
|
|
4
|
+
data.tar.gz: 2721e793e2cba293fcf8859d6b1ff960b7bc5e0ae33cf4374aa09a7532c6724b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c1ab0c74145b108e9b23503fe701a7237defcd72a463d05aa9500da2db1835559b279e3a0199c68774285000012b400d04026636dfbe21e53ee1872058bf7aed
|
|
7
|
+
data.tar.gz: fb65d7c1ebd803e08137a4473260a7050ec8025311696742ae8762b4b1fea77327ce5059a9b08a28c1ea78d3a9d8851378152c6b7f33d7489bc628f4375e2b9c
|
data/README.md
CHANGED
|
@@ -669,6 +669,40 @@ when Readable
|
|
|
669
669
|
end
|
|
670
670
|
```
|
|
671
671
|
|
|
672
|
+
Or pattern matching
|
|
673
|
+
|
|
674
|
+
```ruby
|
|
675
|
+
case args
|
|
676
|
+
in [Iterable => list, String => id]
|
|
677
|
+
# etc
|
|
678
|
+
in [Resolvable => r]
|
|
679
|
+
# etc
|
|
680
|
+
end
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
#### Merging interfaces
|
|
684
|
+
|
|
685
|
+
Use the `+` operator to merge two interfaces into a new one that must support both sets of method names.
|
|
686
|
+
|
|
687
|
+
```ruby
|
|
688
|
+
Iterable = Types::Interface[:each, :map]
|
|
689
|
+
Countable = Types::Interface[:size]
|
|
690
|
+
# This one expects objects with methods :each, :map and :size
|
|
691
|
+
CountableIterable = Iterable + Countable
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
#### Intersecting interfaces
|
|
695
|
+
|
|
696
|
+
Use the `&` operator to produce a new interface with the intersection of method names
|
|
697
|
+
|
|
698
|
+
```ruby
|
|
699
|
+
I1 = Types::Interface[:a, :b, :c]
|
|
700
|
+
I2 = Types::Interface[:b, :c, :d]
|
|
701
|
+
# This one expects methods :b and :c
|
|
702
|
+
I3 = Types::Interface[:b, :c]
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
|
|
672
706
|
TODO: make this a bit more advanced. Check for method arity.
|
|
673
707
|
|
|
674
708
|
### `Types::Hash`
|
data/lib/plumb/array_class.rb
CHANGED
|
@@ -65,7 +65,7 @@ module Plumb
|
|
|
65
65
|
# Steps might return the same result instance, so we map the values directly
|
|
66
66
|
# separate from the errors.
|
|
67
67
|
element_result = result.dup
|
|
68
|
-
errors =
|
|
68
|
+
errors = Hash.new(capacity: result.value.size)
|
|
69
69
|
values = result.value.map.with_index do |e, idx|
|
|
70
70
|
re = element_type.call(element_result.reset(e))
|
|
71
71
|
errors[idx] = re.errors unless re.valid?
|
data/lib/plumb/attributes.rb
CHANGED
|
@@ -228,12 +228,14 @@ module Plumb
|
|
|
228
228
|
super
|
|
229
229
|
end
|
|
230
230
|
|
|
231
|
+
MUST_BE_HASH = ['Must be a Hash of attributes'].freeze
|
|
232
|
+
|
|
231
233
|
# The Plumb::Step interface
|
|
232
234
|
# @param result [Plumb::Result::Valid]
|
|
233
235
|
# @return [Plumb::Result::Valid, Plumb::Result::Invalid]
|
|
234
236
|
def call(result)
|
|
235
237
|
return result if result.value.is_a?(self)
|
|
236
|
-
return result.invalid(errors:
|
|
238
|
+
return result.invalid(errors: MUST_BE_HASH) unless result.value.respond_to?(:to_h)
|
|
237
239
|
|
|
238
240
|
instance = new(result.value.to_h)
|
|
239
241
|
instance.valid? ? result.valid(instance) : result.invalid(instance, errors: instance.errors.to_h)
|
data/lib/plumb/composable.rb
CHANGED
|
@@ -295,6 +295,18 @@ module Plumb
|
|
|
295
295
|
end
|
|
296
296
|
|
|
297
297
|
def call(result) = type.call(result)
|
|
298
|
+
|
|
299
|
+
# Two nodes are equal when they wrap the same type with the same
|
|
300
|
+
# node_name and args. The default Composable#== compares #children,
|
|
301
|
+
# but a Node holds its identity in @node_name/@type/@args, so without
|
|
302
|
+
# this every as_node-wrapped type (Email, Boolean, etc.) would compare
|
|
303
|
+
# equal to every other.
|
|
304
|
+
def ==(other)
|
|
305
|
+
other.is_a?(self.class) &&
|
|
306
|
+
other.node_name == node_name &&
|
|
307
|
+
other.type == type &&
|
|
308
|
+
other.args == args
|
|
309
|
+
end
|
|
298
310
|
end
|
|
299
311
|
|
|
300
312
|
# Wrap a Step in a node with a custom #node_name
|
data/lib/plumb/hash_class.rb
CHANGED
|
@@ -108,24 +108,30 @@ module Plumb
|
|
|
108
108
|
return result unless _schema.any?
|
|
109
109
|
|
|
110
110
|
input = result.value
|
|
111
|
-
errors =
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
111
|
+
errors = nil # Do not allocate errors unless needed
|
|
112
|
+
output = @inclusive ? input.dup : {}
|
|
113
|
+
field_result = Result.valid(nil)
|
|
114
|
+
|
|
115
|
+
_schema.each do |key, field|
|
|
116
116
|
key_s = key.to_key
|
|
117
117
|
if input.key?(key_s)
|
|
118
118
|
r = field.call(field_result.reset(input[key_s]))
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
output[key_s] = r.value
|
|
120
|
+
unless r.valid?
|
|
121
|
+
errors ||= {}
|
|
122
|
+
errors[key_s] = r.errors
|
|
123
|
+
end
|
|
121
124
|
elsif !key.optional?
|
|
122
125
|
r = field.call(BLANK_RESULT)
|
|
123
|
-
|
|
124
|
-
|
|
126
|
+
output[key_s] = r.value unless r.value == Undefined
|
|
127
|
+
unless r.valid?
|
|
128
|
+
errors ||= {}
|
|
129
|
+
errors[key_s] = r.errors
|
|
130
|
+
end
|
|
125
131
|
end
|
|
126
132
|
end
|
|
127
133
|
|
|
128
|
-
errors
|
|
134
|
+
errors ? result.invalid(output, errors:) : result.valid(output)
|
|
129
135
|
end
|
|
130
136
|
|
|
131
137
|
def ==(other)
|
|
@@ -28,10 +28,38 @@ module Plumb
|
|
|
28
28
|
|
|
29
29
|
alias [] of
|
|
30
30
|
|
|
31
|
+
# Merge two interfaces into a new one with the method names of both
|
|
32
|
+
# @example
|
|
33
|
+
# i1 = Types::Interface[:foo]
|
|
34
|
+
# i2 = Types::Interface[:bar, :lol]
|
|
35
|
+
# i3 = i1 + i2 # expects objects with methods :foo, :bar, :lol
|
|
36
|
+
#
|
|
37
|
+
# @param other [InterfaceClass]
|
|
38
|
+
# @return [InterfaceClass]
|
|
39
|
+
def +(other)
|
|
40
|
+
raise ArgumentError, "expected another Types::Interface, but got #{other.inspect}" unless other.is_a?(self.class)
|
|
41
|
+
|
|
42
|
+
self.class.new((method_names + other.method_names).uniq)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Produce a new Interface with the intersection of two interfaces
|
|
46
|
+
# @example
|
|
47
|
+
# i1 = Types::Interface[:foo, :bar]
|
|
48
|
+
# i2 = Types::Interface[:bar, :lol]
|
|
49
|
+
# i3 = i1 + i2 # expects objects with methods :bar
|
|
50
|
+
#
|
|
51
|
+
# @param other [InterfaceClass]
|
|
52
|
+
# @return [InterfaceClass]
|
|
53
|
+
def &(other)
|
|
54
|
+
raise ArgumentError, "expected another Types::Interface, but got #{other.inspect}" unless other.is_a?(self.class)
|
|
55
|
+
|
|
56
|
+
self.class.new(method_names & other.method_names)
|
|
57
|
+
end
|
|
58
|
+
|
|
31
59
|
def call(result)
|
|
32
60
|
obj = result.value
|
|
33
61
|
missing_methods = @method_names.reject { |m| obj.respond_to?(m) }
|
|
34
|
-
return result.invalid(errors: "
|
|
62
|
+
return result.invalid(errors: "Invalid #{self.name}. Missing methods: #{missing_methods.join(', ')}") if missing_methods.any?
|
|
35
63
|
|
|
36
64
|
result
|
|
37
65
|
end
|
data/lib/plumb/or.rb
CHANGED
|
@@ -27,8 +27,13 @@ module Plumb
|
|
|
27
27
|
if right_result.valid?
|
|
28
28
|
right_result
|
|
29
29
|
else
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
# Decrease Array allocations slightly
|
|
31
|
+
# OR can be really expensive in composite ORed types
|
|
32
|
+
left_errors = left_result.errors.is_a?(Array) ? left_result.errors : [left_result.errors]
|
|
33
|
+
right_errors = right_result.errors.is_a?(Array) ? right_result.errors.first : right_result.errors
|
|
34
|
+
left_errors << right_errors
|
|
35
|
+
|
|
36
|
+
right_result.invalid(errors: left_errors)
|
|
32
37
|
end
|
|
33
38
|
end
|
|
34
39
|
end
|
data/lib/plumb/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: plumb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.18
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ismael Celis
|
|
@@ -53,7 +53,6 @@ files:
|
|
|
53
53
|
- bench/compare_parametric_struct.rb
|
|
54
54
|
- bench/parametric_schema.rb
|
|
55
55
|
- bench/plumb_hash.rb
|
|
56
|
-
- docs/styles.css
|
|
57
56
|
- examples/command_objects.rb
|
|
58
57
|
- examples/concurrent_downloads.rb
|
|
59
58
|
- examples/csv_stream.rb
|
|
@@ -116,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
116
115
|
- !ruby/object:Gem::Version
|
|
117
116
|
version: '0'
|
|
118
117
|
requirements: []
|
|
119
|
-
rubygems_version:
|
|
118
|
+
rubygems_version: 4.0.8
|
|
120
119
|
specification_version: 4
|
|
121
120
|
summary: Data validation and transformation library.
|
|
122
121
|
test_files: []
|
data/docs/styles.css
DELETED
|
@@ -1,540 +0,0 @@
|
|
|
1
|
-
/* Reset and Base Styles */
|
|
2
|
-
* {
|
|
3
|
-
margin: 0;
|
|
4
|
-
padding: 0;
|
|
5
|
-
box-sizing: border-box;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
:root {
|
|
9
|
-
--primary-color: #2563eb;
|
|
10
|
-
--primary-dark: #1e40af;
|
|
11
|
-
--secondary-color: #64748b;
|
|
12
|
-
--bg-color: #f8fafc;
|
|
13
|
-
--sidebar-bg: #1e293b;
|
|
14
|
-
--sidebar-text: #cbd5e1;
|
|
15
|
-
--sidebar-hover: #334155;
|
|
16
|
-
--content-bg: #ffffff;
|
|
17
|
-
--text-color: #1e293b;
|
|
18
|
-
--text-light: #64748b;
|
|
19
|
-
--border-color: #e2e8f0;
|
|
20
|
-
--code-bg: #f1f5f9;
|
|
21
|
-
--code-border: #cbd5e1;
|
|
22
|
-
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
|
23
|
-
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
html {
|
|
27
|
-
scroll-behavior: smooth;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
body {
|
|
31
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
|
32
|
-
line-height: 1.6;
|
|
33
|
-
color: var(--text-color);
|
|
34
|
-
background-color: var(--bg-color);
|
|
35
|
-
padding-top: 60px;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/* Top Menu Bar */
|
|
39
|
-
.top-menu {
|
|
40
|
-
position: fixed;
|
|
41
|
-
top: 0;
|
|
42
|
-
left: 0;
|
|
43
|
-
right: 0;
|
|
44
|
-
height: 60px;
|
|
45
|
-
background-color: var(--content-bg);
|
|
46
|
-
border-bottom: 1px solid var(--border-color);
|
|
47
|
-
box-shadow: var(--shadow);
|
|
48
|
-
z-index: 1000;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
.top-menu-content {
|
|
52
|
-
max-width: 100%;
|
|
53
|
-
height: 100%;
|
|
54
|
-
padding: 0 2rem;
|
|
55
|
-
display: flex;
|
|
56
|
-
justify-content: space-between;
|
|
57
|
-
align-items: center;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
.top-menu-brand {
|
|
61
|
-
display: flex;
|
|
62
|
-
align-items: baseline;
|
|
63
|
-
gap: 0.75rem;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
.brand-name {
|
|
67
|
-
font-size: 1.5rem;
|
|
68
|
-
font-weight: 700;
|
|
69
|
-
color: var(--text-color);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
.brand-tagline {
|
|
73
|
-
font-size: 0.875rem;
|
|
74
|
-
color: var(--text-light);
|
|
75
|
-
font-weight: 400;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
.top-menu .github-link {
|
|
79
|
-
display: flex;
|
|
80
|
-
align-items: center;
|
|
81
|
-
gap: 0.5rem;
|
|
82
|
-
padding: 0.5rem 1rem;
|
|
83
|
-
background-color: var(--text-color);
|
|
84
|
-
color: #ffffff;
|
|
85
|
-
border-radius: 6px;
|
|
86
|
-
text-decoration: none;
|
|
87
|
-
font-weight: 600;
|
|
88
|
-
font-size: 0.875rem;
|
|
89
|
-
transition: all 0.2s ease;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
.top-menu .github-link:hover {
|
|
93
|
-
background-color: #000000;
|
|
94
|
-
text-decoration: none;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
.top-menu .github-link svg {
|
|
98
|
-
width: 20px;
|
|
99
|
-
height: 20px;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/* Layout */
|
|
103
|
-
.container {
|
|
104
|
-
display: flex;
|
|
105
|
-
min-height: calc(100vh - 60px);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/* Sidebar */
|
|
109
|
-
.sidebar {
|
|
110
|
-
width: 280px;
|
|
111
|
-
background-color: var(--sidebar-bg);
|
|
112
|
-
color: var(--sidebar-text);
|
|
113
|
-
padding: 2rem 0;
|
|
114
|
-
position: fixed;
|
|
115
|
-
top: 60px;
|
|
116
|
-
height: calc(100vh - 60px);
|
|
117
|
-
overflow-y: auto;
|
|
118
|
-
box-shadow: var(--shadow-lg);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
.sidebar::-webkit-scrollbar {
|
|
122
|
-
width: 8px;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
.sidebar::-webkit-scrollbar-track {
|
|
126
|
-
background: var(--sidebar-bg);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
.sidebar::-webkit-scrollbar-thumb {
|
|
130
|
-
background: var(--sidebar-hover);
|
|
131
|
-
border-radius: 4px;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
.logo {
|
|
135
|
-
padding: 0 1.5rem 1.5rem;
|
|
136
|
-
border-bottom: 1px solid var(--sidebar-hover);
|
|
137
|
-
margin-bottom: 1.5rem;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
.logo h2 {
|
|
141
|
-
font-size: 1.75rem;
|
|
142
|
-
color: #ffffff;
|
|
143
|
-
margin-bottom: 0.25rem;
|
|
144
|
-
font-weight: 700;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
.logo .tagline {
|
|
148
|
-
font-size: 0.875rem;
|
|
149
|
-
color: var(--sidebar-text);
|
|
150
|
-
font-weight: 400;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
.nav-menu {
|
|
154
|
-
list-style: none;
|
|
155
|
-
padding-left: 0;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
.nav-menu li {
|
|
159
|
-
margin: 0;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
.nav-menu a {
|
|
163
|
-
display: block;
|
|
164
|
-
padding: 0.625rem 1.5rem;
|
|
165
|
-
color: var(--sidebar-text);
|
|
166
|
-
text-decoration: none;
|
|
167
|
-
transition: all 0.2s ease;
|
|
168
|
-
font-size: 0.9375rem;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
.nav-menu li:not(.nav-submenu) > a {
|
|
172
|
-
font-weight: 600;
|
|
173
|
-
margin-top: 0.5rem;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
.nav-submenu a {
|
|
177
|
-
padding-left: 2.5rem;
|
|
178
|
-
font-size: 0.875rem;
|
|
179
|
-
color: var(--sidebar-text);
|
|
180
|
-
opacity: 0.9;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
.nav-menu a:hover {
|
|
184
|
-
background-color: var(--sidebar-hover);
|
|
185
|
-
color: #ffffff;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
.nav-menu a.active {
|
|
189
|
-
background-color: var(--primary-color);
|
|
190
|
-
color: #ffffff;
|
|
191
|
-
border-left: 3px solid #ffffff;
|
|
192
|
-
padding-left: calc(1.5rem - 3px);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
.nav-submenu a.active {
|
|
196
|
-
padding-left: calc(2.5rem - 3px);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/* Accordion Menus */
|
|
200
|
-
.nav-accordion {
|
|
201
|
-
position: relative;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
.accordion-toggle {
|
|
205
|
-
display: flex;
|
|
206
|
-
justify-content: space-between;
|
|
207
|
-
align-items: center;
|
|
208
|
-
cursor: pointer;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
.accordion-icon {
|
|
212
|
-
font-size: 0.75rem;
|
|
213
|
-
transition: transform 0.2s ease;
|
|
214
|
-
margin-left: 0.5rem;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
.nav-accordion.expanded .accordion-icon {
|
|
218
|
-
transform: rotate(-180deg);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
.accordion-content {
|
|
222
|
-
list-style: none;
|
|
223
|
-
padding-left: 0;
|
|
224
|
-
max-height: 0;
|
|
225
|
-
overflow: hidden;
|
|
226
|
-
transition: max-height 0.3s ease;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
.nav-accordion.expanded .accordion-content {
|
|
230
|
-
max-height: 500px;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
.nav-h4 a {
|
|
234
|
-
padding-left: 3.5rem;
|
|
235
|
-
font-size: 0.8125rem;
|
|
236
|
-
color: var(--sidebar-text);
|
|
237
|
-
opacity: 0.85;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
.nav-h4 a.active {
|
|
241
|
-
padding-left: calc(3.5rem - 3px);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/* Main Content */
|
|
245
|
-
.content {
|
|
246
|
-
flex: 1;
|
|
247
|
-
margin-left: 280px;
|
|
248
|
-
padding: 3rem;
|
|
249
|
-
max-width: 1200px;
|
|
250
|
-
width: 79vw;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/* Page Header */
|
|
254
|
-
.page-header {
|
|
255
|
-
margin-bottom: 3rem;
|
|
256
|
-
padding-bottom: 2rem;
|
|
257
|
-
border-bottom: 2px solid var(--border-color);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
.page-header h1 {
|
|
261
|
-
font-size: 3rem;
|
|
262
|
-
font-weight: 800;
|
|
263
|
-
color: var(--text-color);
|
|
264
|
-
margin-bottom: 0.5rem;
|
|
265
|
-
letter-spacing: -0.025em;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
.page-header .subtitle {
|
|
269
|
-
font-size: 1.25rem;
|
|
270
|
-
color: var(--text-light);
|
|
271
|
-
font-weight: 400;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/* Sections */
|
|
275
|
-
.section {
|
|
276
|
-
margin-bottom: 4rem;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
.section h2 {
|
|
280
|
-
font-size: 2rem;
|
|
281
|
-
font-weight: 700;
|
|
282
|
-
color: var(--text-color);
|
|
283
|
-
margin-bottom: 1.5rem;
|
|
284
|
-
padding-bottom: 0.5rem;
|
|
285
|
-
border-bottom: 2px solid var(--primary-color);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
.subsection {
|
|
289
|
-
margin-bottom: 2.5rem;
|
|
290
|
-
background-color: var(--content-bg);
|
|
291
|
-
padding: 2rem;
|
|
292
|
-
border-radius: 8px;
|
|
293
|
-
box-shadow: var(--shadow);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
.subsection h3 {
|
|
297
|
-
font-size: 1.5rem;
|
|
298
|
-
font-weight: 600;
|
|
299
|
-
color: var(--text-color);
|
|
300
|
-
margin-bottom: 1rem;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
.subsection h4 {
|
|
304
|
-
font-size: 1.25rem;
|
|
305
|
-
font-weight: 600;
|
|
306
|
-
color: var(--text-color);
|
|
307
|
-
margin: 1.5rem 0 1rem;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/* Typography */
|
|
311
|
-
p {
|
|
312
|
-
margin-bottom: 1rem;
|
|
313
|
-
line-height: 1.75;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
a {
|
|
317
|
-
color: var(--primary-color);
|
|
318
|
-
text-decoration: none;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
a:hover {
|
|
322
|
-
color: var(--primary-dark);
|
|
323
|
-
text-decoration: underline;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
strong {
|
|
327
|
-
font-weight: 600;
|
|
328
|
-
color: var(--text-color);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/* Lists */
|
|
332
|
-
ul, ol {
|
|
333
|
-
margin-bottom: 1rem;
|
|
334
|
-
padding-left: 1.5rem;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
li {
|
|
338
|
-
margin-bottom: 0.5rem;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
.feature-list {
|
|
342
|
-
list-style: none;
|
|
343
|
-
padding-left: 0;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
.feature-list li {
|
|
347
|
-
padding-left: 1.5rem;
|
|
348
|
-
position: relative;
|
|
349
|
-
margin-bottom: 0.75rem;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
.feature-list li::before {
|
|
353
|
-
content: "→";
|
|
354
|
-
position: absolute;
|
|
355
|
-
left: 0;
|
|
356
|
-
color: var(--primary-color);
|
|
357
|
-
font-weight: bold;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/* Code Blocks */
|
|
361
|
-
code {
|
|
362
|
-
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
|
|
363
|
-
font-size: 0.875em;
|
|
364
|
-
background-color: var(--code-bg);
|
|
365
|
-
padding: 0.2em 0.4em;
|
|
366
|
-
border-radius: 3px;
|
|
367
|
-
border: 1px solid var(--code-border);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
pre {
|
|
371
|
-
margin: 1.5rem 0;
|
|
372
|
-
padding: 0;
|
|
373
|
-
overflow-x: auto;
|
|
374
|
-
border-radius: 6px;
|
|
375
|
-
box-shadow: var(--shadow);
|
|
376
|
-
background-color: #282c34;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
pre code {
|
|
380
|
-
display: block;
|
|
381
|
-
padding: 1.25rem;
|
|
382
|
-
background-color: transparent;
|
|
383
|
-
color: inherit;
|
|
384
|
-
border: none;
|
|
385
|
-
border-radius: 6px;
|
|
386
|
-
line-height: 1.6;
|
|
387
|
-
overflow-x: auto;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
pre code::-webkit-scrollbar {
|
|
391
|
-
height: 8px;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
pre code::-webkit-scrollbar-track {
|
|
395
|
-
background: var(--sidebar-hover);
|
|
396
|
-
border-radius: 4px;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
pre code::-webkit-scrollbar-thumb {
|
|
400
|
-
background: var(--sidebar-text);
|
|
401
|
-
border-radius: 4px;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
/* Images */
|
|
405
|
-
.image-container {
|
|
406
|
-
margin: 2rem 0;
|
|
407
|
-
text-align: center;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
.image-container img {
|
|
411
|
-
max-width: 100%;
|
|
412
|
-
height: auto;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
.image-container .caption {
|
|
416
|
-
margin-top: 0.75rem;
|
|
417
|
-
font-size: 0.875rem;
|
|
418
|
-
color: var(--text-light);
|
|
419
|
-
font-style: italic;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
@media (max-width: 1248px) {
|
|
423
|
-
.content p img {
|
|
424
|
-
width: 64vw;
|
|
425
|
-
height: auto;
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
/* Responsive Design */
|
|
430
|
-
@media (max-width: 1024px) {
|
|
431
|
-
.sidebar {
|
|
432
|
-
width: 240px;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
.content {
|
|
436
|
-
width: 76vw;
|
|
437
|
-
margin-left: 240px;
|
|
438
|
-
padding: 2rem;
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
@media (max-width: 768px) {
|
|
443
|
-
/* Stack sidebar and content vertically on handhelds */
|
|
444
|
-
.container {
|
|
445
|
-
flex-direction: column;
|
|
446
|
-
}
|
|
447
|
-
.top-menu-content {
|
|
448
|
-
padding: 0 1rem;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
.brand-tagline {
|
|
452
|
-
display: none;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
.top-menu .github-link span {
|
|
456
|
-
display: none;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
.top-menu .github-link {
|
|
460
|
-
padding: 0.5rem;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
.sidebar {
|
|
464
|
-
display:none;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
.content {
|
|
468
|
-
margin-left: 0;
|
|
469
|
-
width: 100vw;
|
|
470
|
-
padding: 1.5rem;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
.content p img {
|
|
474
|
-
width: 78vw;
|
|
475
|
-
height: auto;
|
|
476
|
-
}
|
|
477
|
-
.page-header h1 {
|
|
478
|
-
font-size: 2rem;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
.page-header .subtitle {
|
|
482
|
-
font-size: 1rem;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
.section h2 {
|
|
486
|
-
font-size: 1.5rem;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
.subsection {
|
|
490
|
-
padding: 1.5rem;
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
.subsection h3 {
|
|
494
|
-
font-size: 1.25rem;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
pre code {
|
|
498
|
-
padding: 1rem;
|
|
499
|
-
font-size: 0.8125rem;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
/* Smooth Scrolling and Anchor Offset */
|
|
504
|
-
section {
|
|
505
|
-
scroll-margin-top: 5rem;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
article {
|
|
509
|
-
scroll-margin-top: 5rem;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
/* Print Styles */
|
|
513
|
-
@media print {
|
|
514
|
-
.top-menu {
|
|
515
|
-
display: none;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
body {
|
|
519
|
-
padding-top: 0;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
.sidebar {
|
|
523
|
-
display: none;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
.content {
|
|
527
|
-
margin-left: 0;
|
|
528
|
-
max-width: 100%;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
.subsection {
|
|
532
|
-
box-shadow: none;
|
|
533
|
-
border: 1px solid var(--border-color);
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
pre code {
|
|
537
|
-
background-color: var(--code-bg);
|
|
538
|
-
color: var(--text-color);
|
|
539
|
-
}
|
|
540
|
-
}
|