Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,12 @@ <h1 id="journey-title"><span>Your AI.</span> <span>Your machine.</span> <em>Your
<div class="buddy-stage" data-buddy-stage data-state="working">
<button class="buddy-control" type="button" data-buddy-control aria-label="Show Buddy's next reaction" aria-describedby="buddy-status">
<span class="buddy-glow" aria-hidden="true"></span>
<video class="buddy-video" muted playsinline loop preload="none" hidden data-buddy-motion="thinking" data-src="media/landing-story/buddy/thinking.webm?v=5" aria-hidden="true"></video>
<video class="buddy-video" muted playsinline loop preload="none" hidden data-buddy-motion="working" data-src="media/landing-story/buddy/working.webm?v=5" aria-hidden="true"></video>
<video class="buddy-video" muted playsinline loop preload="none" hidden data-buddy-motion="idle" data-src="media/landing-story/buddy/idle.webm?v=6" aria-hidden="true"></video>
<video class="buddy-video" muted playsinline loop preload="none" hidden data-buddy-motion="approval" data-src="media/landing-story/buddy/approval.webm?v=5" aria-hidden="true"></video>
<video class="buddy-video" muted playsinline preload="none" hidden data-buddy-motion="success" data-src="media/landing-story/buddy/success.webm?v=5" aria-hidden="true"></video>
<video class="buddy-video" muted playsinline preload="none" hidden data-buddy-motion="error" data-src="media/landing-story/buddy/error.webm?v=5" aria-hidden="true"></video>
<video class="buddy-video" muted playsinline loop preload="none" hidden data-buddy-motion="thinking" data-src="media/landing-story/buddy/thinking.webm?v=5" data-src-hevc="media/landing-story/buddy/thinking.mov?v=1" aria-hidden="true"></video>
<video class="buddy-video" muted playsinline loop preload="none" hidden data-buddy-motion="working" data-src="media/landing-story/buddy/working.webm?v=5" data-src-hevc="media/landing-story/buddy/working.mov?v=1" aria-hidden="true"></video>
<video class="buddy-video" muted playsinline loop preload="none" hidden data-buddy-motion="idle" data-src="media/landing-story/buddy/idle.webm?v=6" data-src-hevc="media/landing-story/buddy/idle.mov?v=1" aria-hidden="true"></video>
<video class="buddy-video" muted playsinline loop preload="none" hidden data-buddy-motion="approval" data-src="media/landing-story/buddy/approval.webm?v=5" data-src-hevc="media/landing-story/buddy/approval.mov?v=1" aria-hidden="true"></video>
<video class="buddy-video" muted playsinline preload="none" hidden data-buddy-motion="success" data-src="media/landing-story/buddy/success.webm?v=5" data-src-hevc="media/landing-story/buddy/success.mov?v=1" aria-hidden="true"></video>
<video class="buddy-video" muted playsinline preload="none" hidden data-buddy-motion="error" data-src="media/landing-story/buddy/error.webm?v=5" data-src-hevc="media/landing-story/buddy/error.mov?v=1" aria-hidden="true"></video>
<img class="buddy-image is-active" data-buddy-image src="media/landing-story/buddy/working.webp" width="885" height="920" alt="" decoding="async" fetchpriority="high" aria-hidden="true">
<noscript><img src="media/landing-story/buddy/working.webp" width="885" height="920" alt="Buddy, Row-Bot's robot companion"></noscript>
</button>
Expand Down Expand Up @@ -151,7 +151,7 @@ <h1 id="journey-title"><span>Your AI.</span> <span>Your machine.</span> <em>Your
<img class="sovereignty-hero__base" src="row_bot_hero.webp" width="1536" height="1024" alt="" loading="lazy" decoding="async" fetchpriority="low">
</div>
<div class="sovereignty-buddy" aria-hidden="true" data-sovereignty-buddy>
<video muted playsinline preload="none" data-poster="media/landing-story/buddy/success.webp" data-sovereignty-buddy-video hidden><source data-src="media/landing-story/buddy/success.webm?v=3" type="video/webm"></video>
<video muted playsinline preload="none" data-poster="media/landing-story/buddy/success.webp" data-sovereignty-buddy-video hidden><source data-src="media/landing-story/buddy/success.webm?v=3" data-src-hevc="media/landing-story/buddy/success.mov?v=1" type="video/webm"></video>
<img class="sovereignty-buddy__fallback" src="media/landing-story/buddy/success.webp" width="885" height="920" alt="" loading="lazy" decoding="async" fetchpriority="low">
</div>
<div class="shell sovereignty-content" id="privacy">
Expand Down Expand Up @@ -202,7 +202,7 @@ <h2>Reason.<br>Orchestrate.<br>Work.<br><em>Keep it yours.</em></h2>

