docyard 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/CHANGELOG.md +20 -1
  4. data/lib/docyard/build/static_generator.rb +1 -1
  5. data/lib/docyard/components/base_processor.rb +6 -0
  6. data/lib/docyard/components/code_block_diff_preprocessor.rb +104 -0
  7. data/lib/docyard/components/code_block_feature_extractor.rb +113 -0
  8. data/lib/docyard/components/code_block_focus_preprocessor.rb +77 -0
  9. data/lib/docyard/components/code_block_icon_detector.rb +40 -0
  10. data/lib/docyard/components/code_block_line_wrapper.rb +46 -0
  11. data/lib/docyard/components/code_block_options_preprocessor.rb +76 -0
  12. data/lib/docyard/components/code_block_patterns.rb +51 -0
  13. data/lib/docyard/components/code_block_processor.rb +135 -14
  14. data/lib/docyard/components/code_line_parser.rb +80 -0
  15. data/lib/docyard/components/code_snippet_import_preprocessor.rb +125 -0
  16. data/lib/docyard/components/registry.rb +4 -4
  17. data/lib/docyard/components/table_of_contents_processor.rb +1 -1
  18. data/lib/docyard/components/tabs_parser.rb +135 -4
  19. data/lib/docyard/components/tabs_range_finder.rb +42 -0
  20. data/lib/docyard/config/validator.rb +8 -0
  21. data/lib/docyard/config.rb +7 -0
  22. data/lib/docyard/icons/file_types.rb +0 -13
  23. data/lib/docyard/markdown.rb +13 -5
  24. data/lib/docyard/rack_application.rb +1 -1
  25. data/lib/docyard/renderer.rb +4 -3
  26. data/lib/docyard/templates/assets/css/code.css +12 -4
  27. data/lib/docyard/templates/assets/css/components/code-block.css +427 -24
  28. data/lib/docyard/templates/assets/css/components/navigation.css +12 -9
  29. data/lib/docyard/templates/assets/css/components/tabs.css +50 -44
  30. data/lib/docyard/templates/assets/css/variables.css +44 -0
  31. data/lib/docyard/templates/partials/_code_block.html.erb +50 -2
  32. data/lib/docyard/version.rb +1 -1
  33. metadata +11 -1
@@ -8,7 +8,6 @@ module Docyard
8
8
  # License: CC BY-SA 4.0 (see LICENSE.vscode-icons)
9
9
  # Copyright (c) 2016 Roberto Huertas
10
10
  module FileTypes
11
- # SVG content for each file type icon
12
11
  # rubocop:disable Layout/LineLength
13
12
  ICONS = {
14
13
  "css" => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" aria-labelledby="css-logo-title css-logo-description"><title>CSS Logo</title><g style="display:inline"><path fill="#639" d="M1.995 1.994h23.52a4.48 4.48 0 0 1 4.48 4.48v19.04a4.48 4.48 0 0 1-4.48 4.48H6.475a4.48 4.48 0 0 1-4.48-4.48Z"/><path fill="#fff" d="M9.079 24.87v-4.704c0-1.876 1.204-2.884 3.024-2.884 1.792-.028 2.912 1.148 2.856 3.136h-2.072c.056-.756-.28-1.316-.84-1.288-.7 0-.896.476-.896 1.372v4.088c0 .868.28 1.288.896 1.316.644 0 .896-.644.84-1.372h2.072c.112 2.044-1.176 3.248-2.996 3.22-1.764 0-2.884-.98-2.884-2.884zm6.636-.336h1.932c.028.896.308 1.456.924 1.456.616 0 .84-.364.84-1.204 0-.7-.308-1.092-1.064-1.456l-.728-.336c-1.288-.616-1.82-1.372-1.82-2.884 0-1.68 1.064-2.856 2.8-2.856 1.736 0 2.66 1.204 2.688 3.164h-1.876c0-.812-.168-1.372-.784-1.372-.56 0-.84.28-.84.98s.252.98.924 1.26l.672.308c1.428.672 2.044 1.54 2.044 3.164 0 1.932-1.092 2.996-2.884 2.996-1.792 0-2.8-1.232-2.828-3.22zm6.328 0h1.96c0 .896.308 1.456.896 1.456.588 0 .84-.364.84-1.204 0-.7-.28-1.092-1.064-1.456l-.728-.336c-1.288-.616-1.792-1.372-1.792-2.884 0-1.68 1.036-2.856 2.8-2.856 1.764 0 2.632 1.204 2.688 3.164h-1.876c-.028-.812-.196-1.372-.812-1.372-.56 0-.812.28-.812.98s.224.98.896 1.26l.7.308c1.4.672 2.016 1.54 2.016 3.164 0 1.932-1.092 2.996-2.884 2.996-1.792 0-2.8-1.232-2.828-3.22z"/></g></svg>',
@@ -35,7 +34,6 @@ module Docyard
35
34
  }.freeze
