chengzeyi commited on
Commit
8cc094f
·
1 Parent(s): fd71752

Rewrite as a model reference page; drop auto-redirect and ad copy

Browse files
Files changed (4) hide show
  1. README.md +17 -4
  2. index.html +202 -16
  3. style.css +562 -15
  4. tabs.js +15 -0
README.md CHANGED
@@ -1,11 +1,24 @@
1
  ---
2
- title: Seedream4
3
- emoji: 🐢
4
  colorFrom: red
5
  colorTo: purple
6
  sdk: static
7
  pinned: false
8
- short_description: Seedream 4.0 is a state-of-art image model by Bytedance.
9
  ---
10
 
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Seedream 4.0
3
+ emoji: 🌊
4
  colorFrom: red
5
  colorTo: purple
6
  sdk: static
7
  pinned: false
8
+ short_description: Reference for ByteDance Seedream 4.0 image model
9
  ---
10
 
11
+ # Seedream 4.0
12
+
13
+ A reference page for **Seedream 4.0**, ByteDance's image generation and editing model.
14
+ A single model covers text-to-image and instruction-based editing, with output up to 4K
15
+ and reference-guided consistency across a series.
16
+
17
+ Model weights are not publicly released; Seedream is available through hosted inference.
18
+
19
+ - Text-to-image: [bytedance/seedream-v4](https://wavespeed.ai/models/bytedance/seedream-v4)
20
+ - Editing: [bytedance/seedream-v4/edit](https://wavespeed.ai/models/bytedance/seedream-v4/edit)
21
+ - Updated checkpoint: [bytedance/seedream-v4.5](https://wavespeed.ai/models/bytedance/seedream-v4.5)
22
+
23
+ Seedream is developed by ByteDance. This Space is maintained by WaveSpeed AI, which
24
+ provides hosted inference for the model.
index.html CHANGED
@@ -1,19 +1,205 @@
1
  <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
  <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Seedream 4.0 — ByteDance image generation and editing</title>
7
+ <meta name="description" content="Reference for ByteDance&#x27;s Seedream 4.0 image model: generation and editing endpoints, high-resolution output, and hosted API usage." />
8
+ <link rel="canonical" href="https://wavespeed.ai/models/bytedance/seedream-v4" />
9
+
10
+ <meta property="og:type" content="website" />
11
+ <meta property="og:title" content="Seedream 4.0 — ByteDance image generation and editing" />
12
+ <meta property="og:description" content="Reference for ByteDance&#x27;s Seedream 4.0 image model: generation and editing endpoints, high-resolution output, and hosted API usage." />
13
+ <meta property="og:url" content="https://wavespeed.ai/models/bytedance/seedream-v4" />
14
+ <meta name="twitter:card" content="summary_large_image" />
15
+
16
+ <link rel="stylesheet" href="style.css" />
17
+ </head>
18
+ <body>
19
+ <header class="site-header">
20
+ <div class="wrap">
21
+ <a class="brand" href="https://wavespeed.ai" target="_blank" rel="noopener">
22
+ <span class="brand-mark" aria-hidden="true"></span>
23
+ <span>WaveSpeed AI</span>
24
+ </a>
25
+ <nav class="header-nav">
26
+ <a class="jump opt" href="#capabilities">Capabilities</a>
27
+ <a class="jump opt" href="#endpoints">Endpoints</a>
28
+ <a class="jump" href="#run">Run it</a>
29
+ <a class="jump" href="#resources">Resources</a>
30
+ <a href="https://wavespeed.ai/models/bytedance/seedream-v4" target="_blank" rel="noopener">wavespeed.ai &#8599;</a>
31
+ </nav>
32
  </div>
33
+ </header>
34
+
35
+ <main class="wrap">
36
+ <div class="hero">
37
+ <p class="eyebrow">ByteDance</p>
38
+ <h1>Seedream 4.0</h1>
39
+ <p class="lede">ByteDance's image generation and editing model. One model covers both text-to-image and instruction-based editing, with output up to 4K. Available as a hosted endpoint; weights are not publicly released.</p>
40
+ <ul class="meta">
41
+ <li><b>Developer</b> ByteDance</li>
42
+ <li><b>Task</b> text-to-image · image editing</li>
43
+ <li><b>Max output</b> 4K</li>
44
+ <li><b>Weights</b> not public</li>
45
+ </ul>
46
+ </div>
47
+
48
+ <section id="capabilities">
49
+ <h2>Capabilities</h2>
50
+ <p class="section-note">Characteristics reported by ByteDance. Treat them as vendor claims rather than independent measurements.</p>
51
+ <div class="grid">
52
+ <div class="card">
53
+ <h3>Generation and editing in one model</h3>
54
+ <p>The same checkpoint handles text-to-image and instruction-driven editing, so a generate-then-refine loop stays within one model.</p>
55
+ </div>
56
+ <div class="card">
57
+ <h3>High-resolution output</h3>
58
+ <p>Native output up to 4K without a separate upscaling pass.</p>
59
+ </div>
60
+ <div class="card">
61
+ <h3>Reference-guided consistency</h3>
62
+ <p>Accepts reference images to keep a subject or style stable across a set of generations — the basis for batch and series work.</p>
63
+ </div>
64
+ <div class="card">
65
+ <h3>Text rendering</h3>
66
+ <p>Handles typography inside the image, including Chinese, which most image models still degrade to unreadable glyphs.</p>
67
+ </div>
68
+ </div>
69
+ </section>
70
+ <section id="endpoints">
71
+ <h2>Endpoints</h2>
72
+ <div class="table-scroll">
73
+ <table>
74
+ <thead><tr><th>Endpoint</th><th>Task</th></tr></thead>
75
+ <tbody>
76
+ <tr><td><code>bytedance/seedream-v4</code></td><td>Text-to-image</td></tr>
77
+ <tr><td><code>bytedance/seedream-v4/edit</code></td><td>Instruction-based editing</td></tr>
78
+ <tr><td><code>bytedance/seedream-v4.5</code></td><td>Updated 4.5 checkpoint</td></tr>
79
+ </tbody>
80
+ </table>
81
+ </div>
82
+ </section>
83
+ <section id="run">
84
+ <h2>Run it</h2>
85
+ <p class="section-note">Use <code>bytedance/seedream-v4/edit</code> with an additional <code>images</code> array to edit rather than generate.</p>
86
+ <div class="code">
87
+ <div class="code-tabs" role="tablist">
88
+ <button type="button" role="tab" aria-selected="true" data-panel="run-0">cURL</button>
89
+ <button type="button" role="tab" aria-selected="false" data-panel="run-1">Python</button>
90
+ <button type="button" role="tab" aria-selected="false" data-panel="run-2">JavaScript</button>
91
+ </div>
92
+ <pre id="run-0" role="tabpanel"><code># 1. submit the job
93
+ curl -X POST &quot;https://api.wavespeed.ai/api/v3/bytedance/seedream-v4&quot; \
94
+ -H &quot;Authorization: Bearer $WAVESPEED_API_KEY&quot; \
95
+ -H &quot;Content-Type: application/json&quot; \
96
+ -d &#x27;{
97
+ &quot;prompt&quot;: &quot;An architectural photograph of a concrete stairwell, hard afternoon light, 35mm&quot;,
98
+ &quot;size&quot;: &quot;2048*2048&quot;,
99
+ &quot;enable_base64_output&quot;: false,
100
+ &quot;enable_sync_mode&quot;: false
101
+ }&#x27;
102
+
103
+ # -&gt; {&quot;code&quot;: 200, &quot;data&quot;: {&quot;id&quot;: &quot;&lt;request-id&gt;&quot;, &quot;status&quot;: &quot;created&quot;, ...}}
104
+
105
+ # 2. poll until status is &quot;completed&quot;
106
+ curl &quot;https://api.wavespeed.ai/api/v3/predictions/&lt;request-id&gt;/result&quot; \
107
+ -H &quot;Authorization: Bearer $WAVESPEED_API_KEY&quot;
108
+
109
+ # -&gt; {&quot;code&quot;: 200, &quot;data&quot;: {&quot;status&quot;: &quot;completed&quot;, &quot;outputs&quot;: [&quot;https://...&quot;]}}</code></pre>
110
+ <pre id="run-1" role="tabpanel" hidden><code>import os, time, requests
111
+
112
+ API = &quot;https://api.wavespeed.ai/api/v3&quot;
113
+ KEY = os.environ[&quot;WAVESPEED_API_KEY&quot;]
114
+ HEADERS = {&quot;Authorization&quot;: f&quot;Bearer {KEY}&quot;}
115
+
116
+ # submit
117
+ res = requests.post(
118
+ f&quot;{API}/bytedance/seedream-v4&quot;,
119
+ headers={**HEADERS, &quot;Content-Type&quot;: &quot;application/json&quot;},
120
+ json={
121
+ &quot;prompt&quot;: &quot;An architectural photograph of a concrete stairwell, hard afternoon light, 35mm&quot;,
122
+ &quot;size&quot;: &quot;2048*2048&quot;,
123
+ &quot;enable_base64_output&quot;: false,
124
+ &quot;enable_sync_mode&quot;: false
125
+ },
126
+ timeout=30,
127
+ )
128
+ res.raise_for_status()
129
+ request_id = res.json()[&quot;data&quot;][&quot;id&quot;]
130
+
131
+ # poll
132
+ while True:
133
+ data = requests.get(
134
+ f&quot;{API}/predictions/{request_id}/result&quot;,
135
+ headers=HEADERS,
136
+ timeout=30,
137
+ ).json()[&quot;data&quot;]
138
+
139
+ if data[&quot;status&quot;] == &quot;completed&quot;:
140
+ print(data[&quot;outputs&quot;][0])
141
+ break
142
+ if data[&quot;status&quot;] == &quot;failed&quot;:
143
+ raise RuntimeError(data.get(&quot;error&quot;, &quot;generation failed&quot;))
144
+ time.sleep(1.5)</code></pre>
145
+ <pre id="run-2" role="tabpanel" hidden><code>const API = &quot;https://api.wavespeed.ai/api/v3&quot;;
146
+ const KEY = process.env.WAVESPEED_API_KEY;
147
+ const headers = { Authorization: `Bearer ${KEY}` };
148
+
149
+ // submit
150
+ const submit = await fetch(`${API}/bytedance/seedream-v4`, {
151
+ method: &quot;POST&quot;,
152
+ headers: { ...headers, &quot;Content-Type&quot;: &quot;application/json&quot; },
153
+ body: JSON.stringify({
154
+ &quot;prompt&quot;: &quot;An architectural photograph of a concrete stairwell, hard afternoon light, 35mm&quot;,
155
+ &quot;size&quot;: &quot;2048*2048&quot;,
156
+ &quot;enable_base64_output&quot;: false,
157
+ &quot;enable_sync_mode&quot;: false
158
+ }),
159
+ });
160
+ const { data: { id } } = await submit.json();
161
+
162
+ // poll
163
+ for (;;) {
164
+ const res = await fetch(`${API}/predictions/${id}/result`, { headers });
165
+ const { data } = await res.json();
166
+
167
+ if (data.status === &quot;completed&quot;) {
168
+ console.log(data.outputs[0]);
169
+ break;
170
+ }
171
+ if (data.status === &quot;failed&quot;) throw new Error(data.error ?? &quot;generation failed&quot;);
172
+ await new Promise((r) =&gt; setTimeout(r, 1500));
173
+ }</code></pre>
174
+ </div>
175
+ <div class="callout"><p>Requests are asynchronous: <code>POST</code> returns a request id, then you poll <code>/predictions/&lt;id&gt;/result</code> until <code>status</code> is <code>completed</code>. Set <code>enable_sync_mode: true</code> to have the call block and return outputs directly.</p><p>API keys are created in the <a href="https://wavespeed.ai/dashboard" target="_blank" rel="noopener">WaveSpeed dashboard</a>.</p></div>
176
+ <div class="btn-row">
177
+ <a class="btn" href="https://wavespeed.ai/models/bytedance/seedream-v4" target="_blank" rel="noopener">Open on WaveSpeed</a>
178
+ <a class="btn secondary" href="https://wavespeed.ai/docs" target="_blank" rel="noopener">API reference</a>
179
+ </div>
180
+ </section>
181
+ <section id="resources">
182
+ <h2>Resources</h2>
183
+ <ul class="links">
184
+ <li><a href="https://wavespeed.ai/models/bytedance/seedream-v4" target="_blank" rel="noopener"><span>Seedream 4.0 endpoint</span><span class="host">wavespeed.ai</span></a></li>
185
+ <li><a href="https://wavespeed.ai/models/bytedance/seedream-v4/edit" target="_blank" rel="noopener"><span>Editing endpoint</span><span class="host">wavespeed.ai</span></a></li>
186
+ <li><a href="https://wavespeed.ai/models/bytedance/seedream-v4.5" target="_blank" rel="noopener"><span>Seedream 4.5</span><span class="host">wavespeed.ai</span></a></li>
187
+ <li><a href="https://wavespeed.ai/collections/bytedance" target="_blank" rel="noopener"><span>ByteDance collection</span><span class="host">wavespeed.ai</span></a></li>
188
+ </ul>
189
+ </section>
190
+ </main>
191
+
192
+ <footer class="site-footer">
193
+ <div class="wrap">
194
+ <p>This page is a model reference maintained by WaveSpeed AI. The model itself is developed and released by its respective authors; trademarks belong to them. WaveSpeed AI provides hosted inference for it.</p>
195
+ <div class="footer-links">
196
+ <a href="https://wavespeed.ai" target="_blank" rel="noopener">WaveSpeed AI</a>
197
+ <a href="https://wavespeed.ai/docs" target="_blank" rel="noopener">Docs</a>
198
+ <a href="https://huggingface.co/wavespeed" target="_blank" rel="noopener">Hugging Face</a>
199
+ </div>
200
+ </div>
201
+ </footer>
202
+
203
+ <script src="tabs.js"></script>
204
+ </body>
205
  </html>
style.css CHANGED
@@ -1,28 +1,575 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  }
5
 
6
  h1 {
7
- font-size: 16px;
8
- margin-top: 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  }
10
 
11
  p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
 
 
 
 
 
 
16
  }
