From 74b86d884b233c6eee2bf2d50dd4cb9eaa82e9df Mon Sep 17 00:00:00 2001 From: ChaiWithCode <142526048+chaiwithcode@users.noreply.github.com> Date: Sat, 24 Oct 2026 20:54:55 +0100 Subject: [PATCH] Refresh Loom 2.0 website and README with current native media --- .github/workflows/pages.yml | 20 +- README.md | 242 +--- docs/DEMO_MEDIA.md | 4 + docs/loom-2.0-captions.vtt | 52 + docs/loom-2.0-notifications.png | Bin 0 -> 67238 bytes pages/index.html | 674 ++---------- pages/site.js | 60 + pages/style.css | 1838 ++++++++++++------------------- scripts/verify-pages-staging.sh | 7 +- scripts/verify-website.py | 54 + 10 files changed, 1029 insertions(+), 1922 deletions(-) create mode 100644 docs/loom-2.0-captions.vtt create mode 100644 docs/loom-2.0-notifications.png create mode 100644 pages/site.js create mode 100644 scripts/verify-website.py diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 9d3f488..0747f9f 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -10,6 +10,7 @@ on: - 'pages/**' - '.github/workflows/pages.yml' - 'docs/**' + - 'scripts/verify-website.py' concurrency: group: pages @@ -39,20 +40,12 @@ jobs: cp -R pages/. site/ fi - if [ -f docs/loom-demo-thumbnail.png ]; then - cp docs/loom-demo-thumbnail.png site/docs/ - fi - - for f in docs/menu-navigation-*.png; do - if [ -f "$f" ]; then - cp "$f" site/docs/ - fi + # Current, audited product captures and accessible trailer captions. + for asset in docs/menu-navigation-*.png docs/widget-showcase.png docs/loom-2.0-poster.png docs/loom-2.0-notifications.png docs/loom-2.0-captions.vtt; do + test -s "$asset" + cp "$asset" site/docs/ done - if [ -f docs/widget-showcase.png ]; then - cp docs/widget-showcase.png site/docs/ - fi - if [ -f Resources/LoomIconMaster.png ]; then cp Resources/LoomIconMaster.png site/Resources/ fi @@ -150,6 +143,9 @@ jobs: echo "Verified asset: ${asset} ($(wc -c < "${asset}" | tr -d ' ') bytes)" done + - name: Verify website references + run: python3 scripts/verify-website.py site + - name: Upload Pages artifact uses: actions/upload-pages-artifact@v3 with: diff --git a/README.md b/README.md index 4fb7a75..0021919 100644 --- a/README.md +++ b/README.md @@ -1,186 +1,97 @@

Three strands woven into one Loom

Loom

-

Three agents. One Loom.
Monitor Codex, Claude Code, and Antigravity from one native macOS menu-bar app.

-Website  ·  Watch Demo  ·  Download

+

Your agents, at a glance.
See what Codex, Claude Code, and Antigravity are doing on your Mac in one native menu bar app.

+Download Loom  ·  Installation  ·  Website  ·  GitHub

- Watch the Loom 2.0 demo trailer -
Loom 2.0 · Demo data is illustrative. Watch the 2.0 trailer. + Watch the Loom 2.0 trailer +
Watch the Loom 2.0 trailer · Session and quota examples are illustrative demo data.

-

- Official Website  •  - Download for macOS (DMG)  •  - Install with Homebrew  •  - GitHub Releases -

+Loom is a local-first macOS app for keeping track of local AI coding agent sessions. Check which sessions are working, waiting, idle, or need attention, and open diagnostics when discovery needs a closer look. Loom observes local processes and provider data; it does not control agents or edit your projects. ---- +**Loom 2.0.0** supports Apple Silicon (`arm64`) and macOS 14 or later. See the [2.0 release notes](docs/RELEASE_NOTES_v2.0.0.md) for details. -## Why Loom? - -When you run multiple local AI coding agents across background terminals, editor extensions, and language servers, answering a simple question becomes surprisingly tedious: *"What are all my local AI coding agents doing on my Mac right now?"* +## Installation -Loom gives you one clean, local-first menu-bar application to see which project each agent is working on and which session needs attention—without switching between terminals, browser dashboards, and apps. For quota-capable providers, Loom shows observed remaining quota and can warn at 15% and 5%; Claude Code quota is unsupported by its upstream local interface, so Loom does not invent a value. +### Homebrew -**Loom 2.0 — Liquid Glass & Smarter Monitoring** brings clearer session states, local quota warnings, and native desktop widgets. See the [2.0 release notes](docs/RELEASE_NOTES_v2.0.0.md); downloads always open the latest published release. +```bash +brew install --cask chaiwithcode/tap/chaiwithcode-loom +``` -The published 2.0.0 app supports Apple Silicon (`arm64`) on macOS 14 or later. +### Download -Built with Swift and SwiftUI for macOS 14+. Featuring a native **Liquid Glass popover redesign**, **actionable desktop notifications**, **session productivity quick actions**, and **instant search & filtering**. Strictly local-first: no Loom account, no hosted backend, no analytics SDKs, and no third-party dependencies except official Sparkle 2 for signed, secure local updates. +Download the latest **Loom** disk image from [GitHub Releases](https://github.com/chaiwithcode/Loom/releases/latest), open it, and drag **Loom.app** into Applications. ---- +Loom includes signed updates through Sparkle. Check for updates from the Loom menu or in **Settings → General**. -## Local Agent Sessions & Productivity +## Sessions -The **Sessions** tab answers *"what is happening right now?"* with attention-first prioritization and rich productivity tools: +The Sessions view brings agent activity into one place. Waiting and failed sessions are surfaced first, followed by working and idle sessions; recent sessions can be collapsed. -

- Loom 2.0 Sessions with Working and Idle states, provider selection, search, and filters -

+

Loom 2.0 Sessions view with session states, provider filters, search, and activity filters

-- **Liquid Glass Redesign**: Native macOS 26+ popover styling adopting Apple's Liquid Glass (`.glassEffect`) with high-craft material fallbacks (`.ultraThinMaterial`) on macOS 14/15. -- **Attention-First Prioritization**: Sessions requiring human input (`waiting`) or encountering errors (`failed`) are surfaced prominently at the top with high-contrast amber/red badges, ensuring pending decisions are never missed. -- **Collapsible Recent Sessions**: Completed tasks tuck cleanly into a collapsible disclosure section, keeping active work front and center. -- **Search & Filter Bar**: Filter sessions instantly using search queries (repository name, Git branch, or host app) or quick category chips (`All`, `Needs Attention`, `Working`, `Recent`). -- **Productivity Quick Actions**: - - **Focus Terminal / IDE**: Brings the active host editor or terminal window (VS Code, Cursor, Terminal, iTerm2) directly to the front. - - **Copy PID**: Copies the root worker process identifier to the pasteboard. - - **Copy Repository Path**: Copies the project path (sanitized when privacy mode is active). - - **Copy Session ID**: Copies the upstream provider session token for correlation with external logs. - - **Reveal in Finder**: Direct disk navigation via macOS Finder. -- **Strict Read-Only Safety**: Loom provides zero destructive controls (`kill`, `SIGTERM`, or prompt injection). +Search by repository, branch, or host app, then filter by attention, working, or recent activity. Available actions include focusing the host app, copying process or session details, and revealing a workspace in Finder. ---- +Loom supports session discovery for **OpenAI Codex**, **Google Antigravity**, and **Anthropic Claude Code**. Discovery and workspace details depend on the evidence each provider exposes locally; Loom reports diagnostics rather than guaranteeing a session for every repository or process. -## Native macOS Notifications +*Loom is an independent open-source project and is not affiliated with or endorsed by OpenAI, Anthropic, or Google.* -Stay informed on background agent execution and quota exhaustion without polling or keeping the popover open: +## Quotas and notifications -- **Quota Threshold Alerts**: Early warnings below **15%** and critical alerts below **5%** for observed Codex and Antigravity quota windows (5-hour and weekly, respectively). Claude Code quota is not exposed by its upstream local interface and is not included. -- **Verified Replenishment Alerts**: Desktop notification when a fresh observation confirms quota has recovered to ~100% in a newly started window (never inferred purely from countdown timers). -- **Session Lifecycle Transitions**: Actionable alerts when an agent transitions into **Waiting for Input**, **Finished**, or **Failed**. -- **Hygiene & Persistent Deduplication**: Cross-restart deduplication keys prevent repeated alert spam across polling cycles. Silently seeds baseline state on launch to prevent notification storms. -- **Privacy & Deep Linking**: File system paths in alerts are automatically masked when **Hide project paths** is active. Clicking a notification routes directly to the relevant provider and session in Loom. +For Codex and Antigravity, Loom can show quota data available through their local interfaces and send threshold or reset notifications. Quota alerts support **15%** and **5%** thresholds. Claude Code does not expose quota through its upstream local interface. Quota readings are observations and may be unavailable when a provider cannot be reached. ---- +

Loom 2.0 Usage view showing observed provider quota and reset information

-## Usage, Quotas & Local Storage +Notifications can cover observed quota thresholds and session state changes. Finished alerts can be set to require a minimum observed working duration; Loom measures that duration while it is monitoring. Notification preferences and macOS permission choices are respected. Test-notification controls are available only in Debug builds. -Keep track of rate limits, pacing velocity, and on-disk footprint in one place: +

Native macOS Codex and Antigravity demo quota alerts
Notification examples use illustrative data.

-

- Loom Usage tab showing provider quota windows and reset times -      - Loom Storage tab showing measured local disk footprint -

+## Local storage -- **Truthful Quotas & Pacing (Usage)**: View remaining quota percentages, consumption velocity, and exact reset dates/times. Missing or unsupported telemetry is never masked as healthy: Claude Code is truthfully marked as *Quota not supported* by upstream local interfaces, while Codex and Antigravity report verified live metrics. -- **Local Disk Footprint (Storage)**: Measure on-disk agent caches, conversation transcripts, language server indices, and log files on demand across all providers with bounded, read-only scans. Displays clean comparative proportions and direct Finder navigation. +Inspect local agent data with on-demand, read-only storage scans and per-folder breakdowns. ---- +

Loom 2.0 Storage view showing local storage usage

-## Native macOS Widgets +## Widgets -Glance at agent sessions and quotas directly on your desktop or Notification Center with three native widgets: +Loom includes Sessions, Quotas, and Overview widgets for the desktop and Notification Center.

- Loom 2.0 native widget previews: Quotas, Overview, and Sessions -
Native widget previews with privacy-safe demo data. + Loom 2.0 Sessions, Quotas, and Overview widget previews +
Widget previews use illustrative, privacy-safe demo data.

