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 0000000..7b07523 Binary files /dev/null and b/docs/loom-2.0-notifications.png differ 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.

-
-
- - View Repository - -
-
-
-
-
- - - - - + + +
+
+
+

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.')