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
389 changes: 389 additions & 0 deletions apps/web/src/app/globals.css

Large diffs are not rendered by default.

12 changes: 8 additions & 4 deletions apps/web/src/app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import Link from "next/link";
import { StrataMark } from "@/components/StrataMark";
import { Caveman } from "@/components/Caveman";

export default function NotFound() {
return (
<article className="flex flex-col items-start gap-6 py-10 sm:flex-row sm:items-center sm:gap-12">
<div className="relative h-56 w-20 shrink-0 overflow-hidden rounded-b-[40%_12px] border border-dashed border-ink-3/60">
<div className="absolute inset-x-0 bottom-0 h-1/4 bg-[#34404f]/15" />
<div className="absolute inset-x-0 top-3 flex justify-center opacity-60">
<StrataMark size={18} />
<div className="flex shrink-0 items-end gap-2">
<div className="relative h-56 w-20 shrink-0 overflow-hidden rounded-b-[40%_12px] border border-dashed border-ink-3/60">
<div className="absolute inset-x-0 bottom-0 h-1/4 bg-[#34404f]/15" />
<div className="absolute inset-x-0 top-3 flex justify-center opacity-60">
<StrataMark size={18} />
</div>
</div>
<Caveman pose="puzzled" size={104} className="-mb-1" />
</div>
<div>
<h1 className="display text-[2.6rem] sm:text-h1">Nothing at this depth.</h1>
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function LibraryCore() {
const individuals = pops.data!.reduce((a, p) => a + (p.n_samples ?? 0), 0);
return (
<figure>
<CoreSample layers={layers} height={540} />
<CoreSample layers={layers} height={540} digger />
<figcaption className="mt-4 text-caption text-ink-3">
<span className="num text-ink-2">{int(total)}</span> populations,{" "}
<span className="num text-ink-2">{int(individuals)}</span> individuals. Click a layer to browse it.
Expand Down
50 changes: 26 additions & 24 deletions apps/web/src/app/upload/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { parseG25 } from "@/lib/g25";
import { relativeExpiry } from "@/lib/format";
import type { Dataset } from "@/lib/types";
import { QcReport } from "@/components/QcReport";
import { Caveman } from "@/components/Caveman";
import { useSession } from "@/components/SessionProvider";
import { Caveat, ErrorNote, PageHeader, Section } from "@/components/typography";

Expand Down Expand Up @@ -88,47 +89,48 @@ export default function UploadPage() {
<p className="mb-3 text-small text-ink-2">
23andMe, AncestryDNA, FamilyTreeDNA, MyHeritage or VCF. Up to 60&nbsp;MB.
</p>
<div
<input
ref={inputRef}
id={ids.file}
type="file"
accept={ACCEPT}
className="dropzone-input sr-only"
onChange={(e) => pick(e.target.files?.[0])}
/>
<label
htmlFor={ids.file}
data-drag={dragging}
onDragOver={(e) => {
e.preventDefault();
setDragging(true);
}}
onDragLeave={() => setDragging(false)}
onDragLeave={(e) => {
if (!e.currentTarget.contains(e.relatedTarget as Node | null)) setDragging(false);
}}
onDrop={(e) => {
e.preventDefault();
setDragging(false);
pick(e.dataTransfer.files[0]);
}}
className={`flex h-56 flex-col items-center justify-center rounded-[var(--radius-panel)] border-2 border-dashed px-6 text-center text-small transition-colors ${
dragging ? "border-malachite bg-malachite-soft" : "border-rule bg-panel"
}`}
className="dropzone cm-host text-small"
>
<Caveman pose={dragging ? "cheer" : "idle"} size={66} className="mb-2" />
{file ? (
<>
<p className="font-medium">{file.name}</p>
<p className="num mt-1 text-caption text-ink-3">{(file.size / 1e6).toFixed(1)} MB</p>
<button type="button" className="linkish mt-3 text-caption" onClick={() => setFile(null)}>
Choose a different file
</button>
<span className="block font-medium">{file.name}</span>
<span className="num mt-1 block text-caption text-ink-3">{(file.size / 1e6).toFixed(1)} MB</span>
<span className="linkish mt-3 text-caption">Choose a different file</span>
</>
) : dragging ? (
<span className="block text-[1.2rem] font-semibold text-malachite">Let go to add it</span>
) : (
<>
<p className="text-[1.2rem] font-semibold">Drop the file here</p>
<p className="my-1 text-caption text-ink-3">or</p>
<label htmlFor={ids.file} className="linkish cursor-pointer">
choose a file from your computer
</label>
<span className="block text-[1.2rem] font-semibold">Drop the file here</span>
<span className="my-1 block text-caption text-ink-3">or</span>
<span className="btn btn-quiet pointer-events-none">Choose a file</span>
</>
)}
<input
ref={inputRef}
id={ids.file}
type="file"
accept={ACCEPT}
className="sr-only"
onChange={(e) => pick(e.target.files?.[0])}
/>
</div>
</label>
<button type="button" className="btn mt-4" disabled={busy || !file} onClick={() => submit("file")}>
{busy ? "Uploading and checking…" : "Upload file"}
</button>
Expand Down
43 changes: 42 additions & 1 deletion apps/web/src/components/BuildDiagram.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,13 @@ export interface BuildStats {
export function BuildDiagram({ stats }: { stats: BuildStats }) {
const [wrap, width] = useWidth<HTMLDivElement>(820);
const [seenRef, seen] = useSeen<HTMLDivElement>(0.25);
const [sel, setSel] = useState<Id>("engine");
const [sel, setSelected] = useState<Id>("engine");
// Until someone points at a box, the others glow in turn to show they can be opened.
const [touched, setTouched] = useState(false);
const setSel = (id: Id) => {
setSelected(id);
setTouched(true);
};
const L = width < 620 ? NARROW : WIDE;
const linked = (e: Edge) => e.from === sel || e.to === sel;

Expand Down Expand Up @@ -224,6 +230,18 @@ export function BuildDiagram({ stats }: { stats: BuildStats }) {
</dl>

<div ref={wrap} className="panel p-3 sm:p-5">
<p className="mb-2 flex items-center gap-2 px-1 text-caption text-ink-3">
<svg aria-hidden viewBox="0 0 20 20" className="size-4 text-malachite" style={touched ? undefined : { animation: "strata-tap 2.4s ease-in-out infinite" }}>
<path
d="M7.5 9.5V3.8a1.3 1.3 0 0 1 2.6 0v5l.2-1.2a1.3 1.3 0 0 1 2.5.4v1l.3-.6a1.3 1.3 0 0 1 2.4.7v4.4c0 2.6-2 4.5-4.6 4.5h-1c-1.6 0-3-.8-3.8-2.1L3.8 12a1.3 1.3 0 0 1 2.1-1.5z"
fill="#e2efe9"
stroke="currentColor"
strokeWidth={1.4}
strokeLinejoin="round"
/>
</svg>
Tap a box to see what it does.
</p>
<svg viewBox={`0 0 ${L.w} ${L.h}`} className="mx-auto block w-full" style={{ maxWidth: L.w }} role="group" aria-label="How Strata is built">
<defs>
<marker id="wire-head" viewBox="0 0 8 8" refX={7} refY={4} markerWidth={7} markerHeight={7} orient="auto-start-reverse">
Expand Down Expand Up @@ -297,6 +315,20 @@ export function BuildDiagram({ stats }: { stats: BuildStats }) {
animation: seen ? `strata-pop 320ms cubic-bezier(.3,1.5,.5,1) ${i * 70}ms both` : undefined,
}}
>
{!touched && !on ? (
<rect
x={cx - w / 2 - 4}
y={cy - h / 2 - 4}
width={w + 8}
height={h + 8}
rx={11}
fill="none"
stroke="#1d6b56"
strokeWidth={2}
strokeOpacity={0}
style={{ animation: `strata-beckon 2.6s ease-in-out ${1.2 + i * 0.33}s infinite` }}
/>
) : null}
<rect
x={cx - w / 2}
y={cy - h / 2 + 3}
Expand All @@ -323,6 +355,15 @@ export function BuildDiagram({ stats }: { stats: BuildStats }) {
<text x={cx} y={cy + 15} textAnchor="middle" style={{ fontSize: 12.5 }} className="num fill-ink-3">
{n.sub}
</text>
{/* A corner badge: plus to open, filled when this box is the one shown below. */}
<g transform={`translate(${cx + w / 2} ${cy - h / 2})`}>
<circle r={8} fill={on ? "#1d6b56" : "#fff"} stroke={on ? "#1d6b56" : "#8a949c"} strokeWidth={1.2} className="transition-[fill,stroke] duration-200" />
{on ? (
<path d="M-3.4 -1.4 L0 2 L3.4 -1.4" fill="none" stroke="#fff" strokeWidth={1.8} strokeLinecap="round" strokeLinejoin="round" />
) : (
<path d="M-3.6 0 H3.6 M0 -3.6 V3.6" stroke="#4f5963" strokeWidth={1.7} strokeLinecap="round" />
)}
</g>
</g>
);
})}
Expand Down
Loading
Loading