docit 0.6.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ad97aecf5e4f97baffb4f944fa8f8a62b449846714e1c2dd2e8bbdc9a167ee22
4
- data.tar.gz: f638754ff073096c6904db2e5d394413e2f1c579966b97af831cfe93ae91af51
3
+ metadata.gz: 7fbb7845b4244fcde9b80e6702c17bf6c1e64274b3dada27a7b5a021929819d8
4
+ data.tar.gz: 21c9ab47ab7c6d5423ed965a57297594093590e916b94addc3a446fd58799c1e
5
5
  SHA512:
6
- metadata.gz: 9efd42b242a8c94f0a4372689205e736819ff0fb8e75fa29e95e708899adefacbefec43af0987449fbd3fd766dbb78b1c3f750d51d3141ee4bcb353d08cf9490
7
- data.tar.gz: b871fd59eabc84e1f135a4638527f7042b48ee033f17f7bd17378ab7002308e81202c37269912ca5883af30f06c1a7a6a8dc4d121d10573225cb464226f91251
6
+ metadata.gz: e809ab547345f748550f1545f52b87ed2b46f7d4aebda3cd96807d27eae4ff451f5d149d3bdc227a3b8aa6f9bfa8183d3ad234f8f7e216c0c5e5c9a648cf6566
7
+ data.tar.gz: bf6f544ac536d88a42a1256670c257cbc3d0a2cc8425057042a5930315f65a07af5ecb3b45aec75d87178ca2ca909ba4c6a51d05e74b2e35389b981fad858a55
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## [0.7.0] - 2026-06-21
2
+
3
+ ### Added
4
+ - `config.default_security :scheme`: apply a security requirement to every endpoint by default, for APIs that are authenticated by default. Emits a top-level OpenAPI `security` so you no longer repeat `security` on each doc block
5
+ - `security :none` in a doc block: opt an endpoint out of the default (emits an empty `security` array, e.g. for public endpoints)
6
+
7
+ ### Changed
8
+ - The System Map's JavaScript and CSS now live in real asset files (`lib/docit/ui/assets/system.js` and `system.css`) read at load time, instead of large Ruby heredocs — same rendered output, easier to maintain
9
+
1
10
  ## [0.6.0] - 2026-06-20
2
11
 
3
12
  ### Added
data/README.md CHANGED
@@ -352,6 +352,28 @@ doc_for :destroy do
352
352
  end
353
353
  ```
354
354
 
355
+ #### Default (global) security
356
+
357
+ If most of your API is authenticated, declare the requirement once instead of on every endpoint. In your initializer:
358
+
359
+ ```ruby
360
+ config.auth :api_key, name: "session", location: "query"
361
+ config.default_security :api_key # applies to every endpoint
362
+ ```
363
+
364
+ Then mark only the **public** endpoints as exceptions with `security :none`:
365
+
366
+ ```ruby
367
+ doc_for :index do
368
+ summary "List banks"
369
+ tags "Banks"
370
+ security :none # public — opts out of the default
371
+ response 200, "OK"
372
+ end
373
+ ```
374
+
375
+ An explicit `security :scheme` on an endpoint still overrides the default.
376
+
355
377
  ### Deprecated endpoints
356
378
 
357
379
  ```ruby
@@ -18,6 +18,7 @@ module Docit
18
18
  @system_graph_excluded_paths = []
19
19
  @default_ui = :scalar
20
20
  @security_schemes = {}
21
+ @default_security = nil
21
22
  @tags = []
22
23
  @servers = []
23
24
  @license = nil
@@ -60,6 +61,17 @@ module Docit
60
61
  @security_schemes.dup
61
62
  end
62
63
 
64
+ # Apply one or more security schemes to every endpoint by default. Individual
65
+ # endpoints opt out with `security :none` in their doc block.
66
+ def default_security(*schemes)
67
+ @default_security = schemes.flatten.map(&:to_sym) unless schemes.empty?
68
+ @default_security
69
+ end
70
+
71
+ def default_security?
72
+ !@default_security.nil? && !@default_security.empty?
73
+ end
74
+
63
75
  def tag(name, description: nil)
64
76
  entry = { name: name.to_s }
65
77
  entry[:description] = description if description
@@ -19,6 +19,10 @@ module Docit
19
19
  }
20
20
  }
21
21
 
22
+ # Top-level security applies to every operation unless an operation
23
+ # overrides it (a documented scheme, or `security :none` to opt out).
24
+ spec[:security] = config.default_security.map { |s| { s.to_s => [] } } if config.default_security?
25
+
22
26
  tag_defs = config.tags
23
27
  spec[:tags] = tag_defs if tag_defs.any?
24
28
 
@@ -78,11 +82,19 @@ module Docit
78
82
 
79
83
  result[:requestBody] = build_request_body(operation._request_body) if operation._request_body
80
84
  result[:deprecated] = true if operation._deprecated
81
- result[:security] = operation._security.map { |s| { s.to_s => [] } } if operation._security.any?
85
+ security = build_operation_security(operation)
86
+ result[:security] = security unless security.nil?
82
87
 
83
88
  result
84
89
  end
85
90
 
91
+ def build_operation_security(operation)
92
+ return nil if operation._security.empty?
93
+ return [] if operation._security.map(&:to_sym) == [:none]
94
+
95
+ operation._security.reject { |s| s.to_sym == :none }.map { |s| { s.to_s => [] } }
96
+ end
97
+
86
98
  def build_responses(response_builders)
87
99
  return { "200" => { description: "Success" } } if response_builders.empty?
88
100
 
@@ -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
+ }