diff --git a/blog/src/_data/media.json b/blog/src/_data/media.json index cab0cfadd..9ac9325f4 100644 --- a/blog/src/_data/media.json +++ b/blog/src/_data/media.json @@ -4544,5 +4544,38 @@ }, "inline": {}, "youtube": [] + }, + "mistral-large-4": { + "title": "Mistral Large 4 (Le Chonk): size, price, scores and weights", + "category": "concepts", + "hero": { + "file": "assets/media/mistral-large-4/hero.png", + "alt": "Title card on white. Mistral Large 4 in large ink type with a lilac kettlebell labelled Le Chonk and the line Checked 6 Oct 2026", + "prompt": "Still of the hero scene in assets/media/mistral-large-4/motion.js.", + "status": "ready" + }, + "inline": {} + }, + "opencode-vs-claude-code": { + "title": "OpenCode vs Claude Code: Licence, Price and Models Compared", + "category": "comparisons", + "hero": { + "file": "assets/media/opencode-vs-claude-code/hero.png", + "alt": "Title card on white. OpenCode vs Claude Code in large ink type beside a signpost with two arrows reading any model and Claude plan", + "prompt": "Still of the hero scene in assets/media/opencode-vs-claude-code/motion.js.", + "status": "ready" + }, + "inline": {} + }, + "claude-cowork-vs-claude-code": { + "title": "Claude Cowork vs Claude Code: Which One Do You Need?", + "category": "comparisons", + "hero": { + "file": "assets/media/claude-cowork-vs-claude-code/hero.png", + "alt": "Title card on white. Claude Cowork vs Claude Code in large ink type, with a key ring holding a yellow document key and a blue terminal key", + "prompt": "Still of the hero scene in assets/media/claude-cowork-vs-claude-code/motion.js.", + "status": "ready" + }, + "inline": {} } } diff --git a/blog/src/assets/media/claude-cowork-vs-claude-code/coins.png b/blog/src/assets/media/claude-cowork-vs-claude-code/coins.png new file mode 100644 index 000000000..1794b08e6 Binary files /dev/null and b/blog/src/assets/media/claude-cowork-vs-claude-code/coins.png differ diff --git a/blog/src/assets/media/claude-cowork-vs-claude-code/fence.png b/blog/src/assets/media/claude-cowork-vs-claude-code/fence.png new file mode 100644 index 000000000..e95fb6a23 Binary files /dev/null and b/blog/src/assets/media/claude-cowork-vs-claude-code/fence.png differ diff --git a/blog/src/assets/media/claude-cowork-vs-claude-code/hero.png b/blog/src/assets/media/claude-cowork-vs-claude-code/hero.png new file mode 100644 index 000000000..d42899522 Binary files /dev/null and b/blog/src/assets/media/claude-cowork-vs-claude-code/hero.png differ diff --git a/blog/src/assets/media/claude-cowork-vs-claude-code/kettles.png b/blog/src/assets/media/claude-cowork-vs-claude-code/kettles.png new file mode 100644 index 000000000..35d4c60ca Binary files /dev/null and b/blog/src/assets/media/claude-cowork-vs-claude-code/kettles.png differ diff --git a/blog/src/assets/media/claude-cowork-vs-claude-code/motion.css b/blog/src/assets/media/claude-cowork-vs-claude-code/motion.css new file mode 100644 index 000000000..857726f00 --- /dev/null +++ b/blog/src/assets/media/claude-cowork-vs-claude-code/motion.css @@ -0,0 +1,13 @@ +/* Bright coded scenes for "Claude Cowork vs Claude Code". Pairs with motion.js. */ +.prose figure.mg { margin: 42px 0; } +.prose figure.mg img, .mg .mg-svg { display: block; width: 100%; height: auto; border: 1px solid #1A1320; border-radius: 18px; background: #fff; } +.mg figcaption { font: 500 13px/1.5 "Space Grotesk", system-ui, sans-serif; color: #6B5878; padding-top: 10px; text-align: center; } +.mg .mg-svg { touch-action: manipulation; -webkit-tap-highlight-color: transparent; } +.mg .mg-svg text { -webkit-user-select: none; user-select: none; pointer-events: none; } +.mg .mg-hit { cursor: pointer; outline: none; } +.mg [role="slider"] { cursor: grab; touch-action: none; } +.mg .mg-ring { stroke-opacity: 0; transition: stroke-opacity 0.15s; } +.mg .mg-hit:focus-visible .mg-ring { stroke-opacity: 1; } +.mg [role="slider"]:focus-visible circle:nth-child(2) { stroke-width: 5; } +@media (hover: hover) { .mg .mg-hit:hover .mg-ring { stroke-opacity: 0.5; } } +@media (prefers-reduced-motion: reduce) { .mg .mg-ring { transition: none; } } diff --git a/blog/src/assets/media/claude-cowork-vs-claude-code/motion.js b/blog/src/assets/media/claude-cowork-vs-claude-code/motion.js new file mode 100644 index 000000000..7c51bf898 --- /dev/null +++ b/blog/src/assets/media/claude-cowork-vs-claude-code/motion.js @@ -0,0 +1,340 @@ +/* Bright coded scenes for "Claude Cowork vs Claude Code". Vanilla JS, no library. +
holds a still image; this script swaps in a live SVG. + Plays only in view, honours reduced motion, "?mgstill" freezes each scene on its still frame. */ +(() => { + const NS = "http://www.w3.org/2000/svg"; + const INK = "#1A1320", YEL = "#FFCA54", BLUE = "#6C8EF5", LIL = "#B69CFF", MINT = "#7FD8AE", SKY = "#9AD8FF", SOFT = "#F1ECF9", MUTE = "#7A6A88"; + const SANS = '"Space Grotesk", system-ui, sans-serif', MONO = '"JetBrains Mono", ui-monospace, monospace'; + const el = (t, a = {}, p) => { const n = document.createElementNS(NS, t); for (const k in a) n.setAttribute(k, a[k]); if (p) p.appendChild(n); return n; }; + const txt = (p, x, y, s, a = {}) => { const n = el("text", Object.assign({ x, y, "font-family": SANS, "font-size": 24, "font-weight": 600, fill: INK }, a), p); n.textContent = s; return n; }; + const cl = (v, a = 0, b = 1) => Math.max(a, Math.min(b, v)); + const seg = (t, a, b) => cl((t - a) / (b - a)); + const oc = (x) => 1 - Math.pow(1 - x, 3); + const ob = (x) => { const c = 1.70158; return 1 + (c + 1) * Math.pow(x - 1, 3) + c * Math.pow(x - 1, 2); }; + const io = (x) => (x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2); + const tf = (n, x, y, s = 1, r = 0) => n.setAttribute("transform", `translate(${x} ${y}) rotate(${r}) scale(${s})`); + const button = (g, label, fn) => { + g.setAttribute("role", "button"); g.setAttribute("tabindex", "0"); g.setAttribute("aria-label", label); g.classList.add("mg-hit"); + g.addEventListener("click", fn); + g.addEventListener("keydown", (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); fn(); } }); + }; + const strip = (svg, y, w = 680) => { + el("rect", { x: (800 - w) / 2, y, width: w, height: 48, rx: 24, fill: SOFT }, svg); + return txt(svg, 400, y + 31, "", { "text-anchor": "middle", "font-size": 20, "font-weight": 500 }); + }; + + const spring = (x) => (x <= 0 ? 0 : 1 - Math.exp(-4 * x) * Math.cos(8 * x)); + const SC = {}; + + /* ---- 1. one post, two arms: each swings round to point at its own kind of work ---- */ + SC.signpost = { + dur: 10.5, still: 7.5, + build(svg, api) { + txt(svg, 44, 62, "One engine, two jobs", { "font-size": 32, "font-weight": 700 }); + txt(svg, 756, 62, "tap an arm", { "text-anchor": "end", "font-size": 20, fill: MUTE, "font-weight": 500 }); + const G = 462, PX = 400; + el("path", { d: `M${PX - 96} ${G} H${PX + 96}`, stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + const post = el("g", {}, svg); + el("rect", { x: -9, y: -326, width: 18, height: 326, rx: 9, fill: SOFT, stroke: INK, "stroke-width": 3 }, post); + el("circle", { cy: -336, r: 13, fill: LIL, stroke: INK, "stroke-width": 3 }, post); + const dyn = el("g", {}, svg); + const LINES = ["Cowork: research, analysis, document creation", "Claude Code: writing, debugging and shipping code"]; + let pinned = -1; + const arm = (dir, y, col, name, i) => { + const g = el("g", {}, dyn), d = dir; + el("rect", { x: d < 0 ? -272 : -28, y: -38, width: 300, height: 76, rx: 18, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, class: "mg-ring" }, g); + el("path", { d: `M${-20 * d} -30 H${226 * d} L${262 * d} 0 L${226 * d} 30 H${-20 * d} Z`, fill: col, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, g); + txt(g, 122 * d, 11, name, { "text-anchor": "middle", "font-size": 30, "font-weight": 700 }); + el("circle", { r: 5.5, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, g); + button(g, LINES[i], () => { pinned = i; api.poke(); }); + return { g, y, d }; + }; + const A = arm(-1, 190, YEL, "Cowork", 0), B = arm(1, 270, SKY, "Claude Code", 1); + /* left: a stack of paper and a spreadsheet */ + const sheet = (x, y, rot) => { + const g = el("g", {}, dyn), s = el("g", { transform: `rotate(${rot})` }, g); + el("rect", { x: -42, y: -52, width: 84, height: 104, rx: 8, fill: "#fff", stroke: INK, "stroke-width": 3 }, s); + el("path", { d: "M-24 -28 H24 M-24 -8 H24 M-24 12 H24 M-24 32 H4", stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, s); + return { g, x, y }; + }; + const papers = [sheet(104, 404, -11), sheet(116, 398, -3), sheet(128, 392, 5)]; + const grid = el("g", {}, dyn); + el("rect", { x: -52, y: -58, width: 104, height: 116, rx: 10, fill: "#fff", stroke: INK, "stroke-width": 3 }, grid); + el("path", { d: "M-52 -30 V-48 Q-52 -58 -42 -58 H42 Q52 -58 52 -48 V-30 Z", fill: MINT, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, grid); + el("path", { d: "M-52 0 H52 M-52 29 H52 M-17 -30 V58 M18 -30 V58", stroke: INK, "stroke-width": 2.5 }, grid); + el("rect", { x: -17, y: 0, width: 35, height: 29, fill: YEL, stroke: INK, "stroke-width": 2.5 }, grid); + const lab1 = txt(dyn, 172, 494, "non-coding knowledge work", { "text-anchor": "middle", "font-size": 20, "font-weight": 500, fill: MUTE }); + /* right: a terminal window, white, with a blinking cursor */ + const term = el("g", {}, dyn); + el("rect", { x: -105, y: -62, width: 210, height: 124, rx: 14, fill: "#fff", stroke: INK, "stroke-width": 3 }, term); + el("path", { d: "M-105 -30 H105", stroke: INK, "stroke-width": 3 }, term); + [YEL, MINT, BLUE].forEach((c, i) => el("circle", { cx: -84 + i * 20, cy: -46, r: 6, fill: c, stroke: INK, "stroke-width": 2 }, term)); + el("path", { d: "M-84 -4 L-70 9 L-84 22", stroke: INK, "stroke-width": 4, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, term); + const typed = txt(term, -56, 17, "", { "font-family": MONO, "font-size": 24, "font-weight": 700 }); + const cur = el("rect", { y: -6, width: 13, height: 30, rx: 3, fill: BLUE }, term); + const lab2 = txt(dyn, 640, 494, "software engineering", { "text-anchor": "middle", "font-size": 20, "font-weight": 500, fill: MUTE }); + const cap = strip(svg, 520, 720); + const WORD = "claude"; + return (t, now) => { + dyn.setAttribute("opacity", 1 - seg(t, 9.7, 10.2)); + tf(post, PX, G); + [[A, 0.9], [B, 3.5]].forEach(([a, t0]) => { + a.g.setAttribute("transform", `translate(${PX} ${a.y}) rotate(${-a.d * 96 * (1 - spring((t - t0) * 0.9))})`); + a.g.setAttribute("opacity", seg(t, t0 - 0.25, t0)); + }); + papers.forEach((p, i) => { const k = ob(seg(t, 2.0 + i * 0.22, 2.35 + i * 0.22)); tf(p.g, p.x, p.y + 30 * (1 - cl(k)), cl(k, 0, 1.2)); p.g.setAttribute("opacity", k <= 0.01 ? 0 : 1); }); + const gk = ob(seg(t, 2.75, 3.15)); tf(grid, 236, 392, cl(gk, 0, 1.2)); grid.setAttribute("opacity", gk <= 0.01 ? 0 : 1); + lab1.setAttribute("opacity", seg(t, 3.0, 3.3)); + const tk = ob(seg(t, 4.6, 5.0)); tf(term, 640, 394, cl(tk, 0, 1.2)); term.setAttribute("opacity", tk <= 0.01 ? 0 : 1); + const n = Math.round(seg(t, 5.3, 6.2) * WORD.length); + typed.textContent = WORD.slice(0, n); + cur.setAttribute("x", -54 + n * 14.4 + 3); + cur.setAttribute("opacity", Math.floor(now * 2.2) % 2 === 0 ? 1 : 0.15); + lab2.setAttribute("opacity", seg(t, 5.0, 5.3)); + cap.textContent = pinned >= 0 ? LINES[pinned] : t < 0.9 || t >= 9.7 ? "Same architecture, different front door" : t < 3.5 ? "Cowork is built for non-coding knowledge work" : t < 6.6 ? "Claude Code is built for software engineering" : "Same architecture, different front door"; + }; + }, + }; + + /* ---- 2. two electric kettles: one sits on a cloud and plugs into it, one sits on a desk ---- */ + SC.kettles = { + dur: 11.5, still: 8.6, + build(svg) { + txt(svg, 44, 62, "Where the work runs", { "font-size": 32, "font-weight": 700 }); + txt(svg, 44, 98, "Same kettle, different socket", { "font-size": 20, "font-weight": 500, fill: MUTE }); + const Y = 322; + const kettle = (parent, col, flip) => { + const g = el("g", {}, parent), k = el("g", { transform: `scale(${flip ? -1 : 1} 1)` }, g); + const steam = [-78, -62, -46].map((x) => ({ n: el("path", { d: "M0 0 q-9 -9 0 -18 q9 -9 0 -18 q-9 -9 0 -18", stroke: INK, "stroke-width": 3, fill: "none", "stroke-linecap": "round" }, k), x })); + el("rect", { x: -56, y: -13, width: 112, height: 13, rx: 6.5, fill: SOFT, stroke: INK, "stroke-width": 3 }, k); + el("path", { d: "M36 -106 Q88 -110 84 -64 Q80 -28 42 -32", stroke: INK, "stroke-width": 6, fill: "none", "stroke-linecap": "round" }, k); + el("path", { d: "M-33 -120 L-64 -108 L-37 -90 Z", fill: col, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, k); + el("path", { d: "M-46 -13 L-36 -122 Q0 -136 36 -122 L46 -13 Z", fill: col, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, k); + el("circle", { cy: -136, r: 7, fill: "#fff", stroke: INK, "stroke-width": 3 }, k); + el("rect", { x: -12, y: -102, width: 24, height: 72, rx: 12, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, k); + el("rect", { x: -8, y: -72, width: 16, height: 38, rx: 8, fill: SKY }, k); + const bub = [0, 1].map(() => el("circle", { r: 3, fill: "#fff", stroke: INK, "stroke-width": 1.5 }, k)); + const lamp = el("circle", { cx: 34, cy: -6.5, r: 4, fill: "#fff", stroke: INK, "stroke-width": 1.5 }, k); + return { g, steam, bub, lamp }; + }; + const socket = (p, x, y) => { el("rect", { x: x - 17, y: y - 17, width: 34, height: 34, rx: 9, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, p); el("path", { d: `M${x - 6} ${y - 5} V${y + 5} M${x + 6} ${y - 5} V${y + 5}`, stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, p); }; + const lead = (p) => { const cord = el("path", { stroke: INK, "stroke-width": 3.5, fill: "none", "stroke-linecap": "round" }, p), plug = el("g", {}, p); el("rect", { x: -15, y: -12, width: 30, height: 24, rx: 8, fill: INK }, plug); return { cord, plug }; }; + + /* Cowork: the kettle rides a cloud */ + const L = el("g", {}, svg); + el("path", { d: "M-90 68 H96 a36 36 0 0 0 8 -71 a52 52 0 0 0 -92 -30 a46 46 0 0 0 -80 14 a44 44 0 0 0 -22 87 Z", fill: SKY, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, L); + socket(L, 64, 30); + const lk = kettle(L, LIL, false), ll = lead(L); + /* Claude Code: the kettle stands on a desk */ + const R = el("g", {}, svg); + el("path", { d: "M-112 16 V96 M112 16 V96", stroke: INK, "stroke-width": 5, "stroke-linecap": "round" }, R); + el("rect", { x: 14, y: 16, width: 98, height: 52, rx: 8, fill: "#fff", stroke: INK, "stroke-width": 3 }, R); + el("rect", { x: -130, y: 0, width: 260, height: 17, rx: 7, fill: YEL, stroke: INK, "stroke-width": 3 }, R); + socket(R, 63, 42); + const rk = kettle(R, MINT, true), rl = lead(R); + txt(svg, 215, 474, "Cowork", { "text-anchor": "middle", "font-size": 24, "font-weight": 700 }); + txt(svg, 215, 502, "on Anthropic's servers", { "text-anchor": "middle", "font-size": 20, "font-weight": 500, fill: MUTE }); + txt(svg, 585, 474, "Claude Code in a terminal", { "text-anchor": "middle", "font-size": 24, "font-weight": 700 }); + txt(svg, 585, 502, "the project folder on your machine", { "text-anchor": "middle", "font-size": 20, "font-weight": 500, fill: MUTE }); + const cap = strip(svg, 524, 720); + const run = (u, ld, t, t0, sx, sy, kx, ky) => { + const out = 1 - seg(t, 10.6, 11.1), p = io(seg(t, t0, t0 + 0.5)) * out, on = p > 0.98 ? 1 : 0, boil = seg(t, t0 + 1.5, t0 + 2.1) * out; + const ex = sx + 62 * (1 - p), ey = sy - 12 * (1 - p); + ld.cord.setAttribute("d", `M${kx + 54} ${ky - 7} C${kx + 150} ${ky - 7} ${ex + 78} ${ey + 4} ${ex + 13} ${ey}`); + tf(ld.plug, ex, ey); + u.lamp.setAttribute("fill", on ? YEL : "#fff"); + u.bub.forEach((b, j) => { const q = (t * 1.3 + j * 0.5) % 1; b.setAttribute("cx", (j ? 3 : -3)); b.setAttribute("cy", -40 - 26 * q); b.setAttribute("opacity", on ? Math.sin(q * Math.PI) : 0); }); + u.steam.forEach((s, j) => { const q = (t * 0.6 + j / 3) % 1; s.n.setAttribute("transform", `translate(${s.x} ${-124 - 24 * q})`); s.n.setAttribute("opacity", boil * Math.sin(q * Math.PI)); }); + u.g.setAttribute("transform", `translate(${kx} ${ky}) rotate(${on && boil < 1 ? Math.sin(t * 24) * 0.9 : 0})`); + }; + return (t, now) => { + L.setAttribute("transform", `translate(215 ${Y + 31 + Math.sin(now * 1.4) * 4})`); + R.setAttribute("transform", `translate(585 ${Y})`); + run(lk, ll, t, 1.2, 64, 30, 0, -31); + run(rk, rl, t, 3.9, 63, 42, 0, 0); + cap.textContent = t < 1.2 || t >= 10.6 ? "Cowork runs in the Claude app, Claude Code where you write code" : t < 3.9 ? "Cowork: “runs on Anthropic's servers, in an isolated environment”" : t < 6.6 ? "Claude Code in a terminal works on your machine" : "Pro and Max: “On October 6, 2026, new Cowork tasks run in the cloud”"; + }; + }, + }; + + /* ---- 3. twenty thin coins a side, one plank with a spirit level, and a billing switch ---- */ + SC.coins = { + dur: 11, still: 8, + build(svg, api) { + txt(svg, 44, 62, "Both start at Claude Pro", { "font-size": 32, "font-weight": 700 }); + const G = 440, CH = 10, N = 20, XS = [250, 550]; + el("path", { d: `M60 ${G + 1.5} H740`, stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + txt(svg, XS[0], G + 36, "Cowork", { "text-anchor": "middle", "font-size": 24, "font-weight": 700 }); + txt(svg, XS[1], G + 36, "Claude Code", { "text-anchor": "middle", "font-size": 24, "font-weight": 700 }); + const coins = []; + XS.forEach((x, s) => { for (let j = 0; j < N; j++) coins.push({ n: el("rect", { x: -60, y: -CH, width: 120, height: CH, rx: 5, fill: YEL, stroke: INK, "stroke-width": 2 }, svg), x, s, j, d: 0.5 + j * 0.15 + s * 0.07 }); }); + const PT = 4.1, plank = el("g", {}, svg); + el("rect", { x: -236, y: -34, width: 472, height: 34, rx: 10, fill: MINT, stroke: INK, "stroke-width": 3 }, plank); + txt(plank, -216, -10, "Claude Pro", { "font-size": 20, "font-weight": 700 }); + el("rect", { x: -36, y: -27, width: 72, height: 20, rx: 10, fill: SKY, stroke: INK, "stroke-width": 2.5 }, plank); + el("path", { d: "M-11 -27 V-7 M11 -27 V-7", stroke: INK, "stroke-width": 2 }, plank); + const bubble = el("circle", { cy: -17, r: 6, fill: "#fff", stroke: INK, "stroke-width": 2 }, plank); + const tag = el("g", {}, svg); + const price = txt(tag, 0, -92, "$20", { "text-anchor": "middle", "font-size": 64, "font-weight": 700 }); + const sub = txt(tag, 0, -54, "", { "text-anchor": "middle", "font-size": 22, "font-weight": 500, fill: MUTE }); + /* the switch */ + const sw = el("g", { transform: "translate(400 532)" }, svg); + el("rect", { x: -206, y: -31, width: 412, height: 62, rx: 31, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, class: "mg-ring" }, sw); + el("rect", { x: -200, y: -25, width: 400, height: 50, rx: 25, fill: SOFT, stroke: INK, "stroke-width": 2.5 }, sw); + const knob = el("rect", { y: -20, width: 190, height: 40, rx: 20, fill: YEL, stroke: INK, "stroke-width": 2.5 }, sw); + txt(sw, -100, 7, "billed monthly", { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + txt(sw, 100, 7, "annual", { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + let to = 0, from = 0, t0 = -9; + sw.setAttribute("aria-pressed", "false"); + button(sw, "Billing: switch between monthly and annual", () => { from = to; to = 1 - to; t0 = api.now(); sw.setAttribute("aria-pressed", to ? "true" : "false"); api.poke(); }); + return (t, now) => { + const a = from + (to - from) * io(cl((now - t0) / 0.5)), out = seg(t, 10.3, 10.8); + coins.forEach((c) => { + const q = seg(t, c.d, c.d + 0.3), b = Math.sin(seg(t, c.d + 0.3, c.d + 0.5) * Math.PI) * -3, gone = c.j >= N - 3 ? a : 0; + tf(c.n, c.x + (c.s ? 1 : -1) * 70 * gone, G - c.j * CH - 230 * (1 - q * q) + b - 26 * gone, 1, (c.s ? 1 : -1) * 30 * gone); + c.n.setAttribute("opacity", seg(t, c.d, c.d + 0.06) * (1 - out) * (1 - gone)); + }); + const top = G - CH * (N - 3 * a); + const q = seg(t, PT, PT + 0.45), land = seg(t, PT + 0.45, PT + 1.5), wob = q >= 1 ? Math.exp(-4 * land) * Math.sin(land * 15) : 0; + tf(plank, 400, top - 260 * (1 - q * q), 1, wob * 3.5 + (1 - q) * -10); + plank.setAttribute("opacity", seg(t, PT, PT + 0.1) * (1 - out)); + bubble.setAttribute("cx", cl(-wob * 22 + (1 - q) * 20, -26, 26)); + const p = ob(seg(t, PT + 0.8, PT + 1.2)); + tf(tag, 400, top + 14 * (1 - cl(p)), 1); + tag.setAttribute("opacity", (p <= 0.01 ? 0 : 1) * (1 - out)); + price.textContent = a > 0.5 ? "$17" : "$20"; + sub.textContent = a > 0.5 ? "a month on an annual subscription" : "a month, if billed monthly"; + knob.setAttribute("x", -195 + 200 * a); + }; + }, + }; + + /* ---- 4. a picket fence rises round three folders; a parcel comes in by the gate; one folder stays outside ---- */ + SC.fence = { + dur: 11, still: 8, + build(svg, api) { + txt(svg, 44, 62, "Only the folders you choose", { "font-size": 32, "font-weight": 700 }); + txt(svg, 756, 100, "tap the folder outside", { "text-anchor": "end", "font-size": 20, fill: MUTE, "font-weight": 500 }); + const BY = 234, FY = 440, BL = 130, BR = 510, FL = 64, FR = 576, BH = 56, FH = 70; + el("path", { d: `M${BL} ${BY} H${BR} L${FR} ${FY} H${FL} Z`, fill: SOFT, stroke: "none" }, svg); + const yard = el("g", {}, svg); + const rails = []; + const rail = (p, x1, y1, x2, y2, at) => rails.push({ n: el("path", { d: `M${x1} ${y1} L${x2} ${y2}`, stroke: INK, "stroke-width": 2.5, "stroke-linecap": "round" }, p), at }); + const pick = []; + const picket = (p, x, y, w, h, at) => { const g = el("g", {}, p); el("path", { d: `M${-w} 0 V${-(h - w * 1.3)} L0 ${-h} L${w} ${-(h - w * 1.3)} V0 Z`, fill: "#fff", stroke: INK, "stroke-width": 2.5, "stroke-linejoin": "round" }, g); pick.push({ g, x, y, at }); }; + /* back run */ + rail(yard, BL, BY - BH * 0.3, BR, BY - BH * 0.3, 0.5); rail(yard, BL, BY - BH * 0.72, BR, BY - BH * 0.72, 0.5); + for (let i = 0; i < 18; i++) picket(yard, 137 + i * 21.5, BY, 7.5, BH, 0.4 + i * 0.045); + /* the two sides, drawn far to near */ + [[BL, FL], [BR, FR]].forEach(([b, f]) => { + const e = b + (f - b) * 0.86, ey = BY + (FY - BY) * 0.86, eh = BH + (FH - BH) * 0.86; rail(yard, b, BY - BH * 0.3, e, ey - eh * 0.3, 1.6); rail(yard, b, BY - BH * 0.72, e, ey - eh * 0.72, 1.6); + for (let i = 1; i <= 5; i++) { const k = i / 6; picket(yard, b + (f - b) * k, BY + (FY - BY) * k, 5.5, BH + (FH - BH) * k, 1.25 + i * 0.09); } + }); + /* three folders inside */ + const folder = (p, col, dash) => { + const a = { fill: col, stroke: dash ? MUTE : INK, "stroke-width": 3, "stroke-linejoin": "round" }; + if (dash) a["stroke-dasharray"] = "7 6"; + el("path", { d: "M-46 -8 V-60 Q-46 -68 -38 -68 H-14 L-4 -56 H38 Q46 -56 46 -48 V-8 Z", ...a }, p); + el("rect", { x: -46, y: -46, width: 92, height: 46, rx: 8, ...a }, p); + }; + const inside = [[196, YEL], [320, BLUE], [444, LIL]].map(([x, col]) => { const g = el("g", {}, svg); folder(g, col, false); return { g, x }; }); + const parcel = el("g", {}, svg); + el("rect", { x: -30, y: -50, width: 60, height: 50, rx: 8, fill: MINT, stroke: INK, "stroke-width": 3 }, parcel); + el("path", { d: "M-30 -34 H30", stroke: INK, "stroke-width": 3 }, parcel); + el("rect", { x: -9, y: -42, width: 18, height: 16, rx: 4, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, parcel); + /* front run, with a gate in the middle */ + const front = el("g", {}, svg); + [[FL, 268], [372, FR]].forEach(([a, b]) => { rail(front, a, FY - FH * 0.3, b, FY - FH * 0.3, 1.9); rail(front, a, FY - FH * 0.72, b, FY - FH * 0.72, 1.9); }); + for (let i = 0; i < 8; i++) { picket(front, 76 + i * 24, FY, 8.5, FH, 1.9 + i * 0.05); picket(front, 396 + i * 24, FY, 8.5, FH, 1.9 + i * 0.05); } + const posts = [268, 372].map((x) => { const g = el("g", {}, front); el("rect", { x: -7, y: -84, width: 14, height: 84, rx: 5, fill: YEL, stroke: INK, "stroke-width": 2.5 }, g); el("circle", { cy: -90, r: 8, fill: YEL, stroke: INK, "stroke-width": 2.5 }, g); return { g, x }; }); + const gate = el("g", {}, front), grail = el("path", { stroke: INK, "stroke-width": 2.5, "stroke-linecap": "round", fill: "none" }, gate); + const gpick = [24, 48, 72].map((x) => ({ x, n: el("path", { d: `M-8.5 0 V${-(FH - 11)} L0 ${-FH} L8.5 ${-(FH - 11)} V0 Z`, fill: "#fff", stroke: INK, "stroke-width": 2.5, "stroke-linejoin": "round" }, gate) })); + /* the one left outside */ + const outG = el("g", {}, svg), outF = el("g", {}, outG); + el("rect", { x: -70, y: -88, width: 140, height: 136, rx: 20, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, class: "mg-ring" }, outG); + folder(outF, SOFT, true); + txt(outG, 0, 34, "not chosen", { "text-anchor": "middle", "font-size": 20, "font-weight": 500, fill: MUTE }); + let poked = -99; + button(outG, "A folder you did not choose. Claude can't reach anything else.", () => { poked = api.now(); api.poke(); }); + const cap = strip(svg, 520, 720); + return (t, now) => { + const out = 1 - seg(t, 10.2, 10.7); + rails.forEach((r) => r.n.setAttribute("opacity", seg(t, r.at, r.at + 0.4) * out)); + pick.forEach((p) => { const k = ob(seg(t, p.at, p.at + 0.35)) * out; p.g.setAttribute("transform", `translate(${p.x} ${p.y}) scale(1 ${Math.max(k, 0.001)})`); p.g.setAttribute("opacity", k <= 0.01 ? 0 : 1); }); + posts.forEach((p) => { const k = ob(seg(t, 2.3, 2.65)) * out; p.g.setAttribute("transform", `translate(${p.x} ${FY}) scale(1 ${Math.max(k, 0.001)})`); p.g.setAttribute("opacity", k <= 0.01 ? 0 : 1); }); + const gk = ob(seg(t, 2.45, 2.8)) * out, open = io(seg(t, 3.5, 4.0)); + const ga = 0.94 - 1.6 * open, gb = 0.42 * open, gx = (x) => 275 + x * ga, gy = (x) => FY + x * gb; + grail.setAttribute("d", [0.3, 0.72].map((r) => `M275 ${FY - FH * r} L${gx(82)} ${gy(82) - FH * r}`).join(" ")); + gpick.forEach((k) => k.n.setAttribute("transform", `translate(${gx(k.x)} ${gy(k.x)}) scale(1 ${Math.max(gk, 0.001)})`)); + gate.setAttribute("opacity", gk <= 0.01 ? 0 : 1); + const m = io(seg(t, 4.2, 5.3)), sq = Math.sin(seg(t, 5.3, 5.65) * Math.PI) * 0.12; + parcel.setAttribute("transform", `translate(320 ${510 - 108 * m}) scale(${(1 - 0.12 * m) * (1 + sq)} ${(1 - 0.12 * m) * (1 - sq)})`); + parcel.setAttribute("opacity", seg(t, 4.2, 4.4) * out); + const hop = Math.sin(seg(t, 5.3, 5.8) * Math.PI); + inside.forEach((f, i) => { const k = ob(seg(t, 0.1 + i * 0.1, 0.5 + i * 0.1)); tf(f.g, f.x, 342 - (i === 1 ? 10 * hop : 0), cl(k, 0, 1.2)); }); + const w = now - poked, shake = w >= 0 && w < 0.8 ? Math.sin(w * 32) * 6 * (1 - w / 0.8) : 0; + tf(outG, 684, 338, 1.2); tf(outF, shake, 0, 1, shake * 0.8); + cap.textContent = w >= 0 && w < 3.5 ? "“Claude can't reach anything else.”" : t < 4.2 || t >= 10.2 ? "“You choose the folders and tools.”" : "Cowork reads, edits and creates files “in folders you specify”"; + }; + }, + }; + + /* ---- title card, 16 by 9: one ring, two keys ---- */ + SC.hero = { + w: 960, h: 540, dur: 1, still: 0, + build(svg) { + txt(svg, 66, 112, "COMPARISONS", { "font-family": MONO, "font-size": 22, "font-weight": 700, fill: MUTE, "letter-spacing": 3 }); + txt(svg, 64, 200, "Claude Cowork", { "font-size": 74, "font-weight": 700 }); + txt(svg, 64, 286, "vs Claude Code", { "font-size": 74, "font-weight": 700 }); + el("path", { d: "M68 314 H560", stroke: YEL, "stroke-width": 14, "stroke-linecap": "round" }, svg); + txt(svg, 64, 374, "Which one do you need?", { "font-size": 28, "font-weight": 500, fill: MUTE }); + const pill = el("rect", { x: 64, y: 410, height: 50, rx: 25, fill: SOFT, stroke: INK, "stroke-width": 2.5 }, svg); + const pt = txt(svg, 88, 443, "Checked 6 Oct 2026", { "font-size": 22, "font-weight": 700 }); + pill.setAttribute("width", pt.getComputedTextLength() + 48); + const RX = 764, RY = 150; + const key = (dx, rot, col, art) => { + const g = el("g", { transform: `translate(${RX + dx} ${RY + 28}) rotate(${rot})` }, svg); + el("path", { d: "M-11 70 H11 V232 L0 246 L-11 232 Z", fill: col, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, g); + el("path", { d: "M11 168 H32 V186 H22 V200 H36 V218 H11", fill: col, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, g); + el("circle", { cy: 40, r: 44, fill: col, stroke: INK, "stroke-width": 3 }, g); + el("circle", { r: 9, fill: "#fff", stroke: INK, "stroke-width": 3 }, g); + art(el("g", { transform: "translate(0 48)" }, g)); + }; + key(-34, 20, YEL, (g) => { el("rect", { x: -17, y: -20, width: 34, height: 42, rx: 5, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, g); el("path", { d: "M-9 -8 H9 M-9 1 H9 M-9 10 H3", stroke: INK, "stroke-width": 2.5, "stroke-linecap": "round" }, g); }); + key(34, -20, SKY, (g) => { el("rect", { x: -24, y: -18, width: 48, height: 38, rx: 7, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, g); el("path", { d: "M-13 -7 L-4 1 L-13 9 M2 10 H13", stroke: INK, "stroke-width": 3, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, g); }); + el("circle", { cx: RX, cy: RY, r: 44, fill: "none", stroke: INK, "stroke-width": 6 }, svg); + return () => {}; + }, + }; + + /* ---- player ---- */ + const q = new URLSearchParams(location.search), frozen = q.has("mgstill"), fixed = parseFloat(q.get("mgstill")); + const calm = matchMedia("(prefers-reduced-motion: reduce)").matches; + const mount = (fig) => { + const sc = SC[fig.dataset.scene], img = fig.querySelector("img"); if (!sc || !img) return; + const W = sc.w || 800, H = sc.h || 600; + const svg = el("svg", { viewBox: `0 0 ${W} ${H}`, class: "mg-svg", role: "group", "aria-label": img.alt || "" }); + svg.style.aspectRatio = `${W} / ${H}`; + el("rect", { width: W, height: H, fill: "#fff" }, svg); + img.replaceWith(svg); + let off = 0, raf = 0, seen = false, held = calm; + const base = performance.now(), clock = () => (performance.now() - base) / 1000; + let draw = () => {}; + const frame = () => { const now = clock(); draw(held ? sc.still : (((now + off) % sc.dur) + sc.dur) % sc.dur, now); }; + const tick = () => { cancelAnimationFrame(raf); frame(); if (seen && !held) raf = requestAnimationFrame(tick); }; + const api = { now: clock, seek: (s) => { off = s - clock(); held = false; tick(); }, poke: () => { held = false; tick(); } }; + draw = sc.build(svg, api); + if (frozen) { draw(isNaN(fixed) ? sc.still : fixed, 0); return; } + new IntersectionObserver(([e]) => { + seen = e.isIntersecting; + if (seen) { tick(); if (!fig.dataset.sent && window.posthog) { fig.dataset.sent = 1; window.posthog.capture("blog_scene_view", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } } + else cancelAnimationFrame(raf); + }, { threshold: 0.25 }).observe(fig); + svg.addEventListener("click", () => { if (!fig.dataset.hit && window.posthog) { fig.dataset.hit = 1; window.posthog.capture("blog_scene_interact", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } }); + frame(); + }; + const go = () => document.querySelectorAll("figure.mg[data-scene]").forEach(mount); + const fonts = document.fonts && document.fonts.load ? Promise.all([document.fonts.load('700 32px "Space Grotesk"'), document.fonts.load('500 20px "Space Grotesk"'), document.fonts.load('700 20px "JetBrains Mono"')]).catch(() => {}) : Promise.resolve(); + const start = () => fonts.then(go, go); + if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", start); else start(); +})(); diff --git a/blog/src/assets/media/claude-cowork-vs-claude-code/signpost.png b/blog/src/assets/media/claude-cowork-vs-claude-code/signpost.png new file mode 100644 index 000000000..3889059b6 Binary files /dev/null and b/blog/src/assets/media/claude-cowork-vs-claude-code/signpost.png differ diff --git a/blog/src/assets/media/mistral-large-4/crate.png b/blog/src/assets/media/mistral-large-4/crate.png new file mode 100644 index 000000000..5b7838b32 Binary files /dev/null and b/blog/src/assets/media/mistral-large-4/crate.png differ diff --git a/blog/src/assets/media/mistral-large-4/flags.png b/blog/src/assets/media/mistral-large-4/flags.png new file mode 100644 index 000000000..c2d0c9495 Binary files /dev/null and b/blog/src/assets/media/mistral-large-4/flags.png differ diff --git a/blog/src/assets/media/mistral-large-4/hero.png b/blog/src/assets/media/mistral-large-4/hero.png new file mode 100644 index 000000000..eebb6d909 Binary files /dev/null and b/blog/src/assets/media/mistral-large-4/hero.png differ diff --git a/blog/src/assets/media/mistral-large-4/motion.css b/blog/src/assets/media/mistral-large-4/motion.css new file mode 100644 index 000000000..5fb8e95a7 --- /dev/null +++ b/blog/src/assets/media/mistral-large-4/motion.css @@ -0,0 +1,13 @@ +/* Bright coded scenes for "Mistral Large 4". Pairs with motion.js. */ +.prose figure.mg { margin: 42px 0; } +.prose figure.mg img, .mg .mg-svg { display: block; width: 100%; height: auto; border: 1px solid #1A1320; border-radius: 18px; background: #fff; } +.mg figcaption { font: 500 13px/1.5 "Space Grotesk", system-ui, sans-serif; color: #6B5878; padding-top: 10px; text-align: center; } +.mg .mg-svg { touch-action: manipulation; -webkit-tap-highlight-color: transparent; } +.mg .mg-svg text { -webkit-user-select: none; user-select: none; pointer-events: none; } +.mg .mg-hit { cursor: pointer; outline: none; } +.mg [role="slider"] { cursor: grab; touch-action: none; } +.mg .mg-ring { stroke-opacity: 0; transition: stroke-opacity 0.15s; } +.mg .mg-hit:focus-visible .mg-ring { stroke-opacity: 1; } +.mg [role="slider"]:focus-visible circle:nth-child(2) { stroke-width: 5; } +@media (hover: hover) { .mg .mg-hit:hover .mg-ring { stroke-opacity: 0.5; } } +@media (prefers-reduced-motion: reduce) { .mg .mg-ring { transition: none; } } diff --git a/blog/src/assets/media/mistral-large-4/motion.js b/blog/src/assets/media/mistral-large-4/motion.js new file mode 100644 index 000000000..e61ef31df --- /dev/null +++ b/blog/src/assets/media/mistral-large-4/motion.js @@ -0,0 +1,304 @@ +/* Bright coded scenes for "Mistral Large 4". Vanilla JS, no library. +
holds a still image; this script swaps in a live SVG. + Plays only in view, honours reduced motion, "?mgstill" freezes each scene on its still frame. */ +(() => { + const NS = "http://www.w3.org/2000/svg"; + const INK = "#1A1320", YEL = "#FFCA54", BLUE = "#6C8EF5", LIL = "#B69CFF", MINT = "#7FD8AE", SKY = "#9AD8FF", SOFT = "#F1ECF9", MUTE = "#7A6A88"; + const SANS = '"Space Grotesk", system-ui, sans-serif', MONO = '"JetBrains Mono", ui-monospace, monospace'; + const el = (t, a = {}, p) => { const n = document.createElementNS(NS, t); for (const k in a) n.setAttribute(k, a[k]); if (p) p.appendChild(n); return n; }; + const txt = (p, x, y, s, a = {}) => { const n = el("text", Object.assign({ x, y, "font-family": SANS, "font-size": 24, "font-weight": 600, fill: INK }, a), p); n.textContent = s; return n; }; + const cl = (v, a = 0, b = 1) => Math.max(a, Math.min(b, v)); + const seg = (t, a, b) => cl((t - a) / (b - a)); + const oc = (x) => 1 - Math.pow(1 - x, 3); + const ob = (x) => { const c = 1.70158; return 1 + (c + 1) * Math.pow(x - 1, 3) + c * Math.pow(x - 1, 2); }; + const io = (x) => (x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2); + const tf = (n, x, y, s = 1, r = 0) => n.setAttribute("transform", `translate(${x} ${y}) rotate(${r}) scale(${s})`); + const button = (g, label, fn) => { + g.setAttribute("role", "button"); g.setAttribute("tabindex", "0"); g.setAttribute("aria-label", label); g.classList.add("mg-hit"); + g.addEventListener("click", fn); + g.addEventListener("keydown", (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); fn(); } }); + }; + const strip = (svg, y, w = 680) => { + el("rect", { x: (800 - w) / 2, y, width: w, height: 48, rx: 24, fill: SOFT }, svg); + return txt(svg, 400, y + 31, "", { "text-anchor": "middle", "font-size": 20, "font-weight": 500 }); + }; + const pill = (p, s, fill, a = {}) => { + const g = el("g", {}, p), r = el("rect", Object.assign({ y: -20, height: 40, rx: 20, fill, stroke: INK, "stroke-width": 2.5 }, a), g), t = txt(g, 0, 7, s, { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + const w = t.getComputedTextLength() + 34; r.setAttribute("x", -w / 2); r.setAttribute("width", w); + return g; + }; + + const SC = {}; + + /* ---- 1. a kitchen scale under a huge flour sack; one small scoop comes out for each token ---- */ + SC.scales = { + dur: 11, still: 7.3, + build(svg) { + txt(svg, 44, 62, "1.05T total, 49B active", { "font-size": 32, "font-weight": 700 }); + txt(svg, 756, 60, "Mistral's model page", { "text-anchor": "end", "font-size": 20, fill: MUTE, "font-weight": 500 }); + el("path", { d: "M44 472 H756", stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + el("path", { d: "M470 380 V472 M716 380 V472", stroke: INK, "stroke-width": 3 }, svg); + el("rect", { x: 418, y: 356, width: 338, height: 24, rx: 12, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + const rollers = [436, 587, 738].map((x) => el("path", { d: "M-5 0 H5", stroke: INK, "stroke-width": 3, "stroke-linecap": "round", transform: `translate(${x} 368)` }, svg)); + el("rect", { x: 228, y: 384, width: 24, height: 18, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + el("rect", { x: 130, y: 398, width: 220, height: 74, rx: 20, fill: LIL, stroke: INK, "stroke-width": 3 }, svg); + el("rect", { x: 96, y: 372, width: 288, height: 14, rx: 7, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + el("circle", { cx: 240, cy: 436, r: 27, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + [-60, -30, 0, 30, 60].forEach((a) => el("path", { d: "M0 -21 V-16", stroke: INK, "stroke-width": 2.5, "stroke-linecap": "round", transform: `translate(240 436) rotate(${a})` }, svg)); + const needle = el("path", { d: "M0 3 V-17", stroke: INK, "stroke-width": 3.5, "stroke-linecap": "round" }, svg); + el("circle", { cx: 240, cy: 436, r: 4, fill: INK }, svg); + el("ellipse", { cx: 240, cy: 168, rx: 80, ry: 14, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + const scoop = el("g", {}, svg); + el("rect", { x: 24, y: -8, width: 34, height: 12, rx: 6, fill: "#fff", stroke: INK, "stroke-width": 3 }, scoop); + el("path", { d: "M-22 -12 Q0 -34 22 -12 Z", fill: "#fff", stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, scoop); + el("path", { d: "M-28 -12 H28 L22 14 Q0 24 -22 14 Z", fill: YEL, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, scoop); + el("path", { d: "M128 200 Q132 170 160 168 A80 14 0 0 0 320 168 Q348 170 352 200 L370 340 Q375 372 355 372 H125 Q105 372 110 340 Z", fill: SOFT, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, svg); + el("path", { d: "M133 214 Q240 236 347 214", stroke: INK, "stroke-width": 2.5, fill: "none", "stroke-dasharray": "2 9", "stroke-linecap": "round" }, svg); + txt(svg, 240, 302, "1.05T", { "text-anchor": "middle", "font-size": 62, "font-weight": 700 }); + txt(svg, 240, 338, "total parameters", { "text-anchor": "middle", "font-size": 20, "font-weight": 500 }); + const T0 = 0.6, P = 1.9, HX = 688, STEP = 104; + const chips = ["one", "scoop", "for", "each", "token"].map((s) => { + const g = el("g", {}, svg), r = el("rect", { x: -46, y: -23, width: 92, height: 46, rx: 14, fill: YEL, stroke: INK, "stroke-width": 3 }, g); + txt(g, 0, 7, s, { "text-anchor": "middle", "font-family": MONO, "font-size": 20, "font-weight": 700 }); + return { g, r }; + }); + const tag = pill(svg, "49B active", "#fff"); + const cap = strip(svg, 512, 700); + return (t) => { + const out = seg(t, 10.3, 10.8); + const i = cl(Math.floor((t - T0) / P), 0, 4), ph = t < T0 ? -1 : t - (T0 + i * P); + const a = ph < 0 ? 0 : oc(seg(ph, 0, 0.3)), b = ph < 0 ? 0 : io(seg(ph, 0.3, 0.75)) - io(seg(ph, 1.0, 1.4)); + const c = ph < 0 ? 0 : seg(ph, 1.4, 1.55) * (1 - seg(ph, 1.8, 1.9)), d = ph < 0 ? 0 : io(seg(ph, 1.5, 1.75)); + const x = 240 + (HX - 240) * b, y = 225 - 67 * a * (1 - d) + 100 * b - 30 * Math.sin(b * Math.PI) + 8 * Math.sin(seg(ph, 0.75, 0.95) * Math.PI); + tf(scoop, x, y, 1, -120 * c); + const show = cl(a * 2 - 1) * (1 - seg(ph, 1.4, 1.55)); + tf(tag, x, y - 54); tag.setAttribute("opacity", show); + needle.setAttribute("transform", `translate(240 436) rotate(${62 - 7 * a * (1 - d)})`); + let moved = 0; + chips.forEach((ch, j) => { + const born = T0 + j * P + 0.8; + let n = 0; for (let k = j + 1; k < 5; k++) n += io(seg(t, T0 + k * P + 0.2, T0 + k * P + 0.7)); + if (j === 0) moved = n; + tf(ch.g, HX - STEP * n, 331, cl(ob(seg(t, born, born + 0.3)), 0, 1.3)); + ch.g.setAttribute("opacity", (t < born ? 0 : 1) * (1 - seg(n, 2, 2.5)) * (1 - out)); + ch.r.setAttribute("fill", n < 0.5 ? YEL : "#fff"); + }); + rollers.forEach((r, j) => r.setAttribute("transform", `translate(${[436, 587, 738][j]} 368) rotate(${-moved * 180})`)); + cap.textContent = t < T0 + 2 * P || t > 10.3 ? "The total sets how big the download will be" : "The active count sets how much work each token costs"; + }; + }, + }; + + /* ---- 2. a crate rolls in on a trolley; its delivery slip says Coming soon twice while a calendar tears off days ---- */ + SC.crate = { + dur: 11.2, still: 9, + build(svg, api) { + txt(svg, 44, 62, "The weights are not out yet", { "font-size": 32, "font-weight": 700 }); + txt(svg, 756, 60, "tap the crate", { "text-anchor": "end", "font-size": 20, fill: MUTE, "font-weight": 500 }); + el("path", { d: "M44 480 H756", stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + /* tear-off calendar */ + el("rect", { x: 548, y: 112, width: 184, height: 196, rx: 16, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + const num = txt(svg, 640, 280, "6", { "text-anchor": "middle", "font-size": 96, "font-weight": 700 }); + const leaf = el("g", {}, svg); + el("rect", { x: -90.5, y: 0, width: 181, height: 138, fill: SOFT }, leaf); + const old = txt(leaf, 0, 112, "", { "text-anchor": "middle", "font-size": 96, "font-weight": 700 }); + el("path", { d: "M548 168 V128 Q548 112 564 112 H716 Q732 112 732 128 V168 Z", fill: SKY, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, svg); + txt(svg, 640, 156, "October", { "text-anchor": "middle", "font-size": 24, "font-weight": 700 }); + [594, 686].forEach((x) => el("rect", { x: x - 6, y: 96, width: 12, height: 26, rx: 6, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg)); + const notes = [["VentureBeat: Oct. 27", "#fff", 346, 6.3], ["Mistral: end of month", YEL, 398, 7.8]].map(([s, fill, y, at]) => ({ g: pill(svg, s, fill), y, at })); + /* trolley and crate */ + const tr = el("g", {}, svg); + el("path", { d: "M84 438 V262 Q84 236 60 236 H46", stroke: INK, "stroke-width": 6, fill: "none", "stroke-linecap": "round" }, tr); + const wheels = [132, 442].map((x) => { const g = el("g", {}, tr); el("circle", { r: 15, fill: "#fff", stroke: INK, "stroke-width": 3 }, g); el("path", { d: "M-9 0 H9 M0 -9 V9", stroke: INK, "stroke-width": 2.5, "stroke-linecap": "round" }, g); return { g, x }; }); + el("rect", { x: 72, y: 430, width: 430, height: 16, rx: 8, fill: SOFT, stroke: INK, "stroke-width": 3 }, tr); + const cr = el("g", {}, tr); + el("rect", { x: -185, y: -262, width: 370, height: 262, rx: 12, fill: MINT, stroke: INK, "stroke-width": 3 }, cr); + el("path", { d: "M-185 -212 H185 M-185 -16 H185", stroke: INK, "stroke-width": 3 }, cr); + [[-166, -237], [166, -237]].forEach(([x, y]) => el("circle", { cx: x, cy: y, r: 4, fill: INK }, cr)); + txt(cr, 0, -228, "Mistral Large 4", { "text-anchor": "middle", "font-size": 26, "font-weight": 700 }); + el("rect", { x: -162, y: -194, width: 324, height: 162, rx: 10, fill: "#fff", stroke: INK, "stroke-width": 3 }, cr); + el("rect", { x: -32, y: -205, width: 64, height: 22, rx: 4, fill: SKY, stroke: INK, "stroke-width": 2.5, transform: "rotate(-3 0 -194)" }, cr); + el("path", { d: "M-140 -110 H140", stroke: SOFT, "stroke-width": 3, "stroke-linecap": "round" }, cr); + txt(cr, -140, -137, "weights", { "font-size": 22, "font-weight": 700 }); + txt(cr, -140, -69, "licence", { "font-size": 22, "font-weight": 700 }); + const stamps = [[-144, -3], [-76, 2.5]].map(([y, rot]) => ({ g: pill(cr, "Coming soon", LIL, { rx: 10 }), y, rot })); + el("rect", { x: -192, y: -269, width: 384, height: 276, rx: 18, fill: "none", stroke: INK, "stroke-width": 2, "stroke-dasharray": "3 7", class: "mg-ring" }, cr); + const cap = strip(svg, 512, 700); + let poked = -99; + button(cr, "The crate is closed. Weights and licence both read Coming soon.", () => { poked = api.now(); api.poke(); }); + return (t, now) => { + const w = now - poked, shake = w >= 0 && w < 0.9 ? Math.sin(w * 30) * 3 * (1 - w / 0.9) : 0; + const roll = oc(seg(t, 0.3, 1.3)) - io(seg(t, 10.3, 11)), dx = -580 * (1 - roll); + tr.setAttribute("transform", `translate(${dx} 0)`); + wheels.forEach((wh) => tf(wh.g, wh.x, 463, 1, (dx / 15) * 57.3)); + cr.setAttribute("transform", `translate(293 430) rotate(${shake})`); + stamps.forEach((s, i) => { const k = seg(t, 1.8 + i * 0.7, 2.1 + i * 0.7); s.g.setAttribute("opacity", k <= 0 ? 0 : 1); tf(s.g, 76, s.y, 2.2 - 1.2 * ob(k), s.rot); }); + let f = t < 3.2 || t > 10.6 ? 0 : Math.min(21, (t - 3.2) / 0.14); + if (t >= 7 && t <= 10.6) f = 21 + Math.min(4, (t - 7) / 0.15); + const day = 6 + Math.floor(f), fr = f - Math.floor(f), moving = fr > 0 && day > 6; + num.textContent = day; old.textContent = day - 1; + leaf.setAttribute("transform", `translate(640 168) scale(1 ${moving ? 1 - seg(fr, 0, 0.8) : 0})`); + leaf.setAttribute("opacity", moving ? 1 : 0); + notes.forEach((n) => { const k = ob(seg(t, n.at, n.at + 0.35)) * (1 - seg(t, 10.3, 10.7)); tf(n.g, 640, n.y, cl(k, 0, 1.05)); n.g.setAttribute("opacity", k <= 0.01 ? 0 : 1); }); + cap.textContent = w >= 0 && w < 3 ? "Not yet. You can only rent it through the API for now." : "Mistral's model page on 6 Oct 2026: both read Coming soon"; + }; + }, + }; + + /* ---- 3. three price tags on a line; a pencil strikes the higher price and the lower one drops in ---- */ + SC.tags = { + dur: 11.6, still: 9, + build(svg, api) { + txt(svg, 44, 62, "Price per million tokens", { "font-size": 32, "font-weight": 700 }); + txt(svg, 44, 98, "Mistral's model page, checked 6 Oct 2026", { "font-size": 20, "font-weight": 500, fill: MUTE }); + txt(svg, 756, 60, "tap a tag", { "text-anchor": "end", "font-size": 20, fill: MUTE, "font-weight": 500 }); + el("path", { d: "M40 142 Q400 166 760 142", stroke: INK, "stroke-width": 2.5, fill: "none", "stroke-linecap": "round" }, svg); + const IT = [["Input", "$0.68", "$1.36", YEL], ["Cached input", "$0.07", "$0.14", SKY], ["Output", "$2.09", "$4.18", MINT]]; + const X = [150, 400, 650], LY = [148, 154, 148], S = (i) => 1.8 + i * 2; + const LINES = IT.map(([n, p, o]) => `${n}: ${p} listed, ${o} struck through`); + let pinned = -1; + const tags = IT.map(([name, price, was, col], i) => { + const g = el("g", {}, svg); + el("rect", { x: -102, y: -2, width: 204, height: 327, rx: 22, fill: "none", stroke: INK, "stroke-width": 2, "stroke-dasharray": "3 7", class: "mg-ring" }, g); + el("path", { d: "M-95 52 L-46 6 H46 L95 52 V302 Q95 318 79 318 H-79 Q-95 318 -95 302 Z", fill: col, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, g); + txt(g, 0, 96, name, { "text-anchor": "middle", "font-size": 24, "font-weight": 700 }); + txt(g, 0, 156, was, { "text-anchor": "middle", "font-size": 34, "font-weight": 700 }); + const strike = el("path", { stroke: INK, "stroke-width": 4, "stroke-linecap": "round", fill: "none" }, g); + const plate = el("g", {}, g); + el("rect", { x: -80, y: -40, width: 160, height: 80, rx: 16, fill: "#fff", stroke: INK, "stroke-width": 3 }, plate); + txt(plate, 0, 17, price, { "text-anchor": "middle", "font-size": 48, "font-weight": 700 }); + const pen = el("g", {}, g); + el("rect", { x: 16, y: -7, width: 60, height: 14, fill: BLUE, stroke: INK, "stroke-width": 2.5 }, pen); + el("rect", { x: 76, y: -7, width: 14, height: 14, rx: 3, fill: LIL, stroke: INK, "stroke-width": 2.5 }, pen); + el("path", { d: "M0 0 L16 -7 V7 Z", fill: "#fff", stroke: INK, "stroke-width": 2.5, "stroke-linejoin": "round" }, pen); + el("path", { d: "M0 0 L6 -2.6 V2.6 Z", fill: INK }, pen); + button(g, LINES[i], () => { pinned = i; poked[i] = api.now(); api.poke(); }); + return { g, strike, plate, pen }; + }); + const poked = [-99, -99, -99]; + X.forEach((x, i) => el("rect", { x: x - 8, y: LY[i] - 22, width: 16, height: 42, rx: 6, fill: LIL, stroke: INK, "stroke-width": 2.5, "pointer-events": "none" }, svg)); + const cap = strip(svg, 512, 700); + return (t, now) => { + const out = seg(t, 10.6, 11.1); + let last = -1; + tags.forEach((k, i) => { + const te = Math.max(0, t - 0.15 * i), w = now - poked[i]; + const a = 10 * Math.exp(-2.2 * te) * Math.sin(6 * te) + (w >= 0 && w < 3 ? 8 * Math.exp(-2.5 * w) * Math.sin(8 * w) : 0); + k.g.setAttribute("transform", `translate(${X[i]} ${LY[i]}) rotate(${a})`); + const s = seg(t, S(i), S(i) + 0.55) * (1 - out), px = -58 + 116 * s; + k.strike.setAttribute("d", `M-58 148 L${px} ${148 - 8 * s}`); k.strike.setAttribute("opacity", s > 0 ? 1 : 0); + tf(k.pen, px, 148 - 8 * s, 1, -28 + Math.sin(t * 40) * 3 * (s > 0 && s < 1 ? 1 : 0)); + k.pen.setAttribute("opacity", seg(t, S(i) - 0.25, S(i) - 0.1) * (1 - seg(t, S(i) + 0.6, S(i) + 0.75))); + const p = seg(t, S(i) + 0.75, S(i) + 1.15); + tf(k.plate, 0, 250 - 80 * (1 - ob(p))); + k.plate.setAttribute("opacity", seg(p, 0, 0.25) * (1 - out)); + if (t >= S(i) + 0.3) last = i; + }); + cap.textContent = pinned >= 0 ? LINES[pinned] : t > 8.4 && t < 10.6 ? "The page did not say how long the lower prices last" : last < 0 || t >= 10.6 ? "A higher price was struck through beside each one" : LINES[last]; + }; + }, + }; + + /* ---- 4. five flags hoisted to their scores; switch the test and the order changes ---- */ + SC.flags = { + dur: 9, still: 3.4, + build(svg, api) { + const M = [["Mistral", "Large 4", YEL], ["Kimi K3", "", SKY], ["GLM-5.3", "", LIL], ["DeepSeek V4", "Pro 0813", BLUE], ["Qwen3.8", "Max", MINT]]; + const B = [["DeepSWE 1.1", [62, 68, 61, 57, 51]], ["Terminal-Bench 4", [28, 21, 40, 10, 17]]]; + const G = 425, P = 4.5, PX = (i) => 56 + i * 150, Y = (b, i) => G - 40 - B[b][1][i] * 2.8; + const lead = B.map(([, v]) => v.indexOf(Math.max(...v))); + txt(svg, 44, 62, "Mistral's own figures", { "font-size": 32, "font-weight": 700 }); + txt(svg, 44, 98, "Two rows we selected from its launch post. We did not run them.", { "font-size": 20, "font-weight": 500, fill: MUTE }); + txt(svg, 756, 60, "tap a test", { "text-anchor": "end", "font-size": 20, fill: MUTE, "font-weight": 500 }); + el("path", { d: `M40 ${G} H760`, stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + const flags = M.map(([n1, n2, col], i) => { + const x = PX(i); + el("path", { d: `M${x} ${G} V150`, stroke: INK, "stroke-width": 4, "stroke-linecap": "round" }, svg); + el("rect", { x: x - 14, y: G - 8, width: 28, height: 8, rx: 3, fill: SOFT, stroke: INK, "stroke-width": 2.5 }, svg); + txt(svg, x + 43, G + 34, n1, { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + if (n2) txt(svg, x + 43, G + 58, n2, { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + const g = el("g", {}, svg), p = el("path", { fill: col, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, g); + const v = txt(g, 38, 10, "", { "text-anchor": "middle", "font-size": 28, "font-weight": 700 }); + return { g, p, v }; + }); + const top = pill(svg, "highest", "#fff"); + let user = null, shown = 0; + const btns = B.map(([name], b) => { + const w = b ? 250 : 200, g = el("g", { transform: `translate(${b ? 508 : 267} 540)` }, svg); + el("rect", { x: -w / 2 - 6, y: -32, width: w + 12, height: 64, rx: 32, fill: "none", stroke: INK, "stroke-width": 2, "stroke-dasharray": "3 7", class: "mg-ring" }, g); + const r = el("rect", { x: -w / 2, y: -26, width: w, height: 52, rx: 26, fill: "#fff", stroke: INK, "stroke-width": 3 }, g); + txt(g, 0, 8, name, { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + button(g, `Show ${name}`, () => { user = { from: shown, to: b, t0: api.now() }; api.poke(); }); + return { g, r }; + }); + return (t, now) => { + let idx, prev, u; + if (user) { idx = user.to; prev = user.from; u = cl((now - user.t0) / P); } + else { idx = Math.floor(t / P) % 2; prev = 1 - idx; u = (t % P) / P; } + shown = idx; + const k = ob(seg(u, 0, 0.25)), ys = []; + flags.forEach((f, i) => { + const y = Y(prev, i) + (Y(idx, i) - Y(prev, i)) * k, wv = Math.sin(now * 3 + i * 1.4) * 3; + ys.push(y); tf(f.g, PX(i), y); + f.p.setAttribute("d", `M0 -26 Q43 ${-26 + wv} 86 -26 L74 0 L86 26 Q43 ${26 + wv} 0 26 Z`); + f.v.textContent = B[idx][1][i]; + }); + const s = ob(seg(u, 0.25, 0.4)); + tf(top, PX(lead[idx]) + 62, ys[lead[idx]] - 52, cl(s, 0, 1.3)); top.setAttribute("opacity", s <= 0.01 ? 0 : 1); + btns.forEach((b, i) => { b.r.setAttribute("fill", i === idx ? YEL : "#fff"); b.g.setAttribute("aria-pressed", i === idx ? "true" : "false"); }); + }; + }, + }; + + /* ---- title card, 16 by 9 ---- */ + SC.hero = { + w: 960, h: 540, dur: 1, still: 0, + build(svg) { + txt(svg, 64, 196, "Mistral Large 4", { "font-size": 76, "font-weight": 700 }); + el("path", { d: "M66 226 H606", stroke: YEL, "stroke-width": 14, "stroke-linecap": "round" }, svg); + txt(svg, 64, 288, "Le Chonk: size, price, scores, weights", { "font-size": 25, "font-weight": 500, fill: MUTE }); + el("rect", { x: 64, y: 326, width: 290, height: 52, rx: 26, fill: SOFT }, svg); + txt(svg, 209, 360, "Checked 6 Oct 2026", { "text-anchor": "middle", "font-family": MONO, "font-size": 20, "font-weight": 700 }); + /* one chunky kettlebell */ + const k = el("g", { transform: "translate(776 396)" }, svg); + el("ellipse", { cx: 0, cy: 96, rx: 118, ry: 10, fill: SOFT }, k); + el("path", { d: "M-50 -62 Q-74 -172 0 -172 Q74 -172 50 -62", stroke: INK, "stroke-width": 32, fill: "none", "stroke-linecap": "round" }, k); + el("path", { d: "M-50 -62 Q-74 -172 0 -172 Q74 -172 50 -62", stroke: LIL, "stroke-width": 26, fill: "none", "stroke-linecap": "round" }, k); + el("circle", { r: 96, fill: LIL, stroke: INK, "stroke-width": 3 }, k); + el("rect", { x: -76, y: -26, width: 152, height: 52, rx: 14, fill: "#fff", stroke: INK, "stroke-width": 3 }, k); + txt(k, 0, 9, "Le Chonk", { "text-anchor": "middle", "font-size": 28, "font-weight": 700 }); + return () => {}; + }, + }; + + /* ---- player ---- */ + const q = new URLSearchParams(location.search), frozen = q.has("mgstill"), fixed = parseFloat(q.get("mgstill")); + const calm = matchMedia("(prefers-reduced-motion: reduce)").matches; + const mount = (fig) => { + const sc = SC[fig.dataset.scene], img = fig.querySelector("img"); if (!sc || !img) return; + const W = sc.w || 800, H = sc.h || 600; + const svg = el("svg", { viewBox: `0 0 ${W} ${H}`, class: "mg-svg", role: "group", "aria-label": img.alt || "" }); + svg.style.aspectRatio = `${W} / ${H}`; + el("rect", { width: W, height: H, fill: "#fff" }, svg); + img.replaceWith(svg); + let off = 0, raf = 0, seen = false, held = calm; + const base = performance.now(), clock = () => (performance.now() - base) / 1000; + let draw = () => {}; + const frame = () => { const now = clock(); draw(held ? sc.still : (((now + off) % sc.dur) + sc.dur) % sc.dur, now); }; + const tick = () => { cancelAnimationFrame(raf); frame(); if (seen && !held) raf = requestAnimationFrame(tick); }; + const api = { now: clock, seek: (s) => { off = s - clock(); held = false; tick(); }, poke: () => { held = false; tick(); } }; + draw = sc.build(svg, api); + if (frozen) { draw(isNaN(fixed) ? sc.still : fixed, 0); return; } + new IntersectionObserver(([e]) => { + seen = e.isIntersecting; + if (seen) { tick(); if (!fig.dataset.sent && window.posthog) { fig.dataset.sent = 1; window.posthog.capture("blog_scene_view", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } } + else cancelAnimationFrame(raf); + }, { threshold: 0.25 }).observe(fig); + svg.addEventListener("click", () => { if (!fig.dataset.hit && window.posthog) { fig.dataset.hit = 1; window.posthog.capture("blog_scene_interact", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } }); + frame(); + }; + const go = () => document.querySelectorAll("figure.mg[data-scene]").forEach(mount); + const fonts = document.fonts && document.fonts.load ? Promise.all([document.fonts.load('700 32px "Space Grotesk"'), document.fonts.load('500 20px "Space Grotesk"'), document.fonts.load('700 20px "JetBrains Mono"')]).catch(() => {}) : Promise.resolve(); + const start = () => fonts.then(go, go); + if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", start); else start(); +})(); diff --git a/blog/src/assets/media/mistral-large-4/scales.png b/blog/src/assets/media/mistral-large-4/scales.png new file mode 100644 index 000000000..0f288ede6 Binary files /dev/null and b/blog/src/assets/media/mistral-large-4/scales.png differ diff --git a/blog/src/assets/media/mistral-large-4/tags.png b/blog/src/assets/media/mistral-large-4/tags.png new file mode 100644 index 000000000..5fdea957f Binary files /dev/null and b/blog/src/assets/media/mistral-large-4/tags.png differ diff --git a/blog/src/assets/media/opencode-vs-claude-code/hero.png b/blog/src/assets/media/opencode-vs-claude-code/hero.png new file mode 100644 index 000000000..a82d069c8 Binary files /dev/null and b/blog/src/assets/media/opencode-vs-claude-code/hero.png differ diff --git a/blog/src/assets/media/opencode-vs-claude-code/jars.png b/blog/src/assets/media/opencode-vs-claude-code/jars.png new file mode 100644 index 000000000..1fe78af64 Binary files /dev/null and b/blog/src/assets/media/opencode-vs-claude-code/jars.png differ diff --git a/blog/src/assets/media/opencode-vs-claude-code/keys.png b/blog/src/assets/media/opencode-vs-claude-code/keys.png new file mode 100644 index 000000000..6cfdb3956 Binary files /dev/null and b/blog/src/assets/media/opencode-vs-claude-code/keys.png differ diff --git a/blog/src/assets/media/opencode-vs-claude-code/lids.png b/blog/src/assets/media/opencode-vs-claude-code/lids.png new file mode 100644 index 000000000..1bbdd22cd Binary files /dev/null and b/blog/src/assets/media/opencode-vs-claude-code/lids.png differ diff --git a/blog/src/assets/media/opencode-vs-claude-code/motion.css b/blog/src/assets/media/opencode-vs-claude-code/motion.css new file mode 100644 index 000000000..eed456696 --- /dev/null +++ b/blog/src/assets/media/opencode-vs-claude-code/motion.css @@ -0,0 +1,13 @@ +/* Bright coded scenes for "OpenCode vs Claude Code". Pairs with motion.js. */ +.prose figure.mg { margin: 42px 0; } +.prose figure.mg img, .mg .mg-svg { display: block; width: 100%; height: auto; border: 1px solid #1A1320; border-radius: 18px; background: #fff; } +.mg figcaption { font: 500 13px/1.5 "Space Grotesk", system-ui, sans-serif; color: #6B5878; padding-top: 10px; text-align: center; } +.mg .mg-svg { touch-action: manipulation; -webkit-tap-highlight-color: transparent; } +.mg .mg-svg text { -webkit-user-select: none; user-select: none; pointer-events: none; } +.mg .mg-hit { cursor: pointer; outline: none; } +.mg [role="slider"] { cursor: grab; touch-action: none; } +.mg .mg-ring { stroke-opacity: 0; transition: stroke-opacity 0.15s; } +.mg .mg-hit:focus-visible .mg-ring { stroke-opacity: 1; } +.mg [role="slider"]:focus-visible circle:nth-child(2) { stroke-width: 5; } +@media (hover: hover) { .mg .mg-hit:hover .mg-ring { stroke-opacity: 0.5; } } +@media (prefers-reduced-motion: reduce) { .mg .mg-ring { transition: none; } } diff --git a/blog/src/assets/media/opencode-vs-claude-code/motion.js b/blog/src/assets/media/opencode-vs-claude-code/motion.js new file mode 100644 index 000000000..111ef6127 --- /dev/null +++ b/blog/src/assets/media/opencode-vs-claude-code/motion.js @@ -0,0 +1,325 @@ +/* Bright coded scenes for "OpenCode vs Claude Code". Vanilla JS, no library. +
holds a still image; this script swaps in a live SVG. + Plays only in view, honours reduced motion, "?mgstill" freezes each scene on its still frame. */ +(() => { + const NS = "http://www.w3.org/2000/svg"; + const INK = "#1A1320", YEL = "#FFCA54", BLUE = "#6C8EF5", LIL = "#B69CFF", MINT = "#7FD8AE", SKY = "#9AD8FF", SOFT = "#F1ECF9", MUTE = "#7A6A88"; + const SANS = '"Space Grotesk", system-ui, sans-serif', MONO = '"JetBrains Mono", ui-monospace, monospace'; + const el = (t, a = {}, p) => { const n = document.createElementNS(NS, t); for (const k in a) n.setAttribute(k, a[k]); if (p) p.appendChild(n); return n; }; + const txt = (p, x, y, s, a = {}) => { const n = el("text", Object.assign({ x, y, "font-family": SANS, "font-size": 24, "font-weight": 600, fill: INK }, a), p); n.textContent = s; return n; }; + const cl = (v, a = 0, b = 1) => Math.max(a, Math.min(b, v)); + const seg = (t, a, b) => cl((t - a) / (b - a)); + const oc = (x) => 1 - Math.pow(1 - x, 3); + const ob = (x) => { const c = 1.70158; return 1 + (c + 1) * Math.pow(x - 1, 3) + c * Math.pow(x - 1, 2); }; + const io = (x) => (x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2); + const tf = (n, x, y, s = 1, r = 0) => n.setAttribute("transform", `translate(${x} ${y}) rotate(${r}) scale(${s})`); + const button = (g, label, fn) => { + g.setAttribute("role", "button"); g.setAttribute("tabindex", "0"); g.setAttribute("aria-label", label); g.classList.add("mg-hit"); + g.addEventListener("click", fn); + g.addEventListener("keydown", (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); fn(); } }); + }; + const strip = (svg, y, w = 680) => { + el("rect", { x: (800 - w) / 2, y, width: w, height: 48, rx: 24, fill: SOFT }, svg); + return txt(svg, 400, y + 31, "", { "text-anchor": "middle", "font-size": 20, "font-weight": 500 }); + }; + + /* a pill that sizes itself to its words, drawn around 0,0 */ + const pill = (p, s, fill, size = 22) => { + const g = el("g", {}, p), r = el("rect", { y: -23, height: 46, rx: 23, fill, stroke: INK, "stroke-width": 3 }, g); + const t = txt(g, 0, size * 0.35, s, { "text-anchor": "middle", "font-size": size, "font-weight": 700 }); + const set = (v) => { t.textContent = v; const w = t.getComputedTextLength() + 40; r.setAttribute("x", -w / 2); r.setAttribute("width", w); }; + set(s); + return { g, r, set }; + }; + const show = (n, k) => n.setAttribute("opacity", k <= 0.01 ? 0 : 1); + + const SC = {}; + + /* ---- 1. two parcels: one lid lifts and a tag pops out, the other is taped shut ---- */ + SC.lids = { + dur: 10, still: 7, + build(svg, api) { + txt(svg, 44, 62, "Which one is open source?", { "font-size": 32, "font-weight": 700 }); + txt(svg, 756, 62, "tap a parcel", { "text-anchor": "end", "font-size": 20, fill: MUTE, "font-weight": 500 }); + const G = 448, BW = 250, BH = 170, LW = 280, LH = 40, LX = 220, RX = 580; + el("rect", { x: 40, y: G, width: 720, height: 18, rx: 9, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + const mk = (x, name, col, lidCol) => { + const g = el("g", { transform: `translate(${x} ${G})` }, svg), back = el("g", {}, g), box = el("g", {}, g); + el("rect", { x: -BW / 2, y: -BH, width: BW, height: BH, rx: 16, fill: col, stroke: INK, "stroke-width": 3 }, box); + txt(box, 0, -BH / 2 + 26, name, { "text-anchor": "middle", "font-size": 30, "font-weight": 700 }); + const over = el("g", {}, g), lid = el("g", {}, g); + el("rect", { x: -LW / 2, y: -LH, width: LW, height: LH, rx: 12, fill: lidCol, stroke: INK, "stroke-width": 3 }, lid); + el("rect", { x: -LW / 2 - 8, y: -BH - LH - 8, width: LW + 16, height: BH + LH + 16, rx: 22, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, "stroke-dasharray": "3 7", class: "mg-ring" }, g); + return { g, back, box, lid, over }; + }; + const A = mk(LX, "OpenCode", YEL, MINT), B = mk(RX, "Claude Code", LIL, LIL); + const mit = el("g", {}, A.back); + el("path", { d: "M0 -4 V70", stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, mit); + el("rect", { x: -70, y: -62, width: 140, height: 62, rx: 14, fill: "#fff", stroke: INK, "stroke-width": 3 }, mit); + el("circle", { cx: -44, cy: -31, r: 7, fill: SOFT, stroke: INK, "stroke-width": 3 }, mit); + txt(mit, 14, -19, "MIT", { "text-anchor": "middle", "font-family": MONO, "font-size": 34, "font-weight": 700 }); + const band = el("rect", { x: -LW / 2 - 5, y: -BH - 31, height: 22, rx: 6, fill: SKY, stroke: INK, "stroke-width": 3 }, B.g); + const strap = el("rect", { x: -19, y: -BH - LH - 5, width: 38, rx: 6, fill: SKY, stroke: INK, "stroke-width": 3 }, B.g); + const arr = el("g", {}, B.g); + el("rect", { x: -138, y: -29, width: 276, height: 58, rx: 14, fill: "#fff", stroke: INK, "stroke-width": 3 }, arr); + txt(arr, 0, 8, "All rights reserved", { "text-anchor": "middle", "font-size": 24, "font-weight": 700 }); + const LINES = ["OpenCode is MIT licensed, so the lid comes off", "Claude Code's licence file: “All rights reserved”"]; + let pinned = -1, poked = -99; + [A, B].forEach((p, i) => button(p.g, LINES[i], () => { pinned = i; poked = api.now(); api.poke(); })); + const cap = strip(svg, 506, 640); + return (t, now) => { + const out = io(seg(t, 9, 9.7)), w = now - poked, live = w >= 0 && w < 0.8 ? Math.sin((w / 0.8) * Math.PI) : 0; + const open = ob(seg(t, 1, 1.8)) - out, hop = pinned === 0 ? live : 0, shake = pinned === 1 && live ? Math.sin(w * 32) * 3 * live : 0; + A.lid.setAttribute("transform", `translate(0 ${-BH - 120 * open - 16 * hop}) rotate(${-5 * cl(open) - 4 * hop})`); + const up = ob(seg(t, 1.7, 2.3)) - out; + mit.setAttribute("transform", `translate(0 ${-BH + 74 - 104 * up}) rotate(${Math.sin(now * 1.6) * 2 * cl(up)})`); + B.g.setAttribute("transform", `translate(${RX} ${G}) rotate(${shake})`); + B.lid.setAttribute("transform", `translate(0 ${-BH})`); + const k1 = oc(seg(t, 3.4, 4.2)) * (1 - out), k2 = oc(seg(t, 4.1, 4.6)) * (1 - out); + band.setAttribute("width", Math.max(0.1, (LW + 10) * k1)); show(band, k1); + strap.setAttribute("height", Math.max(0.1, 96 * k2)); show(strap, k2); + const d = seg(t, 4.9, 5.3), bounce = Math.sin(seg(t, 5.3, 5.7) * Math.PI) * -10; + arr.setAttribute("transform", `translate(0 ${-BH - LH - 34 - 170 * (1 - d * d) + bounce}) rotate(${-3 + 8 * (1 - d)})`); + arr.setAttribute("opacity", seg(t, 4.9, 5.05) * (1 - seg(t, 9, 9.4))); + cap.textContent = pinned >= 0 ? LINES[pinned] : t >= 1 && t < 3.4 ? "OpenCode is MIT licensed" : t >= 3.4 && t < 6.6 ? LINES[1] : "Both checked on GitHub, 6 Oct 2026"; + }; + }, + }; + + /* ---- 2. four jars, one coin for every $10 a month ---- */ + SC.jars = { + dur: 11.4, still: 8.8, + build(svg, api) { + txt(svg, 44, 62, "What it costs a month to start", { "font-size": 32, "font-weight": 700 }); + txt(svg, 756, 62, "tap a jar", { "text-anchor": "end", "font-size": 20, fill: MUTE, "font-weight": 500 }); + const G = 436, JW = 124, JH = 250, CW = 90, CH = 21, X = (i) => 130 + i * 180; + const IT = [ + ["OpenCode", "free models", 0, "Free", "OpenCode's Zen price list marked 12 models as Free"], + ["OpenCode", "Go", 1, "$10", "OpenCode Go was $10 a month"], + ["Claude", "Pro", 2, "$20", "Claude Pro: $20 billed monthly"], + ["Claude", "Max", 10, "from $100", "Claude Max: from $100 a month"], + ]; + el("path", { d: `M30 ${G + 2} H770`, stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + const key = el("g", { transform: "translate(44 92)" }, svg); + el("rect", { x: 0, y: -9, width: 46, height: 18, rx: 9, fill: YEL, stroke: INK, "stroke-width": 3 }, key); + txt(key, 58, 7, "one coin is $10", { "font-size": 20, "font-weight": 500, fill: MUTE }); + let pinned = -1, tcur = 0.8; + const jars = IT.map(([n1, n2, n, price, line], i) => { + const x = X(i), g = el("g", {}, svg); + el("rect", { x: x - JW / 2 - 14, y: G - JH - 62, width: JW + 28, height: JH + 132, rx: 20, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, "stroke-dasharray": "3 7", class: "mg-ring" }, g); + el("rect", { x: x - JW / 2, y: G - JH, width: JW, height: JH, rx: 24, fill: SKY, "fill-opacity": 0.22 }, g); + const start = tcur; + const cs = Array.from({ length: n }, (_, j) => { const c = el("rect", { x: -CW / 2, y: -CH, width: CW, height: CH, rx: 10, fill: YEL, stroke: INK, "stroke-width": 3 }, g); return { c, y: G - 8 - j * CH, d: start + j * (n > 4 ? 0.16 : 0.3) }; }); + const done = n ? cs[n - 1].d + 0.45 : start + 0.5; tcur = done + 0.35; + el("rect", { x: x - JW / 2, y: G - JH, width: JW, height: JH, rx: 24, fill: "none", stroke: INK, "stroke-width": 3 }, g); + el("path", { d: `M${x - JW / 2 + 15} ${G - JH + 34} V${G - 44}`, stroke: "#fff", "stroke-width": 6, "stroke-linecap": "round" }, g); + el("rect", { x: x - 52, y: G - JH - 14, width: 104, height: 20, rx: 10, fill: "#fff", stroke: INK, "stroke-width": 3 }, g); + const lab = txt(g, x, G - JH - 30, price, { "text-anchor": "middle", "font-size": 26, "font-weight": 700 }); + txt(g, x, G + 36, n1, { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + txt(g, x, G + 62, n2, { "text-anchor": "middle", "font-size": 20, "font-weight": 500 }); + button(g, line, () => { pinned = i; api.poke(); }); + return { x, cs, lab, done, ly: G - JH - 30 }; + }); + const END = tcur + 0.2, cap = strip(svg, 516, 640); + return (t) => { + const out = seg(t, 10.6, 11.2); + let last = -1; + jars.forEach((s, i) => { + s.cs.forEach((c) => { const q = seg(t, c.d, c.d + 0.36), b = Math.sin(seg(t, c.d + 0.36, c.d + 0.62) * Math.PI) * -4; tf(c.c, s.x, c.y - (c.y - 172) * (1 - q * q) + b); c.c.setAttribute("opacity", seg(t, c.d, c.d + 0.06) * (1 - out)); }); + const p = ob(seg(t, s.done - 0.15, s.done + 0.25)); + s.lab.setAttribute("transform", `translate(${s.x} ${s.ly}) scale(${cl(p, 0, 1.3)}) translate(${-s.x} ${-s.ly})`); + s.lab.setAttribute("opacity", (p <= 0.01 ? 0 : 1) * (1 - out)); + if (t >= s.done - 0.15) last = i; + }); + cap.textContent = pinned >= 0 ? IT[pinned][4] : t >= END && t < 10.6 ? "Free software is not a free model" : last < 0 || t >= 10.6 ? "Monthly prices to start, 6 Oct 2026" : IT[last][4]; + }; + }, + }; + + /* ---- 3. a power strip that takes every shape of plug, next to a wall socket that takes one ---- */ + SC.sockets = { + dur: 11, still: 8.6, + build(svg) { + txt(svg, 44, 62, "Which models plug in?", { "font-size": 32, "font-weight": 700 }); + txt(svg, 44, 98, "Each plug is a model provider", { "font-size": 20, "font-weight": 500, fill: MUTE }); + const G = 420, SY = G - 62, WX = 598, WY = 310, LX = 722; + el("rect", { x: 40, y: G, width: 720, height: 18, rx: 9, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + const hole = (x, y) => { el("circle", { cx: x, cy: y, r: 26, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); el("path", { d: `M${x - 9} ${y - 8} V${y + 8} M${x + 9} ${y - 8} V${y + 8}`, stroke: INK, "stroke-width": 5, "stroke-linecap": "round" }, svg); }; + const led = (x, y) => el("circle", { cx: x, cy: y, r: 7, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + el("rect", { x: 50, y: G - 108, width: 446, height: 108, rx: 28, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + const R = 29, hex = Array.from({ length: 6 }, (_, i) => `${(Math.cos(i * 1.0472) * R * 1.1).toFixed(1)} ${(Math.sin(i * 1.0472) * R * 1.1).toFixed(1)}`).join(" L"); + const HEAD = [ + `M${-R} 0 a${R} ${R} 0 1 0 ${2 * R} 0 a${R} ${R} 0 1 0 ${-2 * R} 0 Z`, + `M${-R + 8} ${-R} H${R - 8} a8 8 0 0 1 8 8 V${R - 8} a8 8 0 0 1 -8 8 H${-R + 8} a8 8 0 0 1 -8 -8 V${-R + 8} a8 8 0 0 1 8 -8 Z`, + `M${hex} Z`, + `M-9 ${-R} H9 a${R} ${R} 0 0 1 0 ${2 * R} H-9 a${R} ${R} 0 0 1 0 ${-2 * R} Z`, + `M${-R} ${R} V${-R * 0.2} a${R} ${R} 0 0 1 ${2 * R} 0 V${R} Z`, + ]; + const COL = [YEL, BLUE, LIL, SKY, MINT]; + const slots = HEAD.map((d, i) => { + const x = 102 + i * 85.5; hole(x, SY - 6); const l = led(x, G - 17); + return { x, l, d, land: 1.2 + i * 0.85 }; + }); + el("rect", { x: WX - 62, y: WY - 52, width: 124, height: 122, rx: 20, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + hole(WX, WY); const wl = led(WX, WY + 48); + const cord = el("path", { stroke: INK, "stroke-width": 4, fill: "none", "stroke-linecap": "round" }, svg); + const rays = el("path", { d: `M${LX} 184 V166 M${LX - 40} 196 L${LX - 50} 182 M${LX + 38} 196 L${LX + 48} 182`, stroke: INK, "stroke-width": 4, "stroke-linecap": "round" }, svg); + el("path", { d: `M${LX} ${G - 14} V270`, stroke: INK, "stroke-width": 5, "stroke-linecap": "round" }, svg); + el("rect", { x: LX - 34, y: G - 16, width: 68, height: 16, rx: 8, fill: LIL, stroke: INK, "stroke-width": 3 }, svg); + const shade = el("path", { d: `M${LX - 44} 272 L${LX - 26} 206 H${LX + 26} L${LX + 44} 272 Z`, fill: "#fff", stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, svg); + slots.forEach((s, i) => { + const g = el("g", {}, svg); + const b = i % 2 ? -18 : 18, h = 92 + ((i * 2) % 5) * 9; el("path", { d: `M0 -22 C0 ${-h * 0.6} ${b} ${-h * 0.5} ${b} ${-h}`, stroke: INK, "stroke-width": 7, "stroke-linecap": "round", fill: "none" }, g); + el("path", { d: s.d, fill: COL[i], stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, g); + el("circle", { r: 9, fill: "#fff", stroke: INK, "stroke-width": 3 }, g); + s.g = g; + }); + const cp = el("g", {}, svg); + el("rect", { x: -50, y: -31, width: 100, height: 62, rx: 18, fill: YEL, stroke: INK, "stroke-width": 3 }, cp); + txt(cp, 0, 8, "Claude", { "text-anchor": "middle", "font-size": 24, "font-weight": 700 }); + txt(svg, 273, G + 62, "OpenCode", { "text-anchor": "middle", "font-size": 28, "font-weight": 700 }); + txt(svg, 660, G + 62, "Claude Code", { "text-anchor": "middle", "font-size": 28, "font-weight": 700 }); + const p1 = pill(svg, "75+ providers, local models included", MINT), p2 = pill(svg, "Claude models", YEL); + const CT = 6.3; + return (t) => { + const out = seg(t, 10.2, 10.8); + slots.forEach((s) => { + const q = seg(t, s.land - 0.45, s.land), sn = Math.sin(seg(t, s.land, s.land + 0.3) * Math.PI); + tf(s.g, s.x, SY - 6 - 100 * (1 - q * q), 1 + 0.1 * sn); + s.g.setAttribute("opacity", seg(t, s.land - 0.45, s.land - 0.3) * (1 - out)); + s.l.setAttribute("fill", t >= s.land && t < 10.2 ? MINT : "#fff"); + }); + const q = seg(t, CT - 0.5, CT), py = WY - 170 * (1 - q * q) * (1 - out) - 170 * out, on = t >= CT && t < 10.2; + tf(cp, WX, py, 1 + 0.08 * Math.sin(seg(t, CT, CT + 0.3) * Math.PI)); + cp.setAttribute("opacity", seg(t, CT - 0.5, CT - 0.35) * (1 - out)); + cord.setAttribute("d", `M${LX - 34} ${G - 8} C${LX - 80} ${G - 8} ${WX + 110} ${py} ${WX + 50} ${py}`); + cord.setAttribute("opacity", seg(t, CT - 0.5, CT - 0.35) * (1 - out)); + wl.setAttribute("fill", on ? MINT : "#fff"); + shade.setAttribute("fill", on ? YEL : "#fff"); rays.setAttribute("opacity", on ? ob(seg(t, CT, CT + 0.3)) : 0); + const a = ob(seg(t, 5.2, 5.6)) * (1 - out), b = ob(seg(t, CT + 0.5, CT + 0.9)) * (1 - out); + tf(p1.g, 273, G + 108, cl(a, 0, 1.2)); show(p1.g, a); + tf(p2.g, 660, G + 108, cl(b, 0, 1.2)); show(p2.g, b); + }; + }, + }; + + /* ---- 4. three keys take turns at one padlock ---- */ + SC.keys = { + dur: 10.4, still: 8.1, + build(svg, api) { + txt(svg, 44, 62, "Which login opens OpenCode?", { "font-size": 32, "font-weight": 700 }); + txt(svg, 756, 62, "tap a key", { "text-anchor": "end", "font-size": 20, fill: MUTE, "font-weight": 500 }); + const KX = 640, KY = 318, TOP = 262, P = 3; + const K = [ + ["API key", YEL, 1, "An Anthropic API key works: “Manually enter API Key”"], + ["ChatGPT Plus", SKY, 1, "A ChatGPT Plus or Pro account can log in"], + ["Claude Pro and Max", LIL, 0, "Claude Pro and Max: “Anthropic explicitly prohibits this”"], + ]; + const shackle = el("path", { stroke: INK, "stroke-width": 16, fill: "none", "stroke-linecap": "round" }, svg); + const body = el("rect", { x: KX - 84, y: TOP, width: 168, height: 142, rx: 22, fill: LIL, stroke: INK, "stroke-width": 3 }, svg); + el("circle", { cx: KX, cy: KY - 4, r: 15, fill: INK }, svg); + el("path", { d: `M${KX} ${KY} V${KY + 26}`, stroke: INK, "stroke-width": 11, "stroke-linecap": "round" }, svg); + txt(svg, KX, TOP + 124, "OpenCode", { "text-anchor": "middle", "font-size": 24, "font-weight": 700 }); + const res = pill(svg, "does not fit", "#fff"); + let user = null; + const keys = K.map(([name, col, ok, line], i) => { + const y = 176 + i * 112, g = el("g", {}, svg); + el("rect", { x: 34, y: y - 38, width: 432, height: 76, rx: 24, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, "stroke-dasharray": "3 7", class: "mg-ring" }, g); + el("rect", { x: 40, y: y - 30, width: 284, height: 60, rx: 18, fill: SOFT, stroke: INK, "stroke-width": 3 }, g); + txt(g, 100, y + 8, name, { "font-size": 22, "font-weight": 700 }); + const m = el("g", {}, g); + el("circle", { r: 16, fill: ok ? MINT : "#fff", stroke: INK, "stroke-width": 3 }, m); + el("path", { d: ok ? "M-7 0 L-2 6 L8 -6" : "M-6 -6 L6 6 M6 -6 L-6 6", stroke: INK, "stroke-width": 3.5, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, m); + const k = el("g", { "pointer-events": "none" }, svg); + el("path", { d: "M-72 0 H-4 M-34 0 V15 M-18 0 V11", stroke: INK, "stroke-width": 16, "stroke-linecap": "round", "stroke-linejoin": "round", fill: "none" }, k); + el("path", { d: "M-72 0 H-4 M-34 0 V15 M-18 0 V11", stroke: col, "stroke-width": 10, "stroke-linecap": "round", "stroke-linejoin": "round", fill: "none" }, k); + el("circle", { cx: -92, r: 26, fill: col, stroke: INK, "stroke-width": 3 }, k); + el("circle", { cx: -98, r: 9, fill: "#fff", stroke: INK, "stroke-width": 3 }, k); + button(g, line, () => { user = { i, t0: api.now() }; api.poke(); }); + return { g, k, m, y, ok, line }; + }); + const cap = strip(svg, 512, 700); + return (t, now) => { + let lift = 0, act = -1, au = 0; + const loc = (i) => (user ? (user.i === i ? now - user.t0 : -1) : t - (0.6 + i * P)); + keys.forEach((k, i) => { + const u = loc(i), go = io(seg(u, 0, 0.7)) - io(seg(u, 2.25, 2.9)); + const tx = k.ok ? KX - 6 : KX - 34, x = 456 + (tx - 456) * go, y = k.y + (KY - k.y) * go; + let sy = 1, rot = 0, dx = 0; + if (k.ok) { sy = 1 - 2 * (io(seg(u, 0.75, 1.2)) - io(seg(u, 1.9, 2.25))); if (u > 0) lift = Math.max(lift, ob(seg(u, 1.15, 1.5)) - io(seg(u, 1.9, 2.2))); } + else if (u > 0.7 && u < 2.25) { dx = Math.sin((u - 0.7) * 26) * 5 * (1 - seg(u, 1.7, 2.25)); rot = Math.sin((u - 0.7) * 26) * 2.5 * (1 - seg(u, 1.7, 2.25)); } + else if (u >= 2.25 && u < 4.2) rot = 9 * Math.exp(-2.2 * (u - 2.9)) * Math.sin((u - 2.9) * 9) * (u > 2.9 ? 1 : 0); + k.k.setAttribute("transform", `translate(${x + dx} ${y}) rotate(${rot}) scale(1 ${Math.abs(sy) < 0.12 ? 0.12 * (sy < 0 ? -1 : 1) : sy})`); + const seen = user ? user.i !== i || u >= (k.ok ? 1.3 : 1) : u >= (k.ok ? 1.3 : 1) && t < 10; + const mp = user && user.i !== i ? 1 : ob(seg(u, k.ok ? 1.3 : 1, k.ok ? 1.6 : 1.3)); + tf(k.m, 70, k.y, seen ? cl(mp, 0, 1.25) : 0); show(k.m, seen ? 1 : 0); + if (u >= 0 && u < P) { act = i; au = u; } + }); + const L = 28 * cl(lift, 0, 1.15); + shackle.setAttribute("d", `M${KX - 46} ${TOP + 14} V${TOP - 46 - L} a46 46 0 0 1 92 0 V${TOP + 14 - L * 1.5}`); + body.setAttribute("fill", lift > 0.5 ? MINT : LIL); + const ok = act >= 0 && keys[act].ok, a0 = ok ? 1.3 : 1, rp = act < 0 ? 0 : ob(seg(au, a0, a0 + 0.3)) * (1 - seg(au, 2.1, 2.3)); + if (act >= 0) { res.set(ok ? "opens" : "does not fit"); res.r.setAttribute("fill", ok ? MINT : "#fff"); } + tf(res.g, KX, TOP + 186, cl(rp, 0, 1.2)); show(res.g, rp); + cap.textContent = act >= 0 ? keys[act].line : "Three logins tried on OpenCode, 6 Oct 2026"; + }; + }, + }; + + /* ---- title card, 16 by 9 ---- */ + SC.hero = { + w: 960, h: 540, dur: 1, still: 0, + build(svg) { + txt(svg, 66, 96, "COMPARISON", { "font-family": MONO, "font-size": 22, "font-weight": 700, fill: MUTE, "letter-spacing": 3 }); + txt(svg, 64, 190, "OpenCode vs", { "font-size": 76, "font-weight": 700 }); + txt(svg, 64, 276, "Claude Code", { "font-size": 76, "font-weight": 700 }); + el("path", { d: "M66 304 H536", stroke: YEL, "stroke-width": 14, "stroke-linecap": "round" }, svg); + txt(svg, 64, 362, "Licence, price and models compared", { "font-size": 25, "font-weight": 500, fill: MUTE }); + el("rect", { x: 64, y: 400, width: 262, height: 50, rx: 25, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + txt(svg, 195, 433, "Checked 6 Oct 2026", { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + const PX = 752; + el("path", { d: `M${PX - 150} 462 H${PX + 150}`, stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + el("path", { d: `M${PX - 96} 462 q6 -20 12 0 q6 -26 12 0 M${PX + 70} 462 q6 -22 12 0 q6 -16 12 0`, stroke: INK, "stroke-width": 3, fill: MINT, "stroke-linejoin": "round" }, svg); + el("rect", { x: PX - 9, y: 112, width: 18, height: 350, rx: 6, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + el("circle", { cx: PX, cy: 104, r: 14, fill: MINT, stroke: INK, "stroke-width": 3 }, svg); + const a = el("g", { transform: `rotate(-4 ${PX} 190)` }, svg); + el("path", { d: `M${PX + 50} 154 H${PX - 112} L${PX - 150} 190 L${PX - 112} 226 H${PX + 50} Z`, fill: YEL, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, a); + txt(a, PX - 34, 199, "any model", { "text-anchor": "middle", "font-size": 26, "font-weight": 700 }); + const b = el("g", { transform: `rotate(3 ${PX} 290)` }, svg); + el("path", { d: `M${PX - 50} 254 H${PX + 122} L${PX + 160} 290 L${PX + 122} 326 H${PX - 50} Z`, fill: LIL, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, b); + txt(b, PX + 40, 299, "Claude plan", { "text-anchor": "middle", "font-size": 26, "font-weight": 700 }); + return () => {}; + }, + }; + + /* ---- player ---- */ + const q = new URLSearchParams(location.search), frozen = q.has("mgstill"), fixed = parseFloat(q.get("mgstill")); + const calm = matchMedia("(prefers-reduced-motion: reduce)").matches; + const mount = (fig) => { + const sc = SC[fig.dataset.scene], img = fig.querySelector("img"); if (!sc || !img) return; + const W = sc.w || 800, H = sc.h || 600; + const svg = el("svg", { viewBox: `0 0 ${W} ${H}`, class: "mg-svg", role: "group", "aria-label": img.alt || "" }); + svg.style.aspectRatio = `${W} / ${H}`; + el("rect", { width: W, height: H, fill: "#fff" }, svg); + img.replaceWith(svg); + let off = 0, raf = 0, seen = false, held = calm; + const base = performance.now(), clock = () => (performance.now() - base) / 1000; + let draw = () => {}; + const frame = () => { const now = clock(); draw(held ? sc.still : (((now + off) % sc.dur) + sc.dur) % sc.dur, now); }; + const tick = () => { cancelAnimationFrame(raf); frame(); if (seen && !held) raf = requestAnimationFrame(tick); }; + const api = { now: clock, seek: (s) => { off = s - clock(); held = false; tick(); }, poke: () => { held = false; tick(); } }; + draw = sc.build(svg, api); + if (frozen) { draw(isNaN(fixed) ? sc.still : fixed, 0); return; } + new IntersectionObserver(([e]) => { + seen = e.isIntersecting; + if (seen) { tick(); if (!fig.dataset.sent && window.posthog) { fig.dataset.sent = 1; window.posthog.capture("blog_scene_view", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } } + else cancelAnimationFrame(raf); + }, { threshold: 0.25 }).observe(fig); + svg.addEventListener("click", () => { if (!fig.dataset.hit && window.posthog) { fig.dataset.hit = 1; window.posthog.capture("blog_scene_interact", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } }); + frame(); + }; + const go = () => document.querySelectorAll("figure.mg[data-scene]").forEach(mount); + const fonts = document.fonts && document.fonts.load ? Promise.all([document.fonts.load('700 32px "Space Grotesk"'), document.fonts.load('500 20px "Space Grotesk"'), document.fonts.load('700 20px "JetBrains Mono"')]).catch(() => {}) : Promise.resolve(); + const start = () => fonts.then(go, go); + if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", start); else start(); +})(); diff --git a/blog/src/assets/media/opencode-vs-claude-code/sockets.png b/blog/src/assets/media/opencode-vs-claude-code/sockets.png new file mode 100644 index 000000000..659a3adab Binary files /dev/null and b/blog/src/assets/media/opencode-vs-claude-code/sockets.png differ diff --git a/blog/src/posts/chatgpt-dots-alternatives.md b/blog/src/posts/chatgpt-dots-alternatives.md index a20c05bbe..fbb5e4afe 100644 --- a/blog/src/posts/chatgpt-dots-alternatives.md +++ b/blog/src/posts/chatgpt-dots-alternatives.md @@ -36,60 +36,64 @@ New to dots? Read [What is ChatGPT dots?](/blog/what-is-chatgpt-dots/). Dots are pricey, regional and single at launch. From [OpenAI's launch post](https://openai.com/index/introducing-dots/) and help pages, as of 30 Sep 2026: -- **Price.** Your first dot comes with ChatGPT Pro, from $100 a month, or Business Premium. Free, Go and Plus do not get one. -- **Region.** Pro users in the EEA, Switzerland and the UK are left out at launch. -- **One dot.** You get one primary dot. OpenAI writes that "over time, we envision teams of dots working together", which is a plan, not a feature. -- **Where the work lives.** A dot works on its own cloud computer at OpenAI. Access to your own computer is optional and off by default. +- **Price.** Your first dot needs ChatGPT Pro, from $100 a month, or Business Premium. Free, Go and Plus get none. +- **Region.** Launch skips Pro users in the EEA, Switzerland and the UK. +- **One dot.** One primary dot each. OpenAI writes "over time, we envision teams of dots working together": a plan, not a feature. +- **Where the work lives.** A dot works on its own OpenAI cloud computer. Access to your computer is optional, off by default. -Dots still have real strengths: zero setup, a mobile app, voice calls and over 4,000 app plugins. +Dots still have strengths: zero setup, a mobile app, voice calls and 4,000+ app plugins. ## Does it run on your computer or in the cloud? -This settles who holds your files and whether work stops when you close the lid. Three picks run on hardware you control, four on a vendor's cloud, like dots. We drew the same line in our [Grok Bot alternatives](/blog/grok-bot-alternatives/) guide, and [why local first matters for AI agents](/blog/why-local-first-matters-for-ai-agents/) explains which side we lean to. +This settles who holds your files and whether a closed lid stops work. Three picks run on your hardware, four on a vendor's cloud, like dots. [Grok Bot alternatives](/blog/grok-bot-alternatives/) draws the same line; [why local first matters for AI agents](/blog/why-local-first-matters-for-ai-agents/) explains our lean.
### Runs on your computer -**1. OpenClaw.** [OpenClaw](https://github.com/openclaw/openclaw) is an open source (MIT) personal assistant that runs on your own computer and answers in WhatsApp, Telegram, Discord, Slack, Teams, iMessage and 20+ more channels. It runs as a background daemon, keeps memory and credentials on your hardware, and has no paid tier. Best for: a personal errand runner you own. +**1. OpenClaw.** [OpenClaw](https://github.com/openclaw/openclaw) is an open source (MIT) personal assistant that runs on your computer and answers in WhatsApp, Telegram, Discord, Slack, Teams, iMessage and 20+ more. It runs as a background daemon, keeps memory and credentials on your hardware, with no paid tier. Best for: an errand runner you own. **2. Munder Difflin.** [Munder Difflin](https://harnessmd.com/download) is what we make: a free and open source desktop app that runs a team of named AI agents on your own computer. Each agent has a role and runs a CLI engine you already use: Claude Code, Codex, Gemini CLI, OpenCode, Cursor and more. Each one keeps its own `memory.md`, inbox and outbox. You can put missions on a schedule, and a Slack channel can feed work straight to Michael, your lead agent. The catch: agents run while your computer runs, and you install the CLIs yourself. Best for: engineering and project work across several engines. (Munder Difflin 0.5.3, checked 30 Sep 2026.) -**3. Hermes Agent.** [Hermes Agent](https://github.com/NousResearch/hermes-agent) from Nous Research is open source (MIT) and built around a learning loop: it curates its own memory and writes new skills from experience. Its cron scheduler runs jobs unattended, and you reach it over Telegram, Discord, Slack, WhatsApp or Signal. Best for: memory that grows, on a box you control. +**3. Hermes Agent.** [Hermes Agent](https://github.com/NousResearch/hermes-agent) from Nous Research is open source (MIT), built on a learning loop: it curates its memory and writes new skills from experience. Its cron scheduler runs jobs unattended. Reach it on Telegram, Discord, Slack, WhatsApp or Signal. Best for: memory that grows on your box. ### Runs in the cloud -**4. Claude Cowork.** [Cowork](/blog/what-is-claude-cowork/) is Anthropic's agent for longer pieces of work, and it now runs in the cloud. Anthropic's [Cowork help page](https://support.claude.com/en/articles/13345190-get-started-with-cowork) says "Claude's work runs on Anthropic's servers, in an isolated environment" and that scheduled tasks don't need your computer to be awake. Since 16 Sep 2026 Anthropic has been [folding Cowork into the main Claude app](https://claude.com/blog/cowork-is-now-claude), Pro and Max first. Claude Pro was $20 a month, billed monthly, on 30 Sep 2026. Best for: reports, files and research, task by task. +**4. Claude Cowork.** [Cowork](/blog/what-is-claude-cowork/) is Anthropic's agent for longer work, now cloud based. Its [help page](https://support.claude.com/en/articles/13345190-get-started-with-cowork) says "Claude's work runs on Anthropic's servers, in an isolated environment" and scheduled tasks don't need your computer awake. Since 16 Sep 2026 Anthropic has been [folding Cowork into the main Claude app](https://claude.com/blog/cowork-is-now-claude), Pro and Max first. Claude Pro was $20 a month, billed monthly, on 30 Sep 2026. Best for: reports, files and research by task. -**5. Gemini Spark.** [Spark](/blog/what-is-gemini-spark/) is Google's personal agent in the Gemini app. It works from your Connected Apps and chats, and can drive your local Chrome. If your device goes off before a task is done, Google's [Spark help page](https://support.google.com/gemini/answer/17094507) says it "might use a remote browser" in the cloud instead. You need Google AI Pro or Ultra and to be 18 or over. Like dots on Pro, it is not available in the EEA, Switzerland or the UK, nor in Nigeria. Best for: Google app users outside Europe. +**5. Gemini Spark.** [Spark](/blog/what-is-gemini-spark/) is Google's personal agent in the Gemini app. It works from your Connected Apps and chats and can drive your local Chrome. If your device goes off mid task, Google's [help page](https://support.google.com/gemini/answer/17094507) says it "might use a remote browser" in the cloud. It needs Google AI Pro or Ultra and age 18+. Like dots on Pro, it skips the EEA, Switzerland and the UK, plus Nigeria. Best for: Google app users outside Europe. -**6. Grok Bot.** SpaceXAI's docs say your Bots run on "a persistent cloud computer with a browser, filesystem, and terminal", and closing your laptop or phone does not stop a routine. It comes with paid Cursor plans or a SuperGrok account. Cursor's Individual plan was $20 a month on 30 Sep 2026. [Grok Bot pricing](/blog/grok-bot-pricing/) has the other plans. Best for: hosted work agents whose computer never sleeps. +**6. Grok Bot.** SpaceXAI's docs say Bots run on "a persistent cloud computer with a browser, filesystem, and terminal", and closing your laptop or phone does not stop routines. It comes with paid Cursor plans or SuperGrok. Cursor Individual was $20 a month on 30 Sep 2026. [Grok Bot pricing](/blog/grok-bot-pricing/) has the rest. Best for: hosted work agents that never sleep. -**7. Meta Muse.** [Meta's help centre](https://www.meta.com/help/artificial-intelligence/1047255454427887/) says Muse runs on "a cloud-based environment called a virtual machine". On 29 Sep 2026 Meta said Muse is "available in the US and Canada" and free for most use; its [subscriptions page](https://www.meta.com/help/subscriptions/1021145227643680/) lists Power at $20 a month and Max at $100. Best for: a free hosted agent in North America. Elsewhere, see [Meta Muse alternatives](/blog/meta-muse-alternatives/). +**7. Meta Muse.** [Meta's help centre](https://www.meta.com/help/artificial-intelligence/1047255454427887/) says Muse runs on "a cloud-based environment called a virtual machine". On 29 Sep 2026 Meta said Muse is "available in the US and Canada" and free for most use; its [subscriptions page](https://www.meta.com/help/subscriptions/1021145227643680/) lists Power at $20 a month and Max at $100. Best for: a free hosted agent in North America. Elsewhere: [Meta Muse alternatives](/blog/meta-muse-alternatives/). ## ChatGPT dots alternatives, checked 30 Sep 2026 -| Agent | Runs on | Always on or task based | Who can get it | Price to start | +| Agent | Runs on | Always on or tasks | Who gets it | Starting price | | --- | --- | --- | --- | --- | | ChatGPT dots | OpenAI's cloud computer | Always on | Pro outside EEA, Switzerland, UK; Business Premium | Pro, $100/month. | | OpenClaw | Your computer | Always on daemon | Anyone | Free and open source. | | Munder Difflin | Your computer | Runs while your computer does, plus schedules | Anyone on Mac, Windows, Linux | Free and open source. | -| Hermes Agent | Your computer or your server | Always on, cron jobs | Anyone | Free and open source. | -| Claude Cowork | Anthropic's cloud | Tasks, plus scheduled tasks | Paid Claude plans | Claude Pro, $20/month. | +| Hermes Agent | Your computer or server | Always on, cron jobs | Anyone | Free and open source. | +| Claude Cowork | Anthropic's cloud | Tasks and schedules | Paid Claude plans | Claude Pro, $20/month. | | Gemini Spark | Google's cloud and your Chrome | Ongoing tasks and schedules | Google AI Pro or Ultra; not EEA, Switzerland, UK, Nigeria | Google AI Pro, price by country. | | Grok Bot | SpaceXAI's cloud computer | Always on, routines | Paid Cursor plans or SuperGrok | Cursor Individual, $20/month. | | Meta Muse | Meta's cloud VM | Tasks | US and Canada | Free tier. | ## Is there a free alternative to ChatGPT dots? -Yes: OpenClaw, Munder Difflin and Hermes Agent are all free and open source. The software costs nothing, but the model still does. OpenClaw and Hermes Agent take API keys, local models or a provider login. Munder Difflin drives the CLIs you already have, so the Claude or ChatGPT subscription you pay for today does the work. +Yes: OpenClaw, Munder Difflin and Hermes Agent are free and open source. The model still costs money. OpenClaw and Hermes Agent take API keys, local models or a provider login. Munder Difflin drives the CLIs you already have, so the Claude or ChatGPT subscription you pay for today does the work.
-Meta Muse is the only hosted pick with a free tier, and only in the US and Canada. Dots start at $100 a month on Pro (30 Sep 2026), and OpenAI has not said how dots usage will count against your plan after the first month. +Only Meta Muse has a free hosted tier, US and Canada only. Dots start at $100 a month on Pro (30 Sep 2026). OpenAI has not said how usage counts against your plan after month one. ## What is the best open source alternative to ChatGPT dots? -OpenClaw, if you want what a dot does for your personal life: one assistant, in your chat apps, on your hardware. Munder Difflin, if you want agents for work: several named agents with roles, each on the engine that suits the job. Hermes Agent is strongest on memory. +OpenClaw, for a dot's job in your personal life: one assistant in your chat apps, on your hardware. Munder Difflin, if you want agents for work: several named agents with roles, each on the engine that suits the job. Hermes Agent, for memory. -None of the three is a hosted computer that works with your laptop shut: you need a server, or a machine that stays on. We put Munder Difflin and dots side by side in [Munder Difflin vs ChatGPT dots](/blog/munder-difflin-vs-chatgpt-dots/). +None works with your laptop shut: you need a server or an always on machine. We put Munder Difflin and dots side by side in [Munder Difflin vs ChatGPT dots](/blog/munder-difflin-vs-chatgpt-dots/). + +## Is there an open source copy of ChatGPT dots? + +Yes: [OpenDots](https://github.com/CopilotKit/OpenDots) from CopilotKit is an open source template for similar agents. Created on 29 Sep 2026, it is MIT licensed and had about 3,850 stars on 6 Oct 2026. The README describes always on AI coworkers that move between text, calls and Slack. It is not among the seven: the README calls it "A template, not a hosted product." You run it with Node 24, and the README says conversations require CopilotKit Intelligence: a hosted service, a local Docker evaluation or a licensed self-hosted deployment. It carries an alpha badge. diff --git a/blog/src/posts/claude-cowork-vs-claude-code.md b/blog/src/posts/claude-cowork-vs-claude-code.md new file mode 100644 index 000000000..02afcada6 --- /dev/null +++ b/blog/src/posts/claude-cowork-vs-claude-code.md @@ -0,0 +1,109 @@ +--- +title: "Claude Cowork vs Claude Code: Which One Do You Need?" +seoTitle: "Claude Cowork vs Claude Code: Differences, Plans, Price (2026)" +description: "Claude Cowork does office work, Claude Code writes software. What each is, where it runs, which plans include it and what it can touch, checked 6 Oct 2026." +date: 2026-10-06 +category: comparisons +categoryLabel: Comparisons +type: Non-technical +primaryKeyword: "claude cowork vs claude code" +secondaryKeywords: ["claude code vs claude cowork", "difference between claude cowork and claude code", "claude cowork vs openclaw", "is claude cowork the same as claude code", "claude cowork pricing"] +tags: ["Comparisons", "Claude Code", "AI Agents", "Cost"] +faq: + - q: "Is Claude Cowork the same as Claude Code?" + a: "No. Anthropic's help page says Cowork uses the same agentic architecture that powers Claude Code, with no terminal required. Its product page puts Claude Code on software engineering and Cowork on non-coding knowledge work such as research, analysis and documents. Checked 6 Oct 2026." + - q: "What is the difference between Claude Cowork and Claude Code?" + a: "The job. Claude Code reads a codebase, edits files and runs commands, in a terminal, an IDE, a desktop app or a browser. Cowork works in the folders and tools you choose and hands back documents, spreadsheets and research, from the Claude app." + - q: "How much does Claude Cowork cost?" + a: "It has no price of its own. Anthropic's help page says Cowork is available on paid plans: Pro, Max, Team and Enterprise. On 6 Oct 2026 claude.com/pricing listed Pro at $20 a month billed monthly, or $17 a month on an annual subscription." + - q: "Can I use Claude Cowork and Claude Code on the same plan?" + a: "Yes. On 6 Oct 2026 Anthropic's pricing page listed Claude Code on the Pro card, and its help page listed Cowork on every paid plan. One Pro subscription covers both, and neither is in the Free plan's feature list." + - q: "Is Claude Cowork better than OpenClaw?" + a: "They trade off in opposite ways. Cowork is hosted by Anthropic and needs a paid plan. OpenClaw is MIT licensed and its README says it has no paid tier or hosted service, so you run it on your own computer and bring a model." +--- + +Claude Cowork is for office work (documents, spreadsheets, research) and Claude Code is for software, so pick by what you need to get back: a finished file or a commit. Both come with a paid Claude plan, from $20 a month on 6 Oct 2026. Checked 6 Oct 2026. + +This sits in our [Comparisons hub](/blog/topics/comparisons/). For the background on Cowork alone, read [what is Claude Cowork](/blog/what-is-claude-cowork/). + +## What is the difference between Claude Cowork and Claude Code? + +The job is the difference, not the engine. Anthropic's [help page](https://support.claude.com/en/articles/13345190-get-started-with-claude-cowork) says Cowork "uses the same agentic architecture that powers Claude Code, with no terminal required". + +The [Cowork product page](https://claude.com/product/cowork) draws the line itself. Claude Code "is built for software engineering", which it spells out as writing, debugging and shipping code. Cowork "is built for non-coding knowledge work", meaning research, analysis, document creation and other multi-step tasks. + +
Animation. A signpost with two arms swings into place. One arm points to a stack of paper and a spreadsheet, the other to a terminal window with a blinking cursor.
One engine, two jobs: Cowork for knowledge work, Claude Code for software. Anthropic's Cowork product page, 6 Oct 2026.
+ +The [Claude Code docs](https://code.claude.com/docs/en/overview) describe it as "an agentic coding tool that reads your codebase, edits files, runs commands, and integrates with your development tools". + +So is Claude Cowork the same as Claude Code? No. Same architecture, different front door. + +One naming note. On 16 Sep 2026 [Anthropic said](https://claude.com/blog/cowork-is-now-claude) "Claude Cowork and chat are merging into one Claude", rolling out to Pro and Max first. The pricing page banner now reads "Claude Cowork is now just Claude". The announcement does not mention Claude Code. + +## Where does each one run? + +Cowork runs in the Claude app, and Claude Code runs wherever you write code. The help page lists Cowork on Claude Desktop for macOS and Windows on all paid plans, on the web and mobile for Pro, Max and Team, and in the Chrome side panel for Max and Team, with Pro rolling out. + +The Claude Code docs list the terminal, IDE extensions for VS Code and JetBrains, a desktop app and the web. + +Where the work happens differs too. The help page says Cowork's work "runs on Anthropic's servers, in an isolated environment", and carries a notice for Pro and Max plans: "On October 6, 2026, new Cowork tasks run in the cloud". Claude Code in a terminal works in the project folder on your machine. Its web version runs "with no local setup". + +
Animation. Two kettles sit side by side. The first is plugged into a cloud and starts to steam. The second is plugged into a small desk and steams a moment later.
On Pro and Max, new Cowork tasks run in the cloud from 6 Oct 2026, per Anthropic's help page. Claude Code in a terminal runs on your machine.
+ +## Which plans include each, and from what price? + +Both start at Claude Pro, and neither is in the Free plan's feature list. [Anthropic's pricing page](https://claude.com/pricing) listed these prices on 6 Oct 2026: + +- **Pro:** $20 if billed monthly, or $17 a month on an annual subscription ($200 billed up front). +- **Max:** from $100 a month. +- **Team:** a standard seat is $25 monthly, or $20 billed annually. +- **Enterprise:** $20 a seat a month, billed annually, plus usage at API rates. + +The page names Claude Code on the Pro card. It names Cowork only on the Team card ("Claude Code and Claude Cowork"). The help page fills the gap: "Claude Cowork is available on paid plans (Pro, Max, Team, Enterprise)." + +
Animation. Coins drop into two stacks, one labelled Cowork and one labelled Claude Code. Both stop at the same height, 20 dollars, and a plank lands level across them.
Both start at Claude Pro: $20 if billed monthly. Anthropic's pricing page, 6 Oct 2026.
+ +Claude Code has one extra way in. Its docs say most surfaces need a Claude subscription or an Anthropic Console account, and that the terminal CLI, VS Code and JetBrains also support third-party providers. Cowork's [product page](https://claude.com/product/cowork) also lists Amazon Bedrock, Google Cloud and Microsoft Foundry as ways in. [Claude Code pricing](/blog/how-much-does-claude-code-cost/) has the full breakdown. + +## What can each one touch? + +Each reaches only what you allow, but the defaults differ. This table is our selection of rows, each taken from Anthropic's own pages on 6 Oct 2026. + +| | Claude Cowork | Claude Code | +| --- | --- | --- | +| Built for | Non-coding knowledge work | Software engineering | +| Files | Reads, edits and creates files "in folders you specify" | Reads files in the working directory without asking; edits ask first in Manual mode | +| Terminal | "No terminal required" | Runs shell commands, with approval in Manual mode | +| Browser | Opens a browser in the Cowork side panel | Connects to Chrome or Edge through the Claude in Chrome extension | +| Apps | Uses connectors first, then the browser | Connects tools through MCP | +| Computer use | Research preview | Research preview | +| Deleting | "Deleting anything needs your approval" | Not stated on the pages we opened | +| First plan | Pro | Pro | + +The Claude Code rows come from its [permissions page](https://code.claude.com/docs/en/permissions) and [Chrome page](https://code.claude.com/docs/en/chrome). On folders, the Cowork product page is blunt: "You choose the folders and tools. Claude can't reach anything else." + +
Animation. A picket fence rises around three folders. A parcel slides in through the gate and lands inside, while a fourth folder outside the fence stays grey and untouched.
Cowork works only in the folders and tools you choose, per Anthropic's product page, 6 Oct 2026.
+ +## Can you use both? + +Yes, and on one subscription. Pro lists Claude Code on the pricing page and Cowork on the help page, so you do not pay twice. Our view: plenty of people should use both, one for the spec and the spreadsheet, the other for the code. + +If your work is code and you want more than one agent on it, that is a third kind of tool. [Munder Difflin](https://harnessmd.com/download) is free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer. It does not run Cowork. Start with [how to install and use Munder Difflin](/blog/how-to-install-and-use-munder-difflin/). + +## Claude Cowork vs OpenClaw: how do they differ? + +Cowork is hosted and paid, OpenClaw is self-hosted and free. The [OpenClaw README](https://github.com/openclaw/openclaw) says it is MIT licensed and "has no paid tier, hosted service, or token". It answers in chat apps: "Discord, iMessage, Slack, Teams, Telegram, WhatsApp, and 20+ more". Models are plugins, and the README names Claude, Codex and local models. + +The trade is control against convenience. OpenClaw's README warns that "Tools run on the host for the main session unless you configure sandboxing". Cowork runs in Anthropic's isolated environment, on Anthropic's terms and plans. GitHub showed OpenClaw release v2026.9.8, dated 3 Oct, when we checked on 6 Oct 2026. Its README says it was built for Molty, a space lobster AI assistant. See [OpenClaw alternatives](/blog/openclaw-alternatives/) for more options. + +## Who should pick which? + +Pick by what you need handed back. + +- **A report, deck, spreadsheet or tidy folder:** Cowork. +- **A bug fix, feature or pull request:** Claude Code. [How to use Claude Code](/blog/how-to-use-claude-code/) gets you started. +- **Both kinds of work:** one Pro plan, both tools. +- **An assistant in your chat apps that you host yourself:** OpenClaw. +- **No terminal, ever:** Cowork. That is the point of it. + + diff --git a/blog/src/posts/mistral-large-4.md b/blog/src/posts/mistral-large-4.md new file mode 100644 index 000000000..dd1703b96 --- /dev/null +++ b/blog/src/posts/mistral-large-4.md @@ -0,0 +1,104 @@ +--- +title: "Mistral Large 4 (Le Chonk): size, price, scores and weights" +description: "Mistral Large 4 is a 1 trillion parameter open weight model in public preview. Context window, API price, Mistral's own scores and weights, checked 6 Oct 2026." +date: 2026-10-06 +category: concepts +categoryLabel: Concepts +type: Non-technical +primaryKeyword: "mistral large 4" +secondaryKeywords: ["mistral large 4 benchmarks", "mistral large 4 pricing", "mistral large 4 open weights", "le chonk mistral", "mistral large 4 vs", "mistral large 4 context window"] +tags: ["Concepts", "Open Source", "AI Agents"] +faq: + - q: "Is Mistral Large 4 open source?" + a: "Mistral calls it open weight, and the weights are not out yet. Its launch post of 6 October 2026 says it will release the weights by the end of the month. When we checked Mistral's model page on 6 Oct 2026, both the weights and the licence were marked Coming soon. VentureBeat reported the same day that the weights are expected under a custom Mistral license." + - q: "What is the Mistral Large 4 context window?" + a: "1M tokens. Mistral's model page listed a context size of 1M tokens on 6 Oct 2026, and defines the context window as the maximum number of input plus output tokens the model can process at once." + - q: "How much does Mistral Large 4 cost?" + a: "Mistral's model page listed $0.68 per million input tokens, $0.07 per million cached input tokens and $2.09 per million output tokens on 6 Oct 2026. The same page showed $1.36, $0.14 and $4.18 struck through, and the launch post lists $1.36 and $4.18. The page did not say how long the lower prices last." + - q: "Why is Mistral Large 4 called Le Chonk?" + a: "It is Mistral's own nickname for the model. The launch post of 6 October 2026 says the model is unofficially ML4 and very officially le Chonk. Mistral describes it as its largest and most capable model to date." + - q: "Is Mistral Large 4 better than Kimi K3?" + a: "It depends on the test, on the figures Mistral published. Mistral's charts show Kimi K3 ahead on DeepSWE 1.1 (68 against 62) and Mistral Large 4 ahead on Terminal-Bench 4 (28 against 21). VentureBeat reported on 6 October 2026 that Mistral's ranking claim remains provisional until outsiders can test the released weights." +--- + +Our view: Mistral Large 4, nicknamed Le Chonk, is a serious open weight coding model that you can only rent for now. Mistral launched it as a public preview on 6 October 2026, with about 1 trillion parameters, a 1M token context window, an API you can call today and weights promised this month. Checked 6 Oct 2026. + +Open models can drive coding agents, which is why we care. [Munder Difflin](https://harnessmd.com/download) is free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer. We have not tried Mistral Large 4 with it. Our guide to [running Munder Difflin on open models](/blog/run-munder-difflin-on-open-models/) covers the models you can use now, the [install guide](/blog/how-to-install-and-use-munder-difflin/) covers setup, and the [Concepts hub](/blog/topics/concepts/) explains the terms. + +## What is Mistral Large 4? + +Mistral Large 4 is Mistral's largest model so far, released as a public preview. The [launch post](https://mistral.ai/news/mistral-large-4/) says the model is "unofficially ML4, very officially: le Chonk". Yes, that is the vendor's wording, not ours. + +Mistral's [model page](https://docs.mistral.ai/models/mistral-large-4-0) describes an open-weight, general-purpose multimodal model with a Mixture-of-Experts architecture. The launch post says it takes multimodal input and was trained on more than 160 languages. [VentureBeat](https://venturebeat.com/technology/mistral-debuts-large-4-le-chonk-a-1-trillion-parameter-text-output-model-with-high-benchmarks-planned-for-open-weights-release) describes it as a text output model. + +The [Hacker News thread](https://news.ycombinator.com/item?id=49977979) had more than 750 points and 430 comments when we checked on 6 Oct 2026. + +## How big is it, and what is the context window? + +It has about 1 trillion parameters and a 1M token context window. The launch post of 6 October 2026 says "1 trillion-parameter" with 49 billion active parameters. The model page is more exact: 1.05T total parameters, 49B active, and a 1.6B vision encoder. + +In plain words: the total sets how big the download will be, and the active count sets how much work each token costs. The model page lists the context size as 1M tokens, counting input and output together. + +
Animation. A kitchen scale holds a very large flour sack labelled 1.05T total parameters. A small scoop labelled 49B active lifts out of the sack for each new token on a belt beside it, then tips back in.
Mistral's model page lists 1.05T total parameters and 49B active, as of 6 Oct 2026.
+ +## Can you download the weights, and what is the licence? + +No, not yet. The launch post says "We will release the weights by the end of the month", and that until then Mistral is red-teaming the model with cybersecurity partners and state authorities. On the model page, the Weights tab showed "Coming soon" for both the weights and the licence on 6 Oct 2026. + +VentureBeat reported on 6 October 2026 that Mistral plans to publish the weights on Oct. 27, and that they are "expected under a custom Mistral license". Mistral's own pages did not name a licence when we checked. Our view: do not plan a commercial self-hosted deployment until the licence text is public. + +
Animation. A crate marked Mistral Large 4 rolls in on a trolley. Its delivery slip is stamped Coming soon twice, once for weights and once for licence. A wall calendar for October tears off its pages from 6 to 31, with notes for VentureBeat's Oct. 27 and Mistral's end of month.
Weights and licence were both marked Coming soon on Mistral's model page on 6 Oct 2026. Mistral says the weights arrive by the end of the month.
+ +## What does Mistral Large 4 cost on the API? + +Input costs $0.68 per million tokens and output costs $2.09, on the price Mistral's model page listed on 6 Oct 2026. The page showed a higher figure struck through beside each price, and the launch post lists those higher figures. Neither page said how long the lower prices last. + +| Per million tokens | Listed price | Struck through price | +| --- | --- | --- | +| Input | $0.68 | $1.36 | +| Cached input | $0.07 | $0.14 | +| Output | $2.09 | $4.18 | + +Source: [Mistral's model page](https://docs.mistral.ai/models/mistral-large-4-0), checked 6 Oct 2026. The API model name is `mistral-large-4`. + +
Animation. Three price tags hang from pegs on a line, for input, cached input and output. On each tag the higher price is crossed out by a pencil and the lower price drops in below it.
Mistral's model page on 6 Oct 2026: input $0.68 with $1.36 struck through, cached input $0.07 with $0.14 struck through, output $2.09 with $4.18 struck through.
+ +## How does Mistral Large 4 score on benchmarks? + +On the vendor's own figures, it is second among the five models in both charts below, and leads neither. The two rows below are our selection from the charts in Mistral's [launch post](https://mistral.ai/news/mistral-large-4/). We did not run them. The charts round to whole numbers. + +| Benchmark (Mistral's charts) | Mistral Large 4 | Kimi K3 | GLM-5.3 | DeepSeek V4 Pro 0813 | Qwen3.8 Max | +| --- | --- | --- | --- | --- | --- | +| DeepSWE 1.1 | 62 | 68 | 61 | 57 | 51 | +| Terminal-Bench 4 | 28 | 21 | 40 | 10 | 17 | + +In its text, Mistral gives 61.7% on DeepSWE v1.1, 28.3% on Terminal-Bench 4 and 59.4% on SWE-Atlas-QnA, and a combined Coding Agent Index of 49.8%. It also says the model solves 93% of Cybench. In a blind human evaluation of coding quality across five models, Mistral says the preview rated 3.74 out of 5, second to Claude Opus 5 at 4.22. + +VentureBeat noted that the model did not yet appear in Artificial Analysis' public evaluations or on the DeepSWE leaderboard, so the ranking claim "remains provisional". Mistral's charts say the scores were evaluated privately by Artificial Analysis. For another launch this week, see our [Reflection Beam explainer](/blog/reflection-beam/). + +
Animation. Five flags for Mistral Large 4, Kimi K3, GLM-5.3, DeepSeek V4 Pro 0813 and Qwen3.8 Max slide up their poles to heights set by their scores, first for DeepSWE 1.1 and then for Terminal-Bench 4, and the order changes between the two.
Mistral's own figures from its launch post of 6 October 2026, two rows we selected. We did not run them.
+ +## What hardware do you need to run it? + +Mistral has not said. The model page has a column for approximate GPU RAM, and it read "N/A" on 6 Oct 2026. The launch post gives training hardware only: 3,800 NVIDIA Grace Blackwell GPUs in Mistral's own datacenters in Europe. + +Our view: do not read 49B active as "fits on a workstation". The download will hold every parameter. For a local coding model this week, [is Ollama good for coding?](/blog/is-ollama-good-for-coding/) covers what runs on normal hardware. + +## Can Mistral Large 4 drive a coding agent today? + +Through the API, probably, but no source we opened names a coding CLI that ships with it. The model page lists Function Calling on `/v1/chat/completions`, which is what a coding agent needs from a model. The launch post points people to the preview API on Mistral Studio. + +One detail is telling. Mistral's DeepSWE chart labels rivals by harness, such as Qwen3.8 Max with Claude Code and GLM-5.3 with Opencode. Its own bar names none, and the footnote mentions a harness that has not launched publicly. + +We think any agent that accepts a custom API endpoint could be pointed at it, but we have not tested that. For what works now, see our [best AI coding agents](/blog/best-ai-coding-agents/) roundup. + +## What should you do now? + +Try the preview on the API if you are curious, and wait for the weights before committing. + +1. Test `mistral-large-4` on a task from your own repo. +2. Budget on the struck through prices, in case the lower ones end. +3. Read the licence when it is published. +4. Wait for independent scores before you switch models. + + diff --git a/blog/src/posts/opencode-vs-claude-code.md b/blog/src/posts/opencode-vs-claude-code.md new file mode 100644 index 000000000..9dd73f800 --- /dev/null +++ b/blog/src/posts/opencode-vs-claude-code.md @@ -0,0 +1,102 @@ +--- +title: "OpenCode vs Claude Code: Licence, Price and Models Compared" +seoTitle: "OpenCode vs Claude Code (Oct 2026): Price, Models, Licence" +description: "OpenCode vs Claude Code, checked 6 Oct 2026: licence, price to start, models, where each runs, and whether a Claude subscription works in OpenCode." +date: 2026-10-06 +category: comparisons +categoryLabel: Comparisons +type: Non-technical +primaryKeyword: "opencode vs claude code" +secondaryKeywords: ["claude code vs opencode", "opencode vs codex", "is opencode better than claude code", "opencode with claude subscription", "opencode free models"] +tags: ["Comparisons", "Claude Code", "CLI Agents", "Open Source", "Cost"] +faq: + - q: "Is OpenCode better than Claude Code?" + a: "Neither wins outright. We think OpenCode is the better fit if you want open source code and a free choice of model, and Claude Code is the better fit if you already pay for a Claude plan. On 6 Oct 2026 OpenCode was MIT licensed and Claude Code's licence file said all rights reserved." + - q: "Can I use my Claude subscription in OpenCode?" + a: "Not in a way either vendor supports. On 6 Oct 2026 OpenCode's providers page said plugins for Claude Pro and Max exist, that Anthropic explicitly prohibits this, and that OpenCode stopped bundling them as of 1.3.0. Anthropic's legal page says third party developers may not route requests through Free, Pro or Max plan credentials." + - q: "Can I use Claude models in OpenCode?" + a: "Yes, with a key you pay for by the token. The Anthropic section of OpenCode's providers page shows a Manually enter API Key option. OpenCode Zen also lists Claude models: Claude Opus 5.5 was $4.00 per 1M input tokens and $20.00 per 1M output tokens on 6 Oct 2026." + - q: "Does OpenCode have free models?" + a: "Yes. OpenCode's Zen price list marked 12 models as Free on 6 Oct 2026, Big Pickle among them. The same page says each is available for a limited time, so the list will change. Your prompts still go to a hosted model." + - q: "Is OpenCode or Codex the better open source agent?" + a: "Both have open code: OpenCode is MIT and Codex is Apache 2.0, per GitHub on 6 Oct 2026. Codex is OpenAI's agent, and its README recommends signing in with a ChatGPT plan. OpenCode connects to many providers, and its site says you can log in with a ChatGPT Plus or Pro account there too." +--- + +Pick OpenCode if you want an open source agent that runs almost any model, and Claude Code if you want Anthropic's own agent on a Claude plan you already pay for. Checked 6 Oct 2026. + +Both read your repo, edit files and run commands. The differences are licence, price, models and login. New to one of them? Read [what is OpenCode](/blog/what-is-opencode/) first. More head to heads sit in our [Comparisons hub](/blog/topics/comparisons/). + +## OpenCode vs Claude Code at a glance, checked 6 Oct 2026 + +OpenCode is open and model neutral, and Claude Code is closed and built around Claude. The rows are our selection, each checked on 6 Oct 2026 against the vendor's own site, docs or GitHub repo. + +| | OpenCode | Claude Code | +| --- | --- | --- | +| Maker | Anomaly | Anthropic | +| Licence | MIT | Proprietary: "All rights reserved" | +| Latest release | v1.18.34, 30 Sep 2026 | v2.1.291, 6 Oct 2026 | +| Price to start (6 Oct 2026) | Free models; Go, $10 a month | Claude Pro, $20 a month | +| Models | 75+ providers, local models included | Claude models | +| Runs in | Terminal, desktop app, IDE extension | Terminal, IDE, desktop app, web | +| Claude Pro or Max login | No | Yes | +| Anthropic API key | Yes | Yes | + +Sources: [opencode.ai](https://opencode.ai/), the [OpenCode repo](https://github.com/sst/opencode) (which GitHub now serves as anomalyco/opencode), [Anthropic's pricing page](https://claude.com/pricing), the [Claude Code docs](https://code.claude.com/docs/en/overview) and the [Claude Code repo](https://github.com/anthropics/claude-code). + +## Which one is open source? + +OpenCode is, and Claude Code is not. GitHub listed OpenCode under the MIT licence on 6 Oct 2026. The Claude Code repo is public, but its [licence file](https://github.com/anthropics/claude-code/blob/main/LICENSE.md) reads "© Anthropic PBC. All rights reserved." Our post on [whether Claude Code is open source](/blog/is-claude-code-open-source/) covers what that repo does hold. + +
Animation. Two parcels sit side by side. The lid of the parcel marked OpenCode lifts off and a tag reading MIT pops out. A strip of tape wraps the parcel marked Claude Code and a tag reading All rights reserved lands on top.
OpenCode is MIT. Claude Code's licence file says all rights reserved. Both from GitHub, 6 Oct 2026.
+ +## What does each cost to start? + +OpenCode can cost nothing to start, and Claude Code started at $20 a month on 6 Oct 2026. OpenCode's [Zen price list](https://opencode.ai/docs/zen/) marked 12 models as Free on 6 Oct 2026, and says each is available "for a limited time". [OpenCode Go](https://opencode.ai/docs/go/) was an optional plan at $10 a month for open coding models on 6 Oct 2026, with Go Plus at $40. + +On 6 Oct 2026 Anthropic's pricing page listed Claude Code in Pro ($20 billed monthly, or $17 a month billed annually) and Max (from $100 a month), and not in the Free plan. The docs say an Anthropic Console account works too. Our [Claude Code pricing](/blog/how-much-does-claude-code-cost/) post has the full breakdown. + +
Animation. Four glass jars stand in a row. Coins drop in one by one: none in the jar marked OpenCode free models, a short stack in OpenCode Go at 10 dollars, a taller stack in Claude Pro at 20 dollars, and the tallest in Claude Max from 100 dollars.
Monthly prices to start, from OpenCode's docs and Anthropic's pricing page, 6 Oct 2026.
+ +Free software is not a free model. Bring your own key to OpenCode and the provider bills you. + +## Which models can each use? + +OpenCode connects to almost any model, and Claude Code runs Claude models. OpenCode's site says "free models included or connect any model from any provider", with 75+ LLM providers and local models. The Claude Code docs list Anthropic itself plus Amazon Bedrock, Google Cloud's Agent Platform and Microsoft Foundry as places to get those Claude models. + +
Animation. A long power strip marked OpenCode lights up socket by socket as plugs of different shapes click in, with a label reading 75+ providers. Next to it a single wall socket marked Claude Code takes one plug marked Claude and a lamp switches on.
OpenCode's site lists 75+ providers. Claude Code runs Claude models. Checked 6 Oct 2026.
+ +## Where does each one run? + +Both run in the terminal, in an IDE and in a desktop app, and Claude Code also runs on the web. OpenCode's site calls it "a terminal interface, desktop app, and IDE extension", and its repo marks the desktop app as beta. Claude Code's docs list the terminal, VS Code, JetBrains, a desktop app and a browser version at claude.ai/code. + +## What does each have that the other lacks? + +OpenCode has model choice and open code. Claude Code has cloud features tied to a Claude account. We read both sets of docs on 6 Oct 2026 and picked these. + +- **OpenCode:** two built in primary agents, Build and Plan, that you switch with Tab. Built in LSP servers, off by default, that can feed diagnostics back to the agent. A `/share` command that makes a public link to a session. Logins for ChatGPT Plus or Pro and GitHub Copilot. +- **Claude Code:** sessions that run in the browser with no local setup. Routines that run on a schedule in the cloud. Remote Control from your phone. A Slack integration that turns a bug report into a pull request. + +## Can you use a Claude subscription in OpenCode? + +No, not in a way either vendor supports. OpenCode's [providers page](https://opencode.ai/docs/providers/) said on 6 Oct 2026: "There are plugins that allow you to use your Claude Pro/Max models with OpenCode. Anthropic explicitly prohibits this." It adds that older versions bundled those plugins, "but that is no longer the case as of 1.3.0". + +Anthropic's [legal page](https://code.claude.com/docs/en/legal-and-compliance) says it "does not permit third-party developers to offer Claude.ai login into their own applications, or to route requests through Free, Pro, or Max plan credentials on behalf of their users". + +An Anthropic API key does work. The auth menu on the providers page shows "Manually enter API Key". Zen sells Claude too: on 6 Oct 2026 Claude Opus 5.5 was $4.00 per 1M input tokens and $20.00 per 1M output tokens. The key still cuts fine. The lock was changed. + +
Animation. Three labelled keys take turns at a padlock marked OpenCode. The key labelled API key turns and the padlock opens. The key labelled ChatGPT Plus turns and it opens again. The key labelled Claude Pro and Max does not fit and swings back to its place.
What OpenCode's providers page said about logins on 6 Oct 2026.
+ +## OpenCode vs Codex + +OpenCode is the neutral one, and Codex is OpenAI's own agent. The [Codex repo](https://github.com/openai/codex) is Apache 2.0, and its README recommends signing in with a ChatGPT plan. OpenAI's [pricing page](https://learn.chatgpt.com/docs/pricing) listed the CLI from Plus, $20 a month, on 6 Oct 2026. Our view: choose Codex if you only want OpenAI models, and OpenCode if you want to switch. [Codex vs Claude Code](/blog/codex-cli-vs-claude-code/) compares the two lab agents. + +## Who should pick which? + +Pick by what you already pay for and how much you care about the licence. We think the price gap matters less than it looks, because a paid model costs money in either tool. + +- **Pick OpenCode if** you want MIT code, local models, free models to try, or one agent across several providers. +- **Pick Claude Code if** you have Claude Pro or Max and want that plan to cover your coding, or you want its web and cloud features. + +Or run both. Nothing stops you installing two agents on one computer. [Munder Difflin](https://harnessmd.com/download) is free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer. You still install each CLI and sign in with your own plan or key. Start with [how to install and use it](/blog/how-to-install-and-use-munder-difflin/). + + diff --git a/blog/src/posts/reflection-beam.md b/blog/src/posts/reflection-beam.md index 0f56ff0b5..4bcdd5d37 100644 --- a/blog/src/posts/reflection-beam.md +++ b/blog/src/posts/reflection-beam.md @@ -97,7 +97,7 @@ Our view: do not read "23 billion active" as "fits on a laptop". The download wi It means one more open model that may plug into the tools you already use, but not yet. Reflection's [launch post](https://reflection.ai/blog/introducing-beam) says Beam will launch with distribution partners and integration with a broad range of open source libraries and harnesses. It also describes a demo where the team plugged Beam into OpenCode. -The post does not name the partners, so nobody can say yet which agents Beam will work in. For what works now, see our [best AI coding agents](/blog/best-ai-coding-agents/) roundup. +The post does not name the partners, so nobody can say yet which agents Beam will work in. For what works now, see our [best AI coding agents](/blog/best-ai-coding-agents/) roundup. Mistral announced a larger open weight model a day later, covered in our [Mistral Large 4 explainer](/blog/mistral-large-4/). ## What should you do now? diff --git a/blog/src/posts/what-is-claude-cowork.md b/blog/src/posts/what-is-claude-cowork.md index 3a0bb6096..efe17c386 100644 --- a/blog/src/posts/what-is-claude-cowork.md +++ b/blog/src/posts/what-is-claude-cowork.md @@ -113,4 +113,4 @@ It cannot run on the free plan, work offline, or reach files you have not shared Cowork for documents, Claude Code for software. Anthropic's product page describes Claude Code as built for software engineering (writing, debugging and shipping code) and Cowork as built for non-coding knowledge work. -Pick Claude Code when the output is a commit. If you want always on agents from another vendor, our [ChatGPT dots explainer](/blog/what-is-chatgpt-dots/) covers OpenAI's version. +Pick Claude Code when the output is a commit. See [Claude Cowork vs Claude Code](/blog/claude-cowork-vs-claude-code/). For another vendor's always on agents, see our [ChatGPT dots explainer](/blog/what-is-chatgpt-dots/). diff --git a/blog/src/posts/what-is-opencode.md b/blog/src/posts/what-is-opencode.md index 8acf30af2..85a27ef01 100644 --- a/blog/src/posts/what-is-opencode.md +++ b/blog/src/posts/what-is-opencode.md @@ -117,7 +117,7 @@ OpenCode if you want to choose the model, Claude Code if you want Anthropic's ow **Pick Codex CLI if** you pay for ChatGPT and want OpenAI's agent with an open source client. -Our [Codex CLI vs Claude Code](/blog/codex-cli-vs-claude-code/) comparison goes further on those two, and [best AI coding agents](/blog/best-ai-coding-agents/) covers the wider field. +Our [Codex CLI vs Claude Code](/blog/codex-cli-vs-claude-code/) comparison goes further on those two, and [best AI coding agents](/blog/best-ai-coding-agents/) covers the wider field. For the two tool version with prices, see [OpenCode vs Claude Code](/blog/opencode-vs-claude-code/). {% img "note-2" %} diff --git a/docs/blog/agent-tools-today-2026-10-06/index.html b/docs/blog/agent-tools-today-2026-10-06/index.html index 570e89cfa..b35ec1534 100644 --- a/docs/blog/agent-tools-today-2026-10-06/index.html +++ b/docs/blog/agent-tools-today-2026-10-06/index.html @@ -240,7 +240,7 @@

How this list is built