36
35
  # rubocop:enable Layout/LineLength
37
36
 
38
- # Map of file extensions/aliases to icon names
39
37
  EXTENSIONS = {
40
38
  "css" => "css",
41
39
  "go" => "go",
@@ -62,10 +60,6 @@ module Docyard
62
60
  "postgresql" => "pgsql"
63
61
  }.freeze
64
62
 
65
- # Get the SVG content for a file extension
66
- #
67
- # @param extension [String] The file extension (e.g., "js", "ts")
68
- # @return [String, nil] The SVG content, or nil if not found
69
63
  def self.svg(extension)
70
64
  icon_name = EXTENSIONS[extension.to_s.downcase]
71
65
  return nil unless icon_name
@@ -73,17 +67,10 @@ module Docyard
73
67
  ICONS[icon_name]
74
68
  end
75
69
 
76
- # Check if an icon exists for a file extension
77
- #
78
- # @param extension [String] The file extension to check
79
- # @return [Boolean] true if icon exists
80
70
  def self.exists?(extension)
81
71
  EXTENSIONS.key?(extension.to_s.downcase)
82
72
  end
83
73
 
84
- # Get all available file extensions
85
- #
86
- # @return [Array<String>] Array of supported extensions
87
74
  def self.available
88
75
  EXTENSIONS.keys.sort
89
76
  end
@@ -9,6 +9,10 @@ require_relative "components/callout_processor"
9
9
  require_relative "components/tabs_processor"
10
10
  require_relative "components/icon_processor"
11
11
  require_relative "components/code_block_processor"
12
+ require_relative "components/code_snippet_import_preprocessor"
13
+ require_relative "components/code_block_options_preprocessor"
14
+ require_relative "components/code_block_diff_preprocessor"
15
+ require_relative "components/code_block_focus_preprocessor"
12
16
  require_relative "components/table_wrapper_processor"
13
17
  require_relative "components/heading_anchor_processor"
14
18
  require_relative "components/table_of_contents_processor"
@@ -17,10 +21,12 @@ module Docyard
17
21
  class Markdown
18
22
  FRONTMATTER_REGEX = /\A---\s*\n(.*?\n)---\s*\n/m
19
23
 
20
- attr_reader :raw
24
+ attr_reader :raw, :config
21
25
 
22
- def initialize(raw)
26
+ def initialize(raw, config: nil)
23
27
  @raw = raw.freeze
28
+ @config = config
29
+ @context = {}
24
30
  end
25
31
 
26
32
  def frontmatter
@@ -56,7 +62,7 @@ module Docyard
56
62
  end
57
63
 
58
64
  def toc
59
- @toc ||= Thread.current[:docyard_toc] || []
65
+ @context[:toc] || []
60
66
  end
61
67
 
62
68
  private
@@ -75,7 +81,9 @@ module Docyard
75
81
  end
76
82
 
77
83
  def render_html
78
- preprocessed_content = Components::Registry.run_preprocessors(content)
84
+ @context[:config] = config&.data
85
+
86
+ preprocessed_content = Components::Registry.run_preprocessors(content, @context)
79
87
 
