mirror of
https://github.com/zoo-apps/zoolabs.io.git
synced 2026-10-30 14:56:27 +00:00
The clip is the page rather than a picture on it, and the conversation was sitting in the middle of it — a column of type over the animal it exists to show. On a wide screen the column is now on the right and Blue swims in the two thirds nobody is typing over. It moves once, on the first question: before anyone speaks it sits wider and further in, because the greeting is the only content on the page and a narrow ribbon at the edge is not an opening; on the first question it settles to its talking width at the edge. Two concrete lengths, so it animates rather than jumping. A phone has no two thirds to spare, so below 60rem nothing changes. Blue also answers for the foundation now, not only for the ocean — on the front door that is most of what people ask. The counts come from the surface Blue is standing on: zoo.ngo counts its own papers, proposals and open models from the sources, and hands them over. Blue states those numbers and guesses none. They arrive as three integers in a plausible range or they do not arrive at all, because anything a URL can carry reaches a prompt.
54 lines
2.2 KiB
TypeScript
54 lines
2.2 KiB
TypeScript
import { useEffect, useRef, useState } from 'react'
|
|
|
|
/**
|
|
* How a reader got here, and where they go next.
|
|
*
|
|
* Blue is one page. zoo.ngo puts that same page in its hero rather than growing
|
|
* a second Blue, so this file is the whole of the difference between the two:
|
|
* `?embed` drops the chrome the host already draws, and `?q` is a question
|
|
* asked somewhere else that should be answered here rather than forgotten.
|
|
*
|
|
* Read after mount, from location — the site is a static export, so the query
|
|
* is not known at build time and reading it during render would hydrate wrong.
|
|
*/
|
|
/** What the foundation has published, as its own site counts it. */
|
|
export type Corpus = { papers: number; proposals: number; models: number }
|
|
|
|
export type Arrival = { embedded: boolean; seed: string; corpus: Corpus | null }
|
|
|
|
// A count arrives as a number or it does not arrive. Anything a URL can carry
|
|
// reaches a prompt, so nothing here is passed through as text: three integers in
|
|
// a plausible range, or nothing.
|
|
const counted = (q: URLSearchParams): Corpus | null => {
|
|
const n = (k: string) => {
|
|
const v = Number(q.get(k))
|
|
return Number.isInteger(v) && v > 0 && v < 100_000 ? v : null
|
|
}
|
|
const papers = n('papers'), proposals = n('proposals'), models = n('models')
|
|
return papers && proposals && models ? { papers, proposals, models } : null
|
|
}
|
|
|
|
export function useArrival(): Arrival {
|
|
const [it, set] = useState<Arrival>({ embedded: false, seed: '', corpus: null })
|
|
useEffect(() => {
|
|
const q = new URLSearchParams(location.search)
|
|
set({ embedded: q.has('embed'), seed: (q.get('q') ?? '').slice(0, 400), corpus: counted(q) })
|
|
}, [])
|
|
return it
|
|
}
|
|
|
|
/** Ask `question` once, whenever it arrives, and never again. */
|
|
export function useSeed(seed: string, ask: (q: string) => void) {
|
|
const done = useRef(false)
|
|
useEffect(() => {
|
|
if (!seed || done.current) return
|
|
done.current = true
|
|
ask(seed)
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [seed])
|
|
}
|
|
|
|
/** This page, at the top of a window, carrying the question already asked. */
|
|
export const full = (question?: string) =>
|
|
'https://zoolabs.io/' + (question ? '?q=' + encodeURIComponent(question) : '')
|