<footer class="site-footer"><div class="shell footer-grid"><span>Row-Bot &middot; v5.0.0 &middot; Apache 2.0</span><div class="footer-links"><a href="index.html" aria-current="page">Home</a><a href="features.html">Features</a><a href="architecture.html">Architecture</a><a href="docs/">Docs</a><a href="index.html#privacy">Privacy</a><a href="index.html#comparison">Compare</a><a href="contact.html">Contact</a><a href="https://sydsachar.com/category/row-bot/" target="_blank" rel="noopener noreferrer">Blog</a><a href="https://github.com/siddsachar/row-bot" target="_blank" rel="noopener noreferrer">GitHub</a></div></div></footer>
<script src="site.js?v=5.0.0" defer></script>
<script src="landing-story.js?v=36" defer></script>
<script src="landing-story.js?v=37" defer></script>
<script src="knowledge-field.js?v=4" defer></script>
</body>
</html>
20 changes: 18 additions & 2 deletions docs/landing-story.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,18 @@
const SOURCES = Object.fromEntries(STATES.map(state => [state, `media/landing-story/buddy/${state}.webp`]));
const reduceMotionQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
const saveData = Boolean(navigator.connection?.saveData);
// Safari and every iOS browser (Apple WebKit) play WebM but drop its alpha
// channel, so Buddy would sit in a dark box; they get HEVC with alpha.
const appleWebKit = /AppleWebKit/.test(navigator.userAgent || '')
&& !/(Chrome|Chromium|Edg|OPR)\//.test(navigator.userAgent || '');

const HEVC_ALPHA_TYPE = 'video/mp4; codecs="hvc1"';