80
88
  raw_html = Kramdown::Document.new(
81
89
  preprocessed_content,
@@ -85,7 +93,7 @@ module Docyard
85
93
  parse_block_html: true
86
94
  ).to_html
87
95
 
88
- Components::Registry.run_postprocessors(raw_html)
96
+ Components::Registry.run_postprocessors(raw_html, @context)
89
97
  end
90
98
  end
91
99
  end
@@ -13,7 +13,7 @@ module Docyard
13
13
  @file_watcher = file_watcher
14
14
  @config = config
15
15
  @router = Router.new(docs_path: docs_path)
16
- @renderer = Renderer.new(base_url: config&.build&.base_url || "/")
16
+ @renderer = Renderer.new(base_url: config&.build&.base_url || "/", config: config)
17
17
  @asset_handler = AssetHandler.new
18
18
  end
19
19
 
@@ -9,16 +9,17 @@ module Docyard
9
9
  ERRORS_PATH = File.join(__dir__, "templates", "errors")
10
10
  PARTIALS_PATH = File.join(__dir__, "templates", "partials")
11
11
 
12
- attr_reader :layout_path, :base_url
12
+ attr_reader :layout_path, :base_url, :config
13
13
 
14
- def initialize(layout: "default", base_url: "/")
14
+ def initialize(layout: "default", base_url: "/", config: nil)
15
15
  @layout_path = File.join(LAYOUTS_PATH, "#{layout}.html.erb")
16
16
  @base_url = normalize_base_url(base_url)
17
+ @config = config
17
18
  end
18
19
 
19
20
  def render_file(file_path, sidebar_html: "", prev_next_html: "", branding: {})
20
21
  markdown_content = File.read(file_path)
21
- markdown = Markdown.new(markdown_content)
22
+ markdown = Markdown.new(markdown_content, config: config)
22
23
 
23
24
  html_content = strip_md_from_links(markdown.html)
24
25
  toc = markdown.toc
@@ -42,12 +42,16 @@
42
42
  margin: 0;
43
43
  }
44
44
 
45
- .highlight,
46
- .highlight .w {
45
+ .highlight {
47
46
  color: #24292f;
48
47
  background-color: #f6f8fa;
49
48
  }
50
49
 
50
+ .highlight .w {
51
+ color: #24292f;
52
+ background-color: transparent;
53
+ }
54
+
51
55
  /* Keywords */
52
56
  .highlight .k,
53
57
  .highlight .kd,
@@ -214,12 +218,16 @@
214
218
  }
215
219
 
216
220
  /* Dark Mode Syntax Highlighting - GitHub Dark */
