nvrsion: Add, refactor, and fix: use ./app-logo-canary.icon for canary builds in BUILD.md, app-logo-canary.icon/icon.json, and scripts/package-app.sh; refactor chat logic to use advanced technologies in website/index.html; adjust chat atom animation to model realism with 2d lines and circles vanishing at angles; resolve '--build-system native' deprecation in ./app-logo-canary.icon/icon.json; adjust make BUMP= defaults to ‘build’ for canary builds, releases, and local dev in Makefile and scripts/release-macos.sh.
Nucleic-Promote: 1 Co-authored-by: Nucleic <[email protected]>
This commit is contained in:
@@ -85,15 +85,16 @@ dmg-stable:
|
||||
./scripts/make-dmg.sh stable
|
||||
|
||||
# Releases bump ./VERSION: build number always +1, plus a semver bump via BUMP. Default is minor,
|
||||
# except the fast-moving canary channel which defaults to patch. BUMP overrides either.
|
||||
# except the fast-moving canary/dev channels which default to build-only (no marketing-version
|
||||
# change). BUMP overrides either.
|
||||
# make release-beta # 0.1.0 (827) -> 0.2.0 (828): minor (the default)
|
||||
# make release-canary # 0.1.0 (827) -> 0.1.1 (828): patch (canary's default)
|
||||
# make release-canary # 0.1.0 (827) -> 0.1.0 (828): build-only (canary's default)
|
||||
# make release-beta BUMP=major # -> 1.0.0 (828)
|
||||
# make release-beta BUMP=patch # -> 0.1.1 (828)
|
||||
# make release-canary BUMP=minor # -> 0.2.0 (828): override canary's patch default
|
||||
# make release-canary BUMP=patch # -> 0.1.1 (828): override canary's build-only default
|
||||
# make release-beta BUMP=build # -> 0.1.0 (828): build number only
|
||||
|
||||
## release-canary: signed + notarized + stapled DMG release (canary channel). BUMP=major|minor|patch (default: patch)
|
||||
## release-canary: signed + notarized + stapled DMG release (canary channel). BUMP=major|minor|patch|build (default: build)
|
||||
release-canary:
|
||||
NUCLEIC_BUMP=$(BUMP) ./scripts/release-macos.sh canary
|
||||
|
||||
|
||||
@@ -41,8 +41,8 @@
|
||||
"value" : false
|
||||
}
|
||||
],
|
||||
"image-name" : "logo-v1-dark.png",
|
||||
"name" : "logo-v1-dark",
|
||||
"image-name" : "logo-v1-canary.png",
|
||||
"name" : "logo-v1-canary",
|
||||
"position" : {
|
||||
"scale" : 0.25,
|
||||
"translation-in-points" : [
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
# 'nucleic-notary') OR the NUCLEIC_ASC_* API-key env vars (see scripts/notarize.sh).
|
||||
#
|
||||
# Versioning: every release bumps the build number (CFBundleVersion) by 1 in ./VERSION, and
|
||||
# bumps a semver component (CFBundleShortVersionString) — default minor (patch on the fast-moving
|
||||
# canary channel), since we're pre-1.0. The new ./VERSION is committed on success (so versions/build
|
||||
# bumps a semver component (CFBundleShortVersionString) — default minor (build-only on the
|
||||
# fast-moving canary and dev channels), since we're pre-1.0. The new ./VERSION is committed on success (so versions/build
|
||||
# numbers stay monotonic). Both the Makefile shortcut `make release-beta BUMP=major` and a direct
|
||||
# `NUCLEIC_BUMP=build` work.
|
||||
#
|
||||
# Env:
|
||||
# NUCLEIC_SIGN_ID signing identity (default: auto-detect Developer ID Application)
|
||||
# NUCLEIC_BUMP semver bump: minor (default; patch on canary) | major | patch | build (build = build number only)
|
||||
# NUCLEIC_BUMP semver bump: minor (default; build on canary/dev) | major | patch | build (build = build number only)
|
||||
# NUCLEIC_VERSION_COMMIT commit the ./VERSION bump on success (default 1; 0 leaves it unstaged)
|
||||
# NUCLEIC_VERSION pin the marketing version (overrides ./VERSION; forwarded to package-app.sh)
|
||||
# NUCLEIC_NOTARIZE_APP notarize+staple the .app too (default 1; the DMG is always done)
|
||||
@@ -65,18 +65,19 @@ export NUCLEIC_SIGN_ID="$SIGN_ID"
|
||||
echo "▸ Releasing $CHANNEL with identity: $SIGN_ID"
|
||||
|
||||
# 0. Bump the version: build number always +1, plus a semver bump (NUCLEIC_BUMP, default minor —
|
||||
# we're pre-1.0, so each release is a minor, except canary which defaults to a patch; major stays
|
||||
# 0 until an explicit BUMP=major).
|
||||
# we're pre-1.0, so each release is a minor, except the canary/dev channels which default to
|
||||
# build-only (no marketing-version change); major stays 0 until an explicit BUMP=major).
|
||||
# bump-version.sh rewrites ./VERSION; package-app.sh reads it on the very next line. If the
|
||||
# release fails before the artifact exists, an EXIT trap restores ./VERSION to its committed
|
||||
# value so a retry reuses the same number instead of skipping one. On success the bump is
|
||||
# committed (step 7); SUCCEEDED gates the trap so the bump survives.
|
||||
SUCCEEDED=0
|
||||
trap '[ "$SUCCEEDED" = 1 ] || git checkout -- VERSION 2>/dev/null || true' EXIT
|
||||
# Default bump is channel-aware: canary iterates fast so it defaults to a patch; the promotion
|
||||
# Default bump is channel-aware: the fast-moving canary and dev channels iterate constantly, so
|
||||
# they default to build-only (build number +1, marketing version unchanged); the promotion
|
||||
# channels (beta/rc/stable) default to a minor (we're pre-1.0). NUCLEIC_BUMP overrides either —
|
||||
# e.g. `make release-canary BUMP=minor`.
|
||||
DEFAULT_BUMP="minor"; [ "$CHANNEL" = "canary" ] && DEFAULT_BUMP="patch"
|
||||
# e.g. `make release-canary BUMP=patch`.
|
||||
DEFAULT_BUMP="minor"; case "$CHANNEL" in canary|dev) DEFAULT_BUMP="build" ;; esac
|
||||
BUMP="${NUCLEIC_BUMP:-$DEFAULT_BUMP}"
|
||||
read -r REL_VERSION REL_BUILD <<EOF
|
||||
$(scripts/bump-version.sh "$BUMP")
|
||||
|
||||
+162
-86
@@ -171,86 +171,25 @@
|
||||
border-radius:999px;padding:6px 13px;margin:0 0 6px;
|
||||
}
|
||||
|
||||
/* ---------- atom visual — true CSS 3D ----------
|
||||
The brand mark rebuilt as a real 3D system: three tilted circular orbits
|
||||
under perspective (each projects to the brand ellipse), electrons riding
|
||||
each ring, and the whole system slowly precessing. Every animation here
|
||||
is transform/opacity only, so it runs composited off the main thread —
|
||||
no SMIL, no script, no per-frame layout or paint.
|
||||
NOTE: no opacity/filter on .orbit itself — either would force group
|
||||
rasterization and disable the plane splitting that lets the rings
|
||||
visually interlock where they intersect. */
|
||||
/* ---------- atom visual ----------
|
||||
Drawn on <canvas> by the small scoped script beside it: orbits are real
|
||||
circles in 3D, perspective-projected and depth-sorted every frame. The
|
||||
rasterizer anti-aliases each stroke at any angle, and a constant tube
|
||||
width means an orbit turning edge-on reads as a wire — it never goes
|
||||
paper-thin or shimmers the way a flat CSS/SVG ring does. Renders only
|
||||
while on-screen; prefers-reduced-motion gets a single static frame. */
|
||||
.atom-wrap{display:flex;justify-content:center;align-items:center;min-width:0}
|
||||
.atom{
|
||||
width:100%;max-width:420px;aspect-ratio:1;position:relative;
|
||||
perspective:1100px;
|
||||
width:100%;max-width:420px;aspect-ratio:1;height:auto;
|
||||
/* radial clearing: fades the page's graph-paper dotting out beneath the
|
||||
atom so the ring geometry and nucleus ping sit on a clean field */
|
||||
atom so the projected orbits sit on a clean field */
|
||||
background:radial-gradient(closest-side, var(--paper) 52%, color-mix(in srgb,var(--paper) 55%, transparent) 78%, transparent 100%);
|
||||
}
|
||||
.hero-grid>*{min-width:0}
|
||||
.atom .sys3d{
|
||||
position:absolute;inset:0;transform-style:preserve-3d;
|
||||
transform:rotateX(-14deg); /* static pose when animation is off */
|
||||
animation:precess 44s linear infinite;
|
||||
animation-delay:-18s;
|
||||
}
|
||||
@keyframes precess{
|
||||
from{transform:rotateX(-14deg) rotateY(0turn)}
|
||||
to{transform:rotateX(-14deg) rotateY(1turn)}
|
||||
}
|
||||
.atom .orbit{
|
||||
position:absolute;left:15%;top:15%;width:70%;height:70%;
|
||||
border:5px solid var(--c);border-radius:50%;
|
||||
transform:rotateZ(var(--rz)) rotateX(68deg);
|
||||
transform-style:preserve-3d;
|
||||
}
|
||||
.atom .orbit.oa{--c:#5e9bd8;--rz:80deg}
|
||||
.atom .orbit.ob{--c:#db5f97;--rz:20deg}
|
||||
.atom .orbit.oc{--c:#c47e69;--rz:140deg}
|
||||
/* spinner rotates in the ring's own plane and carries the electrons;
|
||||
inset:-2.5px aligns its edges to the stroke's centerline */
|
||||
.atom .spinner{
|
||||
position:absolute;inset:-2.5px;transform-style:preserve-3d;
|
||||
animation:spin var(--dur) linear infinite;animation-delay:var(--ph,0s);
|
||||
}
|
||||
@keyframes spin{to{transform:rotate(360deg)}}
|
||||
.atom .e{
|
||||
position:absolute;left:50%;top:0;width:16px;height:16px;border-radius:50%;
|
||||
transform:translate(-50%,-50%);
|
||||
background:radial-gradient(circle at 32% 30%, color-mix(in srgb,var(--c) 45%, #fff), var(--c) 72%);
|
||||
box-shadow:0 1px 3px rgba(0,0,0,.18);
|
||||
}
|
||||
.atom .e.minor{top:100%;width:10px;height:10px;opacity:.75}
|
||||
/* nucleus overlay — the fixed point of control the 3D system turns around */
|
||||
.atom .nucleus3d{
|
||||
position:absolute;left:50%;top:50%;width:18px;height:18px;border-radius:50%;
|
||||
background:var(--ink);transform:translate(-50%,-50%);
|
||||
}
|
||||
.atom .halo,.atom .ping3d{
|
||||
position:absolute;left:50%;top:50%;width:32px;height:32px;border-radius:50%;
|
||||
border:1.5px solid var(--ink);transform:translate(-50%,-50%);
|
||||
}
|
||||
.atom .halo{opacity:.25}
|
||||
.atom .ping3d{opacity:0;animation:ping 5s linear infinite}
|
||||
@keyframes ping{
|
||||
from{transform:translate(-50%,-50%) scale(1);opacity:.28}
|
||||
to{transform:translate(-50%,-50%) scale(4.2);opacity:0}
|
||||
}
|
||||
@media (max-width:820px){
|
||||
.hero{padding:48px 0 32px}
|
||||
.hero-grid{grid-template-columns:1fr;gap:30px}
|
||||
.atom{max-width:230px}
|
||||
.atom .orbit{border-width:3px}
|
||||
.atom .spinner{inset:-1.5px}
|
||||
.atom .e{width:9px;height:9px}
|
||||
.atom .e.minor{width:6px;height:6px}
|
||||
.atom .nucleus3d{width:11px;height:11px}
|
||||
.atom .halo,.atom .ping3d{width:19px;height:19px}
|
||||
}
|
||||
@media (prefers-reduced-motion:reduce){
|
||||
.atom .sys3d,.atom .spinner{animation:none}
|
||||
.atom .e,.atom .ping3d{display:none}
|
||||
}
|
||||
|
||||
/* ---------- premise band ---------- */
|
||||
@@ -954,23 +893,160 @@
|
||||
</div>
|
||||
|
||||
<div class="atom-wrap">
|
||||
<!-- the brand atom in real 3D: agents (electrons) orbit one nucleus of
|
||||
control while the whole system precesses. Each tilted ring projects
|
||||
to the brand ellipse under perspective; rings interlock via CSS
|
||||
plane splitting. Spinner delays are negative so every orbit is
|
||||
mid-flight at first paint. -->
|
||||
<div class="atom" role="img" aria-label="Coding agents orbiting one point of control">
|
||||
<div class="sys3d">
|
||||
<div class="orbit oa"><div class="spinner" style="--dur:13s;--ph:-5s"><i class="e"></i><i class="e minor"></i></div></div>
|
||||
<div class="orbit ob"><div class="spinner" style="--dur:17s;--ph:-6s"><i class="e"></i><i class="e minor"></i></div></div>
|
||||
<div class="orbit oc"><div class="spinner" style="--dur:21s;--ph:-3s"><i class="e"></i><i class="e minor"></i></div></div>
|
||||
</div>
|
||||
<!-- nucleus ping — the control point broadcasting to its fleet -->
|
||||
<div class="ping3d"></div>
|
||||
<div class="halo"></div>
|
||||
<div class="nucleus3d"></div>
|
||||
</div>
|
||||
<canvas id="atom-cv" class="atom" role="img" aria-label="Coding agents orbiting one point of control"></canvas>
|
||||
<!-- static brand mark if scripting is unavailable -->
|
||||
<noscript>
|
||||
<style>#atom-cv{display:none}</style>
|
||||
<svg class="atom" viewBox="0 0 400 400" aria-hidden="true">
|
||||
<g fill="none" stroke-width="5">
|
||||
<ellipse cx="200" cy="200" rx="140" ry="58" transform="rotate(80 200 200)" stroke="#5e9bd8"/>
|
||||
<ellipse cx="200" cy="200" rx="140" ry="58" transform="rotate(20 200 200)" stroke="#db5f97"/>
|
||||
<ellipse cx="200" cy="200" rx="140" ry="58" transform="rotate(140 200 200)" stroke="#c47e69"/>
|
||||
</g>
|
||||
<circle cx="200" cy="200" r="9" fill="#181520"/>
|
||||
</svg>
|
||||
</noscript>
|
||||
</div>
|
||||
<script>
|
||||
/* Hero atom — a real 3D model rendered on canvas. Orbits are circles in
|
||||
3D (68° inclination, three azimuths), electrons ride them, the nucleus
|
||||
is a rotating cluster of nucleons, and the whole system precesses.
|
||||
Everything is perspective-projected and depth-sorted per frame
|
||||
(painter's algorithm), so orbits and electrons genuinely pass behind
|
||||
and in front of the nucleus. Canvas rasterization keeps strokes
|
||||
anti-aliased at every angle, unlike a flat ring turned edge-on.
|
||||
Perf: one 2D canvas capped at 2× DPR, ~300 primitives/frame; the rAF
|
||||
loop runs only while the canvas is on-screen, and
|
||||
prefers-reduced-motion gets a single static frame. */
|
||||
(() => {
|
||||
const cv = document.getElementById("atom-cv");
|
||||
const ctx = cv && cv.getContext && cv.getContext("2d");
|
||||
if (!ctx) return;
|
||||
const TAU = Math.PI * 2, TILT = 1.187; // 68° orbital inclination
|
||||
const ORB = [ // azimuth ≈ brand angles 80/20/140°
|
||||
{ c: [94, 155, 216], az: 1.396, T: 13, ph: [0.0, 2.6] },
|
||||
{ c: [219, 95, 151], az: 0.349, T: 17, ph: [1.2, 4.1] },
|
||||
{ c: [196, 126, 105], az: 2.443, T: 21, ph: [3.4, 5.9] },
|
||||
];
|
||||
// nucleon cluster: unit-ish directions, 1 = proton (ink), 0 = neutron (grey)
|
||||
const NUC = [
|
||||
[0, 0, 0, 1], [.95, .2, .1, 0], [-.85, .4, .3, 1], [.3, -.9, .35, 0],
|
||||
[-.3, .8, -.55, 0], [.5, .5, -.7, 1], [-.55, -.6, -.55, 1], [.1, -.35, .9, 0],
|
||||
];
|
||||
const SEG = 88, items = [];
|
||||
let W = 0, raf = 0, visible = false;
|
||||
|
||||
const rgba = (c, a) => `rgba(${c[0]},${c[1]},${c[2]},${a})`;
|
||||
|
||||
function draw(t) {
|
||||
const R = W * 0.35, f = W * 2.6, cx = W / 2, cy = W / 2;
|
||||
const pre = t * TAU / 44; // 44s precession
|
||||
const cp = Math.cos(pre), sp = Math.sin(pre);
|
||||
const cb = Math.cos(-0.24), sb = Math.sin(-0.24); // fixed camera tilt
|
||||
const rot = (x, y, z) => { // rotY(pre) then rotX(-14°)
|
||||
const x1 = x * cp + z * sp, z1 = z * cp - x * sp;
|
||||
return [x1, y * cb - z1 * sb, y * sb + z1 * cb];
|
||||
};
|
||||
ctx.clearRect(0, 0, W, W);
|
||||
items.length = 0;
|
||||
|
||||
for (const o of ORB) {
|
||||
// orbit basis vectors: circle in xy → Rx(TILT) → Rz(az) → world
|
||||
const ca = Math.cos(o.az), sa = Math.sin(o.az);
|
||||
const cT = Math.cos(TILT), sT = Math.sin(TILT);
|
||||
const u = rot(ca, sa, 0);
|
||||
const v = rot(-sa * cT, ca * cT, sT);
|
||||
const pt = (a) => {
|
||||
const co = Math.cos(a), si = Math.sin(a);
|
||||
const x = R * (u[0] * co + v[0] * si), y = R * (u[1] * co + v[1] * si),
|
||||
z = R * (u[2] * co + v[2] * si);
|
||||
const s = f / (f - z);
|
||||
return [cx + x * s, cy + y * s, z, s];
|
||||
};
|
||||
const P = [];
|
||||
for (let i = 0; i <= SEG; i++) P.push(pt(i / SEG * TAU));
|
||||
for (let i = 0; i < SEG; i++)
|
||||
items.push({ k: 0, z: (P[i][2] + P[i + 1][2]) / 2, o, a: P[i], b: P[i + 1], R });
|
||||
for (const ph of o.ph) {
|
||||
const a = ph + t * TAU / o.T, p = pt(a), tr = [];
|
||||
for (let j = 1; j <= 8; j++) tr.push(pt(a - j * 0.07));
|
||||
items.push({ k: 1, z: p[2], o, p, tr, R });
|
||||
}
|
||||
}
|
||||
// nucleus — cluster spins slowly on its own axis on top of the precession
|
||||
const cq = Math.cos(t * 0.5), sq = Math.sin(t * 0.5);
|
||||
const rc = W * 0.02, rn = W * 0.0225;
|
||||
for (const [dx0, dy, dz0, kind] of NUC) {
|
||||
const dx = dx0 * cq + dz0 * sq, dz = dz0 * cq - dx0 * sq;
|
||||
const p = rot(dx * rc, dy * rc, dz * rc);
|
||||
const s = f / (f - p[2]);
|
||||
items.push({ k: 2, z: p[2], x: cx + p[0] * s, y: cy + p[1] * s, r: rn * s, kind });
|
||||
}
|
||||
|
||||
items.sort((p, q) => p.z - q.z); // far → near
|
||||
ctx.lineCap = "butt";
|
||||
for (const it of items) {
|
||||
if (it.k === 0) { // orbit tube segment
|
||||
const d = (it.z / it.R + 1) / 2; // 0 far … 1 near
|
||||
ctx.strokeStyle = rgba(it.o.c, 0.22 + 0.6 * d);
|
||||
ctx.lineWidth = W * 0.012 * it.a[3];
|
||||
ctx.beginPath(); ctx.moveTo(it.a[0], it.a[1]); ctx.lineTo(it.b[0], it.b[1]); ctx.stroke();
|
||||
} else if (it.k === 1) { // electron: trail, glow, core
|
||||
const [x, y, , s] = it.p;
|
||||
const d = (it.z / it.R + 1) / 2, re = W * 0.017 * s;
|
||||
ctx.lineCap = "round";
|
||||
for (let j = 0; j < it.tr.length - 1; j++) {
|
||||
const q = 1 - j / it.tr.length;
|
||||
ctx.strokeStyle = rgba(it.o.c, 0.28 * q * q * (0.3 + 0.7 * d));
|
||||
ctx.lineWidth = re * 1.5 * q;
|
||||
ctx.beginPath(); ctx.moveTo(it.tr[j][0], it.tr[j][1]);
|
||||
ctx.lineTo(it.tr[j + 1][0], it.tr[j + 1][1]); ctx.stroke();
|
||||
}
|
||||
ctx.lineCap = "butt";
|
||||
let g = ctx.createRadialGradient(x, y, 0, x, y, re * 2.8);
|
||||
g.addColorStop(0, rgba(it.o.c, 0.4 * (0.4 + 0.6 * d)));
|
||||
g.addColorStop(1, rgba(it.o.c, 0));
|
||||
ctx.fillStyle = g;
|
||||
ctx.beginPath(); ctx.arc(x, y, re * 2.8, 0, TAU); ctx.fill();
|
||||
g = ctx.createRadialGradient(x - re * 0.35, y - re * 0.35, re * 0.15, x, y, re);
|
||||
g.addColorStop(0, rgba([255, 255, 255], 0.9));
|
||||
g.addColorStop(0.35, rgba(it.o.c, 1));
|
||||
g.addColorStop(1, rgba(it.o.c, 0.9));
|
||||
ctx.fillStyle = g;
|
||||
ctx.beginPath(); ctx.arc(x, y, re, 0, TAU); ctx.fill();
|
||||
} else { // nucleon sphere
|
||||
const base = it.kind ? [24, 21, 32] : [122, 114, 128];
|
||||
const hi = it.kind ? [90, 82, 104] : [178, 171, 184];
|
||||
const g = ctx.createRadialGradient(
|
||||
it.x - it.r * 0.35, it.y - it.r * 0.35, it.r * 0.1, it.x, it.y, it.r);
|
||||
g.addColorStop(0, rgba(hi, 1)); g.addColorStop(1, rgba(base, 1));
|
||||
ctx.fillStyle = g;
|
||||
ctx.beginPath(); ctx.arc(it.x, it.y, it.r, 0, TAU); ctx.fill();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const frame = (ts) => { draw(ts / 1000); raf = requestAnimationFrame(frame); };
|
||||
const rm = matchMedia("(prefers-reduced-motion: reduce)");
|
||||
function update() {
|
||||
const want = visible && !rm.matches && W > 0;
|
||||
if (want && !raf) raf = requestAnimationFrame(frame);
|
||||
else if (!want && raf) { cancelAnimationFrame(raf); raf = 0; }
|
||||
if (!raf && W > 0) draw(25.3); // static pose
|
||||
}
|
||||
new ResizeObserver(() => {
|
||||
const dpr = Math.min(devicePixelRatio || 1, 2);
|
||||
W = cv.clientWidth;
|
||||
cv.width = cv.height = Math.round(W * dpr);
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||
if (!raf) update();
|
||||
}).observe(cv);
|
||||
new IntersectionObserver((es) => {
|
||||
visible = es[0].isIntersecting; update();
|
||||
}).observe(cv);
|
||||
rm.addEventListener && rm.addEventListener("change", update);
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user