function alphaSource(media, video = media) {
const hevc = media.dataset.srcHevc;
if (hevc && appleWebKit && video.canPlayType?.(HEVC_ALPHA_TYPE)) return hevc;
return media.dataset.src;
}
const controller = document.querySelector('[data-story-controller]');
const stage = document.querySelector('[data-buddy-stage]');
const control = document.querySelector('[data-buddy-control]');
Expand Down Expand Up @@ -116,7 +128,11 @@
if (!video.getAttribute('poster') && video.dataset.poster) video.poster = video.dataset.poster;
let hasSource = Boolean(video.getAttribute('src'));
video.querySelectorAll('source').forEach(source => {
if (!source.getAttribute('src') && source.dataset.src) source.src = source.dataset.src;
if (!source.getAttribute('src') && source.dataset.src) {
const src = alphaSource(source, video);
if (src === source.dataset.srcHevc) source.type = HEVC_ALPHA_TYPE;
source.src = src;
}
if (source.getAttribute('src')) hasSource = true;
});
if (!hasSource) return false;
Expand Down Expand Up @@ -190,7 +206,7 @@
if (!target || !buddyMotionAllowed()) return;
if (!restart && ((target.classList.contains('is-active') && !target.paused) || buddyPlayPending === state)) return;
if (!target.getAttribute('src') && target.dataset.src) {
target.src = target.dataset.src;
target.src = alphaSource(target);
target.load();
}
pauseBuddyVideos();
Expand Down
Binary file added docs/media/landing-story/buddy/approval.mov
Binary file not shown.
Binary file added docs/media/landing-story/buddy/error.mov
Binary file not shown.
Binary file added docs/media/landing-story/buddy/idle.mov
Binary file not shown.
Binary file added docs/media/landing-story/buddy/success.mov
Binary file not shown.
Binary file added docs/media/landing-story/buddy/thinking.mov
Binary file not shown.
Binary file added docs/media/landing-story/buddy/working.mov
Binary file not shown.
14 changes: 14 additions & 0 deletions docs/media/landing-story/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,20 @@
"result_sha256": "00344446d1e9bb4624e7ed4cbbfadfc0972d905372d3fb4fa3d7f2e2a1003369",
"result_duration_seconds": 4.583
}
},
"hevc_alpha": {
"format": "mov",
"codec": "HEVC with alpha (hvc1)",
"used_by": "Apple WebKit (Safari, and every browser on iOS and iPadOS), which plays WebM but drops its alpha",
"source": "Decoded losslessly from the reviewed WebM states (libvpx-vp9, alpha kept) to ProRes 4444, then encoded on macOS 26 with AVFoundation's HEVC-with-alpha encoder at 900 kbit/s and alpha quality 0.75",
"states": {
"idle": "70f1d8af2409024e19faea645a0a111535b5b1ac9d530ac906a40577eea6ca01",
"thinking": "4d569fd3147420475b01d686012e37c901bdf7ba18536cff80b3851f493afe65",
"working": "d4ab8f1c385d7314a72f1bd00f4b46c2fd4ea3b6a8a16994f95047e08771e126",
"approval": "aacd5fc2f59fd11bf11fd9eb7069e85407300655d6641bb2aec3ff9b696e3349",
"success": "dc2e749d51e7c1c2656eb8ea972c9892a10b1b23d4c530f83e608c89603e0feb",
"error": "abce1770ce873306d0d725007fc23871600993a83632f06e9dfec83d58093d03"
}
}
}
},
Expand Down
78 changes: 78 additions & 0 deletions scripts/docs/buddy_hevc_alpha.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// HEVC-with-alpha cuts of the landing page's Buddy clips, for Safari and iOS,
// which drop WebM alpha. macOS only (AVFoundation). Make the ProRes 4444
// master from a reviewed WebM state first, keeping its alpha:
// ffmpeg -c:v libvpx-vp9 -i buddy/idle.webm -c:v prores_ks -profile:v 4444 -pix_fmt yuva444p10le -alpha_bits 16 -vendor apl0 -an idle-master.mov
// then: swiftc -swift-version 5 -O buddy_hevc_alpha.swift -o buddy_hevc_alpha
// buddy_hevc_alpha idle-master.mov buddy/idle.mov 900000
// buddy_hevc_alpha --frame buddy/idle.mov 1.0 idle.png (check one frame's alpha)
import AVFoundation
import CoreGraphics
import ImageIO
import UniformTypeIdentifiers
import VideoToolbox

let args = CommandLine.arguments

if args.count == 5 && args[1] == "--frame" {
let asset = AVURLAsset(url: URL(fileURLWithPath: args[2]))
let generator = AVAssetImageGenerator(asset: asset)
generator.requestedTimeToleranceBefore = .zero
generator.requestedTimeToleranceAfter = .zero
let image = try! generator.copyCGImage(at: CMTime(seconds: Double(args[3])!, preferredTimescale: 600), actualTime: nil)
let destination = CGImageDestinationCreateWithURL(URL(fileURLWithPath: args[4]) as CFURL, UTType.png.identifier as CFString, 1, nil)!
CGImageDestinationAddImage(destination, image, nil)
CGImageDestinationFinalize(destination)
print("frame \(image.width)x\(image.height) alphaInfo=\(image.alphaInfo.rawValue)")
exit(0)
}

let source = URL(fileURLWithPath: args[1])
let target = URL(fileURLWithPath: args[2])
let bitrate = Int(args[3])!
try? FileManager.default.removeItem(at: target)

let asset = AVURLAsset(url: source)
let track = asset.tracks(withMediaType: .video)[0]
let reader = try! AVAssetReader(asset: asset)
let output = AVAssetReaderTrackOutput(track: track, outputSettings: [
kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA,
])
reader.add(output)

let writer = try! AVAssetWriter(outputURL: target, fileType: .mov)
let size = track.naturalSize
let input = AVAssetWriterInput(mediaType: .video, outputSettings: [
AVVideoCodecKey: AVVideoCodecType.hevcWithAlpha,
AVVideoWidthKey: Int(size.width),
AVVideoHeightKey: Int(size.height),
AVVideoCompressionPropertiesKey: [
AVVideoAverageBitRateKey: bitrate,
AVVideoExpectedSourceFrameRateKey: 24,
kVTCompressionPropertyKey_TargetQualityForAlpha as String: 0.75,
],
])
input.expectsMediaDataInRealTime = false
writer.add(input)