-- **Loom Quotas** (Small, Medium, Large): Displays prominent remaining percentages and reset countdowns. Configurable via *Edit Widget* to monitor all enabled providers together or focus on a specific provider. -- **Loom Sessions** (Small, Medium): Real-time view of active sessions, waiting-for-input alerts, and per-provider counters. -- **Loom Usage Overview** (Medium, Large): Multi-column comparison (Medium) or detailed vertical breakdowns with granular model limits (Large). +The session, usage, storage, widget, and notification screenshots on this page show the current 2.0 interface; session, quota, and storage values shown are illustrative demo data. -See [widget setup and verification guide](docs/WIDGETS.md) and [interface and storage details](docs/INTERFACE_AND_STORAGE.md). +See [widget setup and verification](docs/WIDGETS.md) and [interface and storage details](docs/INTERFACE_AND_STORAGE.md). ---- +## Privacy and local operation -## Supported Providers +Loom has no account or hosted backend. It reads local process and provider information to discover sessions and quota data. It is read-only with respect to agent sessions and project files: it does not send prompts, approve commands, or modify projects. -| Provider | Session Discovery | Quota / Usage | Local Storage | Status | -| :--- | :--- | :--- | :--- | :--- | -| **OpenAI Codex** | Running processes, process tree, and Git metadata | Local app-server interface (`stdio`) | Caches, sessions & logs | Production | -| **Google Antigravity** | Local session metadata and language-server processes | Local loopback HTTPS interface with ephemeral token | Store, artifacts & logs | Production | -| **Anthropic Claude Code** | Running processes, process tree, CLI detection, transcripts | Not exposed by upstream CLI | State, config & transcripts | Production | +Enable **Hide project paths** in Settings to mask paths in the interface and notification content. Antigravity quota access uses a local loopback connection and an ephemeral in-memory token. See [security guidance](SECURITY.md) and the [architecture overview](docs/ARCHITECTURE.md). -Normal launch registers **Codex, Claude Code, and Antigravity**. Process trees correlate child workers and helpers into exactly one logical session per repository task. Expandable discovery diagnostics report processes detected, logical sessions, workspace resolution ratio, metadata health, and discovery latency in milliseconds. +## Settings -*Loom is an independent open-source project and is not affiliated with or endorsed by OpenAI, Anthropic, or Google.* - ---- - -## Installation +Open Settings with **⌘,** or the gear button. Configure providers, menu bar status, refresh behavior, notification preferences, session retention, path privacy, and local storage scans. Provider-specific availability and notification behavior are described in the [2.0 release notes](docs/RELEASE_NOTES_v2.0.0.md). -### Homebrew (Recommended) +## Build and development -Install Loom via the official [ChaiWithCode Homebrew Tap](https://github.com/chaiwithcode/homebrew-tap): - -```bash -brew install --cask chaiwithcode/tap/chaiwithcode-loom -``` - -### Direct Download - -1. Download **`Loom-1.0.dmg`** from [GitHub Releases](https://github.com/chaiwithcode/Loom/releases). -2. Open the disk image. -3. Drag **Loom.app** into your `/Applications` folder. -4. Launch **Loom** from Applications or Spotlight. - -### Software Updates - -Loom integrates native software updates powered by **Sparkle 2**. Updates are delivered over HTTPS from the official [GitHub Pages appcast feed](https://chaiwithcode.github.io/Loom/appcast.xml), cryptographically signed with EdDSA (Ed25519), and notarized by Apple. - -You can check for updates anytime: -- Via **Check for Updates…** in the Loom menu or Dock menu -- In **Settings** (`⌘,`) under the **General** tab - ---- - -## Privacy & Local-First - -Loom is built from the ground up for developer privacy and security: - -- **No Loom Account**: There are no accounts, cloud logins, or external servers. -- **Local Process Inspection**: Loom inspects local processes and metadata on your Mac. It is strictly read-only: it does not inject prompts, approve commands, edit projects, or modify agent sessions. -- **Zero Telemetry**: No tracking, analytics, or behavioral telemetry SDKs. -- **Ephemeral Credentials**: Codex usage communicates via local stdio. Antigravity usage communicates with a discovered local loopback endpoint using an ephemeral memory token. Tokens are never persisted to disk. -- **Path Concealment & Quick Action**: Enable the **Hide project paths** privacy toggle in Settings (`⌘,`) or directly via the popover header quick-toggle to mask filesystem paths across the UI, tooltips, and notification banners. - -See [security guidance](SECURITY.md) and [architecture](docs/ARCHITECTURE.md). - ---- - -## Settings and Configuration - -Press `⌘,` or click the header gear icon to open Loom Settings: - -- **General**: Choose your Menu Bar Status Mode (`Icon Only`, `Active Sessions count`, `Lowest Quota percentage`), toggle Dock visibility, and configure background refresh intervals. -- **Providers**: Enable or disable monitoring individually for Codex, Claude Code, and Antigravity. Disabling a provider immediately halts process discovery and polling tasks. -- **Notifications**: Configure system alert triggers (15% and 5% quota warnings, reset alerts, session state transitions), send test notifications, and inspect the bounded local notification audit log. -- **Sessions & Retention**: Filter to active sessions only or retain completed sessions for a configurable period (Never, 1 hour, 24 hours, 7 days). -- **Storage**: Run on-demand storage scans and inspect per-folder breakdowns with direct Finder navigation. -- **Privacy**: Manage filesystem path masking across all views and alerts. - ---- - -## Build and Run - -Requires **macOS 14+** and **Swift 5.9+ / Xcode 15+**. Install the coding tools you want to monitor separately. From the repository root: +Loom requires **macOS 14+** and **Swift 5.9+ / Xcode 15+**. Install the coding tools you want to monitor separately. From the repository root, build and open the local app bundle: ```bash bash scripts/build-app.sh open .build/Loom.app ``` -This builds a Release executable and packages a local development app with the Loom icon. It is an ad-hoc signed build for local development, not a Developer ID-signed or notarized distribution download. See [Release Documentation](docs/RELEASING.md) for public distribution prerequisites, Developer ID signing, and Apple notarization. - -For Xcode, open `Loom.xcodeproj`, select the **Loom** scheme and **My Mac**, then press **⌘R**. Launch the bundled app when checking icon identity and native UI behavior. +This creates an ad-hoc signed development app, not a Developer ID-signed or notarized distribution download. See [release documentation](docs/RELEASING.md) for distribution. In Xcode, open `Loom.xcodeproj`, select the **Loom** scheme and **My Mac**, then press **⌘R**. -### Preview without live agents +
+Preview with opt-in demo data -Build the bundle first using the command above. Demo data is explicitly opt-in and visibly labeled: +After building, launch one demo mode at a time. These options do not run during a normal launch: ```bash open -n .build/Loom.app --args --demo-mock @@ -189,7 +100,7 @@ open -n .build/Loom.app --args --demo-empty open -n .build/Loom.app --args --demo-failure ``` -The mock fixture includes all three provider identities and normalized states for testing and visual previews. To preview notification banners, add one of the six demo scenarios below; the alert appears after three seconds. Demo alerts require the mock fixture and notification permission, and never run during a normal launch: +To preview illustrative quota notification scenarios, use one command at a time; each alert appears after three seconds and requires macOS notification authorization: ```bash open -n .build/Loom.app --args --demo-mock --demo-alert codex-warning @@ -200,63 +111,14 @@ open -n .build/Loom.app --args --demo-mock --demo-alert antigravity-critical open -n .build/Loom.app --args --demo-mock --demo-alert antigravity-reset ``` -See [demo media details and attribution](docs/DEMO_MEDIA.md). - ---- - -## Development and Verification - -```bash -swift build - -# Portable suite: unit, integration, and cancellation regression tests. -swift test - -# Native menu sizing and interaction probe. -bash scripts/verify-menu-sizing.sh -``` - -The macOS [CI workflow](.github/workflows/ci.yml) builds/packages the app and runs the portable suite. The native probe checks content sizing, tab/disclosure behavior and constrained layouts; it does not replace checking the actual menu bar on a Mac. - -`swift test` runs the full test suite across Codex, Claude Code, Antigravity, settings, and UI components. In local verification, the full suite passes, including SIGTERM-resistant cancellation regression checks. CI executes the portable suite while excluding only the live Antigravity discovery smoke test (`testRealAntigravityDiscoverySmokeTest`), which requires a local Antigravity database installation. SQLite WAL lock contention and code 14 (`SQLITE_CANTOPEN`) are handled gracefully via `?immutable=1&mode=ro` and temporary snapshot fallbacks. Some live usage checks additionally require `ENABLE_LIVE_SMOKE_TESTS=1`. - -### Dedicated Regression Tests (`LoomRegressionTests`) - -A dedicated test target, `LoomRegressionTests`, isolates core regression tests across process trees, session disambiguation, lifecycle transitions, widget state consistency, and headless SwiftUI smoke rendering: - -```bash -# Run regression tests via SwiftPM -swift test --filter 'LoomRegressionTests.*' - -# Run regression tests via Xcode CLI -xcodebuild test -project Loom.xcodeproj -scheme LoomRegressionTests -destination 'platform=macOS' -``` - -- **In Xcode**: Select the **LoomRegressionTests** scheme and press **⌘U**, or open the **Test Navigator (⌘6)** and run `LoomRegressionTests` directly. The primary **Loom** scheme's Test action also includes `LoomRegressionTests`. -- **Adding new regression tests**: Place new tests in `Tests/LoomRegressionTests/` (such as in `LoomRegressionTestSuite.swift`), preserving the canonical MIT license header (`AGENTS.md`) and importing `@testable import LoomCore`. - -Xcode builds are also supported: - -```bash -xcodebuild build -project Loom.xcodeproj -scheme Loom -destination 'platform=macOS' -``` - -See [CONTRIBUTING](CONTRIBUTING.md) for fixtures, review expectations and provider work. - -See [widget verification and troubleshooting](docs/WIDGETS.md) for local widget builds, installation, shared storage, and Notification Center checks. - ---- - -## What Comes Next - -Reliable local discovery comes first: improve state accuracy, cover upstream format changes, and expand diagnostics and accessibility. Remote control, cloud sync and orchestration are outside the current scope. +
-The visual identity stays simple: three independent strands woven into one Loom. The full-color app icon and simplified monochrome menu bar glyph share that silhouette. See [icon design notes](docs/ICON_DESIGN.md) and [product direction](docs/PRODUCT.md). +For development checks, run `swift test` for the portable suite, or `swift test --filter 'LoomRegressionTests.*'` for regression tests. See [CONTRIBUTING](CONTRIBUTING.md), [widget verification](docs/WIDGETS.md), and [demo media notes](docs/DEMO_MEDIA.md) for more details. ---- +## Project direction -## License and Author +Loom prioritizes reliable local agent discovery, session correlation, workspace resolution, truthful lifecycle state, diagnostics, and graceful degradation. Remote control, cloud sync, and orchestration are outside the current scope. Read more in [product direction](docs/PRODUCT.md). -Created by **[Deepak Sharma](https://github.com/chaiwithcode)**. +## License and author -Loom's code, documentation and included assets are licensed under the [MIT License](LICENSE). You can use, modify and distribute them under its terms. [Branding guidance](BRANDING.md) explains how to distinguish your fork from the official project without changing that license. +Created by [Deepak Sharma](https://github.com/chaiwithcode). Loom's source code and documentation are licensed under the [MIT License](LICENSE). Promotional media includes third-party material; see [media attribution and licenses](docs/DEMO_MEDIA.md). See [branding guidance](BRANDING.md) for information about naming and presenting forks. diff --git a/docs/DEMO_MEDIA.md b/docs/DEMO_MEDIA.md index 1bfca7a..1e961bc 100644 --- a/docs/DEMO_MEDIA.md +++ b/docs/DEMO_MEDIA.md @@ -30,3 +30,7 @@ Each demo alert is delayed by three seconds and requires macOS notification auth MacBook Pro photo mockup: [Plinth, Front / Silver](https://plinthmockups.com/?scene=macbook-11-silver). Commercial exports are permitted without credit; see [Plinth mockup terms](https://plinthmockups.com/macbook-mockup). The blank scene is not redistributed. The computer frame is for presentation only. Notification banners are genuine user-provided macOS captures from 24 October 2026, cropped without changing their content. The trailer's session and quota data are illustrative demo content. + +## Website and README captures + +The current Sessions, Usage, Storage and widget images are the same audited native 2.0 captures used in the trailer. The [notification examples](loom-2.0-notifications.png) are genuine macOS banners captured from the explicit demo scenarios; their values are illustrative. The website includes an optional [English caption track](loom-2.0-captions.vtt). The film already contains visible subtitles, so this extra track is off by default to avoid duplicate text. diff --git a/docs/loom-2.0-captions.vtt b/docs/loom-2.0-captions.vtt new file mode 100644 index 0000000..aac0ea7 --- /dev/null +++ b/docs/loom-2.0-captions.vtt @@ -0,0 +1,52 @@ +WEBVTT + +00:00:00.000 --> 00:00:01.899 +Codex is running tests. + +00:00:02.199 --> 00:00:03.884 +Claude is writing code. + +00:00:04.184 --> 00:00:07.299 +Antigravity is working on another project. + +00:00:08.099 --> 00:00:11.832 +But one's been waiting for you. And you don't even know which one. + +00:00:12.132 --> 00:00:13.305 +Meet Loom. + +00:00:13.605 --> 00:00:18.384 +One place to see what your AI coding agents are doing on your Mac. + +00:00:18.796 --> 00:00:21.228 +Every project. Every session. + +00:00:21.528 --> 00:00:25.560 +Working. Waiting. Idle. Finished. + +00:00:25.972 --> 00:00:27.444 +See what's happening. + +00:00:27.744 --> 00:00:29.643 +Know what needs your attention. + +00:00:29.943 --> 00:00:32.055 +And get right back to what matters. + +00:00:32.355 --> 00:00:35.150 +Get alerted before your quota runs out. + +00:00:36.407 --> 00:00:38.284 +See where your storage goes. + +00:00:38.584 --> 00:00:41.848 +Keep everything in view with native Mac widgets. + +00:00:42.760 --> 00:00:45.341 +No cloud account. Just your Mac. + +00:00:45.641 --> 00:00:50.441 +Codex. Claude Code. Antigravity. Three agents. One clear view. + +00:00:50.803 --> 00:00:53.000 +Loom. Your agents, at a glance. diff --git a/docs/loom-2.0-notifications.png b/docs/loom-2.0-notifications.png new file mode 100644 index 0000000000000000000000000000000000000000..7b07523b96ab795e603cfb96772b95f80c59553d GIT binary patch literal 67238 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 literal 0 HcmV?d00001 diff --git a/pages/index.html b/pages/index.html index 0bec15b..9e13fc1 100644 --- a/pages/index.html +++ b/pages/index.html @@ -1,598 +1,88 @@ - + - - - Loom — Monitor Local AI Coding Agents from macOS Menu Bar - - - - - - - - - - - - - - - - - - + + +Loom for macOS — Local AI Coding Agent Monitor + + + + + + + + + + + + + + + + - - - - - -
- -
- -
-
- Three strands woven into one Loom icon -
- -

