Visitar URL original
refactor(site): keep Solid off the static pages until an overlay opens by adamdotdevin · Pull Request #63140 · anomalyco/opencode · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion services/site/script/check-client-bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ console.log(
// browser build is there for its stylesheets only, so none of its scripts may ship, and the script
// may bring no router, no serialized data (seroval) and no hydration. Its budget in kB, gzipped, is
// what it was when last measured, plus a kB of room; lower it when the script gets lighter.
const budget = 11.6
const budget = 11.5
type Chunk = { file: string; imports?: string[] }
const app: Record<string, Chunk> = await Bun.file(path.join(root, ".vite/manifest.json")).json()
const statics: Record<string, Chunk> = await Bun.file(path.join(root, ".vite/static-manifest.json")).json()
Expand Down
20 changes: 0 additions & 20 deletions services/site/src/client/islands/carousel.tsx

This file was deleted.

24 changes: 18 additions & 6 deletions services/site/src/client/islands/home.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { carouselNav } from "~/component/site/carousel-nav"
import { consoleDots } from "~/component/site/console-dots"
import { logoWall } from "~/component/site/logo-wall-motion"
import { onEntrance, revealMotion, washColumns, washItem, washStartTime, washStep } from "~/component/site/reveal"
import { siteSettings } from "~/component/site/settings"
import { spinReel } from "~/component/site/slot-stat"
import { logoWalls } from "~/lib/logo-walls"
import { $, $$, reducedMotion } from "../dom"