17
 
18
  .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  }
25
 
26
- .card p:last-child {
27
- margin-bottom: 0;
 
 
28
  }
 
1
+ /* ==========================================================================
2
+ Shared stylesheet for wavespeed model reference pages.
3
+ Edit here, then run _shared/sync.sh to copy into each Space.
4
+ ========================================================================== */
5
+
6
+ :root {
7
+ --bg: #ffffff;
8
+ --bg-soft: #f7f8fa;
9
+ --bg-code: #f4f5f7;
10
+ --fg: #14161a;
11
+ --fg-muted: #5b6270;
12
+ --fg-faint: #868d9b;
13
+ --line: #e4e6eb;
14
+ --line-strong: #d2d6de;
15
+ --accent: #2f5bd7;
16
+ --accent-soft: #eef2fd;
17
+ --radius: 10px;
18
+ --radius-sm: 6px;
19
+ --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
20
+ --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
21
+ "Noto Sans", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
22
+ --maxw: 880px;
23
+ }
24
+
25
+ @media (prefers-color-scheme: dark) {
26
+ :root {
27
+ --bg: #0e1014;
28
+ --bg-soft: #161920;
29
+ --bg-code: #14171d;
30
+ --fg: #e7e9ee;
31
+ --fg-muted: #99a1b0;
32
+ --fg-faint: #6f7788;
33
+ --line: #252a33;
34
+ --line-strong: #333a46;
35
+ --accent: #7ba1ff;
36
+ --accent-soft: #172136;
37
+ }
38
+ }
39
+
40
+ *,
41
+ *::before,
42
+ *::after {
43
+ box-sizing: border-box;
44
+ }
45
+
46
+ html {
47
+ -webkit-text-size-adjust: 100%;
48
+ scroll-behavior: smooth;
49
+ }
50
+
51
  body {
52
+ margin: 0;
53
+ background: var(--bg);
54
+ color: var(--fg);
55
+ font-family: var(--sans);
56
+ font-size: 16px;
57
+ line-height: 1.65;
58
+ -webkit-font-smoothing: antialiased;
59
+ }
60
+
61
+ a {
62
+ color: var(--accent);
63
+ text-decoration: none;
64
+ }
65
+
66
+ a:hover {
67
+ text-decoration: underline;
68
+ }
69
+
70
+ img,
71
+ video {
72
+ max-width: 100%;
73
+ height: auto;
74
+ display: block;
75
+ }
76
+
77
+ hr {
78
+ border: 0;
79
+ border-top: 1px solid var(--line);
80
+ margin: 3rem 0;
81
+ }
82
+
83
+ .wrap {
84
+ max-width: var(--maxw);
85
+ margin: 0 auto;
86
+ padding: 0 20px;
87
+ }
88
+
89
+ /* --- header ------------------------------------------------------------- */
90
+
91
+ .site-header {
92
+ border-bottom: 1px solid var(--line);
93
+ background: var(--bg);
94
+ position: sticky;
95
+ top: 0;
96
+ z-index: 20;
97
+ }
98
+
99
+ .site-header .wrap {
100
+ display: flex;
101
+ align-items: center;
102
+ justify-content: space-between;
103
+ gap: 16px;
104
+ height: 56px;
105
+ }
106
+
107
+ .brand {
108
+ display: flex;
109
+ align-items: center;
110
+ gap: 9px;
111
+ font-weight: 600;
112
+ font-size: 0.9rem;
113
+ color: var(--fg);
114
+ letter-spacing: -0.01em;
115
+ }
116
+
117
+ .brand:hover {
118
+ text-decoration: none;
119
+ }
120
+
121
+ .brand-mark {
122
+ width: 20px;
123
+ height: 20px;
124
+ border-radius: 5px;
125
+ background: var(--accent);
126
+ flex: 0 0 auto;
127
+ position: relative;
128
+ }
129
+
130
+ .brand-mark::after {
131
+ content: "";
132
+ position: absolute;
133
+ inset: 6px 4px;
134
+ border-left: 2px solid var(--bg);
135
+ border-right: 2px solid var(--bg);
136
+ opacity: 0.85;
137
+ }
138
+
139
+ .header-nav {
140
+ display: flex;
141
+ align-items: center;
142
+ gap: 20px;
143
+ font-size: 0.86rem;
144
+ }
145
+
146
+ .header-nav a {
147
+ color: var(--fg-muted);
148
+ }
149
+
150
+ .header-nav a:hover {
151
+ color: var(--fg);
152
+ text-decoration: none;
153
+ }
154
+
155
+ .brand span:last-child {
156
+ white-space: nowrap;
157
+ }
158
+
159
+ @media (max-width: 760px) {
160
+ .header-nav a.opt {
161
+ display: none;
162
+ }
163
+ }
164
+
165
+ /* On phones the in-page anchors aren't worth the crowding — the page is short
166
+ enough to scroll, and the one outbound link needs the room. */
167
+ @media (max-width: 560px) {
168
+ .header-nav a.jump {
169
+ display: none;
170
+ }
171
+ }
172
+
173
+ /* --- hero --------------------------------------------------------------- */
174
+
175
+ .hero {
176
+ padding: 56px 0 12px;
177
+ }
178
+
179
+ .eyebrow {
180
+ font-size: 0.72rem;
181
+ font-weight: 600;
182
+ letter-spacing: 0.08em;
183
+ text-transform: uppercase;
184
+ color: var(--fg-faint);
185
+ margin: 0 0 14px;
186
  }