Loom

-

Three agents. One Loom.

-

Monitor your local AI coding agents from one native macOS menu-bar app.

- - - -
- - macOS Sonoma 14+ • Apple Silicon & Intel • Notarized by Apple -
-
-
- - -
-
-
- -

Watch Loom in Action

-

See Loom 2.0 session discovery, observed quota alerts, and desktop widgets in this 55-second trailer.

-
- -
-
-
- -
- - Loom Demo — Native macOS Experience -
-
- -
- -
- -
-
- 0:55 - Illustrative sessions and quota examples; see media notes for details. -
- - Vertical mobile cut - - -
-

Music: “Cipher” by Kevin MacLeod, licensed under CC BY 3.0. Demo-media notes and attributions.

-
-
-
-
- - -
-
-
- -

What are all my local AI coding agents doing on my Mac right now?

-

When you juggle multiple autonomous agents across terminals, editor extensions, and language servers, visibility evaporates.

-
- -
-
- -

Terminal & Process Sprawl

-

Codex in VS Code, Claude Code in Terminal tabs, and Antigravity running background helpers. Loom discovers all active agent processes and groups them into clean, logical project sessions.

-
- -
- -

Invisible Rolling Quotas

-

Quota exhaustion can interrupt a refactor. Loom surfaces observed quota percentages and reset windows for Codex and Antigravity, with warnings below 15%, critical alerts below 5%, and notifications when fresh telemetry confirms replenishment.

-
- -
- -

100% Local & Ephemeral

-

No Loom account, no cloud relay, and zero telemetry SDKs. Loom is strictly read-only, communicates over local stdio and loopback HTTPS, and never persists sensitive provider tokens.

-
-
-
-
- - -
-
- -
-
- Live Monitoring -

Local Agent Sessions

-

Know immediately which repository each agent is editing, what phase it is executing, and when it needs your input.

- -
    -
  • - -
    - Glanceable Provider Selector - Switch between OpenAI Codex, Anthropic Claude Code, and Google Antigravity with live session counters. -
    -
  • - -
  • - -
    - Normalized Lifecycle States - Truthful status indicators for working, idle, waiting for input, starting, finished, and failed. -
    -
  • - -
  • - -
    - Process Tree & Directory Inspector - Expand any session to inspect helper PIDs, host apps, Git branches, working paths, and a one-click Reveal in Finder action. -
    -
  • -
-
- -
-
- Loom menu bar popover showing active Sessions tab with process hierarchy and Git metadata -
-
-
- - -
-
- Telemetry & Diagnostics -

Usage Quotas & Local Storage

-

Track rate limits and inspect agent disk consumption without touching terminal commands or third-party web portals.

- -
    -
  • - -
    - Truthful Live Quotas - View observed remaining percentages and reset windows for Codex and Antigravity. Loom can warn below 15%, send critical alerts below 5%, and notify when fresh telemetry confirms replenishment. Claude Code quota is not exposed by its upstream local interface, so Loom labels it unsupported instead of showing a made-up value. -
    -
  • - -
  • - -
    - On-Demand Local Storage Audit - Measure allocated disk space across agent stores, transcript logs, and cache folders. Scans run cooperatively in the background without reading file contents or traversing symlinks. -
    -
  • - -
  • - -
    - Path Concealment Mode - Mask repo directories and sensitive disk paths with one toggle in Settings for secure screen sharing and live streaming. -
    -
  • -
-
- -
-
-
- Loom Usage tab showing real-time quota progress and model limits -
Usage Quotas & Reset Timers
-
-
- Loom Storage tab showing measured local disk footprint per provider -
Local Disk Storage Footprint
-
-
-
-
- - -
-
- Desktop Integration -

Native macOS Widgets

-

Keep agent activity and remaining quotas in your peripheral vision across desktop spaces and Notification Center.

- -
    -
  • - -
    - Three Dedicated Widget Families - Choose from Loom Sessions, Loom Quotas, and Loom Usage Overview across Small, Medium, and Large families. -
    -
  • - -
  • - -
    - Configurable Provider Targeting - Monitor all enabled providers simultaneously or configure a widget to follow a specific coding agent via native Edit Widget controls. -
    -
  • - -
  • - -
    - Native WidgetKit Fidelity - Built with pure SwiftUI for macOS Sonoma. Adapts automatically to Light, Dark, and desktop tinting modes with zero CPU overhead. -
    -
  • -
-
- -
-
- Loom macOS desktop widget showcase featuring Small, Medium, and Large widgets -
-
-
-
-
- - -
-
-
- -

Supported Providers

-

Loom connects directly to local runtimes using verified OS evidence and official local IPC.

-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ProviderSession DiscoveryQuota / Usage InterfaceStorage AccountingStatus
-
- - OpenAI Codex -
-
Running processes, child process tree & Git repository metadataLocal app-server interface over stdioCaches, session data, and execution logsImplemented
-
- - Anthropic Claude Code -
-
Running CLI processes, process hierarchy, active workspaces & transcriptsNot exposed by upstream CLIConfiguration roots, session state & transcriptsImplemented
-
- - Google Antigravity -
-
Local session metadata & language-server processesLocal language-server interface over loopback HTTPS with ephemeral tokenStore database, session artifacts & diagnostic logsImplemented
-
- -

Loom is an independent open-source project and is not affiliated with or endorsed by OpenAI, Anthropic, or Google.

-
-
- - -
-
-
- -

Install Loom on your Mac

-

Choose between the official Homebrew tap or a signed, notarized disk image.

-
- -
- -
- Recommended -

Homebrew Cask

-

Install and keep Loom updated using Homebrew through the official tap:

- -
- brew install --cask chaiwithcode/tap/chaiwithcode-loom - -
- -
    -
  • ✓ Managed via Homebrew updates
  • -
  • ✓ Direct binary verification
  • -
-
- - -
-

Direct Download

-

Download the standalone disk image with Apple Hardened Runtime:

- - - -
    -
  • ✓ Apple Developer ID signed
  • -
  • ✓ Apple Notarization ticket stapled
  • -
-
- - -
-

Automatic Updates

-

Built-in silent or interactive updates powered by Sparkle 2:

- - - -
    -
  • ✓ Ed25519 cryptographic signatures
  • -
  • ✓ Check anytime in Settings (⌘,)
  • -
-
-
-
-
- - -
-
-
- -

Built for Developer Privacy

-

Loom is crafted with a strict local-first architecture to protect your code and credentials.

-
- -
-
- -
-

No Accounts, Zero Telemetry

-

There is no sign-in, no Loom user database, and no cloud server. Loom embeds zero tracking, analytics, or behavioral telemetry frameworks.

-
-
- -
- -
-

Strictly Read-Only Inspection

-

Loom never executes prompts, writes code, modifies agent sessions, or approves dangerous CLI operations. It inspects local runtime state purely for monitoring.

-
-
- -
- -
-

Ephemeral Memory Tokens

-

Loopback authentication tokens for Google Antigravity language-server endpoints are retained strictly in memory and are never written to disk or logs.

-
-
- -
- -
-

Path Concealment Mode

-

Mask absolute filesystem paths and project folder evidence across all menu views with a single toggle in Settings (⌘,) during presentations and recordings.

-
-
-
- -
-
-

100% Free and Open Source

-

Loom is developed in public under the permissive MIT License by Deepak Sharma (ChaiWithCode). Inspect every line of code, verify the build pipeline, or submit contributions on GitHub.

-
- -
-
-
-
- - - - - + + +
+
+
+

Introducing Loom 2.0

+

Your agents,at a glance.

+

See what Codex, Claude Code, and Antigravity are doing on your Mac. One native menu bar app. A little less checking in.

+Download for Mac +

Free & open source · macOS 14+ · Apple Silicon

+
brew install --cask chaiwithcode/tap/chaiwithcode-loom
+

+
+
+Loom 2.0 Sessions view with provider filters, search, working and idle sessions, and discovery diagnostics. +
Native macOS. Now with Liquid Glass.Click to explore
+
+

Genuine app captures. Illustrative, privacy-safe session, quota, and storage data throughout.

+
+
+

A closer look / 55 seconds

Less switching.
More knowing.

From your first session to the next quota reset.
See Loom 2.0 in motion.

+
+ +
Sound on for the full film. Captions are included in the picture.Vertical edition ↗
+
+
+
+

Made for the way you work

A clear view.
A quieter workflow.

+
+

01 / Sessions

Know what needs you.

Working, waiting, idle, or needing attention. Bring your local coding sessions together, with the ones that need you first.

Search by repository, branch, or host app. Focus the app, reveal a workspace in Finder, or inspect discovery diagnostics when the evidence needs a closer look.

What's new in 2.0
+
Sessions organized by provider and activity, with search and expandable discovery diagnostics.
Real app interface · illustrative sessions
+
+
+
Loom Usage view showing observed remaining quota, reset windows, and provider availability.
Observed quotas · illustrative values
+

02 / Quotas & notifications

A heads-up.
Before the limit.

See observed quota and reset windows for Codex and Antigravity. Get a warning below 15%, a critical alert below 5%, and a reset notification when fresh observations confirm replenishment.

Session alerts can tell you when attention is needed or work finishes. Finished alerts default to at least five minutes of working time observed by Loom.

Native macOS Codex and Antigravity demo quota warnings at 12% and 8% remaining.
Genuine macOS notifications · demo scenarios

Availability depends on each provider's local interface. Claude Code does not expose quota locally.

+
+
+

03 / Local storage

See what adds up.

Get a clear breakdown of local agent storage, without digging through hidden folders. Configure local scans in Settings and see the details in Loom's Storage view.

Interface & storage guide
+
Loom Storage view breaking down local agent data by provider.
Local storage overview · illustrative data
+
+
+
+

04 / Desktop widgets

Keep it in
the periphery.

Sessions, Quotas, and Overview widgets bring a little context to your desktop and Notification Center. Native to your Mac, ready when you glance over.
Set up your widgets ↗

+
Loom 2.0 native desktop widgets in Sessions, Quotas, and Overview configurations.
Native widget previews · illustrative, privacy-safe demo data
+
+
+

Three providers. One place.

An honest
picture.

Loom uses the evidence available locally. Missing data stays missing, with diagnostics to help explain why.

+
Local provider capabilities
ProviderSessionsObserved quota
OpenAI CodexSupportedSupported
Claude CodeSupportedNot exposed upstream
AntigravitySupportedSupported

Discovery, workspace details, and quota freshness depend on provider availability and local evidence. Loom does not guarantee a session for every process or repository.

+
+

Local by design

Your work.
Your Mac.

No Loom account. No hosted backend. Your agents stay in your hands.

Loom observes local processes and provider information. It does not send prompts, approve commands, or modify your projects. Hide project paths in Settings to mask them in the interface and notifications.

Antigravity quota access uses a local loopback connection and an ephemeral in-memory token.

Read the security guidance ↗
+

Make room for clarity

Meet your
menu bar's
new thread.

Loom app icon

Download Loom 2.0 for Apple Silicon.
Requires macOS 14 or later.

Download Loom

Open the disk image and drag Loom into Applications. Signed updates are built in.

Liquid Glass on macOS 26. Native system-material fallbacks on earlier supported versions.

+
+ +