reader.startReading()
writer.startWriting()
writer.startSession(atSourceTime: .zero)
let done = DispatchSemaphore(value: 0)
input.requestMediaDataWhenReady(on: DispatchQueue(label: "encode")) {
while input.isReadyForMoreMediaData {
if let sample = output.copyNextSampleBuffer() {
input.append(sample)
} else {
input.markAsFinished()
writer.finishWriting { done.signal() }
return
}
}
}
done.wait()
if writer.status == .completed {
print("ok \(target.lastPathComponent)")
} else {
print("failed \(String(describing: writer.error))")
exit(1)
}
23 changes: 20 additions & 3 deletions tests/docs/landing_story_runtime_test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function element(dataset = {}) {
};
}

function makeRuntime({reducedMotion = false, saveData = false, search = '', intro = false} = {}) {
function makeRuntime({reducedMotion = false, saveData = false, search = '', intro = false, userAgent = '', hevcAlpha = false} = {}) {
const controller = element();
if (intro) controller.classList.add('is-intro');
controller.offsetHeight = 4400;
Expand Down Expand Up @@ -90,7 +90,8 @@ function makeRuntime({reducedMotion = false, saveData = false, search = '', intr
return video;
});
const buddyVideos = ['idle', 'thinking', 'working', 'approval', 'success', 'error'].map(state => {
const video = element({buddyMotion: state});
const video = element({buddyMotion: state, src: `${state}.webm`, srcHevc: `${state}.mov`});
video.canPlayType = type => (hevcAlpha && type === 'video/mp4; codecs="hvc1"' ? 'probably' : '');
video.currentTime = 0;
video.readyState = 1;
video.seeking = false;
Expand Down Expand Up @@ -180,7 +181,7 @@ function makeRuntime({reducedMotion = false, saveData = false, search = '', intr
const context = {
window: runtimeWindow,
document,
navigator: {connection: {saveData}},
navigator: {connection: {saveData}, userAgent},
IntersectionObserver: FakeIntersectionObserver,
URLSearchParams,
console,
Expand Down Expand Up @@ -480,6 +481,22 @@ const flush = () => new Promise(resolve => setImmediate(resolve));
idleFrames.shift()(0, {mediaTime: .18});
idleFrames.shift()(0, {mediaTime: .22});
assert.equal(idleVideo.classList.contains('is-active'), true);

// Safari drops WebM alpha, so Apple WebKit plays the HEVC-with-alpha cut;
// Chromium browsers (which also say AppleWebKit) keep the WebM.
const safari = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Safari/605.1.15';
const chrome = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36';
const motionSource = async options => {
const page = makeRuntime(options);
await flush();
page.api.setScene('automate');
page.runTimers(1000);
await flush();
return page.buddyVideos.find(video => video.dataset.buddyMotion === 'idle').src;
};
assert.equal(await motionSource({userAgent: safari, hevcAlpha: true}), 'idle.mov');
assert.equal(await motionSource({userAgent: chrome, hevcAlpha: true}), 'idle.webm');
assert.equal(await motionSource({userAgent: safari, hevcAlpha: false}), 'idle.webm');
})().catch(error => {
console.error(error);
process.exitCode = 1;
Expand Down
5 changes: 5 additions & 0 deletions tests/docs/test_landing_story.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ def test_reviewed_manifest_hashes_every_public_story_asset() -> None:
for state, expected_hash in manifest["buddy"]["motion"]["states"].items():
video = MEDIA / "buddy" / f"{state}.webm"
assert hashlib.sha256(video.read_bytes()).hexdigest() == expected_hash
hevc_states = manifest["buddy"]["motion"]["hevc_alpha"]["states"]
assert hevc_states.keys() == manifest["buddy"]["motion"]["states"].keys()
for state, expected_hash in hevc_states.items():
video = MEDIA / "buddy" / f"{state}.mov"
assert hashlib.sha256(video.read_bytes()).hexdigest() == expected_hash
idle_edit = manifest["buddy"]["motion"]["edits"]["idle"]
assert idle_edit["source_sha256"] == "f1d4b3ade0d97361163bfd7cb5832b94197671ef922da119b8b0b698187718ca"
assert idle_edit["result_sha256"] == manifest["buddy"]["motion"]["states"]["idle"]
Expand Down
Loading