Website for the Lede browser extension.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Polish landing sections; redesign install journey with rail and copy controls

+686 -181
+35 -19
src/components/sections/EditorialValue.astro
··· 1 - <section id="product" class="section section--editorial wrap reveal-scroll" aria-labelledby="product-heading"> 2 - <header class="section__head"> 3 - <p class="eyebrow">Product</p> 4 - <h2 id="product-heading" class="heading-xl">Built for the tab in front of you</h2> 5 - </header> 6 - <div class="editorial-grid"> 7 - <article class="editorial-block"> 8 - <h3 class="heading-md">Verdict first</h3> 9 - <p>One action pulls the lede out of long articles, tickets, and threads so you can bail early or read with intent.</p> 10 - </article> 11 - <article class="editorial-block"> 12 - <h3 class="heading-md">Questions stay grounded</h3> 13 - <p>Follow-ups stick to the page and the summary you already made—no vague answers from something that never read the article.</p> 14 - </article> 15 - <article class="editorial-block"> 16 - <h3 class="heading-md">Your choice of AI</h3> 17 - <p>Use a model on your machine or one you already pay for—set it once in settings instead of being locked to a single vendor.</p> 18 - </article> 19 - </div> 1 + <section 2 + id="product" 3 + class="section section--editorial wrap reveal-scroll" 4 + aria-labelledby="product-heading" 5 + > 6 + <header class="section__head"> 7 + <p class="eyebrow">Product</p> 8 + <h2 id="product-heading" class="heading-xl"> 9 + Built for the tab in front of you 10 + </h2> 11 + </header> 12 + <div class="editorial-grid"> 13 + <article class="editorial-block"> 14 + <h3 class="heading-md">Verdict first</h3> 15 + <p> 16 + Find out the most important part first. Figure out whether it's 17 + something you want to read more about, or something you don't. 18 + </p> 19 + </article> 20 + <article class="editorial-block"> 21 + <h3 class="heading-md">Questions that inform</h3> 22 + <p> 23 + Ask what you really want to know. Why is this page worth 24 + reading? Is there a certain peice of information you want to 25 + make sure this article has? 26 + </p> 27 + </article> 28 + <article class="editorial-block"> 29 + <h3 class="heading-md">Your choice of AI</h3> 30 + <p> 31 + Use a model on your machine, via Ollama's free cloud, or bring 32 + your own OpenAI compatable provider. Completely up to you. 33 + </p> 34 + </article> 35 + </div> 20 36 </section>
+1 -1
src/components/sections/HeroWithProof.astro
··· 24 24 you want to read more. 25 25 </p> 26 26 <p class="hero-whisper lede--tight reveal-item" style="--i: 3"> 27 - Main point first—rabbit holes optional. 27 + Main point first. Rabbit holes optional. 28 28 </p> 29 29 <ul 30 30 class="hero-highlights reveal-item"
+280 -46
src/components/sections/InstallJourney.astro
··· 21 21 > 22 22 Install the beta 23 23 </h2> 24 + <p class="install-deck reveal-item" style="--i: 2"> 25 + Four steps: get the repo, build for your browser, load it in 26 + developer mode, then point Lede at a model. 27 + </p> 24 28 </header> 25 29 26 - <div 27 - class="journey-band journey-band--callout reveal-item" 28 - style="--i: 2" 29 - > 30 - <p class="journey-band__text"> 31 - Lede isn&apos;t in the Chrome or Firefox stores yet. Download 32 - the extension folder from the project page, then add it to your 33 - browser—full click-by-click steps are there too. 34 - </p> 35 - </div> 36 - 37 - <div 38 - class="journey-strip reveal-item" 30 + <ol 31 + class="install-timeline reveal-item" 39 32 style="--i: 3" 40 - aria-label="Three-step overview" 33 + aria-label="Four steps to install the beta" 41 34 > 42 - <div class="journey-strip__cell"> 43 - <span class="journey-strip__num" aria-hidden="true">1</span> 44 - <h3 class="journey-strip__title">Grab the build</h3> 45 - <p class="journey-strip__body"> 46 - Open the project page and download the extension files to 47 - your computer. 48 - </p> 49 - <a href={tangled} target="_blank" rel="noopener noreferrer" 50 - >Open project page</a 51 - > 52 - </div> 53 - <div class="journey-strip__cell"> 54 - <span class="journey-strip__num" aria-hidden="true">2</span> 55 - <h3 class="journey-strip__title">Add it in the browser</h3> 56 - <p class="journey-strip__body"> 57 - Chrome and Firefox each have a simple “load from folder” 58 - flow for betas. 59 - </p> 60 - </div> 61 - <div class="journey-strip__cell"> 62 - <span class="journey-strip__num" aria-hidden="true">3</span> 63 - <h3 class="journey-strip__title">Pick how Lede thinks</h3> 64 - <p class="journey-strip__body"> 65 - In settings, choose a local model or another provider you 66 - already use. 67 - </p> 68 - </div> 69 - </div> 35 + <li class="install-timeline__step"> 36 + <div class="install-timeline__rail" aria-hidden="true"> 37 + <span class="install-timeline__marker">1</span> 38 + </div> 39 + <div class="install-timeline__content"> 40 + <p class="install-timeline__kicker">Source</p> 41 + <h3 class="install-timeline__title">Get the code</h3> 42 + <p class="install-timeline__body"> 43 + In a terminal, from the directory where you want the 44 + project: 45 + </p> 46 + <div class="install-snippet-wrap"> 47 + <pre 48 + class="install-timeline__snippet" 49 + ><code 50 + >git clone https://tangled.org/ellioth.co/summarizer-extension</code 51 + ></pre> 52 + <button 53 + type="button" 54 + class="install-snippet-copy" 55 + aria-label="Copy git clone command to clipboard" 56 + title="Copy" 57 + > 58 + <span 59 + class="install-snippet-copy__icon install-snippet-copy__icon--copy" 60 + aria-hidden="true" 61 + > 62 + <svg 63 + width="17" 64 + height="17" 65 + viewBox="0 0 24 24" 66 + fill="none" 67 + stroke="currentColor" 68 + stroke-width="2" 69 + stroke-linecap="round" 70 + stroke-linejoin="round" 71 + > 72 + <rect 73 + x="9" 74 + y="9" 75 + width="13" 76 + height="13" 77 + rx="2" 78 + ry="2"></rect> 79 + <path 80 + d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" 81 + ></path> 82 + </svg> 83 + </span> 84 + <span 85 + class="install-snippet-copy__icon install-snippet-copy__icon--check" 86 + aria-hidden="true" 87 + > 88 + <svg 89 + width="17" 90 + height="17" 91 + viewBox="0 0 24 24" 92 + fill="none" 93 + stroke="currentColor" 94 + stroke-width="2.25" 95 + stroke-linecap="round" 96 + stroke-linejoin="round" 97 + > 98 + <polyline points="20 6 9 17 4 12"></polyline> 99 + </svg> 100 + </span> 101 + </button> 102 + <span 103 + class="install-snippet-live" 104 + role="status" 105 + aria-live="polite" 106 + aria-atomic="true"></span> 107 + </div> 108 + <p 109 + class="install-timeline__body install-timeline__body--muted" 110 + > 111 + Prefer a UI? Download the same folder from Tangled in 112 + the browser instead of cloning. 113 + </p> 114 + <a 115 + class="install-timeline__link" 116 + href={tangled} 117 + target="_blank" 118 + rel="noopener noreferrer">Open on Tangled</a 119 + > 120 + </div> 121 + </li> 122 + <li class="install-timeline__step"> 123 + <div class="install-timeline__rail" aria-hidden="true"> 124 + <span class="install-timeline__marker">2</span> 125 + </div> 126 + <div class="install-timeline__content"> 127 + <p class="install-timeline__kicker">Build</p> 128 + <h3 class="install-timeline__title">Build the manifest</h3> 129 + <p class="install-timeline__body"> 130 + Enter the repo, then run the script for the browser you 131 + use: 132 + </p> 133 + <div class="install-snippet-wrap"> 134 + <pre 135 + class="install-timeline__snippet" 136 + ><code>cd summarizer-extension 137 + ./build.sh chrome # or: ./build.sh firefox</code></pre> 138 + <button 139 + type="button" 140 + class="install-snippet-copy" 141 + aria-label="Copy build commands to clipboard" 142 + title="Copy" 143 + > 144 + <span 145 + class="install-snippet-copy__icon install-snippet-copy__icon--copy" 146 + aria-hidden="true" 147 + > 148 + <svg 149 + width="17" 150 + height="17" 151 + viewBox="0 0 24 24" 152 + fill="none" 153 + stroke="currentColor" 154 + stroke-width="2" 155 + stroke-linecap="round" 156 + stroke-linejoin="round" 157 + > 158 + <rect 159 + x="9" 160 + y="9" 161 + width="13" 162 + height="13" 163 + rx="2" 164 + ry="2"></rect> 165 + <path 166 + d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" 167 + ></path> 168 + </svg> 169 + </span> 170 + <span 171 + class="install-snippet-copy__icon install-snippet-copy__icon--check" 172 + aria-hidden="true" 173 + > 174 + <svg 175 + width="17" 176 + height="17" 177 + viewBox="0 0 24 24" 178 + fill="none" 179 + stroke="currentColor" 180 + stroke-width="2.25" 181 + stroke-linecap="round" 182 + stroke-linejoin="round" 183 + > 184 + <polyline points="20 6 9 17 4 12"></polyline> 185 + </svg> 186 + </span> 187 + </button> 188 + <span 189 + class="install-snippet-live" 190 + role="status" 191 + aria-live="polite" 192 + aria-atomic="true"></span> 193 + </div> 194 + </div> 195 + </li> 196 + <li class="install-timeline__step"> 197 + <div class="install-timeline__rail" aria-hidden="true"> 198 + <span class="install-timeline__marker">3</span> 199 + </div> 200 + <div class="install-timeline__content"> 201 + <p class="install-timeline__kicker">Load</p> 202 + <h3 class="install-timeline__title"> 203 + Load it in the browser 204 + </h3> 205 + <div class="install-browser-split"> 206 + <div class="install-browser-split__col"> 207 + <p class="install-browser-split__tag">Chrome</p> 208 + <p 209 + class="install-timeline__body install-timeline__body--compact" 210 + > 211 + Extensions → turn on <strong 212 + >Developer mode</strong 213 + > → <strong>Load unpacked</strong> → pick the built 214 + folder. 215 + </p> 216 + </div> 217 + <div class="install-browser-split__col"> 218 + <p class="install-browser-split__tag">Firefox</p> 219 + <p 220 + class="install-timeline__body install-timeline__body--compact" 221 + > 222 + Debugging → <strong 223 + >Load Temporary Add-on</strong 224 + > → choose <code>manifest.json</code> in that folder. 225 + </p> 226 + </div> 227 + </div> 228 + <p 229 + class="install-timeline__body install-timeline__body--muted" 230 + > 231 + Step-by-step URLs and menus: 232 + <a 233 + class="install-timeline__anchor" 234 + href="#install-browser-details" 235 + >full Chrome &amp; Firefox checklist</a 236 + >. 237 + </p> 238 + </div> 239 + </li> 240 + <li class="install-timeline__step"> 241 + <div class="install-timeline__rail" aria-hidden="true"> 242 + <span class="install-timeline__marker">4</span> 243 + </div> 244 + <div class="install-timeline__content"> 245 + <p class="install-timeline__kicker">Model</p> 246 + <h3 class="install-timeline__title">Choose a model</h3> 247 + <p class="install-timeline__body"> 248 + In Lede&apos;s settings, pick a model and provider. 249 + Ollama is the quickest start; an OpenAI-compatible API 250 + works if you already use one. 251 + </p> 252 + </div> 253 + </li> 254 + </ol> 70 255 71 256 <details 72 - class="disclosure disclosure--animate reveal-item" 257 + id="install-browser-details" 258 + class="disclosure disclosure--animate disclosure--install reveal-item" 73 259 style="--i: 4" 74 260 > 75 - <summary>Step-by-step for Chrome or Firefox</summary> 261 + <summary>Checklist: Installing in Chrome or Firefox</summary> 76 262 <div class="disclosure__collapse"> 77 263 <div class="disclosure__collapse-sheet"> 78 264 <div class="disclosure__body install-wrap"> ··· 128 314 </li> 129 315 </ol> 130 316 <p class="firefox-note"> 131 - Temporary add-ons clear when you fully quit 132 - Firefox—reload during active dev sessions. 317 + A full Firefox quit drops temporary add-ons; 318 + load the add-on again while you&apos;re 319 + iterating. 133 320 </p> 134 321 </div> 135 322 </div> ··· 139 326 </details> 140 327 </div> 141 328 </section> 329 + 330 + <script is:inline> 331 + (function () { 332 + var root = document.getElementById("install"); 333 + if (!root) return; 334 + 335 + root.querySelectorAll(".install-snippet-wrap").forEach(function (wrap) { 336 + var btn = wrap.querySelector(".install-snippet-copy"); 337 + var code = wrap.querySelector("code"); 338 + var live = wrap.querySelector(".install-snippet-live"); 339 + if (!btn || !code || btn.dataset.installCopyBound) return; 340 + btn.dataset.installCopyBound = "1"; 341 + 342 + var labelDefault = btn.getAttribute("aria-label") || "Copy to clipboard"; 343 + 344 + btn.addEventListener("click", function () { 345 + var text = code.textContent || ""; 346 + 347 + function clearLive() { 348 + if (live) live.textContent = ""; 349 + } 350 + 351 + function showCopied() { 352 + wrap.classList.add("is-copied"); 353 + btn.setAttribute("aria-label", "Copied to clipboard"); 354 + if (live) live.textContent = "Copied to clipboard"; 355 + window.setTimeout(function () { 356 + wrap.classList.remove("is-copied"); 357 + btn.setAttribute("aria-label", labelDefault); 358 + clearLive(); 359 + }, 2000); 360 + } 361 + 362 + function showFailed() { 363 + if (live) live.textContent = "Copy failed"; 364 + window.setTimeout(clearLive, 2500); 365 + } 366 + 367 + if (navigator.clipboard && navigator.clipboard.writeText) { 368 + navigator.clipboard.writeText(text).then(showCopied).catch(showFailed); 369 + } else { 370 + showFailed(); 371 + } 372 + }); 373 + }); 374 + })(); 375 + </script>
+46 -32
src/components/sections/UsageMicro.astro
··· 1 - <section id="usage" class="section section--tight-top wrap reveal-scroll" aria-labelledby="usage-heading"> 2 - <header class="section__head"> 3 - <p class="eyebrow">In the popup</p> 4 - <h2 id="usage-heading" class="heading-xl">Three beats</h2> 5 - </header> 6 - <ol class="usage-flow"> 7 - <li> 8 - <span class="usage-flow__label">01</span> 9 - <div> 10 - <h3 class="heading-md">Land on the page</h3> 11 - <p>Article, doc, issue—whatever you need to judge quickly.</p> 12 - </div> 13 - </li> 14 - <li> 15 - <span class="usage-flow__label">02</span> 16 - <div> 17 - <h3 class="heading-md">Open Lede</h3> 18 - <p>Toolbar icon, context menu, or the keyboard shortcut.</p> 19 - </div> 20 - </li> 21 - <li> 22 - <span class="usage-flow__label">03</span> 23 - <div> 24 - <h3 class="heading-md">Summarize or ask</h3> 25 - <p>Summarize for the lede, or type a question about this tab and press Enter.</p> 26 - </div> 27 - </li> 28 - </ol> 1 + <section 2 + id="usage" 3 + class="section section--tight-top wrap reveal-scroll" 4 + aria-labelledby="usage-heading" 5 + > 6 + <header class="section__head"> 7 + <p class="eyebrow">In the popup</p> 8 + <h2 id="usage-heading" class="heading-xl">Three beats</h2> 9 + </header> 10 + <ol class="usage-flow"> 11 + <li> 12 + <span class="usage-flow__label">01</span> 13 + <div> 14 + <h3 class="heading-md">Land on the page</h3> 15 + <p> 16 + Nearly any text-based site. Articles, docs, Reddit 17 + thread—whatever you need to judge quickly. 18 + </p> 19 + </div> 20 + </li> 21 + <li> 22 + <span class="usage-flow__label">02</span> 23 + <div> 24 + <h3 class="heading-md">Open Lede</h3> 25 + <p> 26 + Click the toolbar icon, via the right-click context menu, or 27 + set up a keyboard shortcut for instant summaries. 28 + </p> 29 + </div> 30 + </li> 31 + <li> 32 + <span class="usage-flow__label">03</span> 33 + <div> 34 + <h3 class="heading-md">Summarize or ask</h3> 35 + <p> 36 + Instantly pick out the main takeaways and ask follow-up 37 + questions if needed. 38 + </p> 39 + </div> 40 + </li> 41 + </ol> 29 42 30 - <p class="usage-afterword"> 31 - The preview above matches the real popup—try the theme, <strong>Quick Summary</strong>, and chat. After you install, 32 - those same controls work on whatever page you have open. 33 - </p> 43 + <p class="usage-afterword"> 44 + The demo at the top of the page above matches the real extension, so 45 + click all of the buttons! After you install, those same controls work on 46 + whatever page you have open. 47 + </p> 34 48 </section>
+324 -83
src/styles/global.css
··· 971 971 transition-duration: 0.01ms; 972 972 } 973 973 974 - .journey-strip__cell, 975 - .journey-band--callout, 974 + .install-timeline__link, 975 + .install-snippet-copy, 976 976 .editorial-block, 977 977 .nav a, 978 978 .footer-links a, 979 979 .brand-lockup, 980 - .journey-strip__cell a, 981 980 .hero-highlights li, 982 981 .site-header, 983 982 .site-header::after, ··· 986 985 animation: none !important; 987 986 } 988 987 989 - .journey-strip__cell:hover, 990 - .journey-strip__cell:active, 991 - .journey-band--callout:hover, 992 988 .editorial-block:hover, 993 989 .nav a:hover, 994 990 .footer-links a:hover, 995 991 .brand-lockup:hover, 996 - .journey-strip__cell:hover .journey-strip__num, 997 - .journey-strip__cell a:hover { 992 + .install-timeline__link:hover { 998 993 transform: none; 999 994 } 1000 995 ··· 1004 999 1005 1000 .hero-highlights li:hover .hero-highlights__icon svg { 1006 1001 transform: none; 1002 + } 1003 + 1004 + .install-snippet-copy__icon { 1005 + transition: opacity 0.01ms; 1007 1006 } 1008 1007 } 1009 1008 ··· 1290 1289 } 1291 1290 1292 1291 .section--install { 1292 + position: relative; 1293 + isolation: isolate; 1293 1294 padding-top: clamp(var(--space-2xl), 5vw, var(--space-3xl)); 1294 1295 padding-bottom: var(--section-y-room); 1296 + background: 1297 + radial-gradient(ellipse 70% 55% at 100% 12%, var(--color-brand-soft), transparent 52%), 1298 + radial-gradient(ellipse 55% 45% at 0% 88%, color-mix(in oklch, var(--color-subtle) 65%, var(--color-canvas)), transparent 50%), 1299 + var(--color-canvas); 1300 + border-top: 1px solid color-mix(in oklch, var(--color-border) 80%, transparent); 1301 + } 1302 + 1303 + #install-browser-details { 1304 + scroll-margin-top: clamp(4.5rem, 12vh, 7rem); 1295 1305 } 1296 1306 1297 1307 .section__head--install { 1298 1308 margin-bottom: clamp(var(--space-md), 2vw, var(--space-lg)); 1299 1309 } 1300 1310 1301 - .journey-band { 1302 - padding: clamp(var(--space-md), 2.5vw, var(--space-lg)); 1303 - border: 1px solid var(--color-border); 1304 - border-radius: var(--radius-md); 1305 - margin-bottom: clamp(var(--space-md), 2vw, var(--space-lg)); 1311 + .install-deck { 1312 + margin: clamp(var(--space-xs), 1.5vw, var(--space-sm)) 0 0; 1313 + max-width: 38rem; 1314 + font-family: var(--font-display); 1315 + font-size: clamp(0.9375rem, 0.35vw + 0.86rem, 1.0625rem); 1316 + font-weight: 600; 1317 + line-height: var(--leading-ui); 1318 + letter-spacing: -0.015em; 1319 + color: color-mix(in oklch, var(--color-ink-secondary) 55%, var(--color-ink)); 1320 + } 1321 + 1322 + /* Install: rail + markers (no heavy side stripe on the list) */ 1323 + .install-timeline { 1324 + list-style: none; 1325 + margin: 0 0 clamp(var(--space-xl), 4vw, var(--space-2xl)); 1326 + padding: 0; 1327 + display: flex; 1328 + flex-direction: column; 1329 + gap: clamp(var(--space-lg), 3vw, var(--space-xl)); 1330 + } 1331 + 1332 + .install-timeline__step { 1333 + display: grid; 1334 + grid-template-columns: clamp(2.5rem, 4.2vw, 3.15rem) minmax(0, 1fr); 1335 + column-gap: clamp(var(--space-md), 2.5vw, var(--space-lg)); 1336 + align-items: stretch; 1337 + } 1338 + 1339 + .install-timeline__rail { 1340 + display: flex; 1341 + flex-direction: column; 1342 + align-items: center; 1343 + padding-top: 0.2rem; 1344 + min-width: 0; 1345 + } 1346 + 1347 + .install-timeline__marker { 1348 + display: grid; 1349 + place-items: center; 1350 + width: 2.35rem; 1351 + height: 2.35rem; 1352 + border-radius: 50%; 1353 + flex-shrink: 0; 1354 + font-family: var(--font-display); 1355 + font-weight: 700; 1356 + font-size: 0.9375rem; 1357 + letter-spacing: -0.04em; 1358 + line-height: 1; 1359 + color: var(--color-on-brand); 1360 + background: var(--color-brand); 1361 + box-shadow: 1362 + 0 0 0 2px color-mix(in oklch, var(--color-canvas) 88%, var(--color-brand)), 1363 + 0 6px 18px color-mix(in oklch, var(--color-brand) 22%, transparent); 1306 1364 } 1307 1365 1308 - .journey-band--callout { 1366 + .install-timeline__step:not(:last-child) .install-timeline__rail::after { 1367 + content: ""; 1368 + flex: 1 1 auto; 1369 + width: 1px; 1370 + margin-top: var(--space-xs); 1371 + min-height: clamp(1.25rem, 3vw, 2rem); 1309 1372 background: linear-gradient( 1310 - 135deg, 1311 - color-mix(in oklch, var(--color-brand-soft) 80%, var(--color-subtle)) 0%, 1312 - color-mix(in oklch, var(--color-subtle) 72%, var(--color-canvas)) 100% 1373 + 180deg, 1374 + color-mix(in oklch, var(--color-brand) 42%, var(--color-border)) 0%, 1375 + color-mix(in oklch, var(--color-brand) 8%, var(--color-border)) 100% 1313 1376 ); 1314 - border-color: color-mix(in oklch, var(--color-brand) 22%, var(--color-border)); 1315 - transition: 1316 - border-color 0.24s var(--ease-out), 1317 - box-shadow 0.24s var(--ease-out), 1318 - transform 0.24s var(--ease-out); 1377 + } 1378 + 1379 + .install-timeline__content { 1380 + display: flex; 1381 + flex-direction: column; 1382 + gap: var(--space-sm); 1383 + max-width: 72ch; 1384 + min-width: 0; 1385 + padding-bottom: var(--space-2xs); 1386 + } 1387 + 1388 + .install-timeline__kicker { 1389 + margin: 0; 1390 + font-family: var(--font-display); 1391 + font-size: var(--text-xs); 1392 + font-weight: 700; 1393 + letter-spacing: var(--tracking-label); 1394 + text-transform: uppercase; 1395 + color: color-mix(in oklch, var(--color-brand) 38%, var(--color-ink-muted)); 1319 1396 } 1320 1397 1321 - .journey-band--callout:hover { 1322 - border-color: color-mix(in oklch, var(--color-brand) 35%, var(--color-border)); 1323 - box-shadow: 0 10px 28px color-mix(in oklch, var(--color-brand) 8%, transparent); 1324 - transform: translateY(-2px); 1398 + .install-timeline__title { 1399 + margin: calc(var(--space-3xs) * -1) 0 0; 1400 + font-family: var(--font-display); 1401 + font-weight: 600; 1402 + font-size: var(--text-md); 1403 + letter-spacing: -0.02em; 1404 + line-height: var(--leading-tight); 1405 + color: var(--color-ink); 1325 1406 } 1326 1407 1327 - .journey-band__text { 1408 + .install-timeline__body { 1328 1409 margin: 0; 1329 1410 font-size: var(--text-sm); 1411 + color: var(--color-ink-secondary); 1330 1412 line-height: var(--leading-prose); 1331 - color: var(--color-ink-secondary); 1332 - max-width: 75ch; 1413 + } 1414 + 1415 + .install-timeline__body--compact { 1416 + line-height: var(--leading-ui); 1417 + } 1418 + 1419 + .install-timeline__body--muted { 1420 + color: var(--color-ink-muted); 1421 + font-size: 0.8125rem; 1333 1422 } 1334 1423 1335 - .journey-band__text strong { 1424 + .install-timeline__body code { 1425 + padding: 0.12em 0.32em; 1426 + border-radius: 4px; 1427 + font-size: 0.92em; 1428 + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; 1429 + background: color-mix(in oklch, var(--color-ink) 6%, var(--color-surface)); 1430 + border: 1px solid color-mix(in oklch, var(--color-border) 88%, var(--color-ink)); 1336 1431 color: var(--color-ink); 1337 1432 } 1338 1433 1339 - .journey-strip { 1340 - display: grid; 1341 - gap: clamp(var(--space-md), 2vw, var(--space-lg)); 1342 - margin-bottom: clamp(var(--space-xl), 4vw, var(--space-2xl)); 1434 + .install-snippet-wrap { 1435 + position: relative; 1436 + max-width: 100%; 1343 1437 } 1344 1438 1345 - @media (min-width: 880px) { 1346 - .journey-strip { 1347 - grid-template-columns: repeat(3, 1fr); 1348 - gap: var(--space-lg); 1349 - } 1439 + .install-snippet-wrap .install-timeline__snippet { 1440 + padding-block: var(--space-md); 1441 + padding-inline: var(--space-md) clamp(2.65rem, 10vw, 3.25rem); 1442 + } 1443 + 1444 + .install-snippet-live { 1445 + position: absolute; 1446 + width: 1px; 1447 + height: 1px; 1448 + padding: 0; 1449 + margin: -1px; 1450 + overflow: hidden; 1451 + clip: rect(0, 0, 0, 0); 1452 + white-space: nowrap; 1453 + border: 0; 1454 + } 1455 + 1456 + .install-snippet-copy { 1457 + position: absolute; 1458 + top: var(--space-xs); 1459 + right: var(--space-xs); 1460 + z-index: 2; 1461 + display: grid; 1462 + place-items: center; 1463 + width: 2.125rem; 1464 + height: 2.125rem; 1465 + margin: 0; 1466 + padding: 0; 1467 + color: color-mix(in oklch, var(--color-ink-muted) 88%, var(--color-ink)); 1468 + background: color-mix(in oklch, var(--color-surface) 92%, transparent); 1469 + border: 1px solid color-mix(in oklch, var(--color-border) 72%, transparent); 1470 + border-radius: var(--radius-sm); 1471 + cursor: pointer; 1472 + box-shadow: 0 1px 2px oklch(0% 0 0 / 0.04); 1473 + opacity: 0; 1474 + pointer-events: none; 1475 + transition: 1476 + opacity 0.22s var(--ease-out), 1477 + color 0.18s var(--ease-out), 1478 + background-color 0.18s var(--ease-out), 1479 + border-color 0.18s var(--ease-out), 1480 + box-shadow 0.18s var(--ease-out); 1350 1481 } 1351 1482 1352 - .journey-strip__cell { 1353 - padding: clamp(var(--space-md), 2.5vw, var(--space-lg)); 1354 - background: var(--color-surface); 1355 - border: 1px solid var(--color-border); 1356 - border-radius: var(--radius-md); 1483 + .install-snippet-copy__icon { 1484 + grid-area: 1 / 1; 1357 1485 display: flex; 1358 - flex-direction: column; 1359 - gap: var(--space-xs); 1486 + align-items: center; 1487 + justify-content: center; 1360 1488 transition: 1361 - transform 0.26s var(--ease-out), 1362 - border-color 0.22s var(--ease-out), 1363 - box-shadow 0.26s var(--ease-out), 1364 - background-color 0.22s var(--ease-out); 1489 + opacity 0.2s var(--ease-out), 1490 + transform 0.22s var(--ease-out), 1491 + color 0.18s var(--ease-out); 1365 1492 } 1366 1493 1367 - .journey-strip__cell:hover { 1368 - transform: translateY(-4px); 1369 - border-color: color-mix(in oklch, var(--color-brand) 28%, var(--color-border)); 1370 - box-shadow: 1371 - 0 1px 0 color-mix(in oklch, var(--color-brand) 12%, var(--color-border)), 1372 - 0 14px 36px color-mix(in oklch, var(--color-ink) 6%, transparent); 1373 - background: color-mix(in oklch, var(--color-surface) 92%, var(--color-brand-soft)); 1494 + .install-snippet-copy__icon svg { 1495 + display: block; 1374 1496 } 1375 1497 1376 - .journey-strip__cell:active { 1377 - transform: translateY(-2px); 1498 + .install-snippet-copy__icon--copy { 1499 + transform: scale(1); 1378 1500 } 1379 1501 1380 - .journey-strip__num { 1381 - font-family: var(--font-display); 1382 - font-weight: 700; 1383 - font-size: clamp(2.25rem, 4.5vw + 1rem, 3.35rem); 1502 + .install-snippet-copy__icon--check { 1503 + opacity: 0; 1504 + transform: scale(0.88); 1384 1505 color: var(--color-brand); 1385 - line-height: 0.95; 1386 - letter-spacing: -0.03em; 1387 - transition: transform 0.28s var(--ease-out), color 0.22s var(--ease-out); 1388 1506 } 1389 1507 1390 - .journey-strip__cell:hover .journey-strip__num { 1391 - transform: translateY(-2px); 1392 - color: color-mix(in oklch, var(--color-brand) 92%, var(--color-ink)); 1508 + .install-snippet-wrap.is-copied .install-snippet-copy__icon--copy { 1509 + opacity: 0; 1510 + transform: scale(0.88); 1511 + } 1512 + 1513 + .install-snippet-wrap.is-copied .install-snippet-copy__icon--check { 1514 + opacity: 1; 1515 + transform: scale(1); 1516 + } 1517 + 1518 + @media (hover: hover) and (pointer: fine) { 1519 + .install-snippet-wrap:is(:hover, :focus-within) .install-snippet-copy, 1520 + .install-snippet-wrap.is-copied .install-snippet-copy { 1521 + opacity: 1; 1522 + pointer-events: auto; 1523 + } 1524 + } 1525 + 1526 + @media (hover: none), (pointer: coarse) { 1527 + .install-snippet-copy { 1528 + opacity: 0.72; 1529 + pointer-events: auto; 1530 + } 1531 + 1532 + .install-snippet-wrap:is(:hover, :focus-within) .install-snippet-copy, 1533 + .install-snippet-wrap.is-copied .install-snippet-copy { 1534 + opacity: 1; 1535 + } 1393 1536 } 1394 1537 1395 - .journey-strip__title { 1538 + .install-snippet-copy:hover { 1539 + color: var(--color-ink); 1540 + border-color: color-mix(in oklch, var(--color-brand) 32%, var(--color-border)); 1541 + background: color-mix(in oklch, var(--color-brand-soft) 38%, var(--color-surface)); 1542 + box-shadow: 0 2px 8px color-mix(in oklch, var(--color-brand) 7%, transparent); 1543 + } 1544 + 1545 + .install-snippet-copy:focus-visible { 1546 + outline: var(--focus-ring); 1547 + outline-offset: var(--focus-offset); 1548 + } 1549 + 1550 + .install-timeline__snippet { 1396 1551 margin: 0; 1397 - font-family: var(--font-display); 1398 - font-weight: 600; 1399 - font-size: var(--text-sm); 1552 + padding: var(--space-sm) var(--space-md); 1553 + border-radius: var(--radius-md); 1554 + background: color-mix(in oklch, var(--color-ink) 5.5%, var(--color-surface)); 1555 + border: 1px solid color-mix(in oklch, var(--color-border) 85%, var(--color-ink)); 1556 + font-size: 0.8125rem; 1557 + line-height: 1.55; 1558 + overflow-x: auto; 1559 + -webkit-overflow-scrolling: touch; 1560 + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; 1561 + } 1562 + 1563 + .install-timeline__snippet code { 1564 + font-family: inherit; 1565 + font-size: inherit; 1400 1566 color: var(--color-ink); 1567 + white-space: pre; 1568 + word-break: normal; 1401 1569 } 1402 1570 1403 - .journey-strip__body { 1571 + .install-browser-split { 1572 + display: grid; 1573 + gap: var(--space-md); 1574 + margin-top: var(--space-2xs); 1575 + } 1576 + 1577 + @media (min-width: 640px) { 1578 + .install-browser-split { 1579 + grid-template-columns: 1fr 1fr; 1580 + gap: var(--space-lg); 1581 + align-items: start; 1582 + } 1583 + } 1584 + 1585 + .install-browser-split__col { 1404 1586 margin: 0; 1405 - font-size: var(--text-sm); 1406 - color: var(--color-ink-secondary); 1407 - line-height: var(--leading-prose); 1408 - flex: 1; 1587 + padding-top: var(--space-sm); 1588 + border-top: 1px solid color-mix(in oklch, var(--color-brand) 18%, var(--color-border)); 1589 + } 1590 + 1591 + .install-browser-split__tag { 1592 + margin: 0 0 var(--space-2xs); 1593 + font-family: var(--font-display); 1594 + font-size: var(--text-xs); 1595 + font-weight: 700; 1596 + letter-spacing: var(--tracking-label); 1597 + text-transform: uppercase; 1598 + color: color-mix(in oklch, var(--color-brand) 48%, var(--color-ink-muted)); 1409 1599 } 1410 1600 1411 - .journey-strip__cell a { 1601 + .install-timeline__link { 1412 1602 font-family: var(--font-display); 1413 1603 font-weight: 600; 1414 1604 font-size: 0.8125rem; 1415 - margin-top: var(--space-xs); 1416 1605 align-self: flex-start; 1417 - transition: color 0.18s var(--ease-out), transform 0.2s var(--ease-out); 1606 + margin-top: var(--space-2xs); 1607 + transition: color 0.18s var(--ease-out); 1608 + } 1609 + 1610 + .install-timeline__link:hover { 1611 + color: color-mix(in oklch, var(--color-brand) 55%, var(--color-ink)); 1612 + } 1613 + 1614 + .install-timeline__anchor { 1615 + font-weight: 600; 1616 + text-decoration: underline; 1617 + text-underline-offset: 0.14em; 1618 + } 1619 + 1620 + .disclosure--install { 1621 + background: color-mix(in oklch, var(--color-surface) 55%, var(--color-canvas)); 1622 + border-color: color-mix(in oklch, var(--color-brand) 14%, var(--color-border)); 1623 + } 1624 + 1625 + .disclosure--install summary { 1626 + font-size: var(--text-md); 1627 + letter-spacing: -0.012em; 1418 1628 } 1419 1629 1420 - .journey-strip__cell a:hover { 1421 - transform: translateX(2px); 1630 + .disclosure--install[open] { 1631 + box-shadow: 0 12px 36px color-mix(in oklch, var(--color-brand) 6%, transparent); 1632 + } 1633 + 1634 + @media (prefers-color-scheme: dark) { 1635 + .section--install { 1636 + background: 1637 + radial-gradient(ellipse 68% 52% at 100% 8%, color-mix(in oklch, var(--color-brand) 16%, transparent), transparent 54%), 1638 + radial-gradient(ellipse 50% 42% at 0% 92%, color-mix(in oklch, var(--color-subtle) 45%, transparent), transparent 48%), 1639 + var(--color-canvas); 1640 + } 1641 + 1642 + .install-timeline__marker { 1643 + box-shadow: 1644 + 0 0 0 2px color-mix(in oklch, var(--color-canvas) 55%, var(--color-brand)), 1645 + 0 8px 22px oklch(0% 0 0 / 0.35); 1646 + } 1647 + 1648 + .install-snippet-copy { 1649 + color: color-mix(in oklch, var(--color-ink-muted) 92%, var(--color-ink)); 1650 + background: color-mix(in oklch, var(--color-canvas) 78%, var(--color-subtle)); 1651 + border-color: color-mix(in oklch, var(--color-border) 80%, transparent); 1652 + box-shadow: 0 1px 3px oklch(0% 0 0 / 0.35); 1653 + } 1654 + 1655 + .install-snippet-copy:hover { 1656 + background: color-mix(in oklch, var(--color-brand-soft) 28%, var(--color-subtle)); 1657 + box-shadow: 0 2px 12px oklch(0% 0 0 / 0.42); 1658 + } 1659 + 1660 + .disclosure--install[open] { 1661 + box-shadow: 0 14px 40px oklch(0% 0 0 / 0.38); 1662 + } 1422 1663 } 1423 1664 1424 1665 .section--editorial {