* perf(core): Safari stops re-serializing inline styles on every GSAP write in a playing film
* perf(core): declare the clip-reveal subpath in its descriptor and cut added comment lines
* fix(shader-transitions): shader transitions across a scene boundary keep the scene's contents
Every revealer now goes through one core helper that shows the clip and
lifts the runtime's hidden mark together; the page-side compositor's scene
copies were still carrying the mark. The contents rule keeps its old weight.
* fix(core): films on an older shader-transitions build keep their transition scenes
Published shader builds reveal scenes by inline style without lifting the
hidden mark. When one is on the page, the runtime keeps the inline-style
rules; builds that lift the mark declare it on load and keep the fast rules.
* test(core): pin the hidden-clip contents rule's weight on its selector
* style(core): format the reveal test and move hidden-clip rule notes into names
* test(producer): pin hidden-clip behaviours in real Chrome under the mark-keyed rules
* test(producer): read the forced restyle's result so lint accepts the restyle test
* feat(studio): load GSAP from the CLI's bundled copy by default
Studio preview now loads the GSAP, CustomEase and MotionPathPlugin scripts
it adds from files the CLI ships and serves same-origin at /api/vendor/npm,
instead of jsDelivr. HYPERFRAMES_GSAP_BASE_URL still wins when set, and a
composition on another GSAP version gets its plugin from jsDelivr.
Renders now honor HYPERFRAMES_GSAP_BASE_URL when they replace a missing
local GSAP script, and the setting accepts plain http only for loopback
hosts.
Closes#6205
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* refactor(studio): keep comment share flat for the GSAP local root
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): find the installed gsap with node resolution when run from source
The Windows CI lane installs with the hoisted linker, so gsap is not under
packages/core/node_modules there.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(producer): load a nested composition's missing GSAP from the mirror
A sub-composition's missing local gsap/dist script was hoisted into the
render unchanged, so HYPERFRAMES_GSAP_BASE_URL (and the jsDelivr fallback)
only reached the entry document. Run the same rewrite on every
sub-composition before it is compiled, resolving its path the way the
inliner will hoist it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(core): prepare split-video offsets during preview preload
* chore: prepare conflict resolution against current main
* fix(core): retain scene-local deferral with split-video preparation
* chore: prepare conflict resolution against current main
* fix(core): preserve split preparation with queued media prefetch
* fix(core): resolve a relative data-start inside the clip's own composition
* fix(engine): render-side data-start references resolve in the clip's own composition
The engine's reference resolver still looked an id up document-wide, so
with two different compositions sharing an id the producer placed media
from the first one while the runtime used the clip's own. Scope it the
same way as the runtime's startResolver, with the same document-wide
fallback, and correct the SDK comments that called the runtime lookup global.
* fix(parsers): keep getAttribute-only reference adapters working
The scoped lookup needs parentElement, closest and querySelectorAll, but
RefResolverEl is exported from @hyperframes/parsers and @hyperframes/engine.
Make them optional and fall back to the document-wide lookup when an adapter
lacks them.
* refactor: look up a scoped reference with one id query
* fix(cli): reap only orphaned browsers HyperFrames launched
Orphan cleanup on preview and render killed every PPID-1 process of the
current user whose args named chrome-headless-shell or a Puppeteer temp
profile, which included detached browsers started by other tools.
Every HyperFrames Chrome launch now passes an inert
--hyperframes-owned-browser switch, and the reaper only kills PPID-1
processes that carry it as a whole argument.
Fixes#6203
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(core): serve the browser marker from its own subpath
The GCP image's BeginFrame contract step imports the engine's
browserManager straight from source before core's generated runtime
exists, so pulling the marker from the core root entry broke it.
A dependency-free @hyperframes/core/browser-marker subpath keeps that
import chain as light as it was.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test(cli): expect the browser marker in the capture fallback launches
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* docs(cli): say orphan cleanup runs before preview and render
The cleanup only stops browsers HyperFrames launched, so the opt-out is
no longer needed to protect other tools' browsers.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(core): motion blur no longer blacks out videos inside sub-compositions
* chore(core): drop the explanatory comment from the motion blur isolate move
A playing video is steered back by rate, not seeked, at a flat 3%. A talking-head clip
that waits on data at a cut starts 100-350 ms behind and takes 3-10 s to close, longer
than a short clip plays, so the picture trails the voice for the whole clip.
Measured in Desktop on a Riverside two-camera episode: clips 2-4 started up to 100 ms
behind and closed at 35 ms per second. The steer now steps with the lag (3% up to
100 ms, 10% to 300 ms, 25% beyond), so the rate is still written only when a step
changes.
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
A sub-composition resolves GSAP selectors to a list of in-scope elements.
When that list is empty, GSAP's "target not found" warning stringified it
as an empty name, so check reported `GSAP target not found` with no hint
of which selector missed. Keep the empty list but give it the authored
selector as its text, matching what GSAP prints in a root composition.
Refs #5687
Co-authored-by: serdarucel <196021214+serdarucel@users.noreply.github.com>
* fix(core): color grading builds a new WebGL context when a lost one is never restored
A context the browser evicts and does not restore left every graded clip hidden for good.
After a grace period the runtime now creates a new shared context and moves each clip onto it.
* refactor(core): split the lost-context rebuild into smaller steps and test its retry
* fix(core): color grading rebuilds the graded clips when a lost WebGL context is restored
A clip shown while the context was lost never got an entry.
* fix(core): drop the added comment line from the color grading restore fix
* fix(core): release decoded preview audio once no clip uses its source
The preview transport cached every decoded audio buffer by source until the player was destroyed. Each scheduling pass now drops cached buffers whose source no clip in the document still uses (scene edits, swapped sources), and keeps the rest so playback never re-decodes a clip still in the film.
* fix(core): drop the comment the comment-share ratchet rejects
* fix(core): preserve literal percent in beat source paths
* chore: prepare conflict resolution against current main
* fix(core): retain beat path handling with current main
A `<canvas layoutsubtree class="hf-vfx-src">` nested inside another capture
canvas blocks the renderer main thread in drawElementImage, so `check` and
`snapshot` time out with no output. Refuse either direction of the nest with a
reportVfxError and leave the chain unregistered.
Also scope the two host lookups to immediate children (`:scope >`) so a nested
host's src/out canvas is never bound to the outer host, and read the
`data-vfx-ref-visible` flag in resolveCaptureSource so a canvas reached as both
a host's own source and as a ref gets one consistent `visible`/keepBitmap.
Refs #4405
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(core): preview fetches only the clips due next, and drops passed clips still downloading
A fast-cut film started a fetch per clip inside the look-ahead and let passed clips finish.
* fix(core): a passed clip stays released for the rest of the play
It was refetched each pass inside the look-back window.
The isolation CSS re-showed every descendant of a mover, so a child the
author hid with visibility:hidden was cropped and smeared into the trail
(240 green px in the repro; 0 in the sharp frame). The hide rule now skips
the mover subtree and the mover alone is made visible, so its descendants
inherit and keep their own visibility.
Found by review of #6106.
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* feat(studio): motion blur panel with Off / Subtle / Strong
One three-way control in the right dock. Subtle is the default blur
(240 deg shutter, samples sized to the smear); Strong is 720 deg and 64
samples. Writes the root data-motion-blur attributes through the SDK so
renders match and undo works; reads live attributes from the preview.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(studio): motion blur panel edits the file's own root
The edits were worked out against the preview DOM but written to the file,
so a stale preview (undo inside the hold, a slow reload) could save a level
other than the one picked. They are now computed inside the mutation from
the candidate's root. While locked to a sub-composition no level is shown.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(studio): shorten the motion blur tab so the dock strip fits
Five tabs overflowed the default dock width by 10px and clipped a label
(Studio: load smoke). The tab is now titled Blur.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* feat: check names the filter fallback, the render log names the tier, Studio stops opting out
hyperframes check reports motion_blur_filter_fallback (info) with the reason capture could not
run and the way back to it. The engine's per-session log line gains tier=, and its pixel test
covers the fallback over a WebGL page. Studio no longer sends previewMotionBlur: every Chrome
can preview blur now, through the fallback where it lacks html-in-canvas; the server still
accepts false from an older Studio.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* feat(core): blur picks the cheaper filter tier when every mover only translates
The scan now notes whether any mover turns or scales (the linear part of its transform chain
changes) and whether it already forms a stacking context while it moves. When every mover only
translates, the filter tier draws as well as capture and costs about what no blur costs: on an
8 s bench shot a render took 7.0 s against 9.9 s with capture and 7.3 s with no blur. Anything
that turns or scales, or moves without a transform, stays on capture.
data-motion-blur-tier="capture" or "filter" forces a tier. The report says which one drew
and why (translation-only, tier-attribute); hyperframes check stays quiet for those two, which
are choices, not fallbacks. The pixel tests run the shared assertions through both tiers.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* docs(motion-blur): the two tiers, the tier attribute, and what changed in limits
The guide described blur turning off for WebGL, a root transform and similar. It now says the
filter tier draws instead, how the tier is chosen and forced, what each does with a turn or a
zoom, the measured cost of each, and which findings check emits. The skill note drops the
off-for-WebGL advice and the 1.7x figure.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* feat(core): blur defaults match a 240 degree shutter and at least 32 samples
The shutter opens 240 degrees by default (was 180) and each mover gets at least 32 taps (was
sized to the smear alone, down to 2), growing with the smear as before. These are the settings
the shutter blur Jake built uses; on the card whip the default render is now 51 dB from his
(34.6 dB at the old defaults). The guide's attribute table says so.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* chore(core): trim tier.ts comments to the ratchet
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* chore: allow deleting the Studio previewMotionBlur render test
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(engine): a render no longer decodes the videos it replaces with extracted frames
* fix(engine): shorten the render-frame video hold comment
* fix(engine): keep the render-frame video hold comment under the line limit
* fix(engine): hold only the videos whose render id is replaced by extracted frames
Two scenes can share a video id. The render id tells them apart, so a live video is no longer held.
* feat(core): capture blur falls back to per-element filters where capture cannot run
html-in-canvas cannot capture a WebGL canvas, an iframe or a transform on the root, and some
browsers lack it. Blur used to turn off for the whole composition. Now the runtime hands the
composition to a filter tier: an SVG filter on each mover, 32 offset copies averaged along its
sampled path, softened along the motion so long smears do not step. It filters the live DOM,
so it works over a WebGL canvas, in any Chrome, and leaves glows and shadows on the element.
Turn and scale only soften; capture draws those truthfully, so it stays the first choice and
a scene re-init tries it again.
Capture turning itself off for a defect or three failed captures hands over; the renderer's
explicit off (HDR, distributed, engine blur) stays off. The report names the tier. Core's
unpacked size budget goes to 3,965,000 for the tier.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* test(engine): the backface pages now take the filter tier
The two backface tests still expected blur off; with the filter tier a
backface page reports tier filter. Fails on CI without this.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(cli): check copies hostile videos at the composition size, not the source size
* fix(cli): the check server serves the copy the page names with hf-proxy-box
* feat(cli): check --prepare-media makes the video copies alone, and a stopped copy resumes
A long source is copied in one-minute pieces that stay in the cache, so a copy
that is killed or runs out of time continues from its finished pieces.
check --prepare-media runs only that step: no browser, no report, a per-file
result as JSON, and exit 1 when a copy failed.
* fix(studio-server): keep finished copy pieces while their copy runs
The short limit for dead partial copies would delete the first pieces of a
long copy that is still encoding. Finished pieces now follow their own
one-hour limit and are never swept while their copy is in flight.
* refactor(studio-server): shorten two comments
The comment gate limits a comment line to 120 characters and a run to four lines.
* refactor(studio-server): drop comments the names already carry
The comment ratchet lets a file's comment share only go down.
* fix(cli): the check server pre-warms the copy the page asks for, not the source-size one
The server started a source-size copy of every hostile video next to Chrome on
each check, because its pre-warm passed no box while the page and the up-front
copy use the composition's box. The pre-warm now takes the same box, found by
the one helper the up-front copy uses.
* fix(cli): end a check on no progress, not on a 120 s wall clock
The check ended 120 s after Chrome was ready no matter what it was doing, so a
long project whose sampling and overlap pass each kept working still failed
(the overlap pass was one page call that reported nothing for its whole run).
The deadline is now an idle clock: every phase entered, every sample finished,
every overlap batch of eight times and every operation that settles restarts it,
and the check ends only after 120 s with none. The overlap grid is asked for in
batches so it can report between them.
* refactor(cli): shorten the idle deadline comment
The comment gate allows 120 characters on a comment line.
* test(cli): check's overlap grid runs in page tasks of at most 8 samples
* fix(cli): check test types for the cli lib, studio-server size budget for resumable copies
A script with a top-level const console = ... hides the global from every runtime call to console.info/warn. The runtime build now maps console to globalThis.console.
* fix(core): capture blur sizes its taps to the smear and weights them exactly
A fast move strobed at the default 16 taps (8 px steps on a 1560 px whip). With no
data-motion-blur-samples set, taps are now about one per 2 px of travel (2 to 128); an
explicit samples count still wins.
Canvas quantizes image globalAlpha to 1/255, so 1/n weights left coverage short (n = 104
summed to 0.81, a lighter card body). Each tap now carries a whole number of 1/255 units
that sum to exactly 255.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* feat(core): capture blur draws the movers from their own transparent layer
The crop came from a capture of the whole scene, so the backdrop under a see-through mover
smeared with it (a lighter box with the dotted background erased) and a glow or shadow
outside the mover's box was cut off, leaving a hard-edged box.
Each frame now captures the movers alone, everything else visibility:hidden, so a mover's
pixels carry their true alpha and no backdrop; the background capture is unchanged. The crop
grows by the mover's ink reach (box-shadow, text-shadow, filter, outline, measured during the
scan), so glows travel with it. Two paints per frame, as before.
The output is cleared on wrap instead of showing the previous wrap's last frame, since the
first paint is no longer a drawable sharp frame. Core's unpacked size budget goes to 3,950,000
for the new module.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(core): capture blur finds callback-driven movers when events are on
The mover scan seeked with callbacks suppressed even where the root allows them, so a
composition whose motion lives in a timeline callback (a render(t) driven from onUpdate)
reported 0 movers and rendered sharp. With data-motion-blur-events="on" the scan now seeks
the way the frame reads do. Without it nothing changes: callbacks stay suppressed.
The no-movers finding in hyperframes check now names the attribute.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(core): a scene paints no filter output from an HTML layer before its start or after its end
* fix(core): a scene paints no filter output from its pseudo-element layers outside its window
* fix(cli): preserve video frames when scenes reuse element IDs
* chore(core): keep media identity comments within the ratchet
* test(core): update page-build goldens for distinct media ids across repeated scenes
* fix: data-volume multiplies the volume lane in preview and render
* fix: a clip's data-volume plays ahead of its audio effects, in preview and render
* feat(studio): the media panel shows a clip's Gain beside its Volume level
* fix(audio): cap gain times volume lanes through each ramp
* chore(core): account for shared volume envelopes in package budget
* fix(core): restore static gain when a playing lane is removed
* fix(core): a clip's volume lane stays smooth in preview while the page is busy
A clip with a volume automation lane had two writers on one gain in
preview: the Web Audio transport scheduled the lane as ramps on the
clip's gain, and the per-tick media sync wrote the same lane's value onto
that gain about 60 times a second. Each write landed whenever the main
thread got to it, so a busy page stepped the gain and the clip sounded
choppy. The lane now has its own gain node, like a group's fader beside
its mute gain, and the per-tick volume of a routed clip no longer folds
the lane in.
* fix(core): name the transport's ownership of a routed clip's lane instead of commenting it
* chore(core): raise the unpacked size budget for the volume lane's own gain
The preview fix gives a clip's volume lane its own gain node and tears it
down with the clip, which takes core to 3935950 bytes unpacked, 283 over
the budget main set at its exact size.
* fix(core): volume lanes follow rate changes and edits mid-play
* fix(core): shorten the clip stages comment
* fix(core): point the volume lane comments at the code they name
Tooling and logs need more than "off". Give the runtime's report a reason
code for every way blur can be off (renderer-off, unsupported-browser, each
html-in-canvas defect, capture-failures), and two calls for tooling:
probe() runs the same scan a render would, without painting, and suspend()
stops all painting for the rest of a session without changing the reported
decision.
The engine prints one line per session from what the page reports:
`[motion-blur] on reasonCode=null movers=N failedFrames=K`. The first-frame
line cannot know about later failures, so the session end prints the same
line with "(session end)" when any frame fell back to plain DOM; a clean
session stays quiet. A render where every capture failed no longer reads as
"on" with nothing else.
Add the first test that reads blurred pixels: real Chrome with
html-in-canvas, the engine's frame order (seek, wait, composite, screenshot).
It checks the trail along the motion line, that what does not move matches
the sharp frame, that a box centered with translate is blurred where it
really is, and that a flipped backface card renders the plain frame. It
skips when Chrome or html-in-canvas is missing.
Folded review fixes: R13 (runtime half), R17 (engine half), T1 (the guards
that need reason codes), T3 (log-line and pixel tests).
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Decide the html-in-canvas defects once for the whole timeline instead of at
the frame being drawn. The mover scan already seeks the timeline, so the
defect scan samples along it and a defect that only appears mid-timeline (a
rotated root, a flipped card) turns blur off from frame 0. Defects now block
on every Chrome version, so a render matches the preview it came from, and
GSAP's identity transform on the root no longer counts as one.
A per-frame check stays as a backstop for what scripts add later, and it
costs only that frame: a defect the scan missed (a short root shake, a
backface flip between samples) renders that frame sharp and logs once, and
blur resumes when the defect is gone. A block raised by the scan is lifted
when its cause is removed or the timeline changes; a capture failure stays
for the life of the instance.
The candidates a scan checks include faces whose backface-visibility: hidden
arrives through an animation. Each rescan adds to them and the per-frame
backstop reads the same set, so an animated-only flip card is caught by both.
Look for WebGL canvases in the whole document: a render's shader-transition
compositor appends its canvas to body, while preview puts it inside the root.
Turn page blur off on routes that cannot host it. The HDR and layered
compositors screenshot each DOM layer after hiding the others, which does
not reach blur's canvases, and a distributed (cloud or Lambda) chunk render
has no Studio preview to match and no guarantee of html-in-canvas in the
worker's Chrome. The producer sets pageMotionBlur to hdr-render,
layered-composite or distributed-render there, the distributed plan's
compile-time hint no longer forces screenshot capture, and the runtime says
which route turned it off.
Find the same movers whatever time a scan starts: the baseline is read at
t=0, so each worker of a parallel render picks the same set. An ancestor's
pan is not counted as its child's move.
Folded review fixes: R5, R10, R15, R19.
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
html-in-canvas captures a few things wrongly, and a wrong blurred frame is
worse than a sharp one. Add a defect list the runtime checks before it wraps
a composition: a WebGL or WebGPU canvas (captured frozen), backface-hidden
faces in a 3D context (drawn mirrored), an iframe (reloaded by the wrap and
not recoverable), a transform or opacity below 1 on the root (ignored by the
capture) and a canvas that is already a capture canvas (nesting hangs the
renderer). Any hit turns blur off for the whole composition and the runtime
says which one.
The backface scan reads rules nested inside a style rule (CSS nesting),
sheets pulled in with @import and adopted style sheets. Shadow DOM stays a
documented limit.
A face whose backface-visibility: hidden arrives through an animation counts
too: the scan also takes the elements of running CSS or WAAPI animations that
key it, and elements naming a @keyframes rule that does, as candidates.
Support Chrome 157's html-in-canvas API alongside the older one: a canvas
marked content="drawable" with a drawable attribute on captured children
replaces layoutsubtree. Blur, the VFX runtime and Studio's browser check
detect both through the new @hyperframes/core/html-in-canvas subpath.
Folded review fixes: R14, R9 (the iframe defect entry).
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
html-in-canvas captures a WebGL or WebGPU canvas frozen or black, so the
runtime turns blur off when the composition draws one. Two cases slipped
past the tracker that spots them: a canvas handed to
transferControlToOffscreen (its context is created out of reach) and a
context made by an author script that runs before the runtime.
- The tracker is a self-contained function that also wraps
transferControlToOffscreen (a transferred canvas counts as accelerated)
and keeps its registry on window so independent installs agree.
- The engine evaluates it on every new document, ahead of any page script.
- insertRuntimeTag (bundler and Studio preview) puts it at the very start of
<head> for pages that opt into blur; the runtime install is idempotent
with it.
- report() carries failedFrames, the cumulative count of frames that
rendered plain DOM because their capture failed or timed out.
failures keeps its streak meaning.
Core's unpacked size budget rises from 3903211 to 3930000 bytes: the runtime
lifecycle, the compositor and the tracker together pass the old ceiling.
Folded review fixes: R6, R17 (the report counter; the engine log line lands
with the runtime's reason codes).
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Carry the runtime's blur through the render pipeline and the Studio render
panel.
- engine: installPageMotionBlur sets the page-side off switch and the render's
device scale before any author script runs; CaptureOptions.pageMotionBlur
lets a caller stand the page blur down.
- producer: a composition that asks for data-motion-blur on the root the
runtime would pick forces screenshot capture, because html-in-canvas needs
a real paint; the probe session carries the request's pageMotionBlur like
every other session.
- studio-server and Studio: the render panel sends previewMotionBlur, the
render route turns "off" into pageMotionBlur once, and the Studio dev server
and the CLI server both receive the final value.
- mover scan: a large element that is see-through while it moves (a sweep, a
light leak) is no mover, including a large image or canvas with alpha; the
scan skips elements moved by a 3D individual transform property.
- cli snapshot injects video frames before the seek settles, matching the
engine's order.
Folded review fixes: R1, R2, R3 (scan half), R7, R16, R18, T3 (render
adapters).
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>