clone

one developer profile for links and private tcp services, reached peer to peer over iroh with no open ports

feat: build the signed-out landing page

Dami committed db0fb93
Add the security sequence diagram, platform-aware install controls, collapsible CLI overview, responsive text art, and landing-only interaction polish. Preserve dashboard and viewer styling.
commit
db0fb9313c18ad476dc39263af4f28bbef3ac290
parent
846bda4decfba492973c32834c1901c70a053b02
author
Dami <dami.etoile@gmail.com>
3 files changed, +276 -8
Mweb/app.js112+++++++++++++++++++-
Mweb/index.html6+-
Mweb/site.css166++++++++++++++++++++++++++++++
M web/app.js +106 -6
@@ -12,6 +12,7 @@
1212 const SHOO = 'https://shoo.dev';1313 const $ = (id) => document.getElementById(id);1414 const main = $('main');15+const pageFooter = $('page-footer');1516 1617 let me = null;1718
@@ -127,6 +128,7 @@ async function completeSignIn() {
127128 /// stylesheet in the page. This was wrong until it was tested in a browser, and128129 /// the failure was silent: themes stored, served, and ignored.129130 function applyTheme(handle, declarations = []) {131+ document.querySelector('.brand strong').hidden = false;130132 const root = document.documentElement;131133 // Belt and braces: handles are already restricted to this alphabet server-side.132134 const scope = String(handle).replace(/[^A-Za-z0-9_-]/g, '');
@@ -152,9 +154,12 @@ function applyTheme(handle, declarations = []) {
152154 }153155 154156 function clearTheme() {157+ document.querySelector('.brand strong').hidden = false;155158 delete document.documentElement.dataset.profile;156159 delete document.documentElement.dataset.theme;157160 $('profile-theme').textContent = '';161+ pageFooter.replaceChildren();162+ pageFooter.hidden = true;158163 requestAnimationFrame(updateLogoContrast);159164 }160165
@@ -444,16 +449,111 @@ function openService(item) {
444449 445450 function renderSignedOut() {446451 clearTheme();452+ document.querySelector('.brand strong').hidden = true;453+ const platform = navigator.userAgentData?.platform || navigator.platform || '';454+ const isMacOS = /mac/i.test(platform) || /Macintosh|Mac OS X/i.test(navigator.userAgent);455+ const installControl = isMacOS456+ ? `<fieldset role="group">457+ <input id="install-command" aria-label="Homebrew install command"458+ value="brew install FelineStateMachine/tap/devsite" readonly>459+ <button id="copy-install" type="button">Copy</button>460+ </fieldset>`461+ : `<fieldset role="group">462+ <input aria-label="Latest binary release"463+ value="GitHub · latest devsite binary release" readonly>464+ <a href="https://github.com/FelineStateMachine/devsite/releases/latest"465+ role="button">Download</a>466+ </fieldset>`;447467 main.innerHTML = `448468 <article>449469 <hgroup>450- <h1>Your public work and your private local services, on one page.</h1>451- <p>452- Private services stay on your machine. Nothing is deployed, no port is opened,453- and the page you are reading never carries their traffic.454- </p>470+ <h1 aria-label="dev.site"><span aria-hidden="true"> █ ▀ ▄471+ ▄▄▄█ ▄▄▄ ▄ ▄ ▄▄▄ ▄▄▄ ▄▄█▄▄ ▄▄▄472+█▀ ▀█ █▀ █ ▀▄ ▄▀ █ ▀ █ █ █▀ █473+█ █ █▀▀▀▀ █▄█ ▀▀▀▄ █ █ █▀▀▀▀474+▀█▄██ ▀█▄▄▀ █ █ ▀▄▄▄▀ ▄▄█▄▄ ▀▄▄ ▀█▄▄▀</span></h1>475+ <p>links and services across all of your development sites</p>455476 </hgroup>456- </article>`;477+ <figure class="sequence">478+ <figcaption>authorization only - no service bytes</figcaption>479+ <header aria-hidden="true">480+ <strong>host cli</strong>481+ <strong>dev.site</strong>482+ <strong>approved user</strong>483+ </header>484+ <div>485+ <i aria-hidden="true"></i><i aria-hidden="true"></i><i aria-hidden="true"></i>486+ <ol aria-label="A host publishes as an endpoint. An approved user redeems a one-use ticket into a client-bound session, requests a signed capability for each connection, then opens an end-to-end encrypted Iroh QUIC stream to the host service.">487+ <li><span>publish endpoint id</span></li>488+ <li><span>redeem one-use ticket + client key</span></li>489+ <li><span>client-bound session</span></li>490+ <li><span>request one-stream capability</span></li>491+ <li><span>signed capability + daemon id</span></li>492+ <li><span>end-to-end encrypted Iroh QUIC</span></li>493+ </ol>494+ </div>495+ </figure>496+ </article>497+498+ <section aria-labelledby="install-title">499+ <h2 id="install-title">Install</h2>500+ ${installControl}501+ </section>502+503+ <hr>504+505+ <details>506+ <summary>Overview</summary>507+ <p>Store useful URLs, shared services, and temporary access</p>508+ <article>509+ <h3>Machine Enrollment</h3>510+ <pre><code>devsite login dsm_***</code></pre>511+ <blockquote>Use a revocable machine credential from your dashboard</blockquote>512+ </article>513+ <article>514+ <h3>Add Links to your site</h3>515+ <pre><code>devsite link set --name docs --url https://docs.example.com --public</code></pre>516+ <blockquote>List a public, private, shared URLs on your page</blockquote>517+ </article>518+ <article>519+ <h3>Share your Services</h3>520+ <pre><code>devsite service host 5432 --name postgres</code></pre>521+ <blockquote>522+ <p>Access any local TCP port. never publicly accessible</p>523+ <p>Service is accessed anywhere with a single use ticket.</p>524+ </blockquote>525+ <pre><code>devsite connect dst_***</code></pre>526+ </article>527+ <article>528+ <h3>Persist the Daemon</h3>529+ <pre><code>devsite daemon run</code></pre>530+ <blockquote>autorun it with Homebrew services or systemd</blockquote>531+ </article>532+ </details>`;533+534+ pageFooter.innerHTML = `535+ <nav aria-label="Footer">536+ <ul><li><a href="https://github.com/FelineStateMachine/devsite"537+ target="_blank" rel="noopener">GitHub</a></li></ul>538+ </nav>`;539+ pageFooter.hidden = false;540+541+ if (isMacOS) {542+ const installCommand = $('install-command');543+ const copyInstall = $('copy-install');544+ let copyReset;545+ copyInstall.addEventListener('click', async () => {546+ try {547+ await navigator.clipboard.writeText(installCommand.value);548+ copyInstall.textContent = 'Copied';549+ clearTimeout(copyReset);550+ copyReset = setTimeout(() => { copyInstall.textContent = 'Copy'; }, 1400);551+ } catch {552+ copyInstall.textContent = 'Select';553+ installCommand.select();554+ }555+ });556+ }457557 }458558 459559 function renderClaimHandle() {
M web/index.html +4 -2
M web/site.css +166 -0
@@ -57,6 +57,23 @@ body > main {
5757 padding-block: var(--pico-block-spacing-vertical);5858 }5959 60+body > header > nav {61+ align-items: center;62+}63+64+#page-footer nav {65+ justify-content: flex-end;66+}67+68+main:has(.sequence),69+main:has(.sequence) + #page-footer {70+ user-select: none;71+}72+73+main:has(.sequence) :is(pre, code, #install-command) {74+ user-select: text;75+}76+6077 .brand {6178 display: inline-flex;6279 gap: calc(var(--pico-spacing) * 0.4);
@@ -64,6 +81,8 @@ body > main {
6481 }6582 .brand > svg {6683 flex: none;84+ width: 2.5rem;85+ height: 2.5rem;6786 }6887 6988 .entries + .folder,
@@ -206,6 +225,153 @@ body > main {
206225 .command-secret {207226 color: var(--pico-muted-color);208227 }228+229+/* The sequence diagram is the signed-out page's one bespoke element. */230+main > article:has(> .sequence) h1 > span {231+ display: block;232+ overflow: hidden;233+ font-family: var(--pico-font-family-monospace);234+ font-size: clamp(0.55rem, 1.3vw, 1.1rem);235+ font-weight: 400;236+ line-height: 1;237+ white-space: pre;238+}239+240+main > article:has(> .sequence) h1 {241+ margin-bottom: var(--pico-typography-spacing-vertical);242+}243+244+.sequence {245+ margin: var(--pico-block-spacing-vertical) 0 0;246+}247+.sequence figcaption {248+ color: var(--pico-muted-color);249+ font-size: 0.75rem;250+ text-align: center;251+}252+.sequence > header {253+ display: grid;254+ grid-template-columns: repeat(3, minmax(0, 1fr));255+ gap: calc(var(--pico-spacing) * 0.5);256+}257+.sequence > header > strong {258+ padding: calc(var(--pico-spacing) * 0.5);259+ border-radius: var(--pico-border-radius);260+ background: var(--pico-form-element-background-color);261+ font-size: 0.75rem;262+ text-align: center;263+}264+.sequence > div {265+ position: relative;266+ height: 19rem;267+}268+.sequence > div > i {269+ position: absolute;270+ top: 0;271+ bottom: 0;272+ border-left: var(--pico-border-width) dashed var(--pico-form-element-border-color);273+}274+.sequence > div > i:nth-of-type(1) {275+ left: 16.667%;276+}277+.sequence > div > i:nth-of-type(2) {278+ left: 50%;279+}280+.sequence > div > i:nth-of-type(3) {281+ left: 83.333%;282+}283+.sequence ol {284+ position: absolute;285+ inset: 0;286+ margin: 0;287+ padding: 0;288+}289+.sequence li {290+ position: absolute;291+ height: 1.35rem;292+ padding: 0 0.25rem;293+ border-top: var(--pico-border-width) solid var(--pico-primary);294+ color: var(--pico-muted-color);295+ font-size: clamp(0.58rem, 1.5vw, 0.7rem);296+ line-height: 1.15;297+ text-align: center;298+ list-style: none;299+}300+.sequence li > span {301+ position: relative;302+ display: inline-block;303+ padding-inline: 0.25rem;304+ background: var(--pico-card-background-color);305+ transform: translateY(calc(-100% - 0.2rem));306+}307+.sequence li::after {308+ position: absolute;309+ top: -0.28rem;310+ content: '';311+ border-block: 0.24rem solid transparent;312+}313+.sequence li:nth-child(1),314+.sequence li:nth-child(2),315+.sequence li:nth-child(3),316+.sequence li:nth-child(4),317+.sequence li:nth-child(5) {318+ left: 50%;319+ width: 33.333%;320+}321+.sequence li:nth-child(1) {322+ top: 2.3rem;323+ left: 16.667%;324+}325+.sequence li:nth-child(2) {326+ top: 5rem;327+}328+.sequence li:nth-child(3) {329+ top: 7.7rem;330+}331+.sequence li:nth-child(4) {332+ top: 10.4rem;333+}334+.sequence li:nth-child(5) {335+ top: 13.1rem;336+}337+.sequence li:nth-child(6) {338+ top: 16rem;339+ left: 16.667%;340+ width: 66.666%;341+ border-top-style: dashed;342+ animation: sequence-flow 1.4s ease-in-out infinite alternate;343+}344+.sequence li:nth-child(1)::after,345+.sequence li:nth-child(3)::after,346+.sequence li:nth-child(5)::after {347+ right: -0.05rem;348+ border-left: 0.42rem solid var(--pico-primary);349+}350+.sequence li:nth-child(2)::after,351+.sequence li:nth-child(4)::after,352+.sequence li:nth-child(6)::after {353+ left: -0.05rem;354+ border-right: 0.42rem solid var(--pico-primary);355+}356+.sequence li:nth-child(6)::before {357+ position: absolute;358+ top: -0.28rem;359+ right: -0.05rem;360+ content: '';361+ border-block: 0.24rem solid transparent;362+ border-left: 0.42rem solid var(--pico-primary);363+}364+365+@keyframes sequence-flow {366+ to { opacity: 0.55; }367+}368+369+@media (prefers-reduced-motion: reduce) {370+ .sequence li:nth-child(6) {371+ animation: none;372+ }373+}374+209375 .secret-command > button {210376 flex: none;211377 width: auto;