187
 
188
  h1 {
189
+ font-size: 2.6rem;
190
+ line-height: 1.15;
191
+ letter-spacing: -0.025em;
192
+ font-weight: 700;
193
+ margin: 0 0 14px;
194
+ }
195
+
196
+ .lede {
197
+ font-size: 1.12rem;
198
+ color: var(--fg-muted);
199
+ margin: 0;
200
+ max-width: 68ch;
201
+ }
202
+
203
+ @media (max-width: 620px) {
204
+ h1 {
205
+ font-size: 2rem;
206
+ }
207
+ .hero {
208
+ padding-top: 36px;
209
+ }
210
+ }
211
+
212
+ /* metadata strip under the title */
213
+ .meta {
214
+ display: flex;
215
+ flex-wrap: wrap;
216
+ gap: 8px 10px;
217
+ margin: 26px 0 0;
218
+ padding: 0;
219
+ list-style: none;
220
+ }
221
+
222
+ .meta li {
223
+ font-size: 0.78rem;
224
+ font-family: var(--mono);
225
+ color: var(--fg-muted);
226
+ border: 1px solid var(--line);
227
+ border-radius: 999px;
228
+ padding: 3px 11px;
229
+ white-space: nowrap;
230
+ }
231
+
232
+ .meta li b {
233
+ font-weight: 500;
234
+ color: var(--fg-faint);
235
+ }
236
+
237
+ /* --- sections ----------------------------------------------------------- */
238
+
239
+ section {
240
+ padding: 44px 0 0;
241
+ }
242
+
243
+ h2 {
244
+ font-size: 1.35rem;
245
+ letter-spacing: -0.015em;
246
+ font-weight: 650;
247
+ margin: 0 0 6px;
248
+ scroll-margin-top: 76px;
249
+ }
250
+
251
+ .section-note {
252
+ color: var(--fg-muted);
253
+ margin: 0 0 22px;
254
+ font-size: 0.95rem;
255
+ max-width: 70ch;
256
+ }
257
+
258
+ h3 {
259
+ font-size: 1rem;
260
+ font-weight: 620;
261
+ margin: 0 0 6px;
262
+ letter-spacing: -0.005em;
263
  }
