Site: hero model of six apps, roads, beat-timed growth
This commit is contained in:
9 files changed
+199
-16
No files matched your search
@@ -11,3 +11,22 @@
|
||||
.face.right {
|
||||
fill: color-mix(in oklab, var(--tone), black 20%);
|
||||
}
|
||||
|
||||
/* Each block drops one step and lands on its beat. */
|
||||
.grow .block {
|
||||
animation: land 0.2s cubic-bezier(0.2, 0.8, 0.3, 1) both;
|
||||
animation-delay: calc(var(--land) - 0.2s);
|
||||
}
|
||||
|
||||
@keyframes land {
|
||||
from {
|
||||
transform: translateY(-28px);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.grow .block {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
+23
-8
@@ -1,15 +1,30 @@
|
||||
import "./style.css";
|
||||
import "./iso.css";
|
||||
import { block, stage } from "./iso.ts";
|
||||
import { model } from "./model.ts";
|
||||
|
||||
/** 112 BPM, the film's tempo; blocks land on sixteenths. */
|
||||
const BEAT = 60 / 112;
|
||||
const STEP = BEAT / 4;
|
||||
|
||||
const app = document.querySelector<HTMLElement>("#app");
|
||||
if (!app) throw new Error("index.html has no #app");
|
||||
|
||||
app.append(
|
||||
stage([
|
||||
block({ x: 0, y: 0, w: 12, d: 12, h: 0.5, tone: "#e9e9e6" }),
|
||||
block({ x: 2, y: 2, z: 0.5, w: 3, d: 3, h: 4, tone: "#f4f4f2" }),
|
||||
block({ x: 7, y: 2, z: 0.5, w: 3, d: 3, h: 2, tone: "#f4f4f2" }),
|
||||
block({ x: 2, y: 7, z: 0.5, w: 3, d: 3, h: 1, tone: "#3d7fe5" }),
|
||||
]),
|
||||
);
|
||||
const blocks: SVGGElement[] = [];
|
||||
let beat = 1;
|
||||
for (const part of model) {
|
||||
part.blocks.forEach((spec, i) => {
|
||||
const g = block(spec);
|
||||
g.style.setProperty("--land", `${(beat * BEAT + i * STEP).toFixed(3)}s`);
|
||||
blocks.push(g);
|
||||
});
|
||||
beat += Math.ceil(part.blocks.length / 4);
|
||||
}
|
||||
|
||||
const title = document.createElement("h1");
|
||||
title.textContent = "Ely Office";
|
||||
const model3d = stage(blocks);
|
||||
model3d.classList.add("grow");
|
||||
model3d.setAttribute("role", "img");
|
||||
model3d.setAttribute("aria-label", model.slice(1, -1).map((p) => p.name).join(", "));
|
||||
app.append(title, model3d);
|
||||
@@ -0,0 +1,61 @@
|
||||
// The desk model: six apps, each its own object.
|
||||
import type { Block } from "./iso.ts";
|
||||
|
||||
const PAPER = "#f4f4f2";
|
||||
const DESK = "#e6e6e2";
|
||||
/** The one accent: roads and the selected cell. */
|
||||
export const ACCENT = "#3d7fe5";
|
||||
const TOP = 0.6;
|
||||
|
||||
/** One part of the model, grown together. */
|
||||
export interface Part {
|
||||
name: string;
|
||||
blocks: Required<Block>[];
|
||||
}
|
||||
|
||||
const on = (x: number, y: number, w: number, d: number, h: number, z = 0, tone = PAPER): Required<Block> => ({
|
||||
x, y, z: TOP + z, w, d, h, tone,
|
||||
});
|
||||
|
||||
/** A stack of pages. */
|
||||
const doc = [0, 1, 2, 3].map((i) => on(1 + i * 0.08, 1 - i * 0.08, 2.6, 3, 0.25, i * 0.25));
|
||||
|
||||
/** A three by three sheet; one cell is selected. */
|
||||
const heights = [1.2, 2.4, 1.6, 0.8, 1.8, 1, 0.5, 1.4, 0.7];
|
||||
const table = heights.map((h, i) =>
|
||||
on(5 + (i % 3), 1 + Math.floor(i / 3), 0.9, 0.9, h, 0, i === 4 ? ACCENT : PAPER),
|
||||
);
|
||||
|
||||
/** A slide on its stand. */
|
||||
const show = [on(9.4, 1, 3.2, 0.25, 2.3), on(9.4, 1.25, 3.2, 1.6, 0.25)];
|
||||
|
||||
/** A page with its corner folded down. */
|
||||
const pdf = [on(13, 3.2, 2.4, 2.4, 1), on(13, 5.6, 1.6, 0.8, 1), on(14.6, 5.6, 0.8, 0.8, 0.3)];
|
||||
|
||||
/** A trunk and a branch, joined by a merge. */
|
||||
const tree = [on(6.5, 7.6, 0.7, 0.7, 2.8), on(8.7, 7.6, 0.7, 0.7, 1.8), on(7.2, 7.6, 1.5, 0.7, 0.3, 1.2)];
|
||||
|
||||
/** Headings stepping down. */
|
||||
const mark = [0, 1, 2].map((i) => on(1, 7 + i, 3, 1, 2 - i * 0.6));
|
||||
|
||||
/** Roads: every document exports to PDF; Markdown goes to Git. */
|
||||
const road = (x: number, y: number, w: number, d: number) => on(x, y, w, d, 0.08, 0, ACCENT);
|
||||
const roads = [
|
||||
road(2, 4, 0.6, 0.6),
|
||||
road(6, 4, 0.6, 0.6),
|
||||
road(10.7, 2.85, 0.6, 1.75),
|
||||
road(2, 4.6, 11, 0.6),
|
||||
road(4, 7.6, 2.5, 0.6),
|
||||
];
|
||||
|
||||
/** Growth order: desk, the six apps, then roads. */
|
||||
export const model: Part[] = [
|
||||
{ name: "Desk", blocks: [{ x: 0, y: 0, z: 0, w: 16, d: 11, h: TOP, tone: DESK }] },
|
||||
{ name: "ElyDoc", blocks: doc },
|
||||
{ name: "ElyTable", blocks: table },
|
||||
{ name: "ElyShow", blocks: show },
|
||||
{ name: "ElyPDF", blocks: pdf },
|
||||
{ name: "ElyTree", blocks: tree },
|
||||
{ name: "ElyMark", blocks: mark },
|
||||
{ name: "Roads", blocks: roads },
|
||||
];
|
||||
+17
-1
@@ -12,5 +12,21 @@ body {
|
||||
#app {
|
||||
min-height: 100vh;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
place-content: center;
|
||||
justify-items: center;
|
||||
gap: 6rem;
|
||||
padding: 9rem 1.5rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: clamp(3rem, 8vw, 6rem);
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
|
||||
.stage {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
Reference in new issue
Block a user