Open full image ↗
diff --git a/pages/site.js b/pages/site.js new file mode 100644 index 0000000..11a373d --- /dev/null +++ b/pages/site.js @@ -0,0 +1,60 @@ +'use strict'; +const copyButton = document.querySelector('#copy-brew'); +const command = document.querySelector('#brew-command'); +const copyStatus = document.querySelector('#copy-status'); +copyButton.addEventListener('click', async () => { + const text = command.textContent; + // Copy within the user gesture first. Some embedded browsers leave the + // asynchronous Clipboard API permission request unresolved indefinitely. + const field = document.createElement('textarea'); + field.value = text; + field.setAttribute('readonly', ''); + field.style.position = 'fixed'; + field.style.top = '-9999px'; + document.body.append(field); + field.select(); + let copied = false; + try { copied = document.execCommand('copy'); } catch (_) { copied = false; } + field.remove(); + copyButton.focus(); + if (!copied && navigator.clipboard && window.isSecureContext) { + try { + copied = await Promise.race([ + navigator.clipboard.writeText(text).then(() => true), + new Promise(resolve => setTimeout(() => resolve(false), 1200)) + ]); + } catch (_) { copied = false; } + } + copyStatus.textContent = copied ? 'Homebrew command copied.' : 'Select the command above and copy it manually.'; + if (!copied) { + const selection = window.getSelection(); + const range = document.createRange(); + range.selectNodeContents(command); + selection.removeAllRanges(); + selection.addRange(range); + command.focus(); + } +}); +const dialog = document.querySelector('#image-dialog'); +let imageTrigger; +if (typeof dialog.showModal === 'function') { + document.querySelectorAll('[data-lightbox]').forEach(link => { + link.addEventListener('click', event => { + if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return; + event.preventDefault(); + imageTrigger = link; + const image = link.querySelector('img'); + document.querySelector('#dialog-image').src = link.href; + document.querySelector('#dialog-image').alt = image.alt; + document.querySelector('#dialog-caption').textContent = image.alt; + document.querySelector('#dialog-original').href = link.href; + dialog.showModal(); + }); + }); + dialog.querySelector('.dialog-close').addEventListener('click', () => dialog.close()); + dialog.addEventListener('click', event => { if (event.target === dialog) { + const rect = dialog.getBoundingClientRect(); + if (event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom) dialog.close(); + } }); + dialog.addEventListener('close', () => { if (imageTrigger) imageTrigger.focus(); }); +} diff --git a/pages/style.css b/pages/style.css index a45f899..9599ee2 100644 --- a/pages/style.css +++ b/pages/style.css @@ -1,1272 +1,856 @@ -/* - * Loom — Native macOS-Styled Website Stylesheet - * Copyright © 2026 Deepak Sharma (ChaiWithCode). - * Licensed under the MIT License. - */ - :root { - /* Color Palette - Light Mode Default */ - --bg-canvas: #f8f9fb; - --bg-surface: rgba(255, 255, 255, 0.78); - --bg-surface-solid: #ffffff; - --bg-surface-elevated: #ffffff; - --bg-header: rgba(248, 249, 251, 0.82); - --bg-glass: rgba(255, 255, 255, 0.72); - --bg-code: #f1f3f7; - --bg-code-block: #181b22; - --text-code-block: #e6edf3; - --bg-pill: rgba(0, 0, 0, 0.05); - - --text-primary: #1d1d1f; - --text-secondary: #515358; - --text-tertiary: #86868b; - --text-link: #0071e3; - --text-on-accent: #ffffff; - - --accent: #0071e3; - --accent-hover: #0077ed; - --accent-subtle: rgba(0, 113, 227, 0.1); - --accent-glow: rgba(0, 113, 227, 0.22); - --accent-gradient: linear-gradient(135deg, #0071e3 0%, #6366f1 100%); - - --status-working: #30d158; - --status-idle: #ff9f0a; - --status-waiting: #0a84ff; - --status-failed: #ff453a; - - --border-subtle: rgba(0, 0, 0, 0.07); - --border-medium: rgba(0, 0, 0, 0.12); - --border-window: rgba(0, 0, 0, 0.14); - - --shadow-subtle: 0 2px 8px rgba(0, 0, 0, 0.04); - --shadow-card: 0 8px 24px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04); - --shadow-window: 0 24px 50px -12px rgba(0, 0, 0, 0.18), 0 0 0 1px rgba(0, 0, 0, 0.08); - --shadow-popover: 0 16px 36px rgba(0, 0, 0, 0.14), 0 0 0 1px rgba(0, 0, 0, 0.06); - - --radius-sm: 8px; - --radius-md: 14px; - --radius-lg: 20px; - --radius-xl: 28px; - --radius-full: 9999px; - - --font-sans: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text", "Helvetica Neue", Helvetica, Arial, sans-serif; - --font-mono: "SF Mono", ui-monospace, Menlo, Monaco, Consolas, "Liberation Mono", monospace; - - --container-max: 1160px; - --transition-fast: 0.15s ease; - --transition-normal: 0.25s cubic-bezier(0.16, 1, 0.3, 1); + --paper:#f4f1ea; + --ink:#14110d; + --muted:#655f57; + --rule:rgba(20,17,13,.14); + --accent:#b53e26; + --gutter:24px; + --margin:clamp(20px,5vw,120px); + font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; + color:var(--ink); + background:var(--paper); + font-size:18px; + font-synthesis:none +} +* { + box-sizing:border-box } - -@media (prefers-color-scheme: dark) { - :root { - --bg-canvas: #0c0e12; - --bg-surface: rgba(22, 25, 34, 0.72); - --bg-surface-solid: #161922; - --bg-surface-elevated: #1e222e; - --bg-header: rgba(12, 14, 18, 0.78); - --bg-glass: rgba(26, 30, 42, 0.65); - --bg-code: #161922; - --bg-code-block: #12141a; - --text-code-block: #e6edf3; - --bg-pill: rgba(255, 255, 255, 0.08); - - --text-primary: #f5f5f7; - --text-secondary: #a1a1aa; - --text-tertiary: #71717a; - --text-link: #2997ff; - --text-on-accent: #ffffff; - - --accent: #2997ff; - --accent-hover: #409cff; - --accent-subtle: rgba(41, 151, 255, 0.15); - --accent-glow: rgba(41, 151, 255, 0.28); - --accent-gradient: linear-gradient(135deg, #2997ff 0%, #818cf8 100%); - - --border-subtle: rgba(255, 255, 255, 0.08); - --border-medium: rgba(255, 255, 255, 0.14); - --border-window: rgba(255, 255, 255, 0.16); - - --shadow-subtle: 0 2px 8px rgba(0, 0, 0, 0.25); - --shadow-card: 0 10px 30px rgba(0, 0, 0, 0.45), 0 0 0 1px rgba(255, 255, 255, 0.06); - --shadow-window: 0 30px 60px -15px rgba(0, 0, 0, 0.75), 0 0 0 1px rgba(255, 255, 255, 0.12); - --shadow-popover: 0 20px 40px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.1); - } -} - -/* Reset & Box Model */ -*, *::before, *::after { - box-sizing: border-box; - margin: 0; - padding: 0; -} - html { - scroll-behavior: smooth; - font-size: 16px; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - text-rendering: optimizeLegibility; + scroll-behavior:smooth; + scroll-padding-top:32px } - body { - font-family: var(--font-sans); - background-color: var(--bg-canvas); - color: var(--text-primary); - line-height: 1.6; - min-height: 100vh; - display: flex; - flex-direction: column; - overflow-x: hidden; + margin:0; + line-height:1.6 } - -/* Accessible Skip Link */ -.skip-link { - position: absolute; - top: -100px; - left: 20px; - background: var(--accent); - color: #fff; - padding: 12px 20px; - border-radius: var(--radius-sm); - z-index: 1000; - text-decoration: none; - font-weight: 600; - transition: top var(--transition-fast); +h1,h2,h3,p,figure { + margin:0 } - -.skip-link:focus { - top: 16px; +h1,h2,h3 { + font-weight:400 } - -/* Typography Defaults */ -h1, h2, h3, h4, h5, h6 { - color: var(--text-primary); - font-weight: 700; - line-height: 1.15; - letter-spacing: -0.025em; +h2 { + font-size:clamp(2.7rem,4.8vw,4.7rem); + line-height:1.05; + letter-spacing:-.035em } - -p { - color: var(--text-secondary); +h3 { + font-size:clamp(2.2rem,3.5vw,3.5rem); + line-height:1.1; + letter-spacing:-.03em } - a { - color: var(--text-link); - text-decoration: none; - transition: color var(--transition-fast), opacity var(--transition-fast); + color:inherit; + text-underline-offset:5px; + text-decoration-thickness:1px } - a:hover { - text-decoration: underline; + color:var(--accent) } - -a:focus-visible, button:focus-visible { - outline: 2px solid var(--accent); - outline-offset: 3px; +button { + font:inherit; + cursor:pointer } - -/* Container */ -.container { - width: 100%; - max-width: var(--container-max); - margin-left: auto; - margin-right: auto; - padding-left: 24px; - padding-right: 24px; +a,button { + -webkit-tap-highlight-color:transparent } - -/* Sticky Header & Navigation */ -.site-header { - position: sticky; - top: 0; - z-index: 900; - background-color: var(--bg-header); - backdrop-filter: blur(24px) saturate(180%); - -webkit-backdrop-filter: blur(24px) saturate(180%); - border-bottom: 1px solid var(--border-subtle); - transition: background-color var(--transition-normal), border-color var(--transition-normal); +a:focus-visible,button:focus-visible,code:focus-visible,video:focus-visible { + outline:2px solid var(--accent); + outline-offset:6px } - -.nav-inner { - display: flex; - align-items: center; - justify-content: space-between; - height: 64px; +img { + display:block; + max-width:100%; + height:auto } - -.nav-brand { - display: flex; - align-items: center; - gap: 10px; - color: var(--text-primary); - text-decoration: none; - font-weight: 700; - font-size: 1.15rem; - letter-spacing: -0.02em; +p { + max-width:66ch +} +p+p { + margin-top:24px +} +.wrap { + width:calc(100% - var(--margin)*2); + max-width:1440px; + margin-inline:auto +} +.grid { + display:grid; + grid-template-columns:repeat(12,minmax(0,1fr)); + gap:var(--gutter) +} +.eyebrow { + font-size:11px; + font-weight:600; + line-height:1.4; + letter-spacing:.1em; + text-transform:uppercase +} +.lead { + font-size:23px; + line-height:1.5; + letter-spacing:-.015em +} +.small,figcaption { + font-size:13px; + line-height:1.6; + color:var(--muted) } - -.nav-brand:hover { - text-decoration: none; +.site-header { + display:flex; + align-items:center; + justify-content:space-between; + gap:24px; + padding-block:24px; + border-bottom:1px solid var(--rule) +} +.brand { + display:inline-flex; + align-items:center; + gap:10px; + text-decoration:none; + font-size:23px; + font-weight:600; + letter-spacing:-.025em +} +.brand img { + border-radius:22% +} +.site-header nav { + display:flex; + gap:32px; + font-size:14px +} +.site-header nav a,.header-download { + text-decoration:none +} +.header-download { + font-size:14px; + font-weight:600 +} +.header-download span { + margin-left:12px +} +.hero { + padding-top:88px; + padding-bottom:48px; + align-items:center; + position:relative +} +.hero-copy { + grid-column:1/8; + min-width:0 +} +.release-mark { + display:inline-block; + width:7px; + height:7px; + background:var(--accent); + border-radius:50%; + margin-right:7px; + vertical-align:1px +} +h1 { + font-size:clamp(3.5rem,7.5vw,7rem); + line-height:.96; + font-weight:400; + letter-spacing:-.035em; + margin:24px 0 32px +} +.line { + display:block; + overflow:hidden; + padding-bottom:.07em; + margin-bottom:-.07em +} +.line>span { + display:block; + animation:line-in 900ms cubic-bezier(.16,1,.3,1) both +} +.line:nth-child(2)>span { + animation-delay:60ms +} +.hero .lead { + max-width:33ch; + margin-bottom:32px +} +.button { + display:inline-flex; + align-items:center; + gap:32px; + justify-content:space-between; + padding:14px 24px; + text-decoration:none; + font-size:15px; + font-weight:500; + border:1px solid var(--ink); + border-radius:6px; + transition:transform 150ms ease +} +.button:hover { + transform:translateY(-2px) +} +.primary { + color:var(--paper); + background:var(--ink) +} +.primary:hover { + color:var(--paper) +} +.compat { + font-size:12px; + color:var(--muted); + margin-top:12px +} +.brew { + display:flex; + align-items:center; + gap:12px; + max-width:560px; + border-block:1px solid var(--rule); + margin-top:32px; + padding-block:12px +} +.brew code { + font-family:ui-monospace,SFMono-Regular,Menlo,monospace; + font-size:11px; + overflow:auto; + white-space:nowrap; + flex:1; + scrollbar-width:thin +} +.brew button { + border:0; + background:transparent; + font-size:12px; + font-weight:600; + padding:8px 4px +} +.copy-status { + min-height:24px; + font-size:12px; + color:var(--muted); + margin-top:4px +} +.hero-product { + grid-column:8/13; + margin-right:calc(var(--margin)*-.35); + padding-top:24px +} +.hero-product img { + width:100%; + border-radius:24px; + box-shadow:0 24px 48px rgba(20,17,13,.16) +} +.image-link { + display:block; + cursor:zoom-in +} +.hero-product figcaption { + margin-top:24px; + display:flex; + flex-wrap:wrap; + gap:8px; + justify-content:space-between; + font-size:11px +} +.hero-footnote { + grid-column:1/-1; + font-size:11px; + color:var(--muted); + margin-top:24px +} +.section-heading { + margin-bottom:48px +} +.section-heading .eyebrow { + margin-bottom:24px +} +.section-heading>p:last-child:not(.eyebrow) { + margin-top:24px; + color:var(--muted) +} +.film { + padding-block:96px 64px; + border-top:1px solid var(--rule) +} +.film .section-heading { + display:grid; + grid-template-columns:1fr 1.6fr 1fr; + gap:24px; + align-items:end +} +.film .section-heading .eyebrow { + align-self:start; + margin:8px 0 0 +} +.film .section-heading>p:last-child { + font-size:15px; + max-width:26ch; + margin:0 0 4px +} +.film-player video { + display:block; + width:100%; + height:auto; + aspect-ratio:16/9; + background:var(--ink); + border-radius:8px +} +.film-player figcaption { + display:flex; + justify-content:space-between; + gap:16px; + margin-top:16px; + font-size:12px +} +.features { + padding-top:96px +} +.features>.section-heading { + margin-bottom:80px +} +.feature { + align-items:center; + padding-block:64px; + border-top:1px solid var(--rule); + margin-bottom:48px +} +.feature-copy { + grid-column:1/6 +} +.feature-copy .eyebrow { + margin-bottom:24px +} +.feature-copy h3 { + margin-bottom:24px +} +.feature-copy>p:not(.eyebrow) { + color:var(--muted); + font-size:17px +} +.text-link { + display:inline-block; + font-size:14px; + margin-top:24px; + font-weight:500 +} +.feature-image { + grid-column:8/13; + width:100%; + max-width:460px; + justify-self:end +} +.feature-image img { + width:100%; + border-radius:18px +} +.feature-image figcaption { + margin-top:16px; + font-size:11px +} +.quota { + align-items:start; + padding-block:80px +} +.quota .usage { + grid-column:1/6; + justify-self:start; + max-width:400px +} +.quota .feature-copy { + grid-column:7/13; + padding-top:32px +} +.notification { + margin-top:32px; + max-width:320px +} +.notification img { + border-radius:16px +} +.notification figcaption { + margin-top:12px; + font-size:11px +} +.feature-copy>p.small { + font-size:12px; + margin-top:24px +} +.storage { + margin-bottom:0 +} +.widgets { + padding-block:96px 128px +} +.widget-heading { + align-items:end; + margin-bottom:48px } - -.nav-brand-icon { - width: 32px; - height: 32px; - border-radius: 8px; - display: block; - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); +.widget-heading>div { + grid-column:1/7 +} +.widget-heading .eyebrow { + margin-bottom:24px } - -.nav-links { - display: flex; - align-items: center; - gap: 24px; - list-style: none; +.widget-heading>p { + grid-column:8/13; + color:var(--muted); + font-size:17px } - -.nav-links a { - color: var(--text-secondary); - font-size: 0.92rem; - font-weight: 500; - text-decoration: none; - padding: 6px 4px; - transition: color var(--transition-fast); +.widgets figure img { + width:100%; + border-radius:12px } - -.nav-links a:hover { - color: var(--text-primary); - text-decoration: none; +.widgets figcaption { + margin-top:16px; + font-size:12px } - -.nav-actions { - display: flex; - align-items: center; - gap: 12px; +.providers { + padding-block:80px; + border-top:1px solid var(--rule) } - -.nav-cta { - display: inline-flex; - align-items: center; - gap: 6px; - background: var(--accent); - color: var(--text-on-accent) !important; - font-size: 0.85rem; - font-weight: 600; - padding: 7px 14px; - border-radius: var(--radius-full); - text-decoration: none !important; - transition: background-color var(--transition-fast), transform var(--transition-fast); +.provider-intro { + grid-column:1/5 } - -.nav-cta:hover { - background: var(--accent-hover); - transform: translateY(-1px); +.provider-intro .eyebrow { + margin-bottom:24px } - -/* Hero Section */ -.hero-section { - padding: 80px 0 60px; - text-align: center; - position: relative; - overflow: hidden; +.provider-intro>p:last-child { + margin-top:24px; + color:var(--muted); + font-size:16px } - -.hero-glow { - position: absolute; - top: -120px; - left: 50%; - transform: translateX(-50%); - width: 600px; - height: 450px; - background: radial-gradient(circle, var(--accent-glow) 0%, rgba(0, 113, 227, 0) 70%); - pointer-events: none; - z-index: 0; +.provider-table { + grid-column:6/13; + align-self:center; + min-width:0 } - -.hero-content { - position: relative; - z-index: 1; - max-width: 820px; - margin: 0 auto; +table { + border-collapse:collapse; + width:100%; + font-size:14px; + text-align:left } - -.hero-icon-wrapper { - margin-bottom: 24px; - display: inline-block; - position: relative; +th,td { + padding:20px 12px; + border-bottom:1px solid var(--rule); + vertical-align:top } - -.hero-icon { - width: 120px; - height: 120px; - max-width: 120px; - border-radius: 26px; - display: block; - box-shadow: 0 16px 36px var(--accent-glow), 0 4px 12px rgba(0, 0, 0, 0.12); - transition: transform var(--transition-normal); +th:first-child,td:first-child { + padding-left:0 } - -.hero-icon:hover { - transform: scale(1.03); +thead th { + font-size:11px; + color:var(--muted); + font-weight:500; + text-transform:uppercase; + letter-spacing:.06em } - -.hero-title { - font-size: 3.8rem; - font-weight: 800; - letter-spacing: -0.035em; - margin-bottom: 8px; - background: linear-gradient(180deg, var(--text-primary) 0%, var(--text-secondary) 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; +tbody th { + font-weight:500 } - -.hero-subtitle { - font-size: 1.65rem; - font-weight: 600; - color: var(--accent); - letter-spacing: -0.015em; - margin-bottom: 16px; +td { + color:var(--muted) } - -.hero-tagline { - font-size: 1.25rem; - color: var(--text-secondary); - line-height: 1.5; - margin-bottom: 36px; - max-width: 660px; - margin-left: auto; - margin-right: auto; +.provider-table .small { + margin-top:24px; + font-size:12px } - -/* Call to Actions */ -.hero-cta-group { - display: flex; - align-items: center; - justify-content: center; - flex-wrap: wrap; - gap: 14px; - margin-bottom: 20px; +.privacy { + padding-block:96px 128px; + border-top:1px solid var(--rule) } - -.btn { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 8px; - font-size: 0.96rem; - font-weight: 600; - padding: 12px 22px; - border-radius: var(--radius-md); - text-decoration: none !important; - cursor: pointer; - transition: all var(--transition-fast); - border: 1px solid transparent; +.privacy>div:first-child { + grid-column:1/6 } - -.btn-primary { - background: var(--accent); - color: var(--text-on-accent); - box-shadow: 0 4px 14px var(--accent-glow); +.privacy .eyebrow { + margin-bottom:24px } - -.btn-primary:hover { - background: var(--accent-hover); - transform: translateY(-1px); - box-shadow: 0 6px 18px var(--accent-glow); +.privacy-copy { + grid-column:7/13 } - -.btn-secondary { - background: var(--bg-surface-solid); - color: var(--text-primary); - border-color: var(--border-medium); - box-shadow: var(--shadow-subtle); +.privacy-copy>p:not(.lead) { + color:var(--muted); + font-size:16px } - -.btn-secondary:hover { - background: var(--bg-surface-elevated); - border-color: var(--text-tertiary); - transform: translateY(-1px); +.privacy-copy>p.small { + font-size:12px } - -.btn-subtle { - background: transparent; - color: var(--text-secondary); - border-color: var(--border-subtle); +.install { + padding-block:80px 96px; + border-top:1px solid var(--rule) } - -.btn-subtle:hover { - color: var(--text-primary); - border-color: var(--border-medium); - background: var(--bg-pill); +.install>div:first-child { + grid-column:1/7 } - -.hero-pill-compat { - display: inline-flex; - align-items: center; - gap: 6px; - font-size: 0.8rem; - font-weight: 500; - color: var(--text-tertiary); - background: var(--bg-pill); - padding: 4px 12px; - border-radius: var(--radius-full); - border: 1px solid var(--border-subtle); - max-width: 100%; - flex-wrap: wrap; - justify-content: center; - text-align: center; +.install .eyebrow { + margin-bottom:24px } - -.compat-dot { - width: 6px; - height: 6px; - background-color: var(--status-working); - border-radius: 50%; - display: inline-block; +.install-copy { + grid-column:8/13; + align-self:center } - -/* macOS Window Chrome Mockup Styling */ -.macos-window { - background: var(--bg-surface-elevated); - border-radius: var(--radius-lg); - border: 1px solid var(--border-window); - box-shadow: var(--shadow-window); - overflow: hidden; - position: relative; - transition: box-shadow var(--transition-normal); +.install-icon { + margin-bottom:24px; + border-radius:20px } - -.macos-titlebar { - height: 38px; - background: var(--bg-surface); - border-bottom: 1px solid var(--border-subtle); - display: flex; - align-items: center; - padding: 0 16px; - position: relative; - user-select: none; +.install-copy>p { + font-size:16px; + color:var(--muted); + margin-bottom:24px } - -.macos-traffic-lights { - display: flex; - align-items: center; - gap: 8px; +.install-copy p.small { + font-size:12px; + margin-top:24px } - -.traffic-light { - width: 12px; - height: 12px; - border-radius: 50%; - display: inline-block; +.install-links { + display:flex; + flex-direction:column; + gap:8px; + font-size:13px } - -.traffic-close { background-color: #ff5f56; border: 1px solid #e0443e; } -.traffic-minimize { background-color: #ffbd2e; border: 1px solid #dea123; } -.traffic-expand { background-color: #27c93f; border: 1px solid #1aab29; } - -.macos-window-title { - position: absolute; - left: 50%; - transform: translateX(-50%); - font-size: 0.82rem; - font-weight: 600; - color: var(--text-secondary); - display: flex; - align-items: center; - gap: 6px; +footer { + border-top:1px solid var(--rule); + padding-block:48px 32px } - -/* Demo Section */ -.demo-section { - padding: 40px 0 80px; +.footer-top { + display:grid; + grid-template-columns:2fr 3fr 2fr; + gap:32px; + align-items:start +} +.footer-top>p { + font-size:13px; + color:var(--muted) +} +.footer-top nav { + display:flex; + flex-direction:column; + gap:8px; + font-size:13px +} +.credits { + font-size:11px; + color:var(--muted); + margin-top:48px; + max-width:none } - -.section-header { - text-align: center; - max-width: 720px; - margin: 0 auto 48px; +.skip-link { + position:fixed; + left:16px; + top:-100px; + background:var(--ink); + color:var(--paper); + padding:12px 20px; + z-index:10 } - -.section-label { - display: inline-block; - font-size: 0.8rem; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.08em; - color: var(--accent); - margin-bottom: 8px; +.skip-link:focus { + top:16px } - -.section-title { - font-size: 2.4rem; - font-weight: 700; - letter-spacing: -0.025em; - margin-bottom: 14px; +.sr-only { + position:absolute; + width:1px; + height:1px; + padding:0; + margin:-1px; + overflow:hidden; + clip:rect(0,0,0,0); + white-space:nowrap; + border:0 } - -.section-desc { - font-size: 1.15rem; - color: var(--text-secondary); - line-height: 1.5; +dialog { + border:1px solid var(--rule); + border-radius:12px; + background:var(--paper); + color:var(--ink); + padding:56px 24px 24px; + max-width:min(1000px,94vw); + max-height:94vh; + overflow:auto } - -.demo-window-container { - max-width: 960px; - margin: 0 auto; +dialog::backdrop { + background:rgba(20,17,13,.8) } - -.video-frame { - width: 100%; - display: block; - background-color: #000000; - line-height: 0; +dialog img { + max-height:72vh; + width:auto; + max-width:100%; + object-fit:contain; + margin:auto } - -.video-frame video { - width: 100%; - height: auto; - display: block; - max-height: 560px; - background-color: #000; +dialog p { + font-size:13px; + color:var(--muted); + margin-top:16px } - -.demo-caption-bar { - display: flex; - align-items: center; - justify-content: space-between; - padding: 12px 18px; - background: var(--bg-surface); - border-top: 1px solid var(--border-subtle); - font-size: 0.88rem; - color: var(--text-secondary); +dialog>a { + font-size:13px } - -.demo-meta { - display: flex; - align-items: center; - gap: 10px; +.dialog-close { + position:absolute; + right:16px; + top:12px; + border:0; + background:transparent; + padding:4px 8px; + font-size:14px } - -.badge-duration { - background: var(--bg-pill); - border: 1px solid var(--border-subtle); - padding: 2px 8px; - border-radius: var(--radius-sm); - font-family: var(--font-mono); - font-size: 0.78rem; - color: var(--text-primary); - font-weight: 600; +.dialog-close span { + font-size:22px; + margin-left:12px } - -.demo-vertical-link { - font-weight: 500; - display: inline-flex; - align-items: center; - gap: 4px; +@keyframes line-in { + from { + transform:translateY(105%) } - -/* Why Loom / Problem & Solution */ -.why-section { - padding: 80px 0; - background: var(--bg-canvas); - border-top: 1px solid var(--border-subtle); - border-bottom: 1px solid var(--border-subtle); +to { + transform:translateY(0) } -.why-grid { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 24px; - margin-top: 40px; } -.why-card { - background: var(--bg-surface); - border: 1px solid var(--border-subtle); - border-radius: var(--radius-md); - padding: 30px 26px; - box-shadow: var(--shadow-card); - transition: transform var(--transition-normal), box-shadow var(--transition-normal); - display: flex; - flex-direction: column; +@media(min-width:1600px) { + .hero-product { + margin-right:-40px } -.why-card:hover { - transform: translateY(-2px); - box-shadow: 0 12px 36px rgba(0, 0, 0, 0.08); } - -.why-card-icon { - width: 44px; - height: 44px; - border-radius: var(--radius-sm); - background: var(--accent-subtle); - color: var(--accent); - display: flex; - align-items: center; - justify-content: center; - margin-bottom: 20px; +@media(max-width:1000px) { + .site-header nav { + gap:20px } - -.why-card-icon svg { - width: 24px; - height: 24px; +.hero { + padding-top:64px } - -.why-card h3 { - font-size: 1.25rem; - margin-bottom: 12px; - letter-spacing: -0.015em; +.hero-copy { + grid-column:1/8 } - -.why-card p { - font-size: 0.95rem; - line-height: 1.6; +.hero-product { + grid-column:8/13; + margin-right:0 } - -/* Features Showcase */ -.features-section { - padding: 90px 0; +.hero .lead { + font-size:20px } - -.feature-block { - display: grid; - grid-template-columns: 1fr 1fr; - align-items: center; - gap: 56px; - margin-bottom: 110px; +.image-hint { + display:none } - -.feature-block:last-child { - margin-bottom: 0; +.film .section-heading { + grid-template-columns:1fr 2fr } - -.feature-block.reverse { - grid-template-columns: 1fr 1fr; +.film .section-heading>p:last-child { + grid-column:2; + max-width:none } - -.feature-block.reverse .feature-visual { - order: 1; +.feature-copy>p:not(.eyebrow) { + font-size:16px } - -.feature-block.reverse .feature-content { - order: 2; +.quota .feature-copy { + grid-column:7/13 } - -.feature-content { - display: flex; - flex-direction: column; +.provider-intro { + grid-column:1/5 } - -.feature-pill { - align-self: flex-start; - font-size: 0.78rem; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.06em; - padding: 4px 10px; - border-radius: var(--radius-full); - background: var(--accent-subtle); - color: var(--accent); - margin-bottom: 14px; +.provider-table { + grid-column:6/13 } -.feature-title { - font-size: 2.1rem; - letter-spacing: -0.025em; - margin-bottom: 16px; } - -.feature-lead { - font-size: 1.05rem; - line-height: 1.6; - margin-bottom: 24px; +@media(max-width:760px) { + .grid { + grid-template-columns:repeat(6,minmax(0,1fr)) } - -.feature-list { - list-style: none; - display: flex; - flex-direction: column; - gap: 16px; +.site-header { + padding-block:20px; + gap:16px } - -.feature-list-item { - display: flex; - align-items: flex-start; - gap: 12px; +.brand { + font-size:20px } - -.feature-list-bullet { - width: 20px; - height: 20px; - border-radius: 50%; - background: var(--accent-subtle); - color: var(--accent); - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - margin-top: 2px; +.brand img { + width:32px; + height:32px } - -.feature-list-bullet svg { - width: 12px; - height: 12px; +.site-header nav { + gap:16px; + font-size:12px } - -.feature-list-text strong { - color: var(--text-primary); - display: block; - font-size: 0.98rem; - margin-bottom: 2px; +.site-header nav a:first-child,.site-header nav a:nth-child(3) { + display:none } - -.feature-list-text span { - color: var(--text-secondary); - font-size: 0.9rem; - line-height: 1.5; - display: block; +.header-download { + font-size:12px } - -/* Feature Visuals & Screenshots */ -.feature-visual { - position: relative; +.header-download span { + margin-left:4px } - -.screenshot-card { - background: var(--bg-surface-elevated); - border-radius: var(--radius-lg); - border: 1px solid var(--border-window); - box-shadow: var(--shadow-window); - overflow: hidden; - transition: transform var(--transition-normal); +.hero { + padding-top:48px; + gap:24px } - -.screenshot-card:hover { - transform: translateY(-3px); +.hero-copy { + grid-column:1/-1 } - -.screenshot-img { - width: 100%; - height: auto; - display: block; - object-fit: cover; +h1 { + font-size:clamp(3.5rem,11.5vw,5.8rem); + margin-top:24px; + margin-bottom:24px } - -.dual-screenshot-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 16px; +.hero .lead { + max-width:38ch; + font-size:21px } - -.dual-screenshot-card { - background: var(--bg-surface-elevated); - border-radius: var(--radius-md); - border: 1px solid var(--border-window); - box-shadow: var(--shadow-popover); - overflow: hidden; - display: flex; - flex-direction: column; +.hero-product { + grid-column:2/7; + max-width:460px; + justify-self:end; + padding-top:8px; + margin:0 } - -.screenshot-caption { - padding: 8px 12px; - font-size: 0.78rem; - font-weight: 600; - text-align: center; - background: var(--bg-surface); - color: var(--text-secondary); - border-top: 1px solid var(--border-subtle); +.hero-product img { + border-radius:18px } - -/* Supported Providers Matrix */ -.providers-section { - padding: 90px 0; - background: var(--bg-canvas); - border-top: 1px solid var(--border-subtle); +.hero-footnote { + margin-top:0; + max-width:48ch } - -.table-responsive-wrapper { - overflow-x: auto; - border-radius: var(--radius-md); - border: 1px solid var(--border-window); - box-shadow: var(--shadow-card); - background: var(--bg-surface-elevated); - margin-top: 36px; +.brew { + max-width:100% } - -.matrix-table { - width: 100%; - border-collapse: collapse; - text-align: left; - font-size: 0.94rem; +.film { + padding-block:64px 48px } - -.matrix-table th { - background: var(--bg-surface); - color: var(--text-primary); - font-weight: 600; - padding: 16px 20px; - border-bottom: 1px solid var(--border-medium); - white-space: nowrap; +.film .section-heading { + display:block } - -.matrix-table td { - padding: 18px 20px; - border-bottom: 1px solid var(--border-subtle); - vertical-align: top; - color: var(--text-secondary); +.film .section-heading .eyebrow { + margin-bottom:24px } - -.matrix-table tr:last-child td { - border-bottom: none; +.film .section-heading>p:last-child { + margin-top:24px } - -.matrix-table tr:hover td { - background-color: var(--bg-pill); +.film-player figcaption { + flex-direction:column; + gap:8px } - -.provider-cell { - display: flex; - align-items: center; - gap: 12px; - font-weight: 600; - color: var(--text-primary); - white-space: nowrap; +.features { + padding-top:64px } - -.provider-icon-badge { - width: 32px; - height: 32px; - border-radius: 8px; - background: var(--bg-surface); - border: 1px solid var(--border-subtle); - display: flex; - align-items: center; - justify-content: center; - font-size: 0.9rem; - font-weight: 700; +.features>.section-heading { + margin-bottom:48px } - -.provider-badge-codex { color: #10a37f; } -.provider-badge-claude { color: #d97706; } -.provider-badge-antigravity { color: #3b82f6; } - -.status-badge { - display: inline-flex; - align-items: center; - gap: 6px; - font-size: 0.8rem; - font-weight: 600; - padding: 4px 10px; - border-radius: var(--radius-full); +.feature { + padding-block:48px; + gap:40px; + margin-bottom:24px } - -.badge-implemented { - background: rgba(48, 209, 88, 0.12); - color: #27a844; -} - -@media (prefers-color-scheme: dark) { - .badge-implemented { - color: #30d158; - } +.feature-copy,.quota .feature-copy { + grid-column:1/-1; + padding:0; + max-width:56ch } - -.badge-unsupported { - background: rgba(255, 159, 10, 0.12); - color: #c97d07; -} - -@media (prefers-color-scheme: dark) { - .badge-unsupported { - color: #ff9f0a; - } +.feature-image,.quota .usage { + grid-column:2/7; + justify-self:end; + max-width:400px } - -.disclaimer-text { - font-size: 0.84rem; - color: var(--text-tertiary); - margin-top: 18px; - text-align: center; - font-style: italic; +.quota .usage { + grid-row:2 } - -/* Installation Section */ -.install-section { - padding: 90px 0; - border-top: 1px solid var(--border-subtle); +.notification { + max-width:280px } - -.install-grid { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 24px; - margin-top: 40px; +.widget-heading>div { + grid-column:1/-1 } - -.install-card { - background: var(--bg-surface-elevated); - border-radius: var(--radius-lg); - border: 1px solid var(--border-subtle); - box-shadow: var(--shadow-card); - padding: 32px 28px; - display: flex; - flex-direction: column; - position: relative; +.widget-heading>p { + grid-column:2/7 } - -.install-card.highlight { - border-color: var(--accent); - box-shadow: 0 10px 30px var(--accent-subtle), var(--shadow-card); +.widgets { + padding-block:64px 80px } - -.install-tag { - position: absolute; - top: -12px; - right: 24px; - background: var(--accent); - color: #fff; - font-size: 0.72rem; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.05em; - padding: 3px 10px; - border-radius: var(--radius-full); +.widgets figure img { + border-radius:6px } - -.install-card h3 { - font-size: 1.35rem; - margin-bottom: 10px; +.providers { + padding-block:64px; + gap:40px } - -.install-card p { - font-size: 0.94rem; - margin-bottom: 20px; - flex-grow: 1; +.provider-intro,.provider-table { + grid-column:1/-1 } - -/* Code Snippet Box */ -.terminal-snippet { - background: var(--bg-code-block); - border-radius: var(--radius-md); - padding: 14px 16px; - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - border: 1px solid rgba(255, 255, 255, 0.1); - margin-bottom: 16px; +.provider-intro>p:last-child { + max-width:40ch } - -.terminal-code { - font-family: var(--font-mono); - font-size: 0.85rem; - color: var(--text-code-block); - overflow-x: auto; - white-space: nowrap; - line-height: 1.4; +table { + font-size:12px } - -.copy-btn { - background: rgba(255, 255, 255, 0.1); - color: #fff; - border: none; - padding: 6px 10px; - border-radius: 6px; - font-size: 0.76rem; - font-weight: 600; - cursor: pointer; - flex-shrink: 0; - transition: background var(--transition-fast); +th,td { + padding:16px 8px } - -.copy-btn:hover { - background: rgba(255, 255, 255, 0.2); +thead th { + font-size:10px; + letter-spacing:.03em } - -.install-meta-list { - list-style: none; - font-size: 0.85rem; - color: var(--text-secondary); - display: flex; - flex-direction: column; - gap: 8px; - margin-top: 14px; +.privacy { + padding-block:64px 80px; + gap:40px } - -.install-meta-list li { - display: flex; - align-items: center; - gap: 8px; +.privacy>div:first-child,.privacy-copy { + grid-column:1/-1 } - -.check-icon { - color: var(--status-working); - font-weight: bold; +.install { + padding-block:64px 80px; + gap:40px } - -/* Privacy & Local-First Section */ -.privacy-section { - padding: 90px 0; - background: var(--bg-canvas); - border-top: 1px solid var(--border-subtle); +.install>div:first-child { + grid-column:1/-1 } - -.privacy-grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 24px; - margin-top: 40px; +.install-copy { + grid-column:2/7 } - -.privacy-item { - background: var(--bg-surface); - border-radius: var(--radius-md); - border: 1px solid var(--border-subtle); - padding: 28px; - display: flex; - gap: 20px; - box-shadow: var(--shadow-subtle); -} - -.privacy-item-icon { - width: 44px; - height: 44px; - border-radius: var(--radius-sm); - background: var(--accent-subtle); - color: var(--accent); - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; -} - -.privacy-item-icon svg { - width: 22px; - height: 22px; -} - -.privacy-item-content h3 { - font-size: 1.18rem; - margin-bottom: 8px; -} - -.privacy-item-content p { - font-size: 0.92rem; - line-height: 1.55; -} - -/* Open Source Banner */ -.opensource-banner { - margin-top: 50px; - background: var(--bg-surface-elevated); - border: 1px solid var(--border-medium); - border-radius: var(--radius-lg); - padding: 36px 40px; - display: flex; - align-items: center; - justify-content: space-between; - gap: 30px; - box-shadow: var(--shadow-card); -} - -.opensource-info h3 { - font-size: 1.4rem; - margin-bottom: 8px; -} - -.opensource-info p { - font-size: 0.95rem; - max-width: 680px; -} - -/* Site Footer */ -.site-footer { - margin-top: auto; - background: var(--bg-surface-solid); - border-top: 1px solid var(--border-subtle); - padding: 70px 0 40px; - font-size: 0.9rem; -} - .footer-top { - display: grid; - grid-template-columns: 2fr 1fr 1fr 1fr; - gap: 40px; - margin-bottom: 50px; + grid-template-columns:1fr 2fr } - -.footer-brand-col { - display: flex; - flex-direction: column; - gap: 16px; -} - -.footer-brand { - display: flex; - align-items: center; - gap: 10px; - font-weight: 700; - font-size: 1.2rem; - color: var(--text-primary); - text-decoration: none; +.footer-top nav { + grid-column:2 } - -.footer-brand:hover { - text-decoration: none; +.credits { + margin-top:32px } -.footer-brand-icon { - width: 28px; - height: 28px; - border-radius: 6px; } - -.footer-tagline { - color: var(--text-secondary); - font-size: 0.9rem; - line-height: 1.5; - max-width: 320px; +@media(max-width:380px) { + .site-header nav a:nth-child(2) { + display:none } - -.footer-col h4 { - font-size: 0.85rem; - text-transform: uppercase; - letter-spacing: 0.06em; - color: var(--text-primary); - margin-bottom: 18px; +.hero-product,.feature-image,.quota .usage,.widget-heading>p,.install-copy { + grid-column:1/-1 } - -.footer-col ul { - list-style: none; - display: flex; - flex-direction: column; - gap: 10px; +.brew code { + font-size:10px } - -.footer-col a { - color: var(--text-secondary); - text-decoration: none; - font-size: 0.9rem; - transition: color var(--transition-fast); +.lead,.hero .lead { + font-size:19px } - -.footer-col a:hover { - color: var(--text-primary); - text-decoration: underline; +.footer-top { + grid-template-columns:1fr } - -.footer-bottom { - padding-top: 30px; - border-top: 1px solid var(--border-subtle); - display: flex; - align-items: center; - justify-content: space-between; - flex-wrap: wrap; - gap: 16px; - color: var(--text-tertiary); - font-size: 0.84rem; +.footer-top nav { + grid-column:1 } -.footer-copy a { - color: var(--text-secondary); } - -/* Responsive Media Queries */ -@media (max-width: 1024px) { - .hero-title { - font-size: 3.2rem; - } - - .why-grid { - grid-template-columns: 1fr; - } - - .install-grid { - grid-template-columns: 1fr; - } - - .feature-block, .feature-block.reverse { - grid-template-columns: 1fr; - gap: 40px; - } - - .feature-block.reverse .feature-visual { - order: 2; - } - - .feature-block.reverse .feature-content { - order: 1; - } - - .footer-top { - grid-template-columns: 1fr 1fr; - gap: 36px; - } - - .opensource-banner { - flex-direction: column; - align-items: flex-start; - } +@media(prefers-reduced-motion:reduce) { + html { + scroll-behavior:auto } - -@media (max-width: 768px) { - .nav-links { - display: none; /* Mobile navigation simplified */ - } - - .hero-title { - font-size: 2.6rem; - } - - .hero-subtitle { - font-size: 1.35rem; - } - - .hero-tagline { - font-size: 1.05rem; - } - - .privacy-grid { - grid-template-columns: 1fr; - } - - .dual-screenshot-grid { - grid-template-columns: 1fr; - } - - .footer-top { - grid-template-columns: 1fr; - } +*,*::before,*::after { + animation:none!important; + transition:none!important; + transform:none!important; + opacity:1!important } -@media (max-width: 480px) { - .container { - padding-left: 16px; - padding-right: 16px; - } - - .hero-cta-group { - flex-direction: column; - align-items: stretch; - width: 100%; - } - - .hero-cta-group .btn { - width: 100%; - } - - .hero-pill-compat { - font-size: 0.72rem; - padding: 6px 12px; - border-radius: var(--radius-md); - line-height: 1.4; - } - - .demo-caption-bar { - flex-direction: column; - align-items: flex-start; - gap: 8px; - } } diff --git a/scripts/verify-pages-staging.sh b/scripts/verify-pages-staging.sh index cdb1dd1..92f3610 100755 --- a/scripts/verify-pages-staging.sh +++ b/scripts/verify-pages-staging.sh @@ -144,7 +144,9 @@ mkdir -p "${SIM_DIR}" # Stage files into SIM_DIR/site mkdir -p "${SIM_DIR}/site/docs" "${SIM_DIR}/site/Resources" cp -R pages/. "${SIM_DIR}/site/" - [[ -f docs/loom-demo-thumbnail.png ]] && cp docs/loom-demo-thumbnail.png "${SIM_DIR}/site/docs/" + for asset in docs/menu-navigation-*.png docs/widget-showcase.png docs/loom-2.0-poster.png docs/loom-2.0-notifications.png docs/loom-2.0-captions.vtt; do + cp "$asset" "${SIM_DIR}/site/docs/" + done [[ -f Resources/LoomIconMaster.png ]] && cp Resources/LoomIconMaster.png "${SIM_DIR}/site/Resources/" # Simulate release trigger with tag v2.1.0 @@ -182,6 +184,9 @@ check "simulation: future v2.1.0 release stages v2.0.0 9x16 video" \ check "simulation: website and styling remain intact in future release" \ "[[ -s \"${SIM_DIR}/site/index.html\" && -s \"${SIM_DIR}/site/style.css\" ]]" +check "simulation: all website images, captions, scripts and fragments resolve" \ + "python3 scripts/verify-website.py \"${SIM_DIR}/site\"" + # 4. Negative tests: failure when assets missing FAIL_SIM_DIR="${TEST_TMP}/fail_sim" mkdir -p "${FAIL_SIM_DIR}/site" diff --git a/scripts/verify-website.py b/scripts/verify-website.py new file mode 100644 index 0000000..d9c99fe --- /dev/null +++ b/scripts/verify-website.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +"""Reject broken local website assets and fragment links before deployment.""" +from html.parser import HTMLParser +from pathlib import Path +from urllib.parse import unquote, urlsplit +import re +import sys + +root = Path(sys.argv[1] if len(sys.argv) > 1 else 'site').resolve() + +class References(HTMLParser): + def __init__(self): + super().__init__() + self.ids = set() + self.refs = [] + self.errors = [] + + def handle_starttag(self, tag, attrs): + a = dict(attrs) + if 'id' in a: + if a['id'] in self.ids: + self.errors.append('duplicate id: ' + a['id']) + self.ids.add(a['id']) + for key in ('src', 'href', 'poster'): + if a.get(key): + self.refs.append(a[key]) + if tag == 'img' and 'alt' not in a: + self.errors.append('image missing alt attribute: ' + a.get('src', '?')) + +p = References() +p.feed((root / 'index.html').read_text()) +errors = p.errors +count = 0 +for ref in p.refs: + u = urlsplit(ref) + if u.scheme or u.netloc: + continue + if not u.path: + if u.fragment and unquote(u.fragment) not in p.ids: + errors.append('missing fragment: ' + ref) + continue + target = (root / unquote(u.path)).resolve() + if not target.is_relative_to(root) or not target.is_file() or target.stat().st_size == 0: + errors.append('missing or empty asset: ' + ref) + count += 1 +for css in root.glob('*.css'): + for ref in re.findall(r'url\([\"\']?([^\"\')]+)', css.read_text()): + if not urlsplit(ref).scheme and not ref.startswith('#'): + if not (root / unquote(ref)).is_file(): + errors.append('missing CSS asset: ' + ref) +if errors: + print('\n'.join(errors), file=sys.stderr) + sys.exit(1) +print(f'Website verified: {count} local asset references, {len(p.ids)} fragment targets; no broken assets or duplicate IDs.')