docit 0.5.0 → 0.7.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.
@@ -16,6 +16,9 @@ module Docit
16
16
  add_schemas
17
17
  add_models
18
18
  add_source_nodes
19
+ # Runs last: Graph#add_edge drops edges to not-yet-created nodes, so
20
+ # schema-usage edges must be added after both doc and schema nodes exist.
21
+ add_schema_usage_edges
19
22
  graph
20
23
  end
21
24
 
@@ -110,6 +113,27 @@ module Docit
110
113
  graph.add_edge(edge(doc_id, action_id, "documents", "high", "Docit registry"))
111
114
  end
112
115
 
116
+ # Link each doc node to every shared schema it references via $ref (in its
117
+ # request body or any response). Runs as a final pass because add_edge only
118
+ # keeps edges whose endpoints already exist as nodes. Confidence is high —
119
+ # the references come straight from the Docit registry.
120
+ def add_schema_usage_edges
121
+ graph.nodes.values.select { |node| node.type == "doc" }.each do |doc|
122
+ operation = Registry.find(controller: doc.metadata[:controller], action: doc.metadata[:action])
123
+ next unless operation
124
+
125
+ schema_refs_for(operation).each do |ref|
126
+ graph.add_edge(edge(doc.id, node_id("schema", ref), "uses_schema", "high", "Docit registry $ref"))
127
+ end
128
+ end
129
+ end
130
+
131
+ def schema_refs_for(operation)
132
+ refs = [operation._request_body&.schema_ref]
133
+ operation._responses.each { |res| refs << res.schema_ref }
134
+ refs.compact.uniq
135
+ end
136
+
113
137
  def add_schemas
114
138
  Docit.schemas.each_key do |name|