264
 
265
  p {
266
+ margin: 0 0 1rem;
267
+ }
268
+
269
+ /* --- cards -------------------------------------------------------------- */
270
+
271
+ .grid {
272
+ display: grid;
273
+ grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
274
+ gap: 14px;
275
+ margin: 0;
276
  }
277
 
278
  .card {
279
+ border: 1px solid var(--line);
280
+ border-radius: var(--radius);
281
+ padding: 18px 18px 16px;
282
+ background: var(--bg);
283
+ }
284
+
285
+ .card p {
286
+ margin: 0;
287
+ color: var(--fg-muted);
288
+ font-size: 0.9rem;
289
+ line-height: 1.6;
290
+ }
291
+
292
+ .card p + p {
293
+ margin-top: 0.6rem;
294
+ }
295
+
296
+ /* --- figures ------------------------------------------------------------ */
297
+
298
+ figure {
299
+ margin: 0 0 18px;
300
+ border: 1px solid var(--line);
301
+ border-radius: var(--radius);
302
+ overflow: hidden;
303
+ background: var(--bg-soft);
304
+ }
305
+
306
+ figure img,
307
+ figure video {
308
+ width: 100%;
309
+ display: block;
310
+ }
311
+
312
+ figcaption {
313
+ font-size: 0.82rem;
314
+ color: var(--fg-muted);
315
+ padding: 10px 14px;
316
+ border-top: 1px solid var(--line);
317
+ background: var(--bg);
318
+ }
319
+
320
+ figcaption b {
321
+ color: var(--fg);
322
+ font-weight: 600;
323
+ }
324
+
325
+ .figure-grid {
326
+ display: grid;
327
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
328
+ gap: 14px;
329
+ }
330
+
331
+ .figure-grid figure {
332
+ margin: 0;
333
+ }
334
+
335
+ /* --- tables ------------------------------------------------------------- */
336
+
337
+ .table-scroll {
338
+ overflow-x: auto;
339
+ border: 1px solid var(--line);
340
+ border-radius: var(--radius);
341
+ }
342
+
343
+ table {
344
+ border-collapse: collapse;
345
+ width: 100%;
346
+ font-size: 0.88rem;
347
+ min-width: 480px;
348
+ }
349
+
350
+ th,
351
+ td {
352
+ text-align: left;
353
+ padding: 10px 14px;
354
+ border-bottom: 1px solid var(--line);
355
+ }
356
+
357
+ th {
358
+ font-weight: 600;
359
+ font-size: 0.76rem;
360
+ letter-spacing: 0.05em;
361
+ text-transform: uppercase;
362
+ color: var(--fg-faint);
363
+ background: var(--bg-soft);
364
+ white-space: nowrap;
365
+ }
366
+
367
+ tbody tr:last-child td {
368
+ border-bottom: 0;
369
+ }
370
+
371
+ td code {
372
+ font-size: 0.85em;
373
+ }
374
+
375
+ /* --- code --------------------------------------------------------------- */
376
+
377
+ .code {
378
+ border: 1px solid var(--line);
379
+ border-radius: var(--radius);
380
+ overflow: hidden;
381
+ background: var(--bg-code);
382
+ margin: 0 0 14px;
383
+ }
384
+
385
+ .code-tabs {
386
+ display: flex;
387
+ gap: 2px;
388
+ padding: 6px 6px 0;
389
+ border-bottom: 1px solid var(--line);
390
+ background: var(--bg-soft);
391
+ }
392
+
393
+ .code-tabs button {
394
+ appearance: none;
395
+ border: 0;
396
+ background: transparent;
397
+ font-family: var(--mono);
398
+ font-size: 0.78rem;
399
+ color: var(--fg-muted);
400
+ padding: 7px 12px;
401
+ border-radius: var(--radius-sm) var(--radius-sm) 0 0;
402
+ cursor: pointer;
403
+ line-height: 1;
404
+ }
405
+
406
+ .code-tabs button:hover {
407
+ color: var(--fg);
408
+ }
409
+
410
+ .code-tabs button[aria-selected="true"] {
411
+ background: var(--bg-code);
412
+ color: var(--fg);
413
+ font-weight: 600;
414
+ box-shadow: inset 0 -1px 0 var(--bg-code);
415
+ }
416
+
417
+ .code pre {
418
+ margin: 0;
419
+ padding: 16px 18px;
420
+ overflow-x: auto;
421
+ font-family: var(--mono);
422
+ font-size: 0.815rem;
423
+ line-height: 1.65;
424
+ tab-size: 2;
425
+ }
426
+
427
+ .code pre[hidden] {
428
+ display: none;
429
+ }
430
+
431
+ code {
432
+ font-family: var(--mono);
433
+ font-size: 0.88em;
434
+ }
435
+
436
+ p code,
437
+ li code {
438
+ background: var(--bg-code);
439
+ border: 1px solid var(--line);
440
+ border-radius: 4px;
441
+ padding: 1px 5px;
442
+ }
443
+
444
+ /* --- links list --------------------------------------------------------- */
445
+
446
+ .links {
447
+ display: grid;
448
+ grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
449
+ gap: 10px;
450
+ list-style: none;
451
+ padding: 0;
452
+ margin: 0;
453
+ }
454
+
455
+ .links a {
456
+ display: flex;
457
+ align-items: baseline;
458
+ justify-content: space-between;
459
+ gap: 10px;
460
+ border: 1px solid var(--line);
461
+ border-radius: var(--radius-sm);
462
+ padding: 11px 14px;
463
+ color: var(--fg);
464
+ font-size: 0.9rem;
465
+ font-weight: 500;
466
+ }
467
+
468
+ .links a:hover {
469
+ border-color: var(--line-strong);
470
+ background: var(--bg-soft);
471
+ text-decoration: none;
472
+ }
473
+
474
+ .links .host {
475
+ font-family: var(--mono);
476
+ font-size: 0.72rem;
477
+ color: var(--fg-faint);
478
+ font-weight: 400;
479
+ }
480
+
481
+ /* --- run-it callout (the one place we ask for a click) ------------------- */
482
+
483
+ .callout {
484
+ border: 1px solid var(--line);
485
+ border-left: 3px solid var(--accent);
486
+ background: var(--bg-soft);
487
+ border-radius: var(--radius-sm);
488
+ padding: 16px 18px;
489
+ font-size: 0.92rem;
490
+ color: var(--fg-muted);
491
+ }
492
+
493
+ .callout p {
494
+ margin: 0;
495
+ }
496
+
497
+ .callout p + p {
498
+ margin-top: 0.5rem;
499
+ }
500
+
501
+ .btn-row {
502
+ display: flex;
503
+ flex-wrap: wrap;
504
+ gap: 10px;
505
+ margin-top: 18px;
506
+ }
507
+
508
+ .btn {
509
+ display: inline-flex;
510
+ align-items: center;
511
+ gap: 7px;
512
+ border-radius: var(--radius-sm);
513
+ padding: 9px 16px;
514
+ font-size: 0.89rem;
515
+ font-weight: 550;
516
+ border: 1px solid var(--accent);
517
+ background: var(--accent);
518
+ color: #fff;
519
+ line-height: 1.4;
520
+ }
521
+
522
+ .btn:hover {
523
+ text-decoration: none;
524
+ filter: brightness(1.08);
525
+ }
526
+
527
+ .btn.secondary {
528
+ background: transparent;
529
+ color: var(--fg);
530
+ border-color: var(--line-strong);
531
+ }
532
+
533
+ .btn.secondary:hover {
534
+ background: var(--bg-soft);
535
+ filter: none;
536
+ }
537
+
538
+ @media (prefers-color-scheme: dark) {
539
+ .btn {
540
+ color: #0e1014;
541
+ }
542
+ }
543
+
544
+ /* --- footer ------------------------------------------------------------- */
545
+
546
+ .site-footer {
547
+ margin-top: 64px;
548
+ border-top: 1px solid var(--line);
549
+ padding: 26px 0 44px;
550
+ font-size: 0.83rem;
551
+ color: var(--fg-faint);
552
+ }
553
+
554
+ .site-footer .wrap {
555
+ display: flex;
556
+ flex-wrap: wrap;
557
+ gap: 10px 24px;
558
+ align-items: baseline;
559
+ justify-content: space-between;
560
+ }
561
+
562
+ .site-footer a {
563
+ color: var(--fg-muted);
564
+ }
565
+
566
+ .site-footer p {
567
+ margin: 0;
568
+ max-width: 60ch;
569
  }
570
 
571
+ .footer-links {
572
+ display: flex;
573
+ gap: 16px;
574
+ flex-wrap: wrap;
575
  }
tabs.js ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Code-sample tab switcher. Progressive enhancement: with JS disabled every
2
+ // panel except the first stays hidden via the `hidden` attribute in the markup,
3
+ // so the page still shows one working example.
4
+ document.querySelectorAll(".code-tabs").forEach((tabs) => {
5
+ const buttons = [...tabs.querySelectorAll("button")];
6
+ const panels = buttons.map((b) => document.getElementById(b.dataset.panel));
7
+
8
+ const select = (i) => {
9
+ buttons.forEach((b, j) => b.setAttribute("aria-selected", String(i === j)));
10
+ panels.forEach((p, j) => p && (p.hidden = i !== j));
11
+ };
12
+
13
+ buttons.forEach((b, i) => b.addEventListener("click", () => select(i)));
14
+ select(0);
15
+ });