217
- .dark .highlight,
218
- .dark .highlight .w {
221
+ .dark .highlight {
219
222
  color: #e6edf3;
220
223
  background-color: #161b22;
221
224
  }
222
225
 
226
+ .dark .highlight .w {
227
+ color: #e6edf3;
228
+ background-color: transparent;
229
+ }
230
+
223
231
  /* Keywords */
224
232
  .dark .highlight .k,
225
233
  .dark .highlight .kd,
@@ -1,4 +1,4 @@
1
- /* Code Block Component with Copy Button */
1
+ /* Code Block Component */
2
2
 
3
3
  .docyard-code-block {
4
4
  position: relative;
@@ -9,17 +9,410 @@
9
9
  margin: 0;
10
10
  }
11
11
 
12
- /* Inside tabs - remove wrapper margin when code block is only child */
13
- .docyard-tabs__panel:has(> [class*="language-"]:only-child) .docyard-code-block {
12
+ /* Titled Code Block Layout */
13
+ .docyard-code-block--titled {
14
+ border: 1px solid var(--color-border);
15
+ border-radius: var(--radius-lg);
16
+ overflow: hidden;
17
+ background-color: var(--color-code-bg);
18
+ }
19
+
20
+ .docyard-code-block--titled .docyard-code-block__body {
21
+ display: block;
22
+ }
23
+
24
+ .docyard-code-block--titled .docyard-code-block__content {
25
+ display: block;
26
+ }
27
+
28
+ .docyard-code-block--titled .highlight {
14
29
  margin: 0;
30
+ border: none;
31
+ border-radius: 0;
32
+ box-shadow: none;
15
33
  }
16
34
 
17
- /* Inside tabs with mixed content - use normal spacing */
18
- .docyard-tabs__panel:not(:has(> [class*="language-"]:only-child)) .docyard-code-block {
19
- margin: var(--space-4) 0;
35
+ .docyard-code-block--titled .highlight pre {
36
+ margin: 0;
37
+ }
38
+
39
+ .docyard-code-block--titled .highlight pre code {
40
+ display: block;
41
+ padding: var(--space-4);
42
+ }
43
+
44
+ /* Code Block Header */
45
+ .docyard-code-block__header {
46
+ display: flex;
47
+ align-items: center;
48
+ gap: var(--space-2);
49
+ padding: var(--space-3) var(--space-4);
50
+ background: var(--color-bg-secondary);
51
+ border-bottom: 1px solid var(--color-border);
52
+ font-size: var(--font-size-sm);
53
+ font-weight: var(--font-weight-semibold);
54
+ color: var(--color-text-secondary);
55
+ }
56
+
57
+ .docyard-code-block__icon {
58
+ display: inline-flex;
59
+ align-items: center;
60
+ flex-shrink: 0;
61
+ }
62
+
63
+ .docyard-code-block__icon .docyard-icon {
64
+ width: 1rem;
65
+ height: 1rem;
66
+ display: inline-flex;
67
+ }
68
+
69
+ .docyard-code-block__icon .docyard-icon svg {
70
+ width: 100%;
71
+ height: 100%;
72
+ }
73
+
74
+ .docyard-code-block__title {
75
+ flex: 1;
76
+ min-width: 0;
77
+ overflow: hidden;
78
+ text-overflow: ellipsis;
79
+ white-space: nowrap;
80
+ }
81
+
82
+ /* Copy button in header */
83
+ .docyard-code-block__header .docyard-code-block__copy {
84
+ position: relative;
85
+ top: auto;
86
+ right: auto;
87
+ flex-shrink: 0;
88
+ }
89
+
90
+ /* Body wrapper for titled blocks */
91
+ .docyard-code-block__body {
92
+ position: relative;
93
+ }
94
+
95
+ /* Line Numbers Layout */
96
+ .docyard-code-block--line-numbers {
97
+ border: 1px solid var(--color-border);
98
+ border-radius: var(--radius-lg);
99
+ background-color: var(--color-code-bg);
100
+ overflow: hidden;
101
+ }
102
+
103
+ .docyard-code-block--line-numbers .docyard-code-block__body {
104
+ display: flex;
105
+ }
106
+
107
+ .docyard-code-block--line-numbers .highlight {
108
+ border: none;
109
+ border-radius: 0;
110
+ flex: 1;
111
+ min-width: 0;
112
+ }
113
+
114
+ .docyard-code-block__lines {
115
+ flex-shrink: 0;
116
+ padding: var(--space-4) 0;
117
+ border-right: 1px solid var(--color-border);
118
+ text-align: right;
119
+ user-select: none;
120
+ color: var(--color-text-tertiary);
121
+ font-family: var(--font-mono);
122
+ font-size: var(--font-size-sm);
123
+ line-height: var(--line-height-relaxed);
124
+ background-color: var(--color-code-bg);
125
+ }
126
+
127
+ .docyard-code-block__lines span {
128
+ display: block;
129
+ padding: 0 var(--space-3);
130
+ min-width: 2.5rem;
131
+ }
132
+
133
+ .docyard-code-block__content {
134
+ flex: 1;
135
+ min-width: 0;
136
+ overflow-x: auto;
137
+ }
138
+
139
+ /* Line Highlighting */
140
+ .docyard-code-line {
141
+ display: block;
142
+ }
143
+
144
+ .docyard-code-line--highlighted {
145
+ background-color: rgba(var(--color-primary-rgb), 0.1);
146
+ border-left: 3px solid var(--color-primary);
147
+ margin-left: calc(-1 * var(--space-4));
148
+ margin-right: calc(-1 * var(--space-4));
149
+ padding-left: calc(var(--space-4) - 3px);
150
+ padding-right: var(--space-4);
151
+ }
152
+
153
+ /* Highlighted line number */
154
+ .docyard-code-block__line--highlighted {
155
+ background-color: rgba(var(--color-primary-rgb), 0.15);
156
+ color: var(--color-primary);
157
+ font-weight: 500;
158
+ }
159
+
160
+ /* Diff line styles */
161
+ .docyard-code-line--diff-add {
162
+ background-color: var(--color-diff-add-bg);
163
+ border-left: 3px solid var(--color-diff-add-border);
164
+ margin-left: calc(-1 * var(--space-4));
165
+ margin-right: calc(-1 * var(--space-4));
166
+ padding-left: calc(var(--space-4) - 3px);
167
+ padding-right: var(--space-4);
168
+ }
169
+
170
+ .docyard-code-line--diff-remove {
171
+ background-color: var(--color-diff-remove-bg);
172
+ border-left: 3px solid var(--color-diff-remove-border);
173
+ margin-left: calc(-1 * var(--space-4));
174
+ margin-right: calc(-1 * var(--space-4));
175
+ padding-left: calc(var(--space-4) - 3px);
176
+ padding-right: var(--space-4);
177
+ }
178
+
179
+ /* Error/Warning line styles */
180
+ .docyard-code-line--error {
181
+ background-color: var(--color-code-error-bg, rgba(239, 68, 68, 0.15));
182
+ border-left: 3px solid var(--color-code-error-border, #ef4444);
183
+ margin-left: calc(-1 * var(--space-4));
184
+ margin-right: calc(-1 * var(--space-4));
185
+ padding-left: calc(var(--space-4) - 3px);
186
+ padding-right: var(--space-4);
187
+ }
188
+
189
+ .docyard-code-line--warning {
190
+ background-color: var(--color-code-warning-bg, rgba(245, 158, 11, 0.15));
191
+ border-left: 3px solid var(--color-code-warning-border, #f59e0b);
192
+ margin-left: calc(-1 * var(--space-4));
193
+ margin-right: calc(-1 * var(--space-4));
194
+ padding-left: calc(var(--space-4) - 3px);
195
+ padding-right: var(--space-4);
196
+ }
197
+
198
+ /* Diff gutter indicators */
199
+ .docyard-code-block__line--diff-add {
200
+ background-color: var(--color-diff-add-gutter-bg);
201
+ color: var(--color-diff-add-indicator);
202
+ font-weight: 500;
203
+ }
204
+
205
+ .docyard-code-block__line--diff-remove {
206
+ background-color: var(--color-diff-remove-gutter-bg);
207
+ color: var(--color-diff-remove-indicator);
208
+ font-weight: 500;
209
+ }
210
+
211
+ /* Error/Warning line number indicators */
212
+ .docyard-code-block__line--error {
213
+ background-color: var(--color-code-error-gutter-bg, rgba(239, 68, 68, 0.2));
214
+ color: var(--color-code-error-indicator, #991b1b);
215
+ font-weight: 500;
216
+ }
217
+
218
+ .docyard-code-block__line--warning {
219
+ background-color: var(--color-code-warning-gutter-bg, rgba(245, 158, 11, 0.2));
220
+ color: var(--color-code-warning-indicator, #78350f);
221
+ font-weight: 500;
222
+ }
223
+
224
+ /* Diff-only gutter */
225
+ .docyard-code-block--diff:not(.docyard-code-block--line-numbers) {
226
+ border: 1px solid var(--color-border);
227
+ border-radius: var(--radius-lg);
228
+ background-color: var(--color-code-bg);
229
+ overflow: hidden;
230
+ }
231
+
232
+ .docyard-code-block--diff:not(.docyard-code-block--line-numbers) .docyard-code-block__body {
233
+ display: flex;
234
+ }
235
+
236
+ .docyard-code-block--diff:not(.docyard-code-block--line-numbers) .highlight {
237
+ border: none;
238
+ border-radius: 0;
239
+ flex: 1;
240
+ min-width: 0;
241
+ }
242
+
243
+ .docyard-code-block__diff-gutter {
244
+ flex-shrink: 0;
245
+ padding: var(--space-4) 0;
246
+ border-right: 1px solid var(--color-border);
247
+ text-align: center;
248
+ user-select: none;
249
+ color: var(--color-text-tertiary);
250
+ font-family: var(--font-mono);
251
+ font-size: var(--font-size-sm);
252
+ line-height: var(--line-height-relaxed);
253
+ background-color: var(--color-code-bg);
254
+ }
255
+
256
+ .docyard-code-block__diff-gutter span {
257
+ display: block;
258
+ padding: 0 var(--space-2);
259
+ min-width: 1.5rem;
20
260
  }
21
261
 
22
- /* Inside callouts - use callout's spacing */
262
+ .docyard-code-block__diff-indicator--add {
263
+ background-color: var(--color-diff-add-gutter-bg);
264
+ color: var(--color-diff-add-indicator);
265
+ font-weight: 600;
266
+ }
267
+
268
+ .docyard-code-block__diff-indicator--remove {
269
+ background-color: var(--color-diff-remove-gutter-bg);
270
+ color: var(--color-diff-remove-indicator);
271
+ font-weight: 600;
272
+ }
273
+
274
+ /* With line numbers - reset margins, they extend via negative margin */
275
+ .docyard-code-block--line-numbers .docyard-code-line {
276
+ margin-left: 0;
277
+ margin-right: 0;
278
+ }
279
+
280
+ .docyard-code-block--line-numbers .docyard-code-line--highlighted,
281
+ .docyard-code-block--line-numbers .docyard-code-line--diff-add,
282
+ .docyard-code-block--line-numbers .docyard-code-line--diff-remove,
283
+ .docyard-code-block--line-numbers .docyard-code-line--error,
284
+ .docyard-code-block--line-numbers .docyard-code-line--warning {
285
+ margin-left: calc(-1 * var(--space-4));
286
+ margin-right: calc(-1 * var(--space-4));
287
+ }
288
+
289
+ /* With diff gutter - reset margins, they extend via negative margin */
290
+ .docyard-code-block--diff:not(.docyard-code-block--line-numbers) .docyard-code-line {
291
+ margin-left: 0;
292
+ margin-right: 0;
293
+ }
294
+
295
+ .docyard-code-block--diff:not(.docyard-code-block--line-numbers) .docyard-code-line--diff-add,
296
+ .docyard-code-block--diff:not(.docyard-code-block--line-numbers) .docyard-code-line--diff-remove {
297
+ margin-left: calc(-1 * var(--space-4));
298
+ margin-right: calc(-1 * var(--space-4));
299
+ }
300
+
301
+ /* Error/Warning blocks without line numbers */
302
+ .docyard-code-block--has-error:not(.docyard-code-block--line-numbers):not(.docyard-code-block--diff),
303
+ .docyard-code-block--has-warning:not(.docyard-code-block--line-numbers):not(.docyard-code-block--diff) {
304
+ border: 1px solid var(--color-border);
305
+ border-radius: var(--radius-lg);
306
+ background-color: var(--color-code-bg);
307
+ overflow: hidden;
308
+ }
309
+
310
+ .docyard-code-block--has-error:not(.docyard-code-block--line-numbers):not(.docyard-code-block--diff) .highlight,
311
+ .docyard-code-block--has-warning:not(.docyard-code-block--line-numbers):not(.docyard-code-block--diff) .highlight {
312
+ border: none;
313
+ border-radius: 0;
314
+ }
315
+
316
+ .docyard-code-block__content .highlight {
317
+ background-color: transparent;
318
+ }
319
+
320
+ /* Focus Effect */
321
+ .docyard-code-block--has-focus .docyard-code-line {
322
+ opacity: 0.4;
323
+ filter: blur(0.5px);
324
+ transition: opacity 0.2s ease, filter 0.2s ease;
325
+ }
326
+
327
+ .docyard-code-block--has-focus .docyard-code-line.docyard-code-line--focus {
328
+ opacity: 1;
329
+ filter: none;
330
+ }
331
+
332
+ /* On hover, reveal all lines */
333
+ .docyard-code-block--has-focus:hover .docyard-code-line {
334
+ opacity: 1;
335
+ filter: none;
336
+ }
337
+
338
+ /* Dark mode line numbers */
339
+ .dark .docyard-code-block--line-numbers {
340
+ background-color: #161b22;
341
+ }
342
+
343
+ .dark .docyard-code-block__lines {
344
+ background-color: #161b22;
345
+ border-right-color: var(--color-border);
346
+ }
347
+
348
+ /* Dark mode diff */
349
+ .dark .docyard-code-block--diff:not(.docyard-code-block--line-numbers) {
350
+ background-color: #161b22;
351
+ }
352
+
353
+ .dark .docyard-code-block__diff-gutter {
354
+ background-color: #161b22;
355
+ border-right-color: var(--color-border);
356
+ }
357
+
358
+ /* Dark mode titled block */
359
+ .dark .docyard-code-block--titled {
360
+ background-color: #161b22;
361
+ }
362
+
363
+ .dark .docyard-code-block__header {
364
+ background-color: rgba(255, 255, 255, 0.03);
365
+ }
366
+
367
+ /* Titled block with line numbers */
368
+ .docyard-code-block--titled.docyard-code-block--line-numbers {
369
+ display: block;
370
+ border: 1px solid var(--color-border);
371
+ }
372
+
373
+ .docyard-code-block--titled.docyard-code-block--line-numbers .docyard-code-block__body {
374
+ display: flex;
375
+ }
376
+
377
+ .docyard-code-block--titled.docyard-code-block--line-numbers .highlight {
378
+ flex: 1;
379
+ min-width: 0;
380
+ }
381
+
382
+ /* Titled block with diff */
383
+ .docyard-code-block--titled.docyard-code-block--diff:not(.docyard-code-block--line-numbers) {
384
+ display: block;
385
+ border: 1px solid var(--color-border);
386
+ }
387
+
388
+ .docyard-code-block--titled.docyard-code-block--diff:not(.docyard-code-block--line-numbers) .docyard-code-block__body {
389
+ display: flex;
390
+ }
391
+
392
+ .docyard-code-block--titled.docyard-code-block--diff:not(.docyard-code-block--line-numbers) .highlight {
393
+ flex: 1;
394
+ min-width: 0;
395
+ }
396
+
397
+ /* Inside tabs - with :has() support */
398
+ @supports selector(:has(*)) {
399
+ .docyard-tabs__panel:has(> [class*="language-"]:only-child) .docyard-code-block {
400
+ margin: 0;
401
+ }
402
+
403
+ .docyard-tabs__panel:not(:has(> [class*="language-"]:only-child)) .docyard-code-block {
404
+ margin: var(--space-4) 0;
405
+ }
406
+ }
407
+
408
+ /* Inside tabs - fallback for browsers without :has() */
409
+ @supports not selector(:has(*)) {
410
+ .docyard-tabs__panel .docyard-code-block {
411
+ margin: var(--space-4) 0;
412
+ }
413
+ }
414
+
415
+ /* Inside callouts */
23
416
  .docyard-callout__body .docyard-code-block {
24
417
  margin: var(--space-4) 0;
25
418
  }
@@ -47,14 +440,7 @@
47
440
  background-color: var(--color-bg);
48
441
  color: var(--color-text-secondary);
49
442
  cursor: pointer;
50
- transition: background-color var(--transition-base), border-color var(--transition-base), color var(--transition-base), opacity var(--transition-base), visibility var(--transition-base);
51
- opacity: 0;
52
- visibility: hidden;
53
- }
54
-
55
- .docyard-code-block:hover .docyard-code-block__copy {
56
- opacity: 1;
57
- visibility: visible;
443
+ transition: background-color var(--transition-base), border-color var(--transition-base), color var(--transition-base);
58
444
  }
59
445
 
60
446
  .docyard-code-block__copy:hover {
@@ -68,8 +454,6 @@
68
454
  }
69
455
 
70
456
  .docyard-code-block__copy:focus-visible {
71
- opacity: 1;
72
- visibility: visible;
73
457
  outline: 2px solid var(--color-primary);
74
458
  outline-offset: 2px;
75
459
  }
@@ -147,11 +531,26 @@
147
531
  color: white;
148
532
  }
149
533
 
150
- /* Mobile: Always show copy button */
151
- @media (max-width: 1024px) {
152
- .docyard-code-block__copy {
153
- opacity: 1;
154
- visibility: visible;
534
+ /* Mobile responsive - header */
535
+ @media (max-width: 640px) {
536
+ .docyard-code-block__header {
537
+ padding: var(--space-2) var(--space-3);
538
+ gap: var(--space-2);
539
+ }
540
+
541
+ .docyard-code-block__icon .docyard-icon {
542
+ width: 0.875rem;
543
+ height: 0.875rem;
544
+ }
545
+
546
+ .docyard-code-block__header .docyard-code-block__copy {
547
+ width: 28px;
548
+ height: 28px;
549
+ }
550
+
551
+ .docyard-code-block__header .docyard-code-block__copy svg {
552
+ width: 14px;
553
+ height: 14px;
155
554
  }
156
555
  }
157
556
 
@@ -166,7 +565,7 @@
166
565
  border-color: var(--color-text-tertiary);
167
566
  }
168
567
 
169
- /* Keep success state using primary colors in dark mode */
568
+ /* Dark mode success state */
170
569
  .dark .docyard-code-block__copy.is-success {
171
570
  background-color: var(--color-primary);
172
571
  border-color: var(--color-primary);
@@ -182,7 +581,7 @@
182
581
  border-color: #ef4444;
183
582
  }
184
583
 
185
- /* Reduced motion support - WCAG accessibility requirement */
584
+ /* Reduced motion */
186
585
  @media (prefers-reduced-motion: reduce) {
187
586
  .docyard-code-block__copy,
188
587
  .docyard-code-block__copy svg {
@@ -193,4 +592,8 @@
193
592
  .docyard-code-block__copy:active {
194
593
  transform: none;
195
594
  }
595
+
596
+ .docyard-code-block--has-focus .docyard-code-line {
597
+ transition: none;
598
+ }
196
599
  }
@@ -99,15 +99,18 @@
99
99
  position: relative;
100
100
  }
101
101
 
102
- .sidebar nav ul ul li:has(> a.active)::before {
103
- content: '';
104
- position: absolute;
105
- left: calc(-1 * var(--space-4));
106
- top: 0;
107
- bottom: 0;
108
- width: 2px;
109
- background-color: var(--color-primary);
110
- z-index: 1;
102
+ /* Active indicator with :has() support */
103
+ @supports selector(:has(*)) {
104
+ .sidebar nav ul ul li:has(> a.active)::before {
105
+ content: '';
106
+ position: absolute;
107
+ left: calc(-1 * var(--space-4));
108
+ top: 0;
109
+ bottom: 0;
110
+ width: 2px;
111
+ background-color: var(--color-primary);
112
+ z-index: 1;
113
+ }
111
114
  }
112
115
 
113
116
  .sidebar nav ul ul a {