/**
Expand Down Expand Up @@ -36,13 +39,22 @@ export default function home() {
$$<HTMLElement>('[data-component="console-field"]').forEach((field) =>
consoleDots(field, () => Number(field.dataset.dot) || 2),
)
// The logo row and, for a mouse, the carousel's arrows render in the browser, with the framework
// (solid.tsx). Touch screens swipe the cards and never load the arrows.
// The logo row (logo-wall.tsx) and, for a mouse, the carousel's arrows (products-arrows.tsx); touch
// screens swipe the cards.
const wall = $('[data-component="logo-wall"]')
if (wall) void import("./logo-wall").then((module) => module.default(wall))
const carousel = $('[data-component="products"][data-layout="carousel"]')
if (carousel && matchMedia("(pointer: fine)").matches)
void import("./carousel").then((module) => module.default(carousel))
if (wall) {
const kind = wall.dataset.kind === "providers" ? "providers" : "companies"
logoWall(wall, {
label: wall.dataset.layout ?? "none",
items: logoWalls[kind],
interval: siteSettings.logoInterval,
stagger: siteSettings.logoStagger,
duration: siteSettings.logoTuning[siteSettings.logoAnimation].duration,
})
}
const nav = $('[data-component="products"][data-layout="carousel"] > [data-slot="carousel-nav"]')
const track = $("#products-track")
if (nav && track && matchMedia("(pointer: fine)").matches) carouselNav(nav, track)
}

// The numbers start a fifth of a window early, so they already spin as they come into view; with
Expand Down
33 changes: 0 additions & 33 deletions services/site/src/client/islands/logo-wall.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { Wordmark } from "~/component/site/marks"
import { siteSettings } from "~/component/site/settings"
import { $, html, site } from "../dom"
import { mount } from "../solid"
import { wordmark } from "~/component/site/wordmark"
import { $, html, site, t } from "../dom"
import { setBarColor } from "../theme"

/**
* The footer's wordmark (site.tsx on the hydrated pages): rendered again in the browser as it comes
* near, so it draws itself in and its letters can be dragged. While they are out of order the
* The footer's wordmark (Wordmark in marks.tsx, site.tsx): wordmark.ts draws it in as it comes into
* view and lets its letters be dragged, from the server's markup. While they are out of order the
* "Like letters?" bar slides open at the page's end; filling the bottom of the screen, its blue runs
* on to the screen's bottom edge (the page behind and the browser bar take it).
*/
export default function wordmark(root: HTMLElement) {
export default function wordmarkIsland(root: HTMLElement) {
const bar = $('[data-component="letters-bar"]')
let shown = false
let inView = false
Expand Down Expand Up @@ -70,18 +69,13 @@ export default function wordmark(root: HTMLElement) {
{ threshold: [0, 0.98] },
).observe(bar)

void mount(
() => (
<Wordmark
hatch={siteSettings.wordmarkHatch}
nodes={siteSettings.wordmarkNodes === "on"}
entrance={siteSettings.footerWordmark === "off" ? "off" : siteSettings.footerWordmarkAnimation}
speed={siteSettings.footerWordmarkSpeed}
play={siteSettings.footerWordmarkPlay === "drag"}
onOrder={order}
/>
),
root,
true,
)
const svg = $<SVGSVGElement>('svg[data-slot="face"]', root)
if (svg)
wordmark(svg, {
entrance: siteSettings.footerWordmark === "off" ? "off" : siteSettings.footerWordmarkAnimation,
speed: siteSettings.footerWordmarkSpeed,
play: siteSettings.footerWordmarkPlay === "drag",
t,
onOrder: order,
})
}
14 changes: 14 additions & 0 deletions services/site/src/client/overlays/hex-panel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import HexPanel from "~/component/site/voice/hex-panel"
import { swap } from "../solid"

/**
* The Hex promo's pop-up (hex-panel.tsx), rendered in front of the orb as hex-promo.tsx does, once
* the orb is first hovered, focused or pressed (hex-orb.ts). Resolves with what a click on it does.
*/
export default async function hexPanel(orb: HTMLButtonElement) {
let toggle = () => {}
const spot = document.createElement("span")
orb.before(spot)
await swap(() => <HexPanel orb={orb} toggle={(run) => (toggle = run)} />, spot)
return () => toggle()
}
15 changes: 15 additions & 0 deletions services/site/src/client/overlays/orb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { hexOrb } from "~/component/site/voice/hex-orb"
import { site, t } from "../dom"

/**
* The corner orb (voice/host.ts on the hydrated pages): the Hex promo's orb at rest, with no
* framework code; its pop-up loads on intent (hex-panel.tsx).
*/
export default function mountOrb() {
const root = document.createElement("div")
root.dataset.component = "voice-mode"
root.dataset.promo = ""
const orb = hexOrb(t("site.hexPromo.label"), () => import("./hex-panel").then((module) => module.default(orb.orb)))
root.append(orb.orb)
site.append(root)
}
12 changes: 0 additions & 12 deletions services/site/src/client/overlays/orb.tsx

This file was deleted.

7 changes: 3 additions & 4 deletions services/site/src/client/solid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ import { loadSlice, translated, translator } from "~/i18n"
import { parseLocale } from "~/lib/language"

// The site's own Solid components that the static pages show only once someone asks for them (the
// search palette, the Markdown view, the orb, the footer's wordmark, the legal cover, the products'
// arrows) or that keep changing once shown (the logo row), rendered into the page in the browser:
// nothing of theirs is hydrated. Loaded on intent with the component that needs it, so the page's own
// script carries no framework.
// search palette, the Markdown view, the orb's pop-up, the legal cover), rendered into the page in
// the browser: nothing of theirs is hydrated. Loaded on intent with the component that needs it, so
// the page's own script carries no framework.

const locale = parseLocale(document.documentElement.dataset.locale) ?? "en"
const i18n = { t: translator(locale), translated: (key: Parameters<typeof translated>[1]) => translated(locale, key) }
Expand Down
6 changes: 6 additions & 0 deletions services/site/src/client/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,10 @@ export const clientStrings = [
"site.action.copiedThisCommand",
"site.action.copy",
"site.download.thisComputer",
"site.hexPromo.label",
"footer.wordmark.option",
"footer.wordmark.moved",
"footer.wordmark.picked",
"footer.wordmark.dropped",
"footer.wordmark.cancelled",
] as const satisfies readonly Key[]
9 changes: 5 additions & 4 deletions services/site/src/component/site/carousel-arrows.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import "./carousel-arrows.css"
export function CarouselArrows(props: {
previous: string
next: string
onPrevious: () => void
onNext: () => void
/** Left out where the carousel handles the presses itself (carousel-nav.ts). */
onPrevious?: () => void
onNext?: () => void
atStart?: boolean
atEnd?: boolean
controls?: string
Expand All @@ -23,7 +24,7 @@ export function CarouselArrows(props: {
data-tip={props.previous}
aria-controls={props.controls}
aria-disabled={props.atStart ? "true" : undefined}
onClick={() => !props.atStart && props.onPrevious()}
onClick={() => !props.atStart && props.onPrevious?.()}
>
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
<path d="M7.5 3L4.5 6L7.5 9" stroke="currentColor" stroke-width="1.25" stroke-linecap="square" />
Expand All @@ -35,7 +36,7 @@ export function CarouselArrows(props: {
data-tip={props.next}
aria-controls={props.controls}
aria-disabled={props.atEnd ? "true" : undefined}
onClick={() => !props.atEnd && props.onNext()}
onClick={() => !props.atEnd && props.onNext?.()}
>
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
<path d="M4.5 3L7.5 6L4.5 9" stroke="currentColor" stroke-width="1.25" stroke-linecap="square" />
Expand Down
47 changes: 47 additions & 0 deletions services/site/src/component/site/carousel-nav.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* What the products carousel's previous and next do (ProductsArrows renders them, products-arrows.tsx,
* in the app and on the static pages): shown while the cards overflow, each end aria-disabled once
* the cards reach it, and a press moves by as many whole cards as fit (at least one), which the snap
* lines up. No framework code. `nav` holds the two buttons; returns its stop.
*/
export function carouselNav(nav: HTMLElement, track: HTMLElement) {
const [previous, next] = nav.querySelectorAll("button")
const measure = () => {
const max = track.scrollWidth - track.clientWidth
const at = Math.abs(track.scrollLeft)
nav.hidden = max <= 1
disable(previous, at < 2)
disable(next, at > max - 2)
}
const page = (direction: 1 | -1) => {
const card = track.firstElementChild
if (!(card instanceof HTMLElement)) return
const style = getComputedStyle(track)
const step = card.offsetWidth + (parseFloat(style.columnGap) || 0)
const cards = Math.max(1, Math.floor(track.clientWidth / step))
track.scrollBy({
left: direction * (style.direction === "rtl" ? -1 : 1) * step * cards,
behavior: matchMedia("(prefers-reduced-motion: reduce)").matches ? "instant" : "smooth",
})
}
const click = (event: MouseEvent) => {
const button = (event.target as Element).closest("button")
if (!button || button.getAttribute("aria-disabled") === "true") return
page(button === previous ? -1 : 1)
}
nav.addEventListener("click", click)
const resize = new ResizeObserver(measure)
resize.observe(track)
track.addEventListener("scroll", measure, { passive: true })
measure()
return () => {
nav.removeEventListener("click", click)
resize.disconnect()
track.removeEventListener("scroll", measure)
}
}

function disable(button: HTMLButtonElement, disabled: boolean) {
if (disabled) return button.setAttribute("aria-disabled", "true")
button.removeAttribute("aria-disabled")
}
Loading
Loading