hero terminal: the live URL is the link
The URL in the transcript is what a visitor reaches for, so make it the link and drop the duplicate 'open it' below. Three things had to move with it: - site.js typed lines by assigning textContent, which destroys child nodes — the anchor would have vanished on load, rendering identically and silently not clickable. Lines now keep their markup and get it back when they finish. - the terminal was role=img with an aria-hidden body, so a link inside would have been keyboard-focusable but invisible to assistive tech. The transcript is real text; it is now exposed as such, and only the window chrome and caret stay decorative. - the link stays terminal furniture: its line's color, not the page purple, with the prompt violet as its focus ring (the page accent disappears against Terminal Black). Session refreshed to 6c88010, the build the app is actually serving.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 files changed,
+24
−10
index.html+9 −9site.css+8 −0site.js+7 −1
diff --git a/index.html b/index.html| index f830fe1..0b4eb61 100644 |
| --- a/index.html |
| +++ b/index.html |
| @@ -60,21 +60,21 @@ the verdict, and DESIGN.md. |
| <p class="hero-note">Open source underneath — hosted here, never locked here.</p> |
| </div> |
| <div> |
| - <div class="term" role="img" aria-label="A real terminal session: carlos deploy ships the app, promotes it, and waits until the URL serves the new build; it ends with the app's live URL."> |
| + <div class="term"> |
| <div class="term-bar" aria-hidden="true"><i></i><i></i><i></i><span>you@laptop — carlos</span></div> |
| - <div class="term-body" aria-hidden="true"> |
| + <div class="term-body"> |
| <span class="line cmd" data-type>carlos deploy -app hello ./hello-linux-arm64</span> |
| <span class="line" data-out>using https://console.carloku.com as you@keymail.dev</span> |
| <span class="line" data-out>deploying https://hello.bdf.oncarlos.com</span> |
| - <span class="line ok" data-out>shipped hello a30b802</span> |
| - <span class="line ok" data-out>promoted hello a30b802 -> edge</span> |
| - <span class="line ok" data-out>live https://hello.bdf.oncarlos.com</span> |
| - <span class="line"><span class="term-caret"></span></span> |
| + <span class="line ok" data-out>shipped hello 6c88010</span> |
| + <span class="line ok" data-out>promoted hello 6c88010 -> edge</span> |
| + <span class="line ok" data-out>live <a href="https://hello.bdf.oncarlos.com">https://hello.bdf.oncarlos.com</a></span> |
| + <span class="line" aria-hidden="true"><span class="term-caret"></span></span> |
| </div> |
| </div> |
| - <p class="hero-note">That session is real, and so is the app — |
| - <a href="https://hello.bdf.oncarlos.com">open it</a> · |
| - <a href="https://github.com/carlosframework/hello">source</a>.</p> |
| + <p class="hero-note">That session is real, and so is the app — the last |
| + URL opens it, and its |
| + <a href="https://github.com/carlosframework/hello">source is public</a>.</p> |
| </div> |
| </div> |
| </section> |
diff --git a/site.css b/site.css| index 84d40c2..929a181 100644 |
| --- a/site.css |
| +++ b/site.css |
| @@ -218,6 +218,14 @@ h1 .dot { color: var(--accent); } |
| .term-body .cmt { color: var(--term-title); } |
| .term-body .cmt::before { content: "$ "; color: var(--term-prompt); font-weight: 560; } |
| .term-body .ok { color: var(--term-ok); } |
| +/* A URL in the transcript is a real link, and it stays terminal furniture: |
| + it takes its line's color rather than the page's purple (the One Voice |
| + Rule keeps purple off the terminal), and earns an underline on hover. |
| + The focus ring is the terminal's own prompt violet — the page accent is |
| + too dark to see against Terminal Black. */ |
| +.term-body a { color: inherit; text-decoration-color: rgba(255, 255, 255, 0.35); text-underline-offset: 0.2em; } |
| +.term-body a:hover { text-decoration-color: currentColor; } |
| +.term-body a:focus-visible { outline-color: var(--term-prompt); } |
| .term-caret { |
| display: inline-block; width: 0.55em; height: 1.05em; |
| background: var(--term-prompt); vertical-align: text-bottom; |
diff --git a/site.js b/site.js| index b4694ea..4b4c3e5 100644 |
| --- a/site.js |
| +++ b/site.js |
| @@ -20,6 +20,10 @@ |
| var lines = Array.prototype.slice.call(body.querySelectorAll(".line")); |
| var caretLine = lines.pop(); // trailing caret stays put until the end |
| var texts = lines.map(function (el) { return el.textContent; }); |
| + // A line can hold markup — the last one's URL is a real link — and typing |
| + // sets textContent, which would throw those children away. Keep the |
| + // original markup and put it back the moment the line finishes. |
| + var htmls = lines.map(function (el) { return el.innerHTML; }); |
| lines.forEach(function (el) { |
| el.textContent = ""; |
| el.style.display = "none"; |
| @@ -34,6 +38,7 @@ |
| } |
| var el = lines[li]; |
| var text = texts[li]; |
| + var html = htmls[li]; |
| li++; |
| el.style.display = ""; |
| if (el.hasAttribute("data-type")) { |
| @@ -44,11 +49,12 @@ |
| ci++; |
| setTimeout(typeChar, 14); |
| } else { |
| + el.innerHTML = html; |
| setTimeout(nextLine, 160); |
| } |
| })(); |
| } else { |
| - el.textContent = text; |
| + el.innerHTML = html; |
| setTimeout(nextLine, el.classList.contains("ok") ? 260 : 120); |
| } |
| } |