Public: anyone can read this; the crew can change it.
Design: the front page's hero and its terminal line use the design system, and hero/hero-terminal leave §5d #4071
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/3870-design-the-front-page-s-hero-and-its-ter → main 10 commits fork
5 files
changed +17
−19
docs/DESIGN.md +1 −1
| @@ -393,7 +393,7 @@ gone, so this list only shrinks. | ||
| 393 | 393 | - **`style/base.ts`** — the page shell (topbar, main, footer) and the layout and text utilities every page leans on: `.bar` `.bare` `.foot` `.grow` `.hide-lg` `.hide-sm` `.js-only` `.live-pill` `.mark` `.mb` `.mt` `.muted` `.narrow` `.narrow-wide` `.num` `.reduced` `.shell` `.shell-wide` `.skip` `.small` `.spacer` `.sr-only` `.stack` `.stack-tight` `.topbar` `.topbar-tools` `.truncate` `.wordmark` |
| 394 | 394 | - **`style/components.ts`** — parts of the §5 components that the table does not spell out — the palette's list, the menu's items, a dialog's head and foot, comments, the theme picker — and a few small helpers beside them: `.cat` `.clone` `.clone-text` `.comment` `.comment-body` `.comment-form` `.dash` `.dialog-body` `.dialog-foot` `.dialog-head` `.empty-title` `.error-note` `.field-row` `.form-actions` `.hex` `.ico` `.ico-loader` `.list` `.live-status` `.menu-item` `.menu-panel` `.menu-sep` `.menu-title` `.palette-empty` `.palette-foot` `.palette-group` `.palette-heading` `.palette-input` `.palette-item` `.palette-list` `.passkey` `.passkey-lead` `.passkey-note` `.passkey-off` `.passkey-on` `.preview` `.sep` `.split` `.sprite` `.swatch` `.table-scroll` `.tell-here` `.theme-apply` `.theme-option` `.tick` `.timeline` `.tl-dot` `.toast-body` `.topbar-search` |
| 395 | 395 | - **`style/components.ts`, settings rows** — a list whose rows are settings (limits, capacity): the `<ul class="list setting-list">` lets title and meta wrap instead of ellipsizing, and a row's controls take their own line under 640px. `.row-fields` is the form in that row's trail (input, Save, Default): always visible, never hover-gated, wrapping under the text before it would clip. Use both for any page whose rows show a name, a current value, and a control to change it; use `.row-actions` only for secondary actions (delete, wake, pin, revoke). Flags, telemetry, cloud, people, crew roles and subscription headroom/budget use `.row-fields`. Classes: `.row-fields` `.setting-list` |
| 396 | - **`style/content.ts`, page furniture** — what frames a page's content: page and repo heads, the merge box, the front page and its tiles, explore, backups, boards, the fleet chart's series colours, following, lineage: `.about` `.act` `.app-shelf` `.app-tile` `.app-tile-body` `.app-tile-desc` `.app-tile-look` `.app-tile-name` `.backups-drill` `.backups-error` `.backups-now` `.backups-restore` `.bform` `.bgrid` `.big` `.bmanage` `.boards-result` `.bp-h2` `.bp-h3` `.bp-w2` `.bp-w3` `.break` `.bt-bar` `.bt-grid` `.bt-group` `.bt-hist` `.bt-name` `.bt-procs` `.bt-row` `.bt-val` `.chart` `.chart-box` `.commit-body` `.commit-head` `.connect-more` `.connect-path` `.domain-records` `.explore-filters` `.explore-line` `.explore-main` `.explore-name` `.explore-row` `.fg-s0` `.fg-s1` `.fg-s2` `.fg-s3` `.fg-s4` `.fg-s5` `.fg-s6` `.fg-s7` `.filters` `.flag-form` `.following` `.following-actions` `.following-eyebrow` `.following-foot` `.following-head` `.following-sentence` `.fork-bar` `.front-admin` `.front-controls` `.front-crew` `.front-featured` `.front-featured-body` `.front-featured-look` `.front-issues` `.front-live` `.front-more` `.front-pick` `.front-pin-any` `.front-weights` `.frozen` `.frozen-form` `.frozen-sentence` `.handoff` `.handoff-links` `.headroom` `.hero` `.hero-terminal` `.issue-head` `.issue-meta` `.lineage` `.lineage-children` `.lineage-here` `.merge-box` `.merge-form` `.merge-headline` `.merge-sub` `.mirror-form` `.number` `.origin-link` `.origin-mark` `.origin-name` `.origin-row` `.owner` `.page-head` `.path-head` `.policy-line` `.qf` `.repo-actions` `.repo-audience` `.repo-desc` `.repo-layout` `.repo-lineage` `.repo-title` `.search` `.slash` `.stage-detail` `.stage-dot` `.steps` `.sub` `.sys-pair` `.table-wrap` `.terminal-line` `.tok` `.tree-row` `.tree-total` `.wall-cells` `.where-sep` | |
| 396 | - **`style/content.ts`, page furniture** — what frames a page's content: page and repo heads, the merge box, the front page and its tiles, explore, backups, boards, the fleet chart's series colours, following, lineage: `.about` `.act` `.app-shelf` `.app-tile` `.app-tile-body` `.app-tile-desc` `.app-tile-look` `.app-tile-name` `.backups-drill` `.backups-error` `.backups-now` `.backups-restore` `.bform` `.bgrid` `.big` `.bmanage` `.boards-result` `.bp-h2` `.bp-h3` `.bp-w2` `.bp-w3` `.break` `.bt-bar` `.bt-grid` `.bt-group` `.bt-hist` `.bt-name` `.bt-procs` `.bt-row` `.bt-val` `.chart` `.chart-box` `.commit-body` `.commit-head` `.connect-more` `.connect-path` `.domain-records` `.explore-filters` `.explore-line` `.explore-main` `.explore-name` `.explore-row` `.fg-s0` `.fg-s1` `.fg-s2` `.fg-s3` `.fg-s4` `.fg-s5` `.fg-s6` `.fg-s7` `.filters` `.flag-form` `.following` `.following-actions` `.following-eyebrow` `.following-foot` `.following-head` `.following-sentence` `.fork-bar` `.front-admin` `.front-controls` `.front-crew` `.front-featured` `.front-featured-body` `.front-featured-look` `.front-issues` `.front-live` `.front-more` `.front-pick` `.front-pin-any` `.front-weights` `.frozen` `.frozen-form` `.frozen-sentence` `.handoff` `.handoff-links` `.headroom` `.issue-head` `.issue-meta` `.lineage` `.lineage-children` `.lineage-here` `.merge-box` `.merge-form` `.merge-headline` `.merge-sub` `.mirror-form` `.number` `.origin-link` `.origin-mark` `.origin-name` `.origin-row` `.owner` `.page-head` `.path-head` `.policy-line` `.qf` `.repo-actions` `.repo-audience` `.repo-desc` `.repo-layout` `.repo-lineage` `.repo-title` `.search` `.slash` `.stage-detail` `.stage-dot` `.steps` `.sub` `.sys-pair` `.table-wrap` `.terminal-line` `.tok` `.tree-row` `.tree-total` `.wall-cells` `.where-sep` | |
| 397 | 397 | - **`style/content.ts`, code view** — the source view and the syntax-highlight token classes `highlight-html.ts` builds as `tok-${kind}`: `.blob-view` `.ln` `.tok-attribute` `.tok-comment` `.tok-constant` `.tok-decorator` `.tok-deleted` `.tok-emphasis` `.tok-function` `.tok-heading` `.tok-inserted` `.tok-keyword` `.tok-link` `.tok-meta` `.tok-number` `.tok-operator` `.tok-plain` `.tok-property` `.tok-punctuation` `.tok-regex` `.tok-string` `.tok-tag` `.tok-type` `.tok-variable` |
| 398 | 398 | - **`style/content.ts`, prose (markdown)** — table cell alignment, built by `markdown.ts` as `ta-${align}`: `.ta-c` `.ta-r` |
| 399 | 399 | - **`style/content.ts`, diff viewer** — the diff table: its gutters, line kinds, file head and inline review comments: `.add` `.del` `.dh` `.diff` `.diff-body` `.diff-head` `.diff-note` `.diff-path` `.diff-stat` `.diff-stub` `.diff-summary` `.diff-table` `.dl` `.dl-review` `.dn` `.dn-comment` `.nonl` `.rc-hidden` `.rc-resolve` `.rc-resolved` `.wd` |
src/web/studio/home.ts +11 −11
| @@ -38,6 +38,7 @@ import { | ||
| 38 | 38 | icon, |
| 39 | 39 | listCard, |
| 40 | 40 | timeAgo, |
| 41 | terminalLine, | |
| 41 | 42 | } from "../ui.ts"; |
| 42 | 43 | import { |
| 43 | 44 | downloadsCard, |
| @@ -505,9 +506,10 @@ export function studioDoor(ctx: Ctx): Response { | ||
| 505 | 506 | // comes back to them rather than to a bare home, so they are not lost. |
| 506 | 507 | const back = ctx.url.pathname === "/" ? "/" + ctx.url.search : "/"; |
| 507 | 508 | const nextQuery = back === "/" ? "" : `?next=${encodeURIComponent(back)}`; |
| 508 | const body = html`<section class="hero"> | |
| 509 | const ssh = sshCommand(ctx.app); | |
| 510 | const body = html`<section class="narrow narrow-wide stack"> | |
| 509 | 511 | <h1>Apps, live.</h1> |
| 510 | <p>A software forge that builds and serves its own apps.</p> | |
| 512 | <p class="muted">A software forge that builds and serves its own apps.</p> | |
| 511 | 513 | <div class="bar"> |
| 512 | 514 | ${ctx.auth.user !== undefined |
| 513 | 515 | ? null |
| @@ -525,15 +527,13 @@ export function studioDoor(ctx: Ctx): Response { | ||
| 525 | 527 | })} |
| 526 | 528 | </div> |
| 527 | 529 | ${when( |
| 528 | sshCommand(ctx.app), | |
| 529 | (ssh) => | |
| 530 | html`<p class="hero-terminal"> | |
| 531 | <code class="mono">${ssh}</code> | |
| 532 | <span class="muted" | |
| 533 | >the whole forge in your terminal — your key is your | |
| 534 | identity</span | |
| 535 | > | |
| 536 | </p>`, | |
| 530 | ssh, | |
| 531 | (command) => html`<div class="stack-tight"> | |
| 532 | ${terminalLine({ command })} | |
| 533 | <p class="muted small"> | |
| 534 | The whole forge in your terminal — your key is your identity. | |
| 535 | </p> | |
| 536 | </div>`, | |
| 537 | 537 | )} |
| 538 | 538 | </section> |
| 539 | 539 | ${when(front.featured, (f) => html`<div class="mt">${featuredCard(f)}</div>`)} |
src/web/style/content.ts −6
| @@ -409,13 +409,7 @@ form.inline { display: inline; margin-left: auto; } | ||
| 409 | 409 | .lineage .row[aria-current="page"] { background: var(--muted); } |
| 410 | 410 | @media (max-width: 719px) { .lineage-children { margin-left: 12px; } } |
| 411 | 411 | |
| 412 | .hero { max-width: 620px; margin: 12dvh auto 0; text-align: center; } | |
| 413 | .hero h1 { font-size: clamp(30px, 7vw, 44px); letter-spacing: -0.04em; line-height: 1.05; } | |
| 414 | .hero p { margin: 12px 0 24px; font-size: 16px; color: var(--muted-fg); text-wrap: pretty; } | |
| 415 | .hero-terminal { display: flex; flex-wrap: wrap; justify-content: center; align-items: baseline; gap: 6px 14px; margin: -8px 0 28px; font-size: 14px; } | |
| 416 | .hero-terminal .mono { font-size: 15px; } | |
| 417 | 412 | .terminal-line { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 14px; } |
| 418 | .hero .bar { justify-content: center; } | |
| 419 | 413 | |
| 420 | 414 | /* The studio's front door: the public apps, live first, as pictures. */ |
| 421 | 415 | .app-shelf { |
test/ssh-site.test.ts +4 −1
| @@ -64,7 +64,10 @@ describe("the terminal on the website", () => { | ||
| 64 | 64 | test("the landing page invites you in over ssh", async () => { |
| 65 | 65 | const page = await fetch(`${base}/`).then((r) => r.text()); |
| 66 | 66 | expect(page).toContain("ssh -p 2222 forge.example"); |
| 67 | expect(page).toContain("hero-terminal"); | |
| 67 | expect(page).toContain("terminal-line"); | |
| 68 | expect(page).toContain("your key is your identity"); | |
| 69 | expect(page).not.toContain("hero-terminal"); | |
| 70 | expect(page).not.toContain('class="hero'); | |
| 68 | 71 | }); |
| 69 | 72 | |
| 70 | 73 | test("the dashboard has a Terminal card, with the host key", async () => { |
test/studio-changes.test.ts +1
| @@ -1188,6 +1188,7 @@ describe("the studio, continued", () => { | ||
| 1188 | 1188 | expect(body).toContain("Nothing public is up yet"); |
| 1189 | 1189 | expect(body).toContain('href="/signup"'); |
| 1190 | 1190 | expect(body).toContain('href="/signin"'); |
| 1191 | expect(body).not.toContain("terminal is off"); | |
| 1191 | 1192 | expect(body).not.toContain("<h1>Your code, your machine.</h1>"); |
| 1192 | 1193 | expect(body).not.toContain("Public repositories"); |
| 1193 | 1194 | expect(body).not.toContain(`href="/olga/just-code"`); |
new above
offline