115
139
  graph.add_node(Node.new(
@@ -147,7 +171,8 @@ module Docit
147
171
  target_id = node_id("model", target)
148
172
  next unless graph.nodes.key?(target_id)
149
173
 
150
- graph.add_edge(edge(model_id, target_id, "association", "high", "ActiveRecord reflection: #{association.name}"))
174
+ graph.add_edge(edge(model_id, target_id, "association", "high",
175
+ "ActiveRecord reflection: #{association.name}"))
151
176
  end
152
177
  end
153
178
 
@@ -156,12 +181,14 @@ module Docit
156
181
  source_nodes.each { |node| graph.add_node(node) }
157
182
 
158
183
  labels = graph.nodes.values.select { |node| %w[model service job mailer].include?(node.type) }.map(&:label)
159
- graph.nodes.values.select { |node| %w[controller action service job mailer].include?(node.type) }.each do |source|
184
+ sources = graph.nodes.values.select { |node| %w[controller action service job mailer].include?(node.type) }
185
+ sources.each do |source|
160
186
  scanner.references_for(source.file, labels).each do |label|
161
187
  target = graph.nodes.values.find { |node| node.label == label }
162
188
  next unless target
163
189
 
164
- graph.add_edge(edge(source.id, target.id, edge_type_for(target), "medium", "Constant reference in #{source.file}"))
190
+ graph.add_edge(edge(source.id, target.id, edge_type_for(target), "medium",
191
+ "Constant reference in #{source.file}"))
165
192
  end
166
193
  end
167
194
  end
@@ -203,7 +230,7 @@ module Docit
203
230
  end
204
231
 
205
232
  def node_id(type, label)
206
- "#{type}:#{label.to_s.underscore.tr('/', ':')}"
233
+ "#{type}:#{label.to_s.underscore.tr("/", ":")}"
207
234
  end
208
235
 
209
236
  def controller_file(controller)
@@ -41,8 +41,7 @@ module Docit
41
41
 
42
42
  private
43
43
 
44
- attr_reader :root
45
- attr_reader :excluded_paths
44
+ attr_reader :root, :excluded_paths
46
45
 
47
46
  def scan_dir(dir)
48
47
  full_dir = root.join(dir)
@@ -62,7 +61,7 @@ module Docit
62
61
  end
63
62
 
64
63
  def node_id(type, label)
65
- "#{type}:#{label.underscore.tr('/', ':')}"
64
+ "#{type}:#{label.underscore.tr("/", ":")}"
66
65
  end
67
66
 
68
67
  def full_path(path)
@@ -0,0 +1,554 @@
1
+ /* ───────── Theme tokens ─────────
2
+ Light is the default. [data-theme="dark"] on <html> overrides.
3
+ Accent hues (ember / dusk / method / status) are shared by both
4
+ themes — only the neutrals (surface, text, border) flip. */
5
+ :root {
6
+ /* Shared accents — identical in both themes */
7
+ --ember: #ea6a2e; --dusk: #7c4ddb;
8
+ --success: #1a9e4b; --info: #2563eb; --warning: #d97706; --danger: #dc2626;
9
+ --teal: #0d9488; --pink: #db2777; --amber: #d97706;
10
+
11
+ /* Light neutrals (default) */
12
+ --void: #f6f7f9; /* app background */
13
+ --text: #1a1f2e; /* primary text */
14
+ --smoke: #475067; /* secondary text */
15
+ --haze: #6b7488; /* tertiary / labels */
16
+ --border: #e3e6ec; /* hairlines */
17
+
18
+ --bg-solid: #ffffff; /* opaque surfaces (nodes, code) */
19
+ --bg-glass: rgba(255,255,255,0.86);
20
+ --bg-control: #ffffff;
21
+ --bg-panel: #ffffff;
22
+ --bg-card: #ffffff;
23
+ --bg-card-grad-1: #ffffff;
24
+ --bg-card-grad-2: #f6f7f9;
25
+ --bg-option-hover: rgba(15,23,42,0.04);
26
+ --grid-dot: rgba(15,23,42,0.05);
27
+ --stripe-bg-light: #fafbfc;
28
+ --stripe-card-bg: #ffffff;
29
+ --stripe-bg-sidebar: #fafbfc;
30
+ --bg-code: #f4f5f7; /* inline + block code surface */
31
+
32
+ color-scheme: light;
33
+ }
34
+
35
+ [data-theme="dark"] {
36
+ --void: #0f1117;
37
+ --text: #f2f0ec;
38
+ --smoke: #a8b4c8;
39
+ --haze: #7a8fb5;
40
+ --border: #2a3347;
41
+
42
+ --bg-solid: #161b28;
43
+ --bg-glass: rgba(15,17,23,0.96);
44
+ --bg-control: rgba(26,31,46,0.86);
45
+ --bg-panel: rgba(15,17,23,0.97);
46
+ --bg-card: rgba(26,31,46,0.6);
47
+ --bg-card-grad-1: rgba(26,31,46,0.8);
48
+ --bg-card-grad-2: rgba(15,17,23,0.6);
49
+ --bg-option-hover: rgba(42,51,71,0.3);
50
+ --grid-dot: rgba(168,180,200,0.06);
51
+ --stripe-bg-light: rgba(15,17,23,.4);
52
+ --stripe-card-bg: rgba(26,31,46,.4);
53
+ --stripe-bg-sidebar: rgba(15,17,23,.6);
54
+ --bg-code: rgba(26,31,46,.8);
55
+
56
+ color-scheme: dark;
57
+ }
58
+ *, *::before, *::after { box-sizing: border-box; }
59
+ html, body { margin: 0; min-height: 100%; background: var(--void); color: var(--text); }
60
+ body { overflow: hidden; font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
61
+ button, input, select { font: inherit; }
62
+
63
+ /* Inline icon alignment — SVG glyphs sit centered on the text baseline. */
64
+ .btn-icon, .tip-icon, .welcome-icon {
65
+ display: inline-flex; align-items: center; justify-content: center;
66
+ }
67
+ .btn-icon svg { vertical-align: middle; }
68
+
69
+ /* Shell */
70
+ .system-shell { display: grid; grid-template-rows: auto 1fr; height: calc(100vh - 37px); min-height: 640px; }
71
+
72
+ /* Toolbar */
73
+ .system-toolbar {
74
+ display: flex; align-items: center; gap: 8px; padding: 10px 16px; flex-wrap: wrap;
75
+ border-bottom: 1px solid var(--border); background: var(--bg-glass);
76
+ backdrop-filter: blur(16px) saturate(150%);
77
+ }
78
+ .toolbar-group { display: flex; align-items: center; gap: 6px; }
79
+ .toolbar-brand { margin-right: auto; gap: 10px; }
80
+ .toolbar-filters { gap: 6px; }
81
+ .toolbar-zoom { gap: 4px; padding: 0 6px; border-left: 1px solid var(--border); border-right: 1px solid var(--border); }
82
+ .toolbar-actions { gap: 6px; }
83
+ .toolbar-info { gap: 8px; margin-left: 4px; }
84
+
85
+ .brand-mark {
86
+ width: 28px; height: 28px; border-radius: 8px; display: grid; place-items: center;
87
+ background: linear-gradient(135deg, rgba(245,121,58,.18), rgba(139,92,246,.14));
88
+ border: 1px solid rgba(245,121,58,.28); color: var(--ember); font-size: 14px;
89
+ }
90
+ .toolbar-title { min-width: 170px; }
91
+ .toolbar-title strong {
92
+ display: block; font-family: "Sora", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
93
+ font-size: 14px; letter-spacing: 0;
94
+ }
95
+ .toolbar-title span { color: var(--haze); font-size: 11px; }
96
+ .stat-pill { color: var(--haze); font-size: 11px; font-family: monospace; }
97
+
98
+ .control {
99
+ height: 32px; border: 1px solid var(--border); border-radius: 8px;
100
+ background: var(--bg-control); color: var(--text); padding: 0 10px; outline: none;
101
+ font-size: 12px; min-width: 0;
102
+ }
103
+ .control:focus { border-color: rgba(245,121,58,.6); box-shadow: 0 0 0 2px rgba(245,121,58,.12); }
104
+ input.control { width: 160px; }
105
+ select.control { width: 120px; }
106
+
107
+ .system-btn {
108
+ height: 32px; border: 1px solid rgba(168,180,200,.2); border-radius: 8px;
109
+ background: var(--bg-card); color: var(--smoke); padding: 0 10px;
110
+ cursor: pointer; font-size: 12px; display: flex; align-items: center; gap: 4px;
111
+ transition: all 120ms ease;
112
+ }
113
+ .system-btn:hover { background: var(--bg-option-hover); color: var(--text); }
114
+ .system-btn.active { background: rgba(245,121,58,.15); color: var(--ember); border-color: rgba(245,121,58,.4); }
115
+ .icon-btn { width: 32px; justify-content: center; padding: 0; font-size: 16px; font-weight: 700; }
116
+ .btn-icon { font-size: 13px; }
117
+ .zoom-label { font-size: 11px; color: var(--haze); font-family: monospace; min-width: 38px; text-align: center; }
118
+
119
+ /* Body */
120
+ .system-body { display: grid; grid-template-columns: 1fr 400px; min-height: 0; overflow: hidden; }
121
+
122
+ /* Canvas */
123
+ .canvas-wrap {
124
+ position: relative; height: 100%; min-height: 0; overflow: hidden; cursor: grab;
125
+ background:
126
+ radial-gradient(circle at 25px 25px, var(--grid-dot) 1px, transparent 1px),
127
+ radial-gradient(ellipse 800px 600px at 30% 20%, rgba(245,121,58,.04), transparent 60%),
128
+ radial-gradient(ellipse 700px 500px at 70% 80%, rgba(139,92,246,.04), transparent 60%),
129
+ var(--void);
130
+ background-size: 30px 30px, auto, auto, auto;
131
+ }
132
+ .canvas-wrap.panning,
133
+ .canvas-wrap:active { cursor: grabbing; }
134
+ .canvas { width: 100%; height: 100%; position: absolute; top: 0; left: 0; }
135
+ .canvas svg { display: block; width: 100%; height: 100%; overflow: visible; }
136
+
137
+ /* Nodes — hover & interaction */
138
+ .canvas svg .node { cursor: grab; transition: filter 180ms ease; }
139
+ .canvas svg .node:active { cursor: grabbing; }
140
+ .canvas svg .node:hover rect:first-of-type {
141
+ stroke-opacity: 0.7 !important;
142
+ filter: drop-shadow(0 6px 16px rgba(15,23,42,0.14));
143
+ }
144
+ [data-theme="dark"] .canvas svg .node:hover rect:first-of-type {
145
+ filter: drop-shadow(0 6px 16px rgba(0,0,0,0.35));
146
+ }
147
+ .canvas svg .node.selected rect:first-of-type {
148
+ filter: drop-shadow(0 8px 24px rgba(245,121,58,0.18));
149
+ }
150
+ .canvas svg .swimlane { pointer-events: none; }
151
+ .canvas svg path[marker-end] { transition: opacity 180ms ease, stroke-width 180ms ease; }
152
+ .canvas svg path[marker-end]:hover { opacity: 1 !important; stroke-width: 2.2 !important; }
153
+
154
+ /* Legend */
155
+ .legend {
156
+ position: absolute; bottom: 16px; left: 16px; z-index: 10;
157
+ background: var(--bg-glass); border: 1px solid var(--border); border-radius: 12px;
158
+ backdrop-filter: blur(12px); max-width: 200px; overflow: hidden;
159
+ transition: max-height 200ms ease;
160
+ }
161
+ .legend.collapsed .legend-content { display: none; }
162
+ .legend-toggle {
163
+ display: block; width: 100%; padding: 8px 12px; border: 0; background: transparent;
164
+ color: var(--haze); font-size: 11px; font-family: monospace; cursor: pointer; text-align: left;
165
+ }
166
+ .legend-toggle:hover { color: var(--text); }
167
+ .legend-content { padding: 0 12px 10px; }
168
+ .legend-section { margin-bottom: 8px; }
169
+ .legend-heading { font-size: 10px; color: var(--haze); text-transform: uppercase; letter-spacing: .06em; margin-bottom: 4px; font-family: monospace; }
170
+ .legend-item { display: flex; align-items: center; gap: 8px; padding: 2px 0; font-size: 11px; color: var(--smoke); }
171
+ .legend-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
172
+ .legend-line { width: 16px; height: 2px; border-radius: 1px; background: var(--haze); flex-shrink: 0; }
173
+
174
+ /* Panel */
175
+ .panel {
176
+ border-left: 1px solid var(--border); background: var(--bg-panel);
177
+ overflow-y: auto; overflow-x: hidden; scrollbar-width: thin;
178
+ scrollbar-color: var(--border) transparent;
179
+ }
180
+
181
+ .panel-welcome {
182
+ margin: 20px 16px; padding: 24px 20px; border: 1px solid var(--border); border-radius: 16px;
183
+ background: linear-gradient(145deg, var(--bg-card-grad-1), var(--bg-card-grad-2));
184
+ text-align: center;
185
+ }
186
+ .welcome-icon {
187
+ width: 48px; height: 48px; border-radius: 14px; display: inline-grid; place-items: center;
188
+ background: linear-gradient(135deg, rgba(245,121,58,.15), rgba(139,92,246,.12));
189
+ border: 1px solid rgba(245,121,58,.25); color: var(--ember); font-size: 22px; margin-bottom: 14px;
190
+ }
191
+ .panel-welcome h2 {
192
+ margin: 0 0 8px; font-family: "Sora", -apple-system, BlinkMacSystemFont, sans-serif;
193
+ font-size: 17px; font-weight: 700;
194
+ }
195
+ .panel-welcome p { margin: 0 0 18px; color: var(--haze); font-size: 13px; line-height: 1.5; }
196
+ .welcome-tips { text-align: left; }
197
+ .tip {
198
+ display: flex; align-items: flex-start; gap: 10px; padding: 8px 0;
199
+ border-bottom: 1px solid rgba(42,51,71,.5); font-size: 12px; color: var(--smoke); line-height: 1.4;
200
+ }
201
+ .tip:last-child { border-bottom: 0; }
202
+ .tip-icon { font-size: 14px; flex-shrink: 0; width: 20px; text-align: center; }
203
+ .tip strong { color: var(--ember); }
204
+
205
+ .panel-empty, .panel-section {
206
+ margin: 16px; border: 1px solid var(--border); border-radius: 14px; background: var(--bg-card);
207
+ }
208
+ .panel-empty { padding: 20px; color: var(--haze); font-size: 13px; line-height: 1.5; }
209
+
210
+ .panel-section { overflow: hidden; }
211
+ .panel-section h2 {
212
+ margin: 0; padding: 16px 18px 6px;
213
+ font-family: "Sora", -apple-system, BlinkMacSystemFont, sans-serif;
214
+ font-size: 15px; letter-spacing: 0; font-weight: 700;
215
+ }
216
+ .panel-section h3 {
217
+ margin: 0; padding: 12px 18px 4px; font-size: 12px; color: var(--haze);
218
+ text-transform: uppercase; letter-spacing: .04em; font-family: monospace;
219
+ }
220
+ .panel-section dl { margin: 0; padding: 6px 18px 18px; }
221
+ .panel-section dt {
222
+ margin-top: 14px; color: var(--haze); font-size: 11px; font-family: monospace;
223
+ text-transform: uppercase; letter-spacing: .04em;
224
+ }
225
+ .panel-section dd { margin: 4px 0 0; color: var(--smoke); font-size: 13px; word-break: break-word; line-height: 1.5; }
226
+ .panel-section pre {
227
+ overflow-x: auto; margin: 0; padding: 12px; border-radius: 8px; background: var(--bg-solid);
228
+ color: var(--smoke); font-size: 11px; white-space: pre-wrap; line-height: 1.5;
229
+ }
230
+
231
+ /* Node detail in panel */
232
+ .node-detail-header {
233
+ display: flex; align-items: center; gap: 10px; padding: 16px 18px;
234
+ border-bottom: 1px solid var(--border);
235
+ }
236
+ .node-detail-badge {
237
+ width: 36px; height: 36px; border-radius: 10px; display: grid; place-items: center;
238
+ font-size: 16px; flex-shrink: 0;
239
+ }
240
+ .node-detail-title { font-size: 15px; font-weight: 700; word-break: break-word; }
241
+ .node-detail-type { font-size: 11px; color: var(--haze); font-family: monospace; display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
242
+ .node-fact {
243
+ display: inline-block; padding: 1px 6px; border-radius: 4px; font-size: 10px;
244
+ background: rgba(168,180,200,.1); color: var(--smoke); font-family: monospace; font-weight: 600;
245
+ }
246
+
247
+ /* Edge line in panel */
248
+ .edge-line {
249
+ display: flex; justify-content: space-between; align-items: flex-start; gap: 8px;
250
+ border-bottom: 1px solid var(--border); padding: 8px 0; font-size: 12px;
251
+ }
252
+ .edge-line:last-child { border-bottom: 0; }
253
+ .edge-line-info { color: var(--smoke); line-height: 1.4; }
254
+ .edge-line-type { font-weight: 600; color: var(--text); }
255
+ .edge-line button {
256
+ flex-shrink: 0; border: 0; background: transparent; color: var(--ember);
257
+ cursor: pointer; font-size: 11px; padding: 2px 4px;
258
+ }
259
+ .edge-line button:hover { text-decoration: underline; }
260
+
261
+ /* Connection items in detail panel */
262
+ .connection-item {
263
+ display: flex; align-items: flex-start; gap: 8px; padding: 8px 0;
264
+ border-bottom: 1px solid var(--border); font-size: 12px;
265
+ }
266
+ .connection-item:last-child { border-bottom: 0; }
267
+ .connection-arrow { font-size: 14px; flex-shrink: 0; margin-top: 1px; font-weight: 600; }
268
+ .connection-info { flex: 1; min-width: 0; }
269
+ .connection-verb { display: block; color: var(--smoke); font-size: 11px; line-height: 1.4; }
270
+ .connection-label { display: block; color: var(--text); font-weight: 600; font-size: 12px; word-break: break-word; }
271
+ .connection-type { color: var(--haze); font-size: 10px; font-family: monospace; }
272
+ .connection-remove {
273
+ flex-shrink: 0; width: 20px; height: 20px; border: 0; border-radius: 4px;
274
+ background: transparent; color: var(--haze); cursor: pointer; font-size: 14px;
275
+ display: grid; place-items: center; opacity: 0; transition: opacity 120ms;
276
+ }
277
+ .connection-item:hover .connection-remove { opacity: 1; }
278
+ .connection-remove:hover { color: var(--ember); background: rgba(245,121,58,.1); }
279
+
280
+ /* Controller detail — action list */
281
+ .action-summary {
282
+ padding: 10px 0; border-bottom: 1px solid var(--border);
283
+ }
284
+ .action-summary:last-child { border-bottom: 0; }
285
+ .action-summary-name {
286
+ font-size: 13px; font-weight: 600; color: var(--text); margin-bottom: 4px;
287
+ display: flex; align-items: center; gap: 6px;
288
+ }
289
+ .action-doc-badge {
290
+ font-size: 9px; font-weight: 700; font-family: monospace;
291
+ padding: 1px 5px; border-radius: 3px;
292
+ background: rgba(52,199,89,.12); color: #34c759; border: 1px solid rgba(52,199,89,.2);
293
+ text-transform: uppercase; letter-spacing: .04em;
294
+ }
295
+ .action-summary-route {
296
+ margin: 4px 0; display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
297
+ }
298
+ .action-summary-desc {
299
+ font-size: 12px; color: var(--smoke); line-height: 1.4; margin: 4px 0;
300
+ }
301
+ .action-summary-responses {
302
+ display: flex; gap: 4px; margin-top: 4px; flex-wrap: wrap;
303
+ }
304
+ .response-code {
305
+ font-size: 10px; font-family: monospace; font-weight: 600;
306
+ padding: 1px 6px; border-radius: 3px;
307
+ background: rgba(168,180,200,.08); color: var(--haze); border: 1px solid rgba(168,180,200,.12);
308
+ }
309
+ .response-code[data-status^="2"] { background: rgba(52,199,89,.1); color: #34c759; border-color: rgba(52,199,89,.2); }
310
+ .response-code[data-status^="4"] { background: rgba(255,79,79,.1); color: #ff4f4f; border-color: rgba(255,79,79,.2); }
311
+ .response-code[data-status^="5"] { background: rgba(255,179,64,.1); color: #ffb340; border-color: rgba(255,179,64,.2); }
312
+
313
+ /* Toast */
314
+ .toast {
315
+ position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%); padding: 10px 16px;
316
+ border: 1px solid rgba(245,121,58,.25); border-radius: 999px; background: var(--bg-glass);
317
+ color: var(--ember); font-size: 12px; opacity: 0; pointer-events: none;
318
+ transition: opacity 180ms ease; backdrop-filter: blur(8px); z-index: 100;
319
+ }
320
+ .toast.visible { opacity: 1; }
321
+
322
+ /* ───────── API documentation view ─────────
323
+ A real docs layout: sticky resource nav on the left, a readable
324
+ single-column reference on the right. Reads light-first. */
325
+ .stripe-docs-wrap {
326
+ display: grid; grid-template-columns: 260px minmax(0, 1fr) 400px;
327
+ height: 100%; min-height: 0; overflow: hidden; background: var(--void);
328
+ }
329
+ .mono { font-family: "SF Mono", ui-monospace, SFMono-Regular, Menlo, monospace; }
330
+
331
+ .stripe-sidebar {
332
+ border-right: 1px solid var(--border); background: var(--stripe-bg-sidebar);
333
+ padding: 28px 14px; overflow-y: auto; display: flex; flex-direction: column; gap: 26px;
334
+ scrollbar-width: thin; scrollbar-color: var(--border) transparent;
335
+ }
336
+ .stripe-sidebar-group { display: flex; flex-direction: column; gap: 2px; }
337
+ .stripe-sidebar-heading {
338
+ font-size: 11px; font-weight: 700; color: var(--text); letter-spacing: .02em;
339
+ margin-bottom: 8px; padding-left: 10px;
340
+ }
341
+ .stripe-sidebar-item {
342
+ display: flex; align-items: center; justify-content: space-between; gap: 8px;
343
+ padding: 7px 10px; border-radius: 8px; font-size: 12.5px; color: var(--smoke);
344
+ text-decoration: none; transition: background 120ms ease, color 120ms ease; cursor: pointer;
345
+ border-left: 2px solid transparent;
346
+ }
347
+ .stripe-sidebar-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
348
+ .stripe-sidebar-item:hover { background: var(--bg-option-hover); color: var(--text); }
349
+ .stripe-sidebar-item.active {
350
+ background: var(--bg-option-hover); color: var(--ember); font-weight: 600;
351
+ border-left: 2px solid var(--ember); border-radius: 0 8px 8px 0;
352
+ }
353
+ .stripe-sidebar-badge {
354
+ font-size: 8px; font-weight: 800; font-family: "SF Mono", ui-monospace, monospace;
355
+ padding: 2px 5px; border-radius: 4px; letter-spacing: .03em; flex-shrink: 0;
356
+ }
357
+
358
+ .stripe-content {
359
+ padding: 44px 48px; overflow-y: auto; scroll-behavior: smooth; height: 100%;
360
+ scrollbar-width: thin; scrollbar-color: var(--border) transparent;
361
+ }
362
+ .stripe-controller-block {
363
+ margin-bottom: 64px; border-bottom: 1px solid var(--border); padding-bottom: 48px;
364
+ }
365
+ .stripe-controller-block:last-child { border-bottom: 0; margin-bottom: 24px; }
366
+ .stripe-controller-header {
367
+ display: flex; align-items: flex-start; justify-content: space-between; gap: 16px;
368
+ margin-bottom: 4px; flex-wrap: wrap;
369
+ }
370
+ .stripe-controller-kicker {
371
+ font-size: 11px; font-weight: 700; color: var(--ember); text-transform: uppercase;
372
+ letter-spacing: .08em; margin-bottom: 4px;
373
+ }
374
+ .stripe-controller-title {
375
+ margin: 0; font-family: "Sora", -apple-system, sans-serif; font-size: 26px;
376
+ font-weight: 800; color: var(--text); letter-spacing: -0.01em;
377
+ }
378
+ .stripe-controller-file {
379
+ font-size: 11px; color: var(--haze); font-family: "SF Mono", ui-monospace, monospace;
380
+ padding: 4px 8px; background: var(--bg-option-hover); border-radius: 6px; white-space: nowrap;
381
+ }
382
+ .stripe-controller-sub { margin: 0 0 24px; color: var(--haze); font-size: 13px; }
383
+ .stripe-controller-sub .mono { color: var(--smoke); }
384
+
385
+ .stripe-controller-aside { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
386
+ .stripe-coverage {
387
+ font-size: 11px; font-weight: 700; padding: 4px 9px; border-radius: 999px;
388
+ border: 1px solid; white-space: nowrap;
389
+ }
390
+
391
+ /* ───────── Right-hand detail panel ─────────
392
+ Fills the third column: endpoint request/response on click. */
393
+ .stripe-detail {
394
+ position: relative; border-left: 1px solid var(--border); background: var(--bg-panel);
395
+ overflow-y: auto; height: 100%; min-height: 0;
396
+ scrollbar-width: thin; scrollbar-color: var(--border) transparent;
397
+ }
398
+ .stripe-detail-close {
399
+ position: absolute; top: 14px; right: 14px; z-index: 2;
400
+ width: 28px; height: 28px; display: none; align-items: center; justify-content: center;
401
+ border: 1px solid var(--border); border-radius: 8px; background: var(--bg-card);
402
+ color: var(--haze); cursor: pointer;
403
+ }
404
+ .stripe-detail-close:hover { color: var(--text); background: var(--bg-option-hover); }
405
+ .stripe-detail-body { padding: 28px 24px; }
406
+
407
+ .stripe-detail-empty {
408
+ display: flex; flex-direction: column; align-items: center; justify-content: center;
409
+ text-align: center; min-height: 60vh; color: var(--haze); gap: 14px;
410
+ }
411
+ .stripe-detail-empty-icon {
412
+ width: 52px; height: 52px; border-radius: 14px; display: grid; place-items: center;
413
+ background: var(--bg-option-hover); color: var(--haze);
414
+ }
415
+ .stripe-detail-empty p { margin: 0; font-size: 13px; line-height: 1.5; max-width: 240px; }
416
+ .stripe-detail-empty strong { color: var(--smoke); }
417
+
418
+ /* Detail header */
419
+ .detail-kicker { font-size: 11px; font-weight: 700; color: var(--ember); text-transform: uppercase; letter-spacing: .07em; margin-bottom: 6px; }
420
+ .detail-title {
421
+ margin: 0 0 14px; font-family: "Sora", -apple-system, sans-serif; font-size: 19px;
422
+ font-weight: 700; color: var(--text); line-height: 1.3;
423
+ }
424
+ .detail-endpoint-line {
425
+ display: flex; align-items: center; gap: 10px; font-family: "SF Mono", ui-monospace, monospace;
426
+ font-size: 12.5px; padding: 10px 12px; border: 1px solid var(--border); border-radius: 9px;
427
+ background: var(--bg-solid); margin-bottom: 18px; flex-wrap: wrap;
428
+ }
429
+ .detail-endpoint-line .verb { font-weight: 800; letter-spacing: .03em; }
430
+ .detail-endpoint-line .path { color: var(--text); word-break: break-all; }
431
+ .detail-desc { font-size: 13.5px; line-height: 1.65; color: var(--smoke); margin-bottom: 22px; }
432
+
433
+ /* Detail sections (Parameters, Request, Responses) */
434
+ .detail-section { margin-bottom: 24px; }
435
+ .detail-section-title {
436
+ font-size: 11px; font-weight: 700; color: var(--haze); text-transform: uppercase;
437
+ letter-spacing: .06em; margin-bottom: 10px; padding-bottom: 6px; border-bottom: 1px solid var(--border);
438
+ }
439
+ .detail-param {
440
+ display: flex; flex-direction: column; gap: 2px; padding: 9px 0; border-bottom: 1px solid var(--border);
441
+ }
442
+ .detail-param:last-child { border-bottom: 0; }
443
+ .detail-param-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
444
+ .detail-param-name { font-family: "SF Mono", ui-monospace, monospace; font-size: 12px; font-weight: 700; color: var(--text); }
445
+ .detail-param-type { font-family: "SF Mono", ui-monospace, monospace; font-size: 11px; color: var(--info); }
446
+ .detail-chip {
447
+ font-size: 9px; font-weight: 800; font-family: "SF Mono", ui-monospace, monospace;
448
+ padding: 1px 5px; border-radius: 4px; text-transform: uppercase; letter-spacing: .03em;
449
+ }
450
+ .detail-chip.req { background: rgba(220,38,38,.1); color: var(--danger); }
451
+ .detail-chip.loc { background: var(--bg-option-hover); color: var(--haze); }
452
+ .detail-param-desc { font-size: 12px; color: var(--smoke); line-height: 1.45; }
453
+
454
+ .detail-code {
455
+ margin: 0; padding: 12px 14px; border: 1px solid var(--border); border-radius: 9px;
456
+ background: var(--bg-code); color: var(--smoke); overflow-x: auto;
457
+ font-family: "SF Mono", ui-monospace, monospace; font-size: 11.5px; line-height: 1.6; white-space: pre;
458
+ }
459
+
460
+ .detail-response { padding: 9px 0; border-bottom: 1px solid var(--border); }
461
+ .detail-response:last-child { border-bottom: 0; }
462
+ .detail-response-head { display: flex; align-items: center; gap: 8px; margin-bottom: 3px; }
463
+ .detail-response-desc { font-size: 12px; color: var(--smoke); line-height: 1.45; }
464
+
465
+ .detail-error { color: var(--warning); font-size: 13px; line-height: 1.5; }
466
+
467
+ /* Slide-over drawer on narrow screens */
468
+ @media (max-width: 1100px) {
469
+ .stripe-docs-wrap { grid-template-columns: 240px minmax(0, 1fr); }
470
+ .stripe-detail {
471
+ position: fixed; top: 0; right: 0; bottom: 0; width: min(420px, 88vw); z-index: 200;
472
+ transform: translateX(100%); transition: transform 220ms ease;
473
+ box-shadow: -8px 0 32px rgba(15,23,42,0.18);
474
+ }
475
+ .stripe-detail.open { transform: translateX(0); }
476
+ .stripe-detail-close { display: inline-flex; }
477
+ }
478
+
479
+ .stripe-endpoint-card {
480
+ background: var(--stripe-card-bg); border: 1px solid var(--border); border-radius: 14px;
481
+ margin-bottom: 20px; padding: 24px 26px; transition: border-color 150ms, box-shadow 150ms;
482
+ scroll-margin-top: 24px; cursor: pointer;
483
+ }
484
+ .stripe-endpoint-card:hover {
485
+ border-color: var(--haze);
486
+ box-shadow: 0 4px 20px rgba(15,23,42,0.06);
487
+ }
488
+ .stripe-endpoint-card.detail-active {
489
+ border-color: var(--ember);
490
+ box-shadow: 0 4px 20px rgba(234,106,46,0.10);
491
+ }
492
+ .stripe-endpoint-header {
493
+ display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 16px; gap: 16px;
494
+ }
495
+ .stripe-endpoint-title-wrap { min-width: 0; }
496
+ .stripe-endpoint-kicker {
497
+ display: flex; align-items: center; gap: 8px; margin-bottom: 6px;
498
+ font-size: 11px; color: var(--haze);
499
+ }
500
+ .stripe-endpoint-title {
501
+ margin: 0; font-family: "Sora", -apple-system, sans-serif; font-size: 18px;
502
+ font-weight: 700; color: var(--text); letter-spacing: -0.005em; line-height: 1.3;
503
+ }
504
+ .stripe-endpoint-card .stripe-endpoint-explain { flex-shrink: 0; }
505
+
506
+ .stripe-endpoint-meta {
507
+ display: inline-flex; align-items: center; gap: 10px;
508
+ font-family: "SF Mono", ui-monospace, monospace; font-size: 12.5px;
509
+ padding: 9px 14px; background: var(--bg-solid); border: 1px solid var(--border); border-radius: 9px;
510
+ margin-bottom: 18px;
511
+ }
512
+ .stripe-endpoint-verb { font-weight: 800; letter-spacing: .03em; }
513
+ .stripe-endpoint-path { color: var(--text); }
514
+
515
+ .stripe-endpoint-desc {
516
+ color: var(--smoke); font-size: 14px; line-height: 1.65; margin-bottom: 22px; max-width: 70ch;
517
+ }
518
+ .stripe-endpoint-desc--empty { color: var(--haze); font-style: italic; }
519
+
520
+ .stripe-relations-title {
521
+ font-size: 11px; font-weight: 700; color: var(--haze); text-transform: uppercase;
522
+ letter-spacing: .06em; margin-bottom: 10px;
523
+ }
524
+ .stripe-relations-grid {
525
+ display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 10px;
526
+ }
527
+ .stripe-relation-card {
528
+ background: var(--stripe-bg-light); border: 1px solid var(--border); border-radius: 10px;
529
+ padding: 10px 12px; display: flex; align-items: center; gap: 10px; transition: all 120ms;
530
+ cursor: pointer;
531
+ }
532
+ .stripe-relation-card:hover {
533
+ border-color: var(--ember); background: var(--bg-option-hover);
534
+ }
535
+ .stripe-relation-icon {
536
+ width: 26px; height: 26px; border-radius: 7px; display: grid; place-items: center; flex-shrink: 0;
537
+ }
538
+ .stripe-relation-text { min-width: 0; display: flex; flex-direction: column; }
539
+ .stripe-relation-name {
540
+ font-size: 12px; font-weight: 600; color: var(--text);
541
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
542
+ }
543
+ .stripe-relation-type {
544
+ font-size: 10px; color: var(--haze); font-family: "SF Mono", ui-monospace, monospace;
545
+ }
546
+
547
+ /* Responsive */
548
+ @media (max-width: 980px) {
549
+ body { overflow: auto; }
550
+ .system-body { grid-template-columns: 1fr; }
551
+ .panel { min-height: 320px; border-left: 0; border-top: 1px solid var(--border); }
552
+ input.control { width: 120px; }
553
+ .toolbar-zoom { display: none; }
554
+ }