carlos / carloku Public

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>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev 5dd5301b528b6dbcc230d2516b1c3e3a411abf59 parent d0a5b98
3 files changed, +24 −10
  • index.html +9 −9
  • site.css +8 −0
  • site.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 -&gt; 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 -&gt; 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);
}
}