diff --git a/README.md b/README.md index f9f5f74..d602aa0 100644 --- a/README.md +++ b/README.md @@ -12,3 +12,9 @@ bun run build | version | | | --- | --- | | [blue](designs/renox-as-planet/versions/blue) | Renox as a planet, blue | + +## planet-in-progress + +| version | | +| --- | --- | +| [tiles](designs/planet-in-progress/versions/tiles) | Planet in progress, tiles | diff --git a/designs/planet-in-progress/versions/tiles/README.md b/designs/planet-in-progress/versions/tiles/README.md new file mode 100644 index 0000000..0f5b474 --- /dev/null +++ b/designs/planet-in-progress/versions/tiles/README.md @@ -0,0 +1,20 @@ +# Planet in progress: tiles + +![Planet in progress, dark](logo-dark.png) + +A planet that's still being built, for a company that's still growing. The sphere is made of flat-shaded tiles. The side facing the build direction is already laid, the rest is still bare scaffolding, and a dotted outline marks the full sphere. Three orange tiles are flying in toward their dashed sockets along the build edge. The wordmark underneath is set in Sora Bold. + +The script places a latitude/longitude grid on a sphere and projects it straight onto the page. Each cell is then marked as laid or scaffold by how far it faces the build direction, plus some random jitter so the edge comes out ragged. Laid tiles are shaded by how much they face the light. Laid tiles on the far side are drawn darker, so gaps in the shell show its inside. [opentype.js](https://github.com/opentypejs/opentype.js) turns the wordmark into outlines, and [resvg](https://github.com/thx/resvg-js) renders the PNGs. + +```sh +bun install +bun run build +``` + +| file | what | +| --- | --- | +| `logo-dark.svg` / `logo-dark.png` | sphere + wordmark on navy | +| `logo-light.svg` / `logo-light.png` | sphere + wordmark on off-white | +| `mark.svg` / `mark.png` | sphere only, transparent, works on light and dark | + +The settings at the top of `index.js` control the camera tilt (`T`), spin (`YAW`), tile size (`STEP`), build direction (`B`), light (`L`), tile gaps (`GAP`), how ragged the build edge is (`JITTER`) and how far each incoming tile still has to travel (`FLIGHTS`). The random seed is fixed, so every build comes out the same. diff --git a/designs/planet-in-progress/versions/tiles/bun.lock b/designs/planet-in-progress/versions/tiles/bun.lock new file mode 100644 index 0000000..eeab780 --- /dev/null +++ b/designs/planet-in-progress/versions/tiles/bun.lock @@ -0,0 +1,45 @@ +{ + "lockfileVersion": 2, + "configVersion": 1, + "workspaces": { + "": { + "name": "planet-in-progress-tiles", + "dependencies": { + "@fontsource/sora": "^5.3.0", + "@resvg/resvg-js": "^2.6.2", + "opentype.js": "^2.0.0", + }, + }, + }, + "packages": { + "@fontsource/sora": ["@fontsource/sora@5.3.0", "", {}, "sha512-SqW3OpKxxfKvxDLPlbdrWf2KooPfbd+W3jaYn483o+Y/JrmU87vjFPNptDqwP428c3vY+gnranLhjcbRaXB0Jw=="], + + "@resvg/resvg-js": ["@resvg/resvg-js@2.6.2", "", { "optionalDependencies": { "@resvg/resvg-js-android-arm-eabi": "2.6.2", "@resvg/resvg-js-android-arm64": "2.6.2", "@resvg/resvg-js-darwin-arm64": "2.6.2", "@resvg/resvg-js-darwin-x64": "2.6.2", "@resvg/resvg-js-linux-arm-gnueabihf": "2.6.2", "@resvg/resvg-js-linux-arm64-gnu": "2.6.2", "@resvg/resvg-js-linux-arm64-musl": "2.6.2", "@resvg/resvg-js-linux-x64-gnu": "2.6.2", "@resvg/resvg-js-linux-x64-musl": "2.6.2", "@resvg/resvg-js-win32-arm64-msvc": "2.6.2", "@resvg/resvg-js-win32-ia32-msvc": "2.6.2", "@resvg/resvg-js-win32-x64-msvc": "2.6.2" } }, "sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q=="], + + "@resvg/resvg-js-android-arm-eabi": ["@resvg/resvg-js-android-arm-eabi@2.6.2", "", { "os": "android", "cpu": "arm" }, "sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA=="], + + "@resvg/resvg-js-android-arm64": ["@resvg/resvg-js-android-arm64@2.6.2", "", { "os": "android", "cpu": "arm64" }, "sha512-VcOKezEhm2VqzXpcIJoITuvUS/fcjIw5NA/w3tjzWyzmvoCdd+QXIqy3FBGulWdClvp4g+IfUemigrkLThSjAQ=="], + + "@resvg/resvg-js-darwin-arm64": ["@resvg/resvg-js-darwin-arm64@2.6.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-nmok2LnAd6nLUKI16aEB9ydMC6Lidiiq2m1nEBDR1LaaP7FGs4AJ90qDraxX+CWlVuRlvNjyYJTNv8qFjtL9+A=="], + + "@resvg/resvg-js-darwin-x64": ["@resvg/resvg-js-darwin-x64@2.6.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-GInyZLjgWDfsVT6+SHxQVRwNzV0AuA1uqGsOAW+0th56J7Nh6bHHKXHBWzUrihxMetcFDmQMAX1tZ1fZDYSRsw=="], + + "@resvg/resvg-js-linux-arm-gnueabihf": ["@resvg/resvg-js-linux-arm-gnueabihf@2.6.2", "", { "os": "linux", "cpu": "arm" }, "sha512-YIV3u/R9zJbpqTTNwTZM5/ocWetDKGsro0SWp70eGEM9eV2MerWyBRZnQIgzU3YBnSBQ1RcxRZvY/UxwESfZIw=="], + + "@resvg/resvg-js-linux-arm64-gnu": ["@resvg/resvg-js-linux-arm64-gnu@2.6.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-zc2BlJSim7YR4FZDQ8OUoJg5holYzdiYMeobb9pJuGDidGL9KZUv7SbiD4E8oZogtYY42UZEap7dqkkYuA91pg=="], + + "@resvg/resvg-js-linux-arm64-musl": ["@resvg/resvg-js-linux-arm64-musl@2.6.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg=="], + + "@resvg/resvg-js-linux-x64-gnu": ["@resvg/resvg-js-linux-x64-gnu@2.6.2", "", { "os": "linux", "cpu": "x64" }, "sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw=="], + + "@resvg/resvg-js-linux-x64-musl": ["@resvg/resvg-js-linux-x64-musl@2.6.2", "", { "os": "linux", "cpu": "x64" }, "sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ=="], + + "@resvg/resvg-js-win32-arm64-msvc": ["@resvg/resvg-js-win32-arm64-msvc@2.6.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ=="], + + "@resvg/resvg-js-win32-ia32-msvc": ["@resvg/resvg-js-win32-ia32-msvc@2.6.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-har4aPAlvjnLcil40AC77YDIk6loMawuJwFINEM7n0pZviwMkMvjb2W5ZirsNOZY4aDbo5tLx0wNMREp5Brk+w=="], + + "@resvg/resvg-js-win32-x64-msvc": ["@resvg/resvg-js-win32-x64-msvc@2.6.2", "", { "os": "win32", "cpu": "x64" }, "sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ=="], + + "opentype.js": ["opentype.js@2.0.0", "", { "bin": { "ot": "bin/ot" } }, "sha512-kCyjv6xdDY1W/jLWZ/L3QhhTlKUqDZMQ5+Jdlw12b3dXkKNpYBqqlMMj0YDQPShWFTMwgZI1hG14kN3XUDSg/A=="], + } +} diff --git a/designs/planet-in-progress/versions/tiles/index.js b/designs/planet-in-progress/versions/tiles/index.js new file mode 100644 index 0000000..7306c6b --- /dev/null +++ b/designs/planet-in-progress/versions/tiles/index.js @@ -0,0 +1,118 @@ +// Planet in progress: a faceted sphere still being assembled. Tiles facing the build +// direction are laid, the rest is scaffolding, and a few tiles are flying into place. +import opentype from 'opentype.js' +import { Resvg } from '@resvg/resvg-js' +import { readFileSync, writeFileSync } from 'fs' + +const at = p => new URL(p, import.meta.url) +const fb = readFileSync(at('node_modules/@fontsource/sora/files/sora-latin-700-normal.woff')) +const font = opentype.parse(fb.buffer.slice(fb.byteOffset, fb.byteOffset + fb.byteLength)) + +const { sin, cos, hypot, abs, max, min, PI } = Math +const unit = v => v.map(c => c / hypot(...v)) +const dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2] +const D = PI / 180, S = 1200, CX = 600, CY = 485, R = 290 +const T = 16 * D, YAW = 8 * D, STEP = 15 // camera tilt, globe spin, tile size in degrees +const B = unit([-1, -.32, .12]) // build direction, tiles facing it are finished +const L = unit([-.5, .65, .6]) // light, view space +const GAP = .9 // tile size inside its grid cell +const JITTER = .5 // how ragged the build edge is +const FLIGHTS = [.18, .42, .8] // how far each incoming tile still has to travel +const n = v => +v.toFixed(1) +const rnd = (s => () => (s = (s * 16807) % 2147483647) / 2147483647)(11) + +// lon/lat (degrees) -> view-space unit vector, z > 0 faces the camera +const V = (lon, lat) => { + const x = cos(lat * D) * sin(lon * D + YAW), y = sin(lat * D), z = cos(lat * D) * cos(lon * D + YAW) + return [x, y * cos(T) - z * sin(T), y * sin(T) + z * cos(T)] +} +const xy = v => [CX + R * v[0], CY - R * v[1]] +const poly = pts => 'M' + pts.map(v => xy(v).map(n).join(' ')).join('L') + 'Z' +const ramp = (stops, t) => { + t = max(0, min(1, t)) * (stops.length - 1) + const i = min(stops.length - 2, t | 0), f = t - i, [a, b] = [stops[i], stops[i + 1]].map(s => [1, 3, 5].map(j => parseInt(s.slice(j, j + 2), 16))) + return '#' + a.map((c, j) => Math.round(c + (b[j] - c) * f).toString(16).padStart(2, '0')).join('') +} +const BLUE = ['#140e4f', '#2a1fa8', '#4b4ff0', '#8197ff', '#bccbff'], ORANGE = ['#7a2418', '#e8512f', '#ff8a3d', '#ffc56b', '#fff0c4'] +const lit = nv => .2 + .8 * max(0, dot(nv, L)) + +// grid cells, each laid, in flight or still scaffold. The poles are single round +// caps so the meridians don't bunch up there +const TOP = 90 - STEP, ring = la => Array.from({ length: 360 / STEP }, (_, i) => [i * STEP, la]) +const grid = [[ring(TOP), [0, 90]], [ring(-TOP).reverse(), [0, -90]]] +for (let la = -TOP; la < TOP; la += STEP) for (let lo = 0; lo < 360; lo += STEP) + grid.push([[[lo, la], [lo + STEP, la], [lo + STEP, la + STEP], [lo, la + STEP]], [lo + STEP / 2, la + STEP / 2]]) +const cells = grid.map(([g, mid]) => { + const c = V(...mid), b = dot(c, B) + (rnd() - .5) * JITTER, r = rnd() + const state = b > .1 || b > -.2 && c[2] > .3 && r < .45 ? 'solid' : 'wire' + return { g, c, b, lat: mid[1], state, q: g.map(p => V(...p)) } +}) +// tiles in flight: scaffold cells right at the build edge, one per row +cells.filter(c => c.state == 'wire' && c.b > -.35 && c.c[2] > .35 && abs(c.lat) < 50).sort((a, b) => b.b - a.b) + .reduce((rows, c) => rows.size < FLIGHTS.length && !rows.has(c.lat) ? (c.state = 'flying', rows.add(c.lat)) : rows, new Set()) +const tile = (q, c, s = GAP, r = 1) => q.map(p => p.map((v, i) => (c[i] + (v - c[i]) * s) * r)) + +// scaffold edges and joints, deduped across neighbouring cells +const key = ([lo, la]) => la % 90 ? lo % 360 + ',' + la : la +const edges = new Map(), joints = new Map() +for (const { g, q, state } of cells) if (state != 'solid') g.forEach((p, i) => { + const j = (i + 1) % g.length + if (key(p) != key(g[j])) edges.set([p, g[j]].map(key).sort().join('|'), [q[i], q[j]]) + joints.set(key(p), q[i]) +}) +const front = v => v[2] > 0 +const line = ([a, b]) => 'M' + xy(a).map(n).join(' ') + 'L' + xy(b).map(n).join(' ') +const wire = f => [...edges.values()].filter(e => f(e[0][2] + e[1][2] > 0)).map(line).join('') +const dots = (f, r) => [...joints.values()].filter(v => f(front(v))).map(v => ``).join('') + +const byDepth = (a, b) => a.c[2] - b.c[2] +const inside = cells.filter(c => c.state == 'solid' && c.c[2] <= 0).sort(byDepth) + .map(({ q, c }) => ``).join('') +const outside = cells.filter(c => c.state == 'solid' && c.c[2] > 0).sort(byDepth) + .map(({ q, c }) => ``).join('') +const sockets = cells.filter(c => c.state == 'flying') + .map(({ q, c }) => ``).join('') +// flying tiles drift in from the upper right, lifted off their sockets +const flying = cells.filter(c => c.state == 'flying').map(({ q, c }, i) => { + const o = [.8, .55, .25].map((v, j) => v * FLIGHTS[i] + c[j] * .05) + const t = tile(q, c, GAP * 1.04).map(p => p.map((v, k) => v + o[k])), [a, b] = [c, c.map((v, k) => v + o[k])].map(v => xy(v).map(n).join(' ')) + return `` +}).join('') + +// wordmark, laid out glyph by glyph so the tracking can be opened up +const word = (text, size, track, y) => { + const gs = font.stringToGlyphs(text), adv = g => g.advanceWidth / font.unitsPerEm * size + const w = gs.reduce((s, g) => s + adv(g), 0) + track * size * (gs.length - 1) + let x = CX - w / 2 + const pd = cmds => cmds.map(c => c.type + [c.x1, c.y1, c.x2, c.y2, c.x, c.y].filter(v => v != null).map(n).join(' ')).join('') + return gs.map(g => (d => (x += adv(g) + track * size, d))(pd(g.getPath(x, y, size).commands))).join('') +} +const wordmark = word('RENOX', 124, .16, 985) + +const THEMES = { + dark: { bg: '#0b0a24', ink: '#ffffff', wire: '#8c95ff', ghost: '#5b63d9', back: .28 }, + light: { bg: '#f5f4ff', ink: '#16123f', wire: '#4a4fd1', ghost: '#8d92e8', back: .16 }, + mark: { wire: '#6f78f2', ghost: '#7f86e6', back: .2 }, +} +const svg = ({ bg, ink, wire: w, ghost, back }) => ` + +${bg ? `` : ''} + + + + ${dots(f => !f, 2)} + ${inside} + + ${dots(f => f, 3.6)} + ${outside} + ${sockets} + ${flying} + +${ink ? `` : ''} +` + +for (const [name, theme] of Object.entries(THEMES)) { + const s = svg(theme), file = name == 'mark' ? 'mark' : 'logo-' + name + writeFileSync(at(file + '.svg'), s) + writeFileSync(at(file + '.png'), new Resvg(s, { fitTo: { mode: 'width', value: 2048 } }).render().asPng()) +} diff --git a/designs/planet-in-progress/versions/tiles/logo-dark.png b/designs/planet-in-progress/versions/tiles/logo-dark.png new file mode 100644 index 0000000..a621cd9 Binary files /dev/null and b/designs/planet-in-progress/versions/tiles/logo-dark.png differ diff --git a/designs/planet-in-progress/versions/tiles/logo-dark.svg b/designs/planet-in-progress/versions/tiles/logo-dark.svg new file mode 100644 index 0000000..2da7f30 --- /dev/null +++ b/designs/planet-in-progress/versions/tiles/logo-dark.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/designs/planet-in-progress/versions/tiles/logo-light.png b/designs/planet-in-progress/versions/tiles/logo-light.png new file mode 100644 index 0000000..22e2415 Binary files /dev/null and b/designs/planet-in-progress/versions/tiles/logo-light.png differ diff --git a/designs/planet-in-progress/versions/tiles/logo-light.svg b/designs/planet-in-progress/versions/tiles/logo-light.svg new file mode 100644 index 0000000..b19c429 --- /dev/null +++ b/designs/planet-in-progress/versions/tiles/logo-light.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/designs/planet-in-progress/versions/tiles/mark.png b/designs/planet-in-progress/versions/tiles/mark.png new file mode 100644 index 0000000..e1f9fb5 Binary files /dev/null and b/designs/planet-in-progress/versions/tiles/mark.png differ diff --git a/designs/planet-in-progress/versions/tiles/mark.svg b/designs/planet-in-progress/versions/tiles/mark.svg new file mode 100644 index 0000000..1a08761 --- /dev/null +++ b/designs/planet-in-progress/versions/tiles/mark.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/designs/planet-in-progress/versions/tiles/package.json b/designs/planet-in-progress/versions/tiles/package.json new file mode 100644 index 0000000..bd5262a --- /dev/null +++ b/designs/planet-in-progress/versions/tiles/package.json @@ -0,0 +1,14 @@ +{ + "name": "planet-in-progress-tiles", + "private": true, + "type": "module", + "description": "A planet still being built: solid tiles on one side, scaffolding on the other", + "scripts": { + "build": "bun index.js" + }, + "dependencies": { + "@fontsource/sora": "^5.3.0", + "@resvg/resvg-js": "^2.6.2", + "opentype.js": "^2.0.0" + } +}