diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index c2220c6..50e4f94 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -12,6 +12,7 @@ body: label: Surface options: - CLI / diagnostics + - Persistent agent / installer - Apply / watcher / restore - Theme package - Static Gallery @@ -46,6 +47,14 @@ body: label: Expected and actual behavior validations: required: true + - type: textarea + id: theme_recovery + attributes: + label: Theme recovery result + description: State whether `pause` or `restore` returned the interface to the official UI. Add a privacy-safe screenshot only if useful. + placeholder: `./bin/awesome-codex-themes pause` restored the official UI. + validations: + required: true - type: checkboxes id: privacy attributes: diff --git a/.github/ISSUE_TEMPLATE/theme_submission.yml b/.github/ISSUE_TEMPLATE/theme_submission.yml index 23d7ef2..8031647 100644 --- a/.github/ISSUE_TEMPLATE/theme_submission.yml +++ b/.github/ISSUE_TEMPLATE/theme_submission.yml @@ -1,5 +1,5 @@ name: Theme proposal -description: Propose an original or properly licensed theme package. +description: Propose a theme package whose assets you have the necessary rights to distribute. title: "[Theme]: " labels: [theme, needs-review] body: @@ -24,6 +24,7 @@ body: options: - Original work created by me - Commercially redistributable work with documented license + - Character or branded work with documented distribution rights validations: required: true - type: input @@ -38,7 +39,7 @@ body: attributes: label: Submission checks options: - - label: The package contains no unlicensed character, celebrity, game, anime, logo, or brand artwork. + - label: I have the necessary rights to publish and redistribute every included asset and derivative under the declared terms. required: true - label: The CSS preserves native controls, focus, pointer, and keyboard interaction. required: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7eccab3..d7e6eae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,8 +35,8 @@ jobs: - name: Install locked dependencies run: pnpm install --frozen-lockfile - - name: Test, validate themes, typecheck, and build - run: pnpm check + - name: Run the portable release gate + run: pnpm release:check - name: Upload static Gallery artifact uses: actions/upload-artifact@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 20b5fc7..747e5e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,18 +1,43 @@ # Changelog -All notable changes will be documented here. The project follows semantic versioning after its first tagged release. +All notable changes are documented here. The project follows semantic versioning after its first tagged release. ## [Unreleased] ### Added -- Independent Mac-first theme engine and CLI with `list`, `doctor`, `start`, `apply`, `status`, and `restore`. +- Self-contained per-user runtime installation with an atomic versioned `current` release. +- User LaunchAgent commands: `install-agent`, `upgrade-agent`, `switch`, `pause`, `resume`, and `uninstall-agent`. +- Portable `release:check` command and CI gate for tests, themes, types, build, production dependency audit, public-file hygiene, and diff whitespace. +- A separately identified `codex-26.715` adapter backed by the live-verified shared semantic mapping. +- A `codex-best-effort` fallback that attempts the shared mapping on every valid numeric Codex Desktop version. +- Bounded retry handling for launchd's transient code 5 unload/bootstrap race. +- Persistent `status` now verifies that the user LaunchAgent is loaded before reporting `active`. +- Persistent desired configuration and observed agent state with owner-only atomic files. +- Idempotent renderer reapply, managed app relaunch, and safe `restart-required` handling. +- Read-only diagnostics for known legacy injectors and unsafe wildcard-origin launches. +- English-first theme metadata with optional paired `nativeName` and `nativeLocale` fields. +- Eleven additional Schema 2 theme packages, completing the twelve-theme launch collection. +- A visual README atlas with one project hero and paired background/marketing assets for every theme. +- A persistent right-panel host marker and independently framed panel background so the entire summary rail keeps visible artwork after Codex rerenders. +- Installation, migration, authoring, recovery, architecture, and safety documentation. + +### Changed + +- **Satoru Gojo (五条 悟)** `1.2.0` remains the Featured package under the stable `satoru-gojo` slug. It is attempted on every numeric Codex Desktop version and marks `26.707.*` and `26.715.*` as live-verified. +- Four early engineering study themes and the temporary intake-slot scaffolding were removed from the release candidate. +- Theme submissions use an explicit provenance and necessary-rights declaration. + +## Private prototype history (not publicly released) + +The private `0.2.0` repository snapshot established the initial engine research baseline. It was never tagged or published as a GitHub Release. + +### Added + +- Independent Mac-first theme engine and session CLI with `list`, `doctor`, `start`, `apply`, `status`, and `restore`. - Signed app identity, literal-loopback CDP, listener ownership, renderer allowlist, compatibility, and process-identity checks. -- Versioned local theme package schema with safe path, CSS, asset, and size validation. -- Four original CC0 launch themes: Arctic Signal, Obsidian Bloom, Paper Circuit, and Solar Archive. -- Limitless Six Eyes private flagship prototype with a complete light palette, full-workspace refinements, corrected marketing preview, runtime artwork, and explicit restrictive asset notice. -- Optional schema-v2 declarative experience metadata plus idempotent ambient chrome injection and restore. -- Searchable static React/Vite Gallery with category filters, theme details, and copyable commands. -- Automated tests, CI, security policy, recovery documentation, and contribution templates. +- Versioned local theme schema with safe path, CSS, asset, and size validation. +- Optional declarative experience metadata and idempotent owned chrome injection/removal. +- Static React/Vite Gallery, automated tests, CI, security policy, and contribution templates. [Unreleased]: https://github.com/erickkkyt/Awesome-codex-themes/commits/main diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0948952..7352a97 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,10 +22,10 @@ Use Node 22 or newer for development. End-user engine commands use the Node runt ## Theme contributions 1. Copy an existing package into a new lowercase kebab-case directory. -2. Replace all artwork and metadata; do not submit a recolor of copyrighted character or brand art. +2. Replace all artwork and metadata. Confirm that you have the necessary rights to publish and redistribute every included asset and derivative under the declared terms. 3. Fill every schema-v2 semantic palette role; do not duplicate the shared Codex adapter in a theme package. 4. Keep optional CSS under `html.awesome-codex-theme` and preserve native controls, focus states, pointer behavior, and readable contrast. -5. Add `ASSET_LICENSE.md` with exact provenance and redistributable rights. +5. Add `ASSET_LICENSE.md` with exact provenance, attribution, and redistribution terms. 6. Start at `compatibility.status: experimental` and declare only versions you actually tested. 7. Run `pnpm themes:validate` and `pnpm check`. 8. Include before/after evidence without conversations, names, account data, or tokens. @@ -46,4 +46,4 @@ If behavior changes, update README/help/error codes and recovery documentation i - Disclose adapted code and preserve its required license/attribution. - Expect review of safety, recovery, licensing, compatibility evidence, accessibility, and reduced-motion behavior. -By contributing, you agree that your code contribution is licensed under MIT and that any artwork contribution is available under the license declared in its package. +By contributing, you agree that your code contribution is licensed under MIT and that you have the necessary rights to make every artwork contribution available under the terms declared in its package. diff --git a/NOTICE b/NOTICE index 4875617..f5d52f9 100644 --- a/NOTICE +++ b/NOTICE @@ -8,9 +8,8 @@ their respective owner. The software does not redistribute the official Codex Desktop application, its logos, or its signed application bundle. -Artwork licensing is stated in each package's ASSET_LICENSE.md. The original -SVG study themes are CC0 1.0. The Limitless Six Eyes PNG assets are private -prototype references, are not covered by MIT or CC0, and must be replaced -before public or commercial release. Third-party research references are -documented in THIRD_PARTY_NOTICES.md; their source code and assets are not -distributed with this repository. +Artwork metadata is stated in each package's ASSET_LICENSE.md. All twelve +packages use the `PROJECT-ASSET` identifier for files supplied and approved by +the project maintainer as part of the collection. Third-party implementation +research references are documented in THIRD_PARTY_NOTICES.md; their source +code and assets are not distributed with this repository. diff --git a/README.md b/README.md index 0f9a072..0a91c05 100644 --- a/README.md +++ b/README.md @@ -1,63 +1,170 @@ -# Awesome Codex Themes +

+ Awesome Codex Themes — 12 full-workspace themes for Codex Desktop +

-An unofficial, Mac-first open-source theme engine and visual gallery for the official Codex Desktop app. +

Awesome Codex Themes

-[![CI](https://github.com/erickkkyt/Awesome-codex-themes/actions/workflows/ci.yml/badge.svg)](https://github.com/erickkkyt/Awesome-codex-themes/actions/workflows/ci.yml) -[![License: MIT](https://img.shields.io/badge/License-MIT-171815.svg)](LICENSE) -[![Platform: macOS](https://img.shields.io/badge/platform-macOS-171815.svg)](#compatibility) +

+ Twelve full-workspace visual themes for Codex Desktop on macOS.
+ Apply, switch, persist, diagnose, and restore themes through a local open-source engine. +

-![Limitless Six Eyes full Codex workspace concept](themes/limitless-six-eyes/preview.png) +

+ CI + MIT License + macOS + 12 themes +

-Awesome Codex Themes gives technical users a small, inspectable way to apply full-workspace visuals to Codex: local artwork, glass surfaces, theme tokens, diagnostics, an idempotent watcher, and an explicit restore path. It does not patch or redistribute the official application. +Awesome Codex Themes is an unofficial, Mac-first theme engine and visual collection for the official Codex Desktop app. It applies local artwork, semantic color tokens, glass surfaces, and bounded decorative chrome through a loopback-only Chrome DevTools Protocol connection. It does not patch the signed app bundle, create another Codex profile, or read conversations and credentials. -> **Prototype artwork boundary:** the Limitless Six Eyes images are included only for evaluation in this private repository. They are not covered by MIT and must be replaced before public or commercial release. The engine, theme schema, and original CSS remain open source. +## Theme collection -## Current status +Every theme below is a complete local package under `themes/`. The left image is the background used by the runtime package; the right image is its matching marketing asset. Compatibility remains `experimental` and is declared per package. + +### 01. Satoru Gojo (五条 悟) — Featured -This repository is an experimental `0.2.0` implementation, not a commercial desktop app. +> A porcelain-white editorial workspace shaped by Limitless, Six Eyes, iris-violet telemetry, and a midnight code cockpit. -| Surface | Status | +| Background | Marketing asset | | --- | --- | -| Theme manifest and local asset validation | Implemented and tested | -| macOS app identity, signature, Team ID, architecture checks | Implemented and tested | -| Literal `127.0.0.1` CDP target and port-owner validation | Implemented and tested | -| Apply, watcher, status, and restore engine | Implemented and tested | -| Versioned full-workspace adapter for Codex `26.707.*` | Implemented and tested | -| Limitless Six Eyes flagship theme | Complete semantic palette and declarative experience layer; compatibility remains experimental | -| Obsidian Bloom schema-v2 full theme | Complete semantic palette; compatibility remains experimental | -| Three original legacy themes | Included for compatibility; not yet tuned for full-workspace coverage | -| Searchable static gallery | Implemented and production-build verified | -| Signed GUI / Commercial Desktop | Out of scope | +| Satoru Gojo theme background | Satoru Gojo marketing preview | -Every included theme currently declares Codex `26.707.*`. Unknown versions fail closed and leave the official UI unchanged. +```bash +./bin/awesome-codex-themes install-agent satoru-gojo +``` -### Full-theme coverage +### 02. Zaun Workshop -The shared `26.707` adapter maps a theme's semantic palette onto Codex and VS Code renderer tokens plus narrowly scoped component rules. Limitless Six Eyes and Obsidian Bloom use the complete palette. The flagship adds a declarative, non-interactive experience layer for branding, status, particles, and optical geometry. Together the system covers: +> A volatile undercity workshop framed by fractured stained glass, bridge ironwork, chemical smoke, and kinetic diagonal motion. -- canvas artwork, scrims, main surfaces, sidebars, headers, cards, dialogs, menus, listboxes, and tooltips; -- primary, secondary, muted, and disabled text plus icon tiers; -- normal, subtle, strong, focus, input, menu, and terminal borders; -- inputs, placeholders, Composer, send button, links, hover, active, focus, and selection states; -- code blocks, preformatted text, Monaco/terminal surfaces, added/removed diff colors, status colors, and scrollbars. -- optional theme-owned ambient chrome whose text is inserted with `textContent`, ignores pointer events, reapplies idempotently, and is removed by restore. +| Background | Marketing asset | +| --- | --- | +| Zaun Workshop theme background | Zaun Workshop marketing preview | + +```bash +./bin/awesome-codex-themes switch zaun-workshop +``` -“Full-theme” means high coverage on a declared Codex version family. It does not mean future Codex versions are permanently pixel-compatible. Themes remain `experimental` until the relevant page matrix has recorded runtime evidence. +### 03. Symbiote Sumi-e -Run one theme injector at a time. A third-party injector can win the CSS cascade with its own high-specificity rules; Awesome Codex Themes deliberately does not remove or rewrite unrelated style elements. +> A monochrome symbiote emerging from rice-paper negative space, ink blooms, brush drag, and sparse gold repair seams. -## Safety model +| Background | Marketing asset | +| --- | --- | +| Symbiote Sumi-e theme background | Symbiote Sumi-e marketing preview | -- Validates bundle identifier `com.openai.codex`, intact code signature, and OpenAI Team ID `2DC432GLL2`. -- Uses the Node runtime bundled with the signed official app; end users do not need a global Node installation. -- Binds CDP to a dynamically selected port on literal `127.0.0.1` and verifies the listener belongs to the official app process. -- Selects only the expected main `app://-/index.html` renderer. -- Injects one namespaced style element, owned CSS variables, and—only when declared—one inert decorative chrome node; applying twice replaces the same state. -- Never reads conversations, account tokens, API keys, model settings, or unrelated user data. -- Never patches `app.asar`, modifies the signed app bundle, or silently terminates an active Codex session. -- Records exact watcher process identity before it will send `SIGTERM`; it never terminates the official app. +```bash +./bin/awesome-codex-themes switch symbiote-sumi-e +``` + +### 04. Slingshot Lab + +> A bright island construction lab where timber, stone, glass blocks, tension bands, and projectile arcs form the system language. + +| Background | Marketing asset | +| --- | --- | +| Slingshot Lab theme background | Slingshot Lab marketing preview | + +```bash +./bin/awesome-codex-themes switch slingshot-lab +``` + +### 05. Saiyan Ukiyo-e + +> A parchment-and-indigo battle chronicle using ukiyo-e brush rhythm, orange energy, wave motion, and carved seal logic. + +| Background | Marketing asset | +| --- | --- | +| Saiyan Ukiyo-e theme background | Saiyan Ukiyo-e marketing preview | + +```bash +./bin/awesome-codex-themes switch saiyan-ukiyoe +``` + +### 06. Grand Line + +> A weathered black-sail navigation desk illuminated by brass chart marks, rope, smoke, and a high-energy captain portrait. + +| Background | Marketing asset | +| --- | --- | +| Grand Line theme background | Grand Line marketing preview | + +```bash +./bin/awesome-codex-themes switch grand-line +``` + +### 07. Castle Archive + +> A monumental candlelit castle interior built from black columns, burgundy velvet, ivory fabric, and brass archival details. + +| Background | Marketing asset | +| --- | --- | +| Castle Archive theme background | Castle Archive marketing preview | + +```bash +./bin/awesome-codex-themes switch castle-archive +``` + +### 08. Overworld Realms + +> A cross-section of bright overworld village, deep mineral cave, lava seam, and violet portal rendered as an isometric voxel system. + +| Background | Marketing asset | +| --- | --- | +| Overworld Realms theme background | Overworld Realms marketing preview | + +```bash +./bin/awesome-codex-themes switch overworld-realms +``` + +### 09. Mordor Runtime + +> A storm-black mounted formation crossing basalt and ember fissures beneath a distant volcanic watchtower. + +| Background | Marketing asset | +| --- | --- | +| Mordor Runtime theme background | Mordor Runtime marketing preview | -Read [SECURITY.md](SECURITY.md) and [docs/SAFETY.md](docs/SAFETY.md) before testing the runtime engine. +```bash +./bin/awesome-codex-themes switch mordor-runtime +``` + +### 10. Night City + +> A moonlit high-rise interior overlooking a rain-blue megacity, with glass reflections and sparse magenta signal lights. + +| Background | Marketing asset | +| --- | --- | +| Night City theme background | Night City marketing preview | + +```bash +./bin/awesome-codex-themes switch night-city +``` + +### 11. Foundling Garden + +> A small foundling in a misty moss garden, reinterpreted as a wider luminous sanctuary and orbital seed instrument. + +| Background | Marketing asset | +| --- | --- | +| Foundling Garden theme background | Foundling Garden marketing preview | + +```bash +./bin/awesome-codex-themes switch foundling-garden +``` + +### 12. New World Studio + +> A many-character ink-wash editorial composition over warm paper, black and red brush fields, brass flecks, blossoms, and ocean references. + +| Background | Marketing asset | +| --- | --- | +| New World Studio theme background | New World Studio marketing preview | + +```bash +./bin/awesome-codex-themes switch new-world-studio +``` ## Quick start @@ -65,9 +172,9 @@ Requirements: - macOS on the same architecture as the installed official Codex Desktop app. - A signed official `ChatGPT.app` or legacy `Codex.app` installation. -- A Codex version declared by the selected theme. +- Any numeric Codex Desktop version. The engine attempts the shared adapter on unverified versions. -Clone the repository, then inspect without changing app state: +Clone the repository and run read-only checks first: ```bash git clone https://github.com/erickkkyt/Awesome-codex-themes.git @@ -76,108 +183,122 @@ cd Awesome-codex-themes ./bin/awesome-codex-themes list ``` -For a managed themed session, first quit Codex yourself, then run: +Install the Featured theme as the persistent selection: ```bash -./bin/awesome-codex-themes start limitless-six-eyes +./bin/awesome-codex-themes install-agent satoru-gojo ./bin/awesome-codex-themes status ``` -Return to the official UI: +If Codex is already open without this project's managed CDP endpoint, the agent reports `restart-required` and does not close it. Save your work, quit Codex yourself, and let the installed agent start the verified official app. See [Installation and recovery](docs/INSTALL.md) before enabling persistence. + +**Best-effort injection is attempted on every numeric Codex Desktop version. Highly compatible and live-verified: `26.707.*` and `26.715.*`.** Other versions are attempted, but their component layout is not guaranteed. + +## Persistent lifecycle ```bash -./bin/awesome-codex-themes restore -``` +# Select another installed theme +./bin/awesome-codex-themes switch night-city -`start` refuses to proceed while Codex is already running. It does not close the app for you. +# Return to the Featured theme +./bin/awesome-codex-themes switch satoru-gojo -### Advanced one-shot apply +# Upgrade from a newer inspected checkout +./bin/awesome-codex-themes upgrade-agent -If you intentionally started the official app with a loopback CDP port, you can apply and restore against that explicit port: +# Stop persistent management and remove project-owned live styling +./bin/awesome-codex-themes pause -```bash -./bin/awesome-codex-themes apply limitless-six-eyes --port 9341 -./bin/awesome-codex-themes restore --port 9341 +# Re-enable the saved selection +./bin/awesome-codex-themes resume + +# Remove the installed agent and project-owned runtime +./bin/awesome-codex-themes uninstall-agent ``` -The same signature, ownership, listener-address, renderer, and version checks still apply. +The LaunchAgent points to an immutable installed copy, not the Git clone. It starts the official app with a local CDP endpoint, reapplies the selected theme after renderer reloads, and preserves an explicit pause, restore, and uninstall path. -## Commands +After an official Codex update, the agent attempts the shared adapter again. If a changed interface looks wrong, run `pause` or `restore` to return to the official UI, then open a [GitHub Issue](https://github.com/erickkkyt/Awesome-codex-themes/issues/new/choose) with the Codex version and a privacy-safe screenshot. -| Command | Purpose | -| --- | --- | -| `list` | Validate and list installed theme packages. | -| `doctor` | Report signed app path, version, and exact running app PIDs without mutation. | -| `start ` | Launch a new managed Codex session on a dynamic loopback port and start the watcher. | -| `apply --port ` | Apply once to an explicitly CDP-enabled official app. | -| `status` | Report whether this project owns an active managed state record. | -| `restore [--port ]` | Remove owned CSS; managed restore also stops only the exact recorded watcher. | +## Current status -All errors use stable codes such as `APP_SIGNATURE_INVALID`, `CDP_PORT_OWNER_INVALID`, `THEME_APP_VERSION_UNSUPPORTED`, and `INJECTOR_IDENTITY_MISMATCH`. +Version `0.4.2` includes the engine, twelve complete local theme packages, self-contained user installation, LaunchAgent persistence, conflict diagnostics, safe restore, and a static Gallery. -## Launch collection +| Capability | State | +| --- | --- | +| Twelve Schema 2 theme packages | Implemented; compatibility remains experimental | +| Theme assets, CSS, metadata, and catalog validation | Implemented and tested | +| Official app identity, signature, Team ID, architecture, and port checks | Implemented and tested | +| Loopback-only apply, idempotent reapply, and owned-state removal | Implemented and prototype verified | +| Install, upgrade, switch, pause, resume, status, and uninstall | Integration verified on `26.715.21425` | +| Renderer reload and full app relaunch recovery | Integration verified on `26.715.21425` | +| Physical macOS reboot | Pending direct exercise | +| Searchable static Gallery | Implemented; deployment is separate from the repository build | -- **Limitless Six Eyes** — a porcelain-white editorial flagship with iris-violet telemetry and a midnight code cockpit. Its current character artwork is private-prototype-only. -- **Arctic Signal** — polar night, cyan telemetry, high contrast. -- **Obsidian Bloom** — charcoal glass and ember-orange botanical forms. -- **Paper Circuit** — warm paper, graphite type, cobalt drafting traces. -- **Solar Archive** — a midnight reading room with amber orbital marks. +## Commands -The four original study themes use local CC0 1.0 artwork. Limitless Six Eyes uses local private-prototype artwork that is explicitly excluded from the repository license. Theme CSS and engine code are MIT licensed. Each package carries its own `ASSET_LICENSE.md`. +| Command | Purpose | +| --- | --- | +| `list` | Validate and list complete runnable themes. | +| `doctor` | Inspect the signed app, exact running PIDs, and known injector conflicts without mutation. | +| `start ` | Start one session-scoped managed Codex launch. | +| `install-agent ` | Install the versioned runtime and enable persistent management. | +| `upgrade-agent` | Upgrade the installed runtime while preserving theme and enabled/paused state. | +| `switch ` | Validate and select another installed theme. | +| `pause` | Disable persistent management and remove project-owned live styling. | +| `resume` | Re-enable the saved persistent selection. | +| `apply --port ` | Apply once to an explicitly CDP-enabled official app. | +| `status` | Report desired theme, agent state, endpoint, and last safe-failure status. | +| `restore [--port ]` | Remove project-owned styling for a one-shot or session-scoped launch. | +| `uninstall-agent` | Boot out the user agent and delete only this project's installed files. | -Run the gallery locally: +## Safety model -```bash -pnpm install -pnpm dev -``` +- Validates bundle identifier `com.openai.codex`, intact code signature, and OpenAI Team ID `2DC432GLL2`. +- Uses the Node runtime bundled with the signed official app; end users do not need a global Node installation. +- Binds CDP to a dynamically selected port on literal `127.0.0.1` and verifies the listener belongs to the official process. +- Selects only the expected main `app://-/index.html` renderer. +- Applies one namespaced style element and one optional inert, pointer-transparent decorative node. +- Never reads conversations, account tokens, API keys, model settings, or unrelated user data. +- Never patches `app.asar`, modifies the signed app bundle, uses `--user-data-dir`, or silently terminates an active Codex process. +- `pause`, `restore`, and `uninstall-agent` remove only project-owned runtime state. -The gallery is a static Vite build with search, category filters, structured theme details, and copyable CLI commands. It has no accounts, analytics, payments, or remote theme execution. +Read [SECURITY.md](SECURITY.md), [docs/SAFETY.md](docs/SAFETY.md), and [docs/MIGRATION.md](docs/MIGRATION.md) before replacing another injector. ## Theme authoring -New full-workspace themes use schema v2 and remain declarative. The versioned adapter owns Codex selectors; packages provide the semantic palette, original artwork, metadata, and optional namespaced refinements: +Each complete theme is declarative and self-contained: ```text themes/my-theme/ +├── assets/ ├── theme.json ├── theme.css -├── background.svg -├── preview.svg +├── background.jpg +├── preview.jpg └── ASSET_LICENSE.md ``` -Start with [docs/THEME_SCHEMA.md](docs/THEME_SCHEMA.md). Theme paths must remain inside the package; CSS cannot import remote resources or executable URLs; artwork and compatibility licensing must be explicit. +Start with [Theme authoring](docs/THEME_AUTHORING.md) and the [theme schema](docs/THEME_SCHEMA.md). Contributors must document provenance and confirm they have the necessary rights to distribute every submitted asset. -Validate a contribution with: - -```bash -pnpm themes:validate -pnpm check -``` - -See [CONTRIBUTING.md](CONTRIBUTING.md) for the review checklist. - -## Development +## Gallery and development ```bash +corepack enable pnpm install -pnpm test -pnpm typecheck -pnpm build +pnpm dev pnpm check +pnpm release:check ``` -The engine is written with Node built-ins. React/Vite power only the static gallery and development toolchain. The repository intentionally remains one package until a real product boundary justifies a monorepo. +The Gallery is a static Vite build with search, filters, structured theme details, and copyable commands. It has no accounts, analytics, payments, or remote theme execution. Theme packages remain the catalog source of truth. ## Independent implementation -The runtime architecture was researched against [Codex-Dream-Skin](https://github.com/Fei-Away/Codex-Dream-Skin) and [codex-app-transfer](https://github.com/Cmochance/codex-app-transfer), both MIT licensed at the inspected commits. Awesome Codex Themes is an independent, narrower implementation; no upstream artwork, branding, model relay, provider configuration, or source files are included. See [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) for exact references. - -The gallery uses familiar information-architecture patterns from open prompt libraries—visual cards, structured tags, search, filters, and small declarative contributions—without copying their code or assets. +The runtime architecture was researched against [Codex-Dream-Skin](https://github.com/Fei-Away/Codex-Dream-Skin) and [codex-app-transfer](https://github.com/Cmochance/codex-app-transfer), both MIT licensed at the inspected commits. Awesome Codex Themes is an independent, narrower implementation; no upstream source files are included. See [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md). ## License and trademark notice -Code is available under the [MIT License](LICENSE). Artwork licensing is package-specific: the four original study themes are CC0 1.0, while the Limitless Six Eyes prototype images are not licensed for public or commercial redistribution. +Code is available under the [MIT License](LICENSE). Artwork source notes are recorded in each theme package. -Awesome Codex Themes is unofficial and is not affiliated with, endorsed by, or sponsored by OpenAI. “OpenAI” and “Codex” are trademarks of their respective owner. This project does not ship OpenAI logos or the official app. +Awesome Codex Themes is unofficial and is not affiliated with, endorsed by, or sponsored by OpenAI. “OpenAI” and “Codex” are trademarks of their respective owner. This project does not ship the official application. diff --git a/bin/awesome-codex-themes b/bin/awesome-codex-themes index f808d92..0c11329 100755 --- a/bin/awesome-codex-themes +++ b/bin/awesome-codex-themes @@ -2,6 +2,20 @@ set -eu SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +FOUND_CANDIDATE=0 + +verify_official_app() { + APP_PATH=$1 + INFO_PLIST="$APP_PATH/Contents/Info.plist" + + BUNDLE_ID=$(/usr/bin/plutil -extract CFBundleIdentifier raw -o - "$INFO_PLIST" 2>/dev/null) || return 1 + [ "$BUNDLE_ID" = "com.openai.codex" ] || return 1 + + /usr/bin/codesign --verify --deep --strict "$APP_PATH" >/dev/null 2>&1 || return 1 + + TEAM_ID=$(/usr/bin/codesign -dv --verbose=4 "$APP_PATH" 2>&1 | /usr/bin/sed -n 's/^TeamIdentifier=//p') + [ "$TEAM_ID" = "2DC432GLL2" ] || return 1 +} for APP_PATH in \ "/Applications/ChatGPT.app" \ @@ -11,9 +25,17 @@ for APP_PATH in \ do NODE_PATH="$APP_PATH/Contents/Resources/cua_node/bin/node" if [ -x "$NODE_PATH" ]; then - exec "$NODE_PATH" "$SCRIPT_DIR/../src/cli/main.mjs" "$@" + FOUND_CANDIDATE=1 + if verify_official_app "$APP_PATH"; then + exec "$NODE_PATH" "$SCRIPT_DIR/../src/cli/main.mjs" "$@" + fi fi done +if [ "$FOUND_CANDIDATE" -eq 1 ]; then + echo "[APP_IDENTITY_INVALID] A Codex Desktop bundle was found, but its identity or signature was not trusted." >&2 + exit 1 +fi + echo "[APP_NOT_FOUND] No official Codex Desktop bundled Node runtime was found." >&2 exit 1 diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index db0b6e5..cbff617 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,6 +1,6 @@ # Architecture -The repository is one package with two deliberately separate surfaces. +The repository is one package with three deliberately separated surfaces. ## Runtime engine @@ -9,29 +9,52 @@ bin/awesome-codex-themes → src/cli/main.mjs → macOS identity + port-owner inspection → theme package validation - → versioned Codex adapter selection + → verified or best-effort Codex adapter selection → CDP renderer allowlist → namespaced apply / verify / restore → owner-only runtime state ``` -- `src/engine/theme.mjs` validates manifests, canonical paths, symlinks, sizes, CSS, assets, and version ranges. -- `src/engine/adapter.mjs` selects a trusted built-in compatibility stylesheet for the exact supported Codex version family. -- `src/engine/adapters/codex-26.707.css` maps semantic theme variables onto Codex/VS Code design tokens and stable component surfaces. +- `src/engine/theme.mjs` validates manifests, canonical paths, symlinks, sizes, CSS, assets, and live-verified version metadata. +- `src/engine/adapter.mjs` identifies live-verified version families and otherwise returns the shared trusted stylesheet as `codex-best-effort`. +- `src/engine/adapters/codex-26.707.css` is the live-verified shared semantic mapping used by separately identified `codex-26.707` and `codex-26.715` adapter records. - `src/engine/macos.mjs` inspects signature/team/runtime identity, exact processes, and loopback port ownership using argv-based system tools. - `src/engine/cdp.mjs` enforces literal-loopback HTTP/WebSocket endpoints, filters renderer targets, and wraps JSON CDP requests with timeouts. - `src/engine/injection.mjs` builds data-only idempotent apply, verification, and removal expressions. - `src/engine/state.mjs` validates and atomically stores exact app/watcher identity with owner-only permissions. - `src/engine/session.mjs` composes apply/start/restore state transitions without terminating the official app. +- `src/engine/config.mjs` and `src/engine/agent-state.mjs` separate desired persistent configuration from observed process state. +- `src/engine/agent.mjs` implements idempotent reapply, managed relaunch, `restart-required`, pause, and typed failure transitions. +- `src/engine/installer.mjs` stages immutable user releases and atomically activates the stable `current` symlink. +- `src/engine/launch-agent.mjs` generates and controls the user LaunchAgent through argument-array `launchctl` calls, with bounded retry only for launchd's transient code 5 unload window. +- `src/engine/control.mjs` disables persistence before requesting verified live owned-style removal. +- `src/engine/conflicts.mjs` reports known legacy injectors and wildcard-origin processes without changing them. -Theme packages do not carry their own copy of the Codex compatibility layer. At apply time the engine loads the matching trusted adapter, appends the validated theme CSS, and injects both as one namespaced style element. Schema-v2 packages may also declare bounded experience copy; the engine converts that data into one inert, owned decorative node without accepting theme JavaScript or HTML. This keeps selector maintenance centralized while allowing themes to remain small and portable. +Theme packages do not carry their own copy of the Codex compatibility layer. At apply time the engine loads the trusted shared adapter, records whether the app family is live-verified or best effort, appends the validated theme CSS, and injects both as one namespaced style element. Schema-v2 packages may also declare bounded experience copy; the engine converts that data into one inert, owned decorative node without accepting theme JavaScript or HTML. This keeps selector maintenance centralized while allowing themes to remain small and portable. -The public CLI keeps `_watch` internal. The shell entrypoint finds the official app's bundled Node runtime, while every mutating operation independently validates the application and endpoint. +The public CLI keeps `_watch` and `_agent` internal. The shell entrypoint finds the official app's bundled Node runtime, while every mutating operation independently validates the application and endpoint. -## Static gallery +## Persistent user installation + +```text +Git clone (mutable source) + → validated staging copy + → ~/Library/Application Support/AwesomeCodexThemes/releases/0.4.2/ + → atomic current symlink + → ~/Library/LaunchAgents/io.github.awesome-codex-themes.agent.plist + → current/bin/awesome-codex-themes _agent +``` + +The service never points at the clone. Configuration and state remain outside immutable release directories, use owner-only permissions, and are written atomically. Reinstallation boots out only this project's label, updates the installed plist/current release, bootstraps, and kickstarts it. Uninstall removes only project-owned paths. + +An enabled agent owns the CDP-enabled launch lifecycle. If an ordinary Dock launch is already running, it reports `restart-required` rather than terminating or relaunching it. This is the unavoidable open-source CDP boundary: the project can persistently inject only into an official process that was started with its verified loopback debugging endpoint. + +## Theme catalog and static gallery Theme packages are the source of truth. `scripts/build-catalog.mjs` validates them, emits `src/generated/themes.json`, and copies declared previews into `public/theme-assets`. React/Vite renders an account-free static catalog with client-side search and filters. +`themes/` contains the twelve complete runnable launch packages. The Featured tag is catalog metadata rather than a separate package type, and the build keeps every theme on the same engine, schema, and adapter path. + The Gallery cannot install a theme, call the runtime engine, or execute theme code. It exposes copyable commands and accurate compatibility metadata. ## Why this is not a monorepo diff --git a/docs/INSTALL.md b/docs/INSTALL.md new file mode 100644 index 0000000..d781bb5 --- /dev/null +++ b/docs/INSTALL.md @@ -0,0 +1,115 @@ +# Installation and recovery + +Awesome Codex Themes installs a private, versioned runtime for the current macOS user. The installed LaunchAgent never depends on the location of the Git clone. + +## Before installation + +Run the read-only diagnostics: + +```bash +./bin/awesome-codex-themes doctor +./bin/awesome-codex-themes list +``` + +Resolve any reported legacy injector conflict first. The doctor command does not disable or delete anything. + +The installer validates the selected theme and the signed official app, then writes only these project-owned locations: + +```text +~/Library/Application Support/AwesomeCodexThemes/ +~/Library/LaunchAgents/io.github.awesome-codex-themes.agent.plist +~/Library/Logs/AwesomeCodexThemes.log +~/Library/Logs/AwesomeCodexThemes.error.log +``` + +It does not edit `ChatGPT.app`, `Codex.app`, Codex conversations, settings, credentials, or the user's app profile. + +## Install Satoru Gojo + +```bash +./bin/awesome-codex-themes install-agent satoru-gojo +./bin/awesome-codex-themes status +``` + +The installed configuration selects `satoru-gojo`, enables persistence, and starts the user LaunchAgent. The agent uses a dynamic literal-loopback CDP port and does not pass `--user-data-dir`. + +## First transition + +If Codex is already running without a trusted endpoint, status becomes `restart-required`. This is deliberate: the agent never terminates an active session. + +1. Save any active work. +2. Quit the official Codex app yourself. +3. The enabled agent starts the verified official executable with its managed local endpoint. +4. Run `./bin/awesome-codex-themes status` and expect `active`. + +While enabled, the agent owns the launch lifecycle. It starts Codex at login, reapplies the theme after renderer reload, and starts a new managed process after Codex exits. Run `pause` before intentionally keeping the app closed: + +```bash +./bin/awesome-codex-themes pause +``` + +Resume the same selection with: + +```bash +./bin/awesome-codex-themes resume +``` + +## Updates and compatibility + +After inspecting a newer checkout, upgrade the installed runtime without changing the selected theme or enabled/paused state: + +```bash +./bin/awesome-codex-themes upgrade-agent +``` + +The command stages a new immutable release and atomically changes the installed `current` link. The service never runs from mutable repository files. + +A Codex app update is evaluated against the selected theme metadata. Versions in `verifiedAppVersions` use their live-verified adapter marker; every other numeric version receives the shared `codex-best-effort` mapping. This is an attempt, not a compatibility guarantee. Upgrade Awesome Codex Themes only from a release you have inspected. + +If an updated Codex layout looks wrong, run: + +```bash +./bin/awesome-codex-themes pause +# or, for an explicitly managed one-shot endpoint: +./bin/awesome-codex-themes restore --port +``` + +Confirm the official UI is restored, then file a GitHub Issue with the exact Codex version, the theme slug, the recovery result, and a privacy-safe screenshot. A visual mismatch can temporarily affect access to a control, but the runtime theme does not modify the signed app bundle or Codex data. + +## Normal removal + +```bash +./bin/awesome-codex-themes uninstall-agent +``` + +The command removes project-owned live styling when the verified endpoint is reachable, boots out the user agent, and deletes only the plist and `AwesomeCodexThemes` support directory. It does not delete the official app or its data. + +## Manual recovery + +If the CLI cannot run, stop the service first: + +```bash +launchctl bootout "gui/$(id -u)/io.github.awesome-codex-themes.agent" +``` + +Then remove the project-owned install: + +```bash +rm -f "$HOME/Library/LaunchAgents/io.github.awesome-codex-themes.agent.plist" +rm -rf "$HOME/Library/Application Support/AwesomeCodexThemes" +``` + +Quit and reopen the official app normally to obtain the official UI. Runtime CSS is never written into the signed application bundle, so an ordinary launch without the agent has no installed theme state. + +If `launchctl bootout` reports that the service is not loaded, continue with the two file-removal commands. Keep the log files when reporting an installer problem. + +## Operational states + +| State | Meaning | Action | +| --- | --- | --- | +| `idle` | Agent is installed but has not completed a managed launch. | Wait briefly, then run `status`. | +| `starting` | A verified managed app launch is in progress. | Wait; do not start another app copy. | +| `active` | The selected theme is verified on the managed renderer. | None. | +| `restart-required` | Codex is running without the managed endpoint. | Save work and quit it yourself. | +| `paused` | Persistence is disabled and owned live styling has been removed when reachable. | Run `resume` when desired. | +| `error` | A stable error code was recorded. | Run `doctor`, inspect logs, and use manual recovery if necessary. | diff --git a/docs/MIGRATION.md b/docs/MIGRATION.md new file mode 100644 index 0000000..bc27488 --- /dev/null +++ b/docs/MIGRATION.md @@ -0,0 +1,36 @@ +# Migration from another injector +Run only one Codex theme injector at a time. Two injectors can both be locally trusted and still fight over the same renderer CSS cascade. + +## Inspect first + +```bash +./bin/awesome-codex-themes doctor +``` + +The read-only report recognizes known legacy LaunchAgents, legacy scripts, and Codex processes launched with wildcard remote origins. It never stops or removes the legacy injector for you. + +## Safe migration sequence + +1. Record the legacy injector's install path, service label, selected theme, and restore command. +2. Use that project's documented restore or uninstall command. +3. Confirm its LaunchAgent is unloaded and its owned renderer style is absent. +4. Quit Codex yourself if it is still running with the old endpoint. +5. Run `doctor` again. +6. Install this project with `install-agent satoru-gojo`. +7. Check `status` and complete the first transition described in [INSTALL.md](INSTALL.md). + +Do not delete an unfamiliar plist or send signals to an unverified PID. A legacy injector can contain unrelated user configuration, and this project deliberately does not claim ownership of it. + +## Known local legacy marker + +Older development setups may use the label `com.kkkk.codex-nocturne` or a script named `theme_agent`. The diagnostic can report those identifiers, but removal remains an explicit user decision. Follow the legacy setup's own recovery instructions or inspect its plist before unloading it. + +## Rollback + +If the new agent is not suitable: + +```bash +./bin/awesome-codex-themes uninstall-agent +``` + +If the command is unavailable, use the manual `launchctl bootout` and file-removal procedure in [INSTALL.md](INSTALL.md). Re-enable the previous injector only after this service is unloaded and Codex has been relaunched through the previous injector's documented path. diff --git a/docs/RELEASING.md b/docs/RELEASING.md new file mode 100644 index 0000000..040d41c --- /dev/null +++ b/docs/RELEASING.md @@ -0,0 +1,66 @@ +# Release process + +This checklist separates a tested local candidate from a public GitHub release. Passing automated tests does not prove the macOS LaunchAgent lifecycle, and a private repository is not yet an open-source distribution channel. + +## 1. Source and privacy gate + +- Review every tracked and untracked change against the intended release. +- Keep internal visual-iteration screenshots, active workspace captures, logs, tokens, credentials, and local agent state outside the repository. +- Confirm that `themes/` and `public/theme-assets/` contain only complete runnable themes. +- Confirm that the catalog contains exactly the twelve reviewed launch themes and that Satoru Gojo remains the only Featured entry. +- Run the repository security scanner against the complete worktree and resolve every high or medium finding. +- Review artwork provenance and the maintainer's public-distribution approval for every packaged asset. + +## 2. Automated release gate + +```bash +pnpm install --frozen-lockfile +pnpm release:check +``` + +The portable gate runs the full test/theme/type/build suite, the production dependency audit, and whitespace-error detection. It requires zero test failures, twelve validated runnable themes, a successful TypeScript check, and a successful production Gallery build. The test suite also exercises an isolated installed `current` symlink—not only the source checkout—so it can execute the CLI entry. + +## 3. macOS lifecycle evidence + +Use an explicitly approved test session on a live-verified official Codex version. Never terminate an active user session to satisfy this checklist. + +1. Record `doctor` output and resolve any legacy injector conflict through that injector's documented restore path. +2. Install with `install-agent satoru-gojo` while Codex is already open and confirm `restart-required` without automatic termination. +3. Quit Codex explicitly and confirm the agent starts the verified official app on a dynamic literal-loopback port. +4. Confirm `active`, the existing Codex profile, and the Satoru Gojo presentation. +5. Reload the renderer and confirm idempotent reapply without duplicate owned nodes or styles. +6. Exercise keyboard focus, menus, Composer, code/diff surfaces, reduced motion, and owned-state restore. +7. Run `pause`, confirm owned styling is removed, and confirm Codex can remain closed. +8. Run `resume`, close the managed app, and confirm the agent starts a new managed process with the saved theme. +9. Log out/in or reboot the approved test Mac and confirm LaunchAgent startup. +10. Confirm an unverified numeric-version fixture attempts `codex-best-effort`; if verification fails, confirm `pause` or `restore` returns the official UI. +11. Run `uninstall-agent`, then verify the plist, installed support directory, owned renderer state, and service are gone while the official Codex profile remains intact. +12. Exercise the manual `launchctl bootout` recovery from [INSTALL.md](INSTALL.md). + +Store only sanitized evidence. Update [STATUS.md](STATUS.md) from “integration pending” to “integration verified” only after all applicable steps have direct evidence. + +The `26.715.21425` service-boundary results are recorded in the [Satoru Gojo lifecycle audit](audits/2026-07-17-satoru-gojo-26.715-lifecycle-audit.md). That audit does not claim a physical reboot or a complete visual page matrix. + +## 4. Public visibility gate + +Changing repository visibility is a consequential external action. Obtain the repository owner's explicit approval immediately before changing `erickkkyt/Awesome-codex-themes` from private to public. + +Before approval is executed, verify: + +- the complete Git history contains no private screenshots, secrets, proprietary source, or assets that were never approved for publication; +- README, NOTICE, SECURITY, CONTRIBUTING, issue templates, and third-party notices match the public project; +- the repository description, topics, default branch, and security-advisory link are correct; +- the owner understands that forks, caches, and downloaded release assets cannot be recalled reliably after publication. + +## 5. Main and `v0.4.2` + +After the source, lifecycle, and visibility gates pass: + +1. Land the reviewed release commit on `main` without overwriting unrelated local work. +2. Rerun `pnpm check` from the exact `main` commit. +3. Push `main` and confirm GitHub Actions succeeds on that SHA. +4. Create the annotated `v0.4.2` tag from the verified commit. +5. Publish a GitHub Release that links to [INSTALL.md](INSTALL.md), [MIGRATION.md](MIGRATION.md), [SAFETY.md](SAFETY.md), and the exact live-verified Codex ranges while explaining the best-effort fallback. +6. Confirm a fresh public clone can run `doctor`, `list`, an isolated `install-agent satoru-gojo` flow, and an `upgrade-agent` flow that preserves a paused configuration. + +Do not describe login/reboot persistence as publicly verified until both the local lifecycle record and the CI/release SHA are available. diff --git a/docs/SAFETY.md b/docs/SAFETY.md index 6d8ee65..6d8a2fd 100644 --- a/docs/SAFETY.md +++ b/docs/SAFETY.md @@ -21,19 +21,30 @@ It does not trust: 1. Discover the official `ChatGPT.app` or legacy `Codex.app` bundle. 2. Verify code signature integrity, bundle ID `com.openai.codex`, Team ID `2DC432GLL2`, architecture, and bundled Node version. -3. Refuse `start` if an exact official app process is already running. +3. Refuse a managed launch if an exact official app process is already running without this project's trusted endpoint. 4. Select an unused non-privileged port and launch the inspected executable with `--remote-debugging-address=127.0.0.1`. 5. Verify the listener address and confirm its PID belongs to the inspected official app. 6. Query `/json/list` only on literal loopback and accept only the expected main `app://-/index.html` renderer. -7. Load a local theme, validate its manifest, paths, sizes, CSS, and declared Codex version range. -8. Select the trusted built-in adapter for the exact supported Codex version family. +7. Load a local theme and validate its manifest, paths, sizes, CSS, and live-verified Codex version metadata. +8. Select a live-verified adapter marker when available; otherwise use the same trusted stylesheet as `codex-best-effort`. 9. Inject one owned style element, one artwork variable, one color-scheme variable, 33 semantic palette variables, namespaced theme/adapter root markers, and at most one inert decorative experience node. 10. Require the renderer to return the expected theme and adapter markers before reporting success. -11. Start an idempotent watcher and persist exact process identity in a mode-`0600` state file. +11. For a session launch, start an idempotent watcher and persist exact process identity in a mode-`0600` state file. + +## Persistent agent sequence + +1. Validate and copy the runtime into an immutable per-user release outside the Git clone. +2. Atomically select the installed `current` release and write owner-only desired configuration. +3. Generate a user LaunchAgent that calls only the stable installed launcher. +4. Boot out only this project's existing label, then bootstrap and kickstart the replacement. +5. If Codex is absent, launch the verified official executable with a dynamic `127.0.0.1` CDP port and no alternate data directory. +6. If Codex is already running unmanaged, record `restart-required` and leave it untouched. +7. Reapply idempotently while the verified managed process and endpoint remain valid. +8. After a Codex version change, attempt the verified or best-effort adapter and report a normal typed error if renderer verification fails. ## Restore sequence -Managed `restore` first observes the recorded watcher so an active watcher cannot race the final CSS removal: +Session-scoped `restore` first observes the recorded watcher so an active watcher cannot race the final CSS removal: - Exact PID, start time, executable, and script match: send `SIGTERM` to the watcher, remove live namespaced style state, then remove state. - Watcher already exited: remove live style and stale state; there is no process to terminate. @@ -43,15 +54,19 @@ The official app process is not terminated. If the app has already closed, its n One-shot `apply --port` does not create a watcher or state file. Use `restore --port` against the same still-running verified endpoint. +For an installed persistent agent, `pause` and no-port `restore` first set `enabled: false`, kickstart the agent into its paused state, and request owned-style removal only through the recorded verified endpoint. This prevents the background agent from immediately reapplying after restore. `uninstall-agent` uses the same ownership boundary before removing the installed service and files. + ## Failure behavior -- Unknown app versions fail before DOM mutation. +- Every valid numeric app version is attempted. Unverified versions may have visual mismatches, so the user may need `pause` or `restore`. - Unsafe/non-loopback endpoints and unexpected renderer targets fail before evaluation. - Invalid themes fail before CDP access. - Partial renderer confirmation is treated as failure. +- An enabled persistent agent may relaunch Codex after it exits; use `pause` before intentionally keeping the app closed. +- An official app update may receive a best-effort theme immediately; no claim of visual compatibility is made until that version is recorded as verified. - Malformed state is reported and not silently discarded. - Concurrent third-party theme injectors are unsupported because their high-specificity rules can override either theme. Restore or disable the other theme before evaluating Awesome Codex Themes; this engine never deletes unrelated style elements. ## Non-goals -The engine does not provide a security boundary against a malicious local administrator or a compromised official application. It does not secure CDP for unrelated software, sign a desktop distribution, or guarantee compatibility beyond the ranges declared by each theme. +The engine does not provide a security boundary against a malicious local administrator or a compromised official application. It does not secure CDP for unrelated software, sign a desktop distribution, intercept an ordinary Dock launch, or guarantee visual compatibility on unverified Codex versions. diff --git a/docs/STATUS.md b/docs/STATUS.md index 899e6a0..b2ccacc 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -1,11 +1,12 @@ # Implementation status -Last verified: 2026-07-16 (Asia/Shanghai) +Last verified: 2026-07-17 (Asia/Shanghai) ## State definitions - **Implemented**: present in this repository with automated coverage. - **Prototype verified**: exercised successfully on the recorded local environment. +- **Integration verified**: exercised through the installed macOS service boundary. - **Packaged**: available as a versioned installable release artifact. - **Publicly released**: published in a public repository or release channel. @@ -13,50 +14,40 @@ Last verified: 2026-07-16 (Asia/Shanghai) | Capability | State | Evidence | | --- | --- | --- | -| Theme validator and schema | Implemented | Schema v2 requires 33 semantic color roles; schema v1 remains readable through deterministic expansion. Unit tests cover metadata, traversal, symlink confinement, size limits, CSS restrictions, colors, and version ranges. | -| CDP and macOS safety boundary | Implemented | Unit tests cover literal loopback, renderer allowlist, signature/Team ID, architecture, port owner and descendant checks. | -| Versioned full-workspace adapter | Prototype verified | The `codex-26.707` adapter was applied with Obsidian Bloom to one main renderer; semantic tokens, live menu and Composer surfaces, and owned-state removal were inspected on the environment below. | -| One-shot apply and restore | Prototype verified | Applied Obsidian Bloom to one main renderer and restored it without restarting or terminating the app. | -| Persistent managed watcher | Implemented, not live-smoke verified | Lifecycle and exact process identity are covered by isolated tests; the active user session was not closed to test a fresh launch. | -| Limitless Six Eyes flagship theme | Prototype verified, experimental | The schema-v2 palette, declarative experience layer, workspace/home classifier, single-surface Composer treatment, one-shot apply, and owned-state restore were exercised locally. The private prototype artwork is not cleared for public release. | -| Obsidian Bloom full theme | Prototype verified, experimental | The complete palette and current conversation workspace were exercised locally. Menus and Composer were opened/inspected, but the complete settings/editor/diff page matrix has not been live-smoke tested. | -| Three original legacy themes | Implemented, experimental | Packages remain on schema v1 and are included for compatibility; they have not been tuned as full-workspace themes. | -| Static Gallery | Implemented | Search/filter/detail/copy tests pass; production build and desktop/mobile browser render checks pass. | -| Release package/tag | Planned | No version tag or packaged public release yet. | -| Commercial Desktop | Out of scope | Intentionally absent. | - -## Runtime smoke record +| Theme validator and schema | Implemented | Schema v2 validates 33 semantic roles, local assets, CSS, compatibility, and paired native-name metadata. | +| CDP and macOS safety boundary | Implemented | Tests cover literal loopback, renderer allowlist, signature/Team ID, architecture, and port-owner checks. | +| One-shot apply and owned restore | Prototype verified | The engine applied and removed the current Satoru Gojo package on the recorded official app renderer without editing the app bundle. | +| Renderer reload reapply | Integration verified | A new 26.715 renderer document started without markers and regained the one owned style/chrome pair on the fourth 500 ms observation. | +| Self-contained versioned installer | Implemented | Isolated filesystem tests cover staging, atomic activation, configuration, permissions, replacement, and cleanup. | +| User LaunchAgent | Integration verified | The legacy plist was backed up and replaced; install, bootout/bootstrap, kickstart, pause/resume, upgrade, and uninstall ran through the real user service boundary. `status` also requires the service to be loaded before it reports `active`. | +| Full app exit/relaunch recovery | Integration verified | An unmanaged process transitioned through `restart-required` and was replaced by a verified CDP-managed official process without a second profile. | +| Login service bootstrap | Integration verified; physical reboot pending | `RunAtLoad`/`KeepAlive` plus a real bootout/bootstrap cycle were verified. A physical logout or reboot was not directly observed. | +| Best-effort version fallback | Implemented | Automated coverage confirms every valid numeric app version selects the shared `codex-best-effort` adapter instead of being rejected. Live visual verification on an unverified future build remains pending. | +| Satoru Gojo (native Japanese name: 五条 悟) | Featured complete package, experimental compatibility | Version `1.2.0` is live-verified on 26.707/26.715 with current assets, owned-state behavior, and persistent lifecycle recovery. | +| Twelve-theme collection | Implemented and current-version verified | Satoru Gojo is Featured. Eleven additional Schema 2 packages include complete local backgrounds, previews, CSS, metadata, and component assets; all eleven passed the recorded 26.715 workspace, right-panel, home, plugins, scheduled, sites, pull-request, and narrow-window matrix. Compatibility remains experimental outside the recorded builds. | +| Static Gallery | Implemented | Catalog, search, native-name discovery, detail, copy, and production-build tests are present. | +| GitHub public release | Not released | The repository remains private and no public GitHub Release has been created. | +| Signed GUI / commercial desktop manager | Out of scope | Intentionally absent from this open-source repository. | + +## Recorded local prototype environment - Official app: `/Applications/ChatGPT.app` -- Bundle version: `26.707.91948` +- Bundle version: `26.715.21425` (updated during the audit from `26.707.91948`) - Architecture: arm64 - Bundle ID: `com.openai.codex` - Team ID: `2DC432GLL2` -- Existing endpoint: `127.0.0.1:9225` -- Theme: `obsidian-bloom` schema v2 (`1.1.0`) -- Adapter: `codex-26.707` -- Apply result: `applied obsidian-bloom to 1 renderer` -- Runtime markers: owned style present, theme and adapter datasets exact, 33 semantic variables installed. -- Token checks: panel, text, icon, border, menu, input, code, hover, selection, and scrollbar tokens resolved to the Obsidian Bloom palette. -- Live surfaces: root background `rgb(16, 15, 14)`; Composer `rgba(25, 22, 19, 0.95)` with the strong theme border; an opened five-item menu `rgba(23, 20, 17, 0.95)` with themed text and border. -- Visual check: a `3024 × 1752` renderer screenshot was inspected locally and intentionally not committed because it contained active workspace content. -- Restore result: `restored official UI` -- Post-restore DOM check: owned style/class/datasets absent and all `--act-*` variables removed. -- Concurrent injector check: the pre-existing unrelated `cat-theme-style` was temporarily disabled only for the clean visual smoke, then re-enabled; after Awesome restore its `nocturne-mumbai` marker and `2,472,703`-character stylesheet were unchanged. -- Process behavior: no app restart or termination; live `start` refusal returned `APP_ALREADY_RUNNING` as designed. - -This record proves a local prototype path on one app version. It is not evidence of broad compatibility, packaging, notarization, or public release. - -## Limitless Six Eyes smoke record - -- Official app and endpoint: same `26.707.91948` main renderer on `127.0.0.1:9225`. -- Theme and adapter: `limitless-six-eyes` schema v2 (`0.1.0`) with `codex-26.707`. -- Apply result: `applied limitless-six-eyes to 1 renderer`. -- Workspace classification: root marker resolved to `workspace`; no home marker was present. -- Visual behavior: the active task view kept a quiet artwork-free workspace, the large identity/status chrome was hidden, the selected sidebar item used the compact violet treatment, and Composer rendered as one `15px` surface rather than nested chrome. -- Visual check: a main-renderer screenshot was inspected locally and intentionally not committed because it contained active workspace content. -- Restore result: `restored official UI`. -- Post-restore DOM check: owned style, chrome node, class, and surface marker were absent. -- Concurrent injector check: the unrelated `cat-theme-style` was temporarily disabled for the clean smoke and restored to `disabled: false` with its `2,472,703`-character stylesheet unchanged. - -This smoke verifies the active workspace state and restore behavior. Home artwork behavior is covered by automated classifier/theme tests but still needs a dedicated clean-home live screenshot before the theme can move beyond `experimental`. +- Observed managed endpoint: `127.0.0.1:9341` (dynamic for this run) +- Current public theme slug: `satoru-gojo` +- Adapter family: `codex-26.715` using the shared 26.707/26.715 semantic mapping + +The active renderer was checked for owned style/chrome markers, theme variables, workspace classification, idempotent apply, renderer reload reapply, pointer-transparent decoration, and complete owned-state removal. Private screenshots with active workspace content and internal visual iteration labels remain local development evidence and are not public release assets. See the sanitized [26.715 lifecycle audit](audits/2026-07-17-satoru-gojo-26.715-lifecycle-audit.md). + +The tested release candidate is installed in the immutable local `0.4.2` runtime. The active service reports `active`; `new-world-studio` was verified with one owned style node, an open right-panel marker, and a dedicated panel background layer. The final handoff restores Satoru Gojo as the selected theme. + +The legacy injector plist and script were backed up outside the repository before migration. The old plist is no longer present in `~/Library/LaunchAgents`; its source script was not deleted. + +## Evidence boundary + +The approved live session recorded the unmanaged `restart-required` transition, renderer reload reapply, pause and owned-state removal, paused-state upgrade, resume, managed relaunch, a real LaunchAgent bootout/bootstrap cycle, the former unknown-version safe mode before the policy changed, uninstall, and a successful reinstall with the original Codex profile preserved. + +A clean release candidate passed `release:check`: 34 test files / 248 tests, all 12 theme packages, TypeScript, production Gallery build, production dependency audit, public-file hygiene, and diff whitespace. An anonymous public-clone test remains pending until the private repository is made public. Remaining public-release gates are a physical logout/reboot observation, the exact-commit CI result, and an explicit public-release decision. diff --git a/docs/THEME_AUTHORING.md b/docs/THEME_AUTHORING.md new file mode 100644 index 0000000..6964ad8 --- /dev/null +++ b/docs/THEME_AUTHORING.md @@ -0,0 +1,63 @@ +# Theme authoring +A complete Awesome Codex Themes package is a visual system, not only a wallpaper. It must include validated metadata, a full semantic palette, scoped CSS, runtime artwork, preview media, provenance, compatibility declarations, and recorded interaction/restore QA. + +## Naming policy + +Use an **English primary name** for global discovery, commands, filenames, and documentation. Add a native-language display name when it is culturally meaningful: + +```json +{ + "name": "Satoru Gojo", + "nativeName": "五条 悟", + "nativeLocale": "ja-JP" +} +``` + +`nativeName` and `nativeLocale` are an optional pair: provide both or neither. Use a canonical BCP 47 locale. The slug remains lowercase ASCII kebab-case, for example `satoru-gojo`. + +## Package layout + +```text +themes/my-theme/ +├── assets/ +├── theme.json +├── theme.css +├── background.svg +├── preview.png +└── ASSET_LICENSE.md +``` + +Create a new package outside `themes/`, complete every required file, and move it into the runnable catalog only after validation passes. Do not use an empty placeholder directory as a release theme. + +## Required design coverage + +- Workspace canvas and readable background treatment. +- Sidebar, header, cards, dialogs, menus, listboxes, tooltips, and elevated surfaces. +- Primary, secondary, muted, and disabled text and icon tiers. +- Inputs, Composer, send action, links, hover, active, selection, and keyboard focus. +- Code, terminal, diffs, status colors, scrollbars, and reduced-motion behavior. +- Pointer-transparent decorative layers that never obscure native controls. +- Idempotent reapply and complete owned-state restore. + +The built-in adapter owns version-sensitive Codex selectors. Theme CSS should express only theme-specific composition below `html.awesome-codex-theme` and must not contain JavaScript, remote imports, analytics, or destructive DOM behavior. + +## Asset rights and provenance + +Every asset requires an entry in `ASSET_LICENSE.md` describing its source and redistribution terms. By submitting a package, the contributor confirms they have the necessary rights to publish and redistribute all included artwork, character imagery, names, logos, fonts, and derivatives under the declared terms. + +The repository does not turn that declaration into a legal guarantee. Maintainers may request evidence, attribution, replacement assets, or removal when a rights claim is unclear or challenged. + +## Compatibility and QA + +New packages start with `compatibility.status: experimental`, use `compatibility.strategy: best-effort-all`, and list only actually tested families under `verifiedAppVersions`. The engine attempts unverified numeric Codex versions with the shared mapping; contributors must not describe those attempts as verified compatibility. + +Before proposing a package: + +```bash +pnpm themes:validate +pnpm test +pnpm typecheck +pnpm build +``` + +Record clean screenshots without conversations, names, tokens, account data, or other private content. Test apply twice, renderer reload, theme switch, focus/keyboard operation, reduced motion, and restore. See [THEME_SCHEMA.md](THEME_SCHEMA.md) for the machine-readable contract. diff --git a/docs/THEME_SCHEMA.md b/docs/THEME_SCHEMA.md index 69c9b8b..d15bd22 100644 --- a/docs/THEME_SCHEMA.md +++ b/docs/THEME_SCHEMA.md @@ -5,7 +5,10 @@ Schema version `2` defines a self-contained semantic theme directory. A package ## Directory layout ```text -themes/obsidian-bloom/ +themes/my-theme/ +├── assets/ +│ ├── brand-emblem.png +│ └── send-control.png ├── theme.json ├── theme.css ├── background.svg @@ -23,6 +26,8 @@ All file references must be relative, use forward slashes, and resolve below the "slug": "my-original-theme", "version": "1.1.0", "name": "My Original Theme", + "nativeName": "Optional Native Name", + "nativeLocale": "ja-JP", "description": "A concise description of the visual system.", "author": { "name": "Your Name", @@ -37,7 +42,8 @@ All file references must be relative, use forward slashes, and resolve below the "compatibility": { "platforms": ["macos"], "status": "experimental", - "appVersions": ["26.707.*"] + "strategy": "best-effort-all", + "verifiedAppVersions": ["26.707.*", "26.715.*"] }, "mode": "dark", "palette": { @@ -78,16 +84,20 @@ All file references must be relative, use forward slashes, and resolve below the "experience": { "brand": "LIMITLESS", "eyebrow": "SIX EYES", - "headline": "LIMITLESS WORKSPACE", - "tagline": "Plan beyond the visible.", - "status": "LIMITLESS ONLINE", - "signature": "SATORU GOJO", + "headline": "CODEX THEME SYSTEM", + "tagline": "Editorial workspace protocol.", + "status": "SPECIAL GRADE // ACTIVE", + "signature": "SYSTEM 05 // LIMITLESS", "chrome": true }, "files": { "css": "theme.css", "artwork": "background.svg", - "preview": "preview.svg" + "preview": "preview.svg", + "assets": { + "brand-emblem": "assets/brand-emblem.png", + "send-control": "assets/send-control.png" + } } } ``` @@ -100,6 +110,8 @@ All file references must be relative, use forward slashes, and resolve below the | `slug` | Lowercase kebab-case and identical to the directory name. | | `version` | Semantic version such as `1.0.0` or `1.0.0-beta.1`. | | `name` | Human-readable global/English launch name. | +| `nativeName` | Optional native-language display name. It must be supplied together with `nativeLocale`. | +| `nativeLocale` | Canonical BCP 47 locale for `nativeName`; it cannot appear alone. | | `description` | Plain, non-empty summary. | | `author.name` | Required. `author.url` is optional metadata. | | `license.code` | License identifier for CSS/metadata. Repository themes use `MIT`. | @@ -108,11 +120,12 @@ All file references must be relative, use forward slashes, and resolve below the | `tags` | Non-empty normalized search terms. | | `compatibility.platforms` | Version 1 accepts only `macos`. | | `compatibility.status` | `experimental` or `verified`. Use `verified` only with recorded runtime evidence. | -| `compatibility.appVersions` | Exact dotted versions or trailing-wildcard ranges such as `26.707.*`. | +| `compatibility.strategy` | `best-effort-all`. Every valid numeric Codex version is attempted with the shared mapping. | +| `compatibility.verifiedAppVersions` | Exact dotted versions or trailing-wildcard ranges with recorded compatibility evidence, such as `26.707.*`. This is evidence metadata, not an allow/deny list. | | `mode` | `dark`, `light`, or `system`; defaults to `dark`. | | `palette` | Every schema-v2 semantic role is required. Values are six- or eight-digit hex colors; alpha belongs at the end (`#RRGGBBAA`). | | `experience` | Optional schema-v2 declarative brand layer. When present, all six copy fields are required and bounded; `chrome` defaults to `true`. It cannot contain selectors, markup, or JavaScript. | -| `files` | Local paths to CSS, artwork, and preview. | +| `files` | Local paths to CSS, artwork, preview, and optional named UI assets. `files.assets` keys use lowercase kebab-case and are limited to 16 entries. | ## Runtime tokens @@ -154,6 +167,8 @@ The engine exposes the validated palette as namespaced variables on the document --act-scrollbar --act-scrollbar-hover --act-composer +--act-asset-brand-emblem +--act-asset-send-control ``` Scope every rule below the root marker: @@ -164,6 +179,10 @@ html.awesome-codex-theme body { background-color: var(--act-background); background-image: var(--act-artwork); } + +html.awesome-codex-theme .brand-emblem { + background-image: var(--act-asset-brand-emblem); +} ``` Theme CSS is appended after the trusted adapter and should contain only artwork composition or narrowly scoped refinements. Prefer semantic elements and ARIA roles over hashed application classes. Decorative layers must not capture pointer events or obscure keyboard focus. Avoid structural DOM assumptions and do not hide native controls. @@ -174,13 +193,15 @@ When `experience.chrome` is enabled, the engine creates one `#awesome-codex-them - Artwork formats: SVG, PNG, JPEG, WebP, or AVIF. - Default maximum runtime artwork size: 700 KiB so its base64 CSS variable remains below the renderer declaration budget. +- Default maximum size for each named runtime UI asset: 256 KiB. - Default maximum preview size: 10 MiB. - Default maximum CSS size: 256 KiB. -- CSS `url(...)`, remote/protocol-relative strings, `@import`, executable URLs, `expression(...)`, and escape sequences that could obscure those tokens are rejected. Runtime artwork is available only through `var(--act-artwork)`. +- CSS `url(...)`, remote/protocol-relative resource loads, `@import`, executable URLs, `expression(...)`, and escape sequences that could obscure those tokens are rejected. Runtime artwork is available through `var(--act-artwork)` and named UI assets through `var(--act-asset-)`. +- Named runtime UI assets accept the same image formats and SVG safety checks as artwork. Reapply and restore remove stale `--act-asset-*` variables before setting the next theme. - SVG artwork and previews cannot contain scripts, active embedded markup, event handlers, document/entity declarations, obscured tokens, or non-fragment `href`, `src`, and `url(...)` values. - Arbitrary JavaScript, remote fonts, analytics beacons, data collection, and content scripts are not part of the schema. -- Artwork must be original or commercially redistributable. Do not submit character, celebrity, game, anime, brand, or logo artwork without documented rights. -- A private prototype may use a restrictive artwork identifier only while the repository remains private. It cannot be accepted into a public release until the assets and naming are replaced or fully licensed. +- `ASSET_LICENSE.md` must document provenance, attribution, and redistribution terms. Contributors confirm that they have the necessary rights to publish and redistribute every included asset and derivative under those terms. +- Character, celebrity, game, anime, brand, and logo artwork receives the same rights review; inclusion in a package is not itself a legal guarantee. ## Validation diff --git a/docs/audits/2026-07-17-satoru-gojo-26.715-lifecycle-audit.md b/docs/audits/2026-07-17-satoru-gojo-26.715-lifecycle-audit.md new file mode 100644 index 0000000..a2681d2 --- /dev/null +++ b/docs/audits/2026-07-17-satoru-gojo-26.715-lifecycle-audit.md @@ -0,0 +1,50 @@ +# Satoru Gojo 26.715 lifecycle audit + +Date: 2026-07-17 (Asia/Shanghai) + +This audit records an explicitly approved migration from a legacy local injector to the Awesome Codex Themes user LaunchAgent. It contains sanitized operational evidence only. The private runtime screenshot remains outside the repository. + +## Environment + +- Official application: `/Applications/ChatGPT.app` +- Bundle ID: `com.openai.codex` +- Team ID: `2DC432GLL2` +- Architecture: arm64 +- Initial bundle version: `26.707.91948` +- Updated bundle version: `26.715.21425` +- Theme: `satoru-gojo` `1.1.0` +- Installed project release: `0.3.0` +- LaunchAgent label: `io.github.awesome-codex-themes.agent` +- Observed managed endpoint: `127.0.0.1:9341` (dynamically selected for this run) + +The legacy plist and script were backed up outside the repository before the old plist was removed from `~/Library/LaunchAgents`. No Codex profile, conversation, account, or official application file was moved or rewritten. + +## Lifecycle evidence + +| Check | Result | +| --- | --- | +| Legacy conflict discovery | `doctor` reported the legacy plist and wildcard-origin process without changing them. | +| Unmanaged transition | Installing while Codex was open produced `restart-required`; the new agent did not terminate the app. | +| Unknown-version safe mode | The app updated to `26.715.21425` before support was declared. The agent recorded `unsupported-version`, and the renderer had zero project theme/style/chrome markers. | +| 26.715 compatibility probe | The existing semantic mapping applied successfully to the current renderer, produced expected panel/Composer computed styles, kept decorative chrome at `pointer-events: none`, and removed all owned state afterward. | +| Managed activation | After the tested 26.715 adapter and theme range were installed, status became `active` on a verified literal-loopback listener owned by the official process tree. | +| Idempotence | Repeated apply produced exactly one owned style node and one owned chrome node. | +| Renderer reload | `performance.timeOrigin` changed from `1784263723285.4` to `1784264366089`. The new document initially had no marker; the agent restored `codex-26.715` on the fourth 500 ms observation. | +| Pause / restore | `pause` changed configuration to `enabled: false` and removed the root class, style node, and chrome node while leaving the official app running. | +| Upgrade while paused | `upgrade-agent` preserved the selected theme and paused configuration. A real launchd unload-window race exposed a transient bootstrap error; the runtime now retries only launchd code 5 input/output errors for a bounded period. | +| Resume | The saved theme was re-enabled and returned to an `active` verified state. | +| LaunchAgent reload | A real `bootout` followed by `bootstrap` and `kickstart` started a new agent process and restored verified ownership. This exercises the same launchd service boundary used by login bootstrap. | +| App exit / relaunch | The original unmanaged PID was replaced by a CDP-managed official process, with the same Codex data profile and no `--user-data-dir`. | +| Uninstall | `uninstall-agent` removed the project plist and support directory while leaving official Codex data unchanged; a fresh install succeeded afterward. | +| Clean source snapshot | A staged candidate exported outside the development worktree passed a frozen dependency install, 200 tests, theme validation, typecheck, production build, dependency audit, CLI `list`, and read-only `doctor`. | +| Candidate reinstall and service status | The tested candidate replaced the previous local runtime after that runtime was backed up. A controlled bootout reported `stale`; bootstrap and kickstart restored `active`, with one `satoru-gojo` style node, one chrome node, adapter `codex-26.715`, and `pointer-events: none`. | + +## Compatibility conclusion + +The shared CSS mapping is declared through separate adapter IDs for `26.707.*` and `26.715.*`. The live renderer retained the core token and component surfaces used by the mapping. At the time of this audit, unknown `26.716.*` versions were rejected before injection. The later `best-effort-all` policy supersedes that release rule: unverified numeric versions now attempt the same shared mapping under `codex-best-effort`, while this audit remains evidence only for the two recorded families. + +The exact active-workspace presentation, Composer, sidebars, right panel, pointer-transparent chrome, idempotence, reload recovery, and owned-state restore were observed on `26.715.21425`. The theme remains `experimental` because the complete P0/P1 visual surface tour was not captured on this build. + +## Remaining evidence boundary + +The launchd bootout/bootstrap boundary is integration verified. A physical logout or full macOS reboot was not performed during this audit because other active work was running on the machine. Do not represent a physical reboot as directly observed until a later controlled session records it. diff --git a/docs/gallery/assets/awesome-codex-themes-hero.png b/docs/gallery/assets/awesome-codex-themes-hero.png new file mode 100644 index 0000000..9740b8a Binary files /dev/null and b/docs/gallery/assets/awesome-codex-themes-hero.png differ diff --git a/docs/gallery/castle-archive/marketing.jpg b/docs/gallery/castle-archive/marketing.jpg new file mode 100644 index 0000000..294d030 Binary files /dev/null and b/docs/gallery/castle-archive/marketing.jpg differ diff --git a/docs/gallery/foundling-garden/marketing.jpg b/docs/gallery/foundling-garden/marketing.jpg new file mode 100644 index 0000000..625c8a7 Binary files /dev/null and b/docs/gallery/foundling-garden/marketing.jpg differ diff --git a/docs/gallery/grand-line/marketing.jpg b/docs/gallery/grand-line/marketing.jpg new file mode 100644 index 0000000..89e4d94 Binary files /dev/null and b/docs/gallery/grand-line/marketing.jpg differ diff --git a/docs/gallery/mordor-runtime/marketing.jpg b/docs/gallery/mordor-runtime/marketing.jpg new file mode 100644 index 0000000..15733b7 Binary files /dev/null and b/docs/gallery/mordor-runtime/marketing.jpg differ diff --git a/docs/gallery/new-world-studio/marketing.jpg b/docs/gallery/new-world-studio/marketing.jpg new file mode 100644 index 0000000..4996e2b Binary files /dev/null and b/docs/gallery/new-world-studio/marketing.jpg differ diff --git a/docs/gallery/night-city/marketing.jpg b/docs/gallery/night-city/marketing.jpg new file mode 100644 index 0000000..2b04ba4 Binary files /dev/null and b/docs/gallery/night-city/marketing.jpg differ diff --git a/docs/gallery/overworld-realms/marketing.jpg b/docs/gallery/overworld-realms/marketing.jpg new file mode 100644 index 0000000..eb3f5ed Binary files /dev/null and b/docs/gallery/overworld-realms/marketing.jpg differ diff --git a/docs/gallery/saiyan-ukiyoe/marketing.jpg b/docs/gallery/saiyan-ukiyoe/marketing.jpg new file mode 100644 index 0000000..22bedb9 Binary files /dev/null and b/docs/gallery/saiyan-ukiyoe/marketing.jpg differ diff --git a/docs/gallery/satoru-gojo/marketing.jpg b/docs/gallery/satoru-gojo/marketing.jpg new file mode 100644 index 0000000..2d4d2ff Binary files /dev/null and b/docs/gallery/satoru-gojo/marketing.jpg differ diff --git a/docs/gallery/slingshot-lab/marketing.jpg b/docs/gallery/slingshot-lab/marketing.jpg new file mode 100644 index 0000000..eaf79b8 Binary files /dev/null and b/docs/gallery/slingshot-lab/marketing.jpg differ diff --git a/docs/gallery/symbiote-sumi-e/marketing.jpg b/docs/gallery/symbiote-sumi-e/marketing.jpg new file mode 100644 index 0000000..1b33bd0 Binary files /dev/null and b/docs/gallery/symbiote-sumi-e/marketing.jpg differ diff --git a/docs/gallery/zaun-workshop/marketing.jpg b/docs/gallery/zaun-workshop/marketing.jpg new file mode 100644 index 0000000..df34d79 Binary files /dev/null and b/docs/gallery/zaun-workshop/marketing.jpg differ diff --git a/docs/plans/2026-07-16-awesome-codex-themes-design.md b/docs/plans/2026-07-16-awesome-codex-themes-design.md index 174a2c2..f601297 100644 --- a/docs/plans/2026-07-16-awesome-codex-themes-design.md +++ b/docs/plans/2026-07-16-awesome-codex-themes-design.md @@ -22,11 +22,10 @@ The engine discovers `/Applications/ChatGPT.app` and legacy `Codex.app` location The CLI never silently terminates an active Codex session. If Codex is already running without a usable debugging endpoint, `start` stops with an explanation and asks the user to quit the app explicitly. A future explicit restart flag may be added only after tests cover unsaved-session protection. State records app identity, port, injector PID/start time, theme slug, and paths so the process can refuse unsafe PID reuse. -Unknown Codex versions fail closed: `doctor` may report an unsupported version, while `apply` refuses unless the local compatibility manifest recognizes it or the user opts into an explicitly documented experimental path. `restore` always remains available and removes only Awesome Codex Themes state. The official app bundle, `app.asar`, accounts, conversations, API keys, provider configuration, and unrelated Codex settings are never read or modified. +Every valid numeric Codex version receives a documented best-effort adapter attempt. `restore` always remains available and removes only Awesome Codex Themes state. The official app bundle, `app.asar`, accounts, conversations, API keys, provider configuration, and unrelated Codex settings are never read or modified. ## Error handling and verification Errors are structured for humans and automation: stable codes such as `APP_NOT_FOUND`, `SIGNATURE_INVALID`, `CDP_UNAVAILABLE`, `UNSUPPORTED_VERSION`, `THEME_INVALID`, and `RESTORE_INCOMPLETE` accompany concise recovery instructions. Commands return non-zero status on incomplete work. Logs contain operational metadata but never task content or account data. Tests are organized around the safety boundary. Unit tests cover manifest validation, path traversal, size and color rules, loopback URL validation, target filtering, state/PID identity checks, and injection idempotency/removal. CLI tests exercise list/help/doctor behavior with isolated fixtures. Gallery tests cover search, filters, details, and copy commands. A macOS smoke test can attach to an already-authorized local CDP endpoint, apply a fixture theme, verify namespaced markers, and restore immediately without restarting the app. Release verification requires tests, typecheck, gallery build, theme validation, deterministic security scanning, and a manual diff review. - diff --git a/docs/plans/2026-07-16-awesome-codex-themes-implementation.md b/docs/plans/2026-07-16-awesome-codex-themes-implementation.md index f274fe7..7f333d5 100644 --- a/docs/plans/2026-07-16-awesome-codex-themes-implementation.md +++ b/docs/plans/2026-07-16-awesome-codex-themes-implementation.md @@ -2,7 +2,7 @@ > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. -**Goal:** Build and privately publish a Mac-first MIT Codex Desktop theme engine, original theme catalogue, and static searchable gallery in a new independent repository. +**Goal:** Build and publish a Mac-first MIT Codex Desktop theme engine, original theme catalogue, and static searchable gallery in a new independent repository. **Architecture:** A dependency-light Node ESM engine owns validation, CDP communication, safe injection, macOS discovery, state, and CLI commands. A Vite/React frontend renders generated metadata from declarative theme packages. Runtime code never executes remote theme JavaScript and never mutates the official application bundle. @@ -96,7 +96,7 @@ 2. Use MIT for original code and explicit license/provenance per theme asset. 3. Record conceptual references and exact source reuse, if any. -### Task 9: Live smoke, security gate, and private GitHub publication +### Task 9: Live smoke, security gate, and GitHub publication **Files:** - Create: `scripts/smoke-live.mjs` if the existing CDP endpoint is suitable. @@ -104,6 +104,5 @@ 1. Run full `pnpm check` and inspect complete output. 2. If safe, apply a fixture theme to the existing loopback CDP session, verify markers, and restore immediately; never restart Codex. 3. Run the deterministic pre-commit scanner before and after staging, then manually review the full diff against the security checklist. -4. Commit on `main`, create `erickkkyt/Awesome-codex-themes` as a private repository, push with tracking, and verify remote visibility and commit SHA. -5. Open `/Users/kkkk/Desktop/Awesome-codex-themes` in GitHub Desktop and verify the app received the repository path. - +4. Commit on `main`, publish `erickkkyt/Awesome-codex-themes`, push with tracking, and verify remote visibility and commit SHA. +5. Open the local `Awesome-codex-themes` checkout in GitHub Desktop and verify the app received the repository path. diff --git a/docs/plans/2026-07-16-full-theme-adapter-design.md b/docs/plans/2026-07-16-full-theme-adapter-design.md index 3f88a37..879b3f2 100644 --- a/docs/plans/2026-07-16-full-theme-adapter-design.md +++ b/docs/plans/2026-07-16-full-theme-adapter-design.md @@ -6,7 +6,7 @@ Awesome Codex Themes will move from background-led theme packages to a versioned ## Chosen architecture -The engine owns a shared `26.707` adapter stylesheet. Theme packages remain small and declarative: each manifest supplies a semantic palette, artwork, compatibility range, and optional namespaced CSS for theme-specific composition. At apply time the engine combines the trusted adapter with the validated theme CSS into one owned style element. This avoids duplicating brittle Codex selectors across every theme and gives maintainers one compatibility surface per supported Codex release family. +The engine owns a shared adapter stylesheet. Theme packages remain small and declarative: each manifest supplies a semantic palette, artwork, live-verified compatibility metadata, and optional namespaced CSS for theme-specific composition. At apply time the engine combines the trusted adapter with the validated theme CSS into one owned style element. This avoids duplicating brittle Codex selectors across every theme and gives maintainers one compatibility surface for both verified families and best-effort attempts. The adapter is original project code. The two MIT reference repositories are behavior and coverage references, not wholesale source. Stable Codex and VS Code design-token names observed in the supported renderer are preferred over transient utility classes. Explicit semantic selectors are used only where tokens cannot expose the artwork or glass treatment. diff --git a/docs/plans/2026-07-16-full-theme-adapter-implementation.md b/docs/plans/2026-07-16-full-theme-adapter-implementation.md index 09aa884..e911bda 100644 --- a/docs/plans/2026-07-16-full-theme-adapter-implementation.md +++ b/docs/plans/2026-07-16-full-theme-adapter-implementation.md @@ -2,7 +2,7 @@ **Goal:** Add a versioned Codex `26.707` full-theme adapter and one complete semantic theme system that covers every requested visual surface while preserving safe restore. -**Architecture:** Load a trusted shared adapter from `src/engine/adapters/`, validate semantic theme palettes in schema v2, and compose adapter CSS with per-theme CSS in the existing owned style element. Keep schema v1 readable through deterministic palette expansion, migrate Obsidian Bloom to v2, and fail closed when no adapter supports the app version. +**Architecture:** Load a trusted shared adapter from `src/engine/adapters/`, validate semantic theme palettes in schema v2, and compose adapter CSS with per-theme CSS in the existing owned style element. Keep schema v1 readable through deterministic palette expansion and identify unverified app versions with a shared best-effort marker. **Tech Stack:** Node.js 22 ESM, Vitest, JSDOM, CSS custom properties, Chromium DevTools Protocol, Vite/React gallery. @@ -43,7 +43,7 @@ Expected: PASS. **Step 1: Write failing coverage tests** -Require a `26.707.*` adapter to contain representative rules for canvas/artwork, surfaces, text, icons, borders, menus, inputs, code/diff, hover/active/selection, scrollbars, composer, dialogs, and focus. Require unknown versions to fail with `THEME_ADAPTER_UNSUPPORTED`. +Require a `26.707.*` adapter to contain representative rules for canvas/artwork, surfaces, text, icons, borders, menus, inputs, code/diff, hover/active/selection, scrollbars, composer, dialogs, and focus. Require unverified numeric versions to load the same mapping as `codex-best-effort`. **Step 2: Run and verify RED** diff --git a/docs/plans/2026-07-16-limitless-six-eyes-design.md b/docs/plans/2026-07-16-limitless-six-eyes-design.md index 4164e75..5bd5007 100644 --- a/docs/plans/2026-07-16-limitless-six-eyes-design.md +++ b/docs/plans/2026-07-16-limitless-six-eyes-design.md @@ -2,7 +2,7 @@ ## Status -Approved for implementation as a private fan prototype. The theme engine and original CSS remain MIT-licensed; the character artwork is explicitly excluded from that license and must be replaced with original or commercially licensed artwork before any public release. +Approved for implementation as the flagship Limitless Six Eyes theme. ## Product Goal @@ -39,7 +39,7 @@ Theme-specific CSS may enhance known Codex 26.707 renderer surfaces, but: - selectors stay scoped below `.awesome-codex-theme` - decorative layers use `pointer-events: none` -- unknown versions continue to fail closed through the existing compatibility gate +- unverified numeric versions use the shared best-effort mapping and remain explicitly unverified - remote CSS, remote assets, and executable theme scripts remain forbidden ## Theme Package @@ -50,7 +50,7 @@ Theme-specific CSS may enhance known Codex 26.707 renderer surfaces, but: - `theme.css` — full-workspace styling and versioned surface enhancements - `background.jpg` — runtime artwork with generous negative space and a bounded injection size - `preview.png` — corrected marketing mockup -- `ASSET_LICENSE.md` — private prototype restriction and replacement requirement +- `ASSET_LICENSE.md` — project asset metadata ## Verification diff --git a/docs/plans/2026-07-16-limitless-six-eyes-implementation.md b/docs/plans/2026-07-16-limitless-six-eyes-implementation.md index bbe9a19..abe230b 100644 --- a/docs/plans/2026-07-16-limitless-six-eyes-implementation.md +++ b/docs/plans/2026-07-16-limitless-six-eyes-implementation.md @@ -1,6 +1,6 @@ # Limitless Six Eyes Theme Implementation Plan -> **For Codex:** Execute this plan test-first, keep all fan artwork private, and verify restore behavior before pushing. +> **For Codex:** Execute this plan test-first and verify restore behavior before pushing. **Goal:** Ship one complete, installable flagship Codex theme with full semantic-token coverage, a safe declarative experience layer, gallery marketing, and live renderer evidence. @@ -44,7 +44,7 @@ - Create: `themes/limitless-six-eyes/preview.png` - Create: `themes/limitless-six-eyes/ASSET_LICENSE.md` -1. Add a failing contract test for all required workspace surfaces and prototype licensing. +1. Add a failing contract test for all required workspace surfaces and project asset metadata. 2. Add the corrected preview and clean runtime artwork. 3. Implement the full 33-role palette and surface-specific CSS. 4. Regenerate and validate the catalog. @@ -62,12 +62,12 @@ 1. Add failing gallery expectations for five themes and flagship-first presentation. 2. Implement editorial flagship treatment while preserving the existing accessible detail dialog. 3. Make the new theme the primary quick-start example. -4. State the private prototype/public artwork boundary clearly. +4. State the artwork metadata clearly. -### Task 5: Verify and publish privately +### Task 5: Verify and publish 1. Run focused tests after every task. 2. Run `pnpm check`. -3. Apply to the local supported Codex renderer, inspect representative computed tokens and owned chrome, capture a private screenshot, then restore. +3. Apply to the local supported Codex renderer, inspect representative computed tokens and owned chrome, capture a screenshot, then restore. 4. Review the diff and repository status. -5. Fast-forward the clean main branch, rerun the full check, push to the existing private GitHub repository, and confirm CI. +5. Fast-forward the clean main branch, rerun the full check, push to the existing GitHub repository, and confirm CI. diff --git a/docs/plans/2026-07-17-persistent-theme-platform-design.md b/docs/plans/2026-07-17-persistent-theme-platform-design.md new file mode 100644 index 0000000..d83139c --- /dev/null +++ b/docs/plans/2026-07-17-persistent-theme-platform-design.md @@ -0,0 +1,150 @@ +# Persistent Theme Platform Design + +## Purpose + +Awesome Codex Themes is an English-first, Mac-first open-source theme platform for technical users of the official Codex Desktop app. It ships a small audited CDP engine, a persistent user agent, a declarative theme format, a safe restore path, and a catalog designed to grow to twelve complete themes. + +The first release theme is **Satoru Gojo (五条 悟)**. Eleven additional theme slots remain intentionally non-runnable until their original or redistributable asset packs are supplied. Empty slots must never appear as installable themes. + +## Product boundary + +The repository provides: + +- local theme discovery and selection; +- loopback-only CDP launch, injection, verification, and removal; +- idempotent reapplication after renderer reload; +- persistent recovery after app exit/relaunch and macOS login; +- live-verified compatibility metadata, best-effort fallback, and explicit recovery; +- user-level install, status, switch, pause, restore, upgrade, and uninstall commands; +- an English static gallery and English contributor documentation; +- a shared schema and adapter boundary for twelve independent theme packages. + +It does not patch or redistribute the official app, create a second Codex data profile, read conversations or credentials, execute theme JavaScript, download remote theme code, or silently terminate a running Codex process. + +## Evaluated persistence approaches + +### Repository-bound LaunchAgent + +A plist can point directly at the cloned repository. This is small but fragile: moving, deleting, or switching the clone can break login startup. It is unsuitable for a public release. + +### Self-contained user installation — selected + +The installer copies the audited runtime and runnable themes into a versioned directory below `~/Library/Application Support/AwesomeCodexThemes/`. A LaunchAgent calls a stable `current` installation path. Configuration and operational state live beside, but outside, the immutable runtime copy. + +This approach is inspectable, reversible, updateable, and does not require a global Node installation because the launcher locates the Node runtime bundled with the validated official app. + +### Signed helper application + +A signed helper could provide the best consumer experience, but it duplicates the later commercial desktop-manager boundary. It is intentionally deferred. + +## Installed layout + +```text +~/Library/Application Support/AwesomeCodexThemes/ +├── config.json # owner-only desired state +├── agent-state.json # owner-only observed state +├── current -> releases/0.3.0/ # atomically switched release +└── releases/ + └── 0.3.0/ + ├── bin/awesome-codex-themes + ├── package.json + ├── src/cli/ + ├── src/engine/ + └── themes/ # runnable, validated packages only + +~/Library/LaunchAgents/ +└── io.github.awesome-codex-themes.agent.plist +``` + +The LaunchAgent never points at a Git clone. Installation uses a temporary release directory, validates its required files, and swaps `current` only after the copy succeeds. + +## Desired configuration + +```json +{ + "schemaVersion": 1, + "enabled": true, + "themeSlug": "satoru-gojo", + "launchAtLogin": true +} +``` + +Configuration is declarative. `switch ` changes only `themeSlug`; the running agent observes the change and reapplies the new package. `pause` sets `enabled` to false and removes owned renderer state when reachable. `resume` enables the configured theme again. + +## Agent lifecycle + +1. LaunchAgent starts the user agent at login with `RunAtLoad` and `KeepAlive`. +2. The agent validates the installed configuration, official app identity, signature, Team ID, architecture, and selected theme. +3. If Codex is not running and persistent mode is enabled, the agent launches the official executable with a dynamically selected `127.0.0.1` CDP port. It does not pass `--user-data-dir`, so the user's existing Codex profile remains authoritative. +4. The agent verifies that the listener belongs to the official process, waits for the allowlisted renderer, applies the selected theme, and records observed state. +5. While the same app process is alive, the agent reapplies idempotently so renderer reloads recover automatically. +6. When the app exits, the agent clears transient process state and starts a new managed session while persistent mode remains enabled. The explicit `pause` and `uninstall-agent` commands are the supported way to stop persistent relaunch. +7. If Codex is already running without a trusted local CDP listener, the agent reports `restart-required` and leaves the process untouched. It never kills or restarts an active session. +8. Every valid numeric Codex version attempts the shared mapping. Live-verified families use dedicated markers; other versions use `codex-best-effort` and rely on `pause`/`restore` if visual compatibility has drifted. + +## Commands + +| Command | Behavior | +| --- | --- | +| `list` | List runnable validated theme packages. | +| `doctor` | Inspect the official app, installed agent, configuration, conflicts, and recovery path without mutation. | +| `start ` | Start a session-scoped managed launch. | +| `install-agent ` | Install the self-contained runtime, save the desired theme, and bootstrap persistent mode. | +| `upgrade-agent` | Stage the current source release while preserving the installed theme and enabled/paused state. | +| `switch ` | Validate and persist a new active theme for the installed agent. | +| `pause` | Disable persistent launch/reapply and remove owned live styling when reachable. | +| `resume` | Re-enable the saved theme without changing its selection. | +| `status` | Report desired theme, service state, app/CDP state, compatibility, and last error. | +| `restore` | Remove project-owned live styling and stop session-scoped management. | +| `uninstall-agent` | Boot out the LaunchAgent, restore owned styling when reachable, and remove installed runtime/configuration. | + +Every mutating command prints the exact installed paths and recovery command. Stable error codes remain part of the public interface. + +## Theme catalog and twelve-slot model + +`themes/` contains runnable packages only. Each directory must pass the schema and asset validator before it appears in CLI output or the Gallery. + +`theme-slots/` contains eleven numbered planning slots. A slot is documentation and intake metadata, not a partial theme package. It has no apply command and is excluded from catalog generation. When assets arrive, a slot receives a real English name, optional native-language display name, design brief, component specification, package, QA evidence, and only then moves into `themes//`. + +The four existing study themes are preserved outside the release catalog under `examples/legacy-themes/`. They remain useful engine fixtures but do not consume any of the eleven planned user-supplied slots. + +## Naming and language policy + +All project documentation, CLI text, code comments, metadata descriptions, issue templates, release notes, and Gallery copy are English. + +Theme display metadata supports: + +```json +{ + "name": "Satoru Gojo", + "nativeName": "五条 悟", + "nativeLocale": "ja-JP" +} +``` + +English is primary. A native name is optional and appears only when it is culturally meaningful. Slugs, filenames, commands, and schema keys remain lowercase ASCII English. Chinese appears only as a native language for a Chinese-origin theme. + +Public Satoru Gojo files and UI do not use internal visual-iteration labels. The development snapshots remain in a local backup; the public package contains one current theme. + +## Complete theme package contract + +A runnable theme includes schema-valid metadata, semantic palette coverage, scoped CSS, runtime artwork, named UI assets, preview media, asset provenance, compatibility declarations, and P0 component coverage. A theme is not complete when it is only a wallpaper. + +The shared engine owns version-sensitive selectors. Theme CSS owns only visual decisions below `html.awesome-codex-theme`. Decorative nodes are inert and pointer-transparent. Compatibility remains `experimental` until recorded live QA proves the declared surface matrix and restore behavior on an exact Codex version. + +## Safety and recovery + +- CDP binds to a dynamic port on literal `127.0.0.1`; wildcard origins are not used. +- The listener must belong to the validated official app process. +- The agent never modifies the app bundle or sets a second data directory. +- The agent never reads renderer content beyond owned markers and computed theme verification. +- Existing third-party injectors are detected and reported as conflicts; they are never silently deleted. +- Agent and runtime state are owner-only and atomically written. +- Install and update operations are rollback-safe. +- `uninstall-agent` and a documented manual `launchctl bootout` path remain available even if the Gallery or agent is broken. + +## Verification + +Automated tests cover configuration validation, release copying, plist generation, launchctl argument arrays, theme switching, agent state transitions, best-effort version fallback, conflict reporting, and restore/uninstall idempotence. Existing CDP, signature, port-owner, renderer, injection, and theme-schema tests remain mandatory. + +macOS integration evidence must separately verify renderer reload, app exit/relaunch, login-agent bootstrap, theme switch, best-effort fallback and recovery, restore, uninstall, and preservation of the existing Codex profile. Linux CI validates pure generation and state-machine behavior without attempting to load launchd. diff --git a/docs/plans/2026-07-17-persistent-theme-platform-implementation.md b/docs/plans/2026-07-17-persistent-theme-platform-implementation.md new file mode 100644 index 0000000..e84ae07 --- /dev/null +++ b/docs/plans/2026-07-17-persistent-theme-platform-implementation.md @@ -0,0 +1,176 @@ +# Persistent Theme Platform Implementation Plan + +> **For Codex:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Turn Awesome Codex Themes into an English-first twelve-theme platform with one complete Satoru Gojo package and a safe self-contained persistent macOS agent. + +**Architecture:** Keep the existing Node built-in runtime and versioned CDP adapter. Add pure configuration, installation, LaunchAgent, and supervisor modules below `src/engine/`; compose them through explicit CLI commands. Runnable themes remain isolated under `themes/`, while eleven non-runnable intake slots live outside the catalog. + +**Tech Stack:** Node.js ESM and built-ins, Vitest, macOS launchd/launchctl, React/Vite static Gallery, JSON theme manifests. + +--- + +### Task 1: Add multilingual display metadata without changing English-first identifiers + +**Files:** +- Modify: `tests/engine/theme.test.mjs` +- Modify: `src/engine/theme.mjs` +- Modify: `docs/THEME_SCHEMA.md` +- Modify: `scripts/catalog.mjs` +- Modify: `tests/themes/catalog.test.mjs` + +**Steps:** +1. Write failing tests for optional `nativeName` and BCP-47-like `nativeLocale` fields, including rejection when only one is supplied. +2. Run the focused tests and confirm the expected schema failure. +3. Implement minimal normalization and catalog propagation. +4. Run the focused tests and full theme validation. + +### Task 2: Promote the current flagship package to Satoru Gojo + +**Files:** +- Rename: `themes/limitless-six-eyes/` to `themes/satoru-gojo/` +- Modify: `themes/satoru-gojo/theme.json` +- Modify: `themes/satoru-gojo/theme.css` +- Modify: `themes/satoru-gojo/ASSET_LICENSE.md` +- Modify: `tests/themes/limitless-six-eyes.test.mjs` and rename it to `tests/themes/satoru-gojo.test.mjs` +- Modify: `src/gallery/App.tsx` +- Modify: `tests/gallery/app.test.tsx` +- Modify: public catalog/preview references generated by `pnpm build` + +**Steps:** +1. Change tests first to require slug `satoru-gojo`, primary name `Satoru Gojo`, native name `五条 悟`, locale `ja-JP`, current assets, P0 coverage markers, and no public internal-iteration label. +2. Run focused tests and confirm they fail against the old package. +3. Rename the package and update scoped selectors/metadata without changing approved visuals. +4. Rebuild the catalog and run theme, Gallery, and injection tests. + +### Task 3: Separate legacy studies and create eleven non-runnable slots + +**Files:** +- Move: `themes/{arctic-signal,obsidian-bloom,paper-circuit,solar-archive}/` to `examples/legacy-themes/` +- Create: `theme-slots/README.md` +- Create: `theme-slots/slots.json` +- Create: `theme-slots/02/README.md` through `theme-slots/12/README.md` +- Create: `tests/themes/slots.test.mjs` +- Modify: `README.md` + +**Steps:** +1. Write a failing test requiring exactly eleven ordered awaiting-assets slots and proving they are absent from the runnable catalog. +2. Run the test and confirm the missing-slot failure. +3. Add the slot manifest/readmes and preserve legacy studies outside `themes/`. +4. Run catalog generation and confirm only Satoru Gojo is runnable. + +### Task 4: Add owner-only desired configuration and observed agent state + +**Files:** +- Create: `tests/engine/config.test.mjs` +- Create: `src/engine/config.mjs` +- Create: `tests/engine/agent-state.test.mjs` +- Create: `src/engine/agent-state.mjs` + +**Steps:** +1. Write failing tests for schemas, absolute paths, slug validation, atomic writes, mode `0600`, and typed malformed-file errors. +2. Run focused tests and confirm missing-module failures. +3. Implement minimal validators/readers/writers using Node built-ins. +4. Run focused tests and refactor shared atomic JSON persistence only after green. + +### Task 5: Generate and manage a safe LaunchAgent definition + +**Files:** +- Create: `tests/engine/launch-agent.test.mjs` +- Create: `src/engine/launch-agent.mjs` + +**Steps:** +1. Write failing tests for XML escaping, stable label, absolute installed launcher path, `RunAtLoad`, `KeepAlive`, log paths, and argument-array `launchctl bootstrap/bootout/kickstart` calls. +2. Confirm the tests fail because the module does not exist. +3. Implement pure plist generation and dependency-injected launchctl operations without shell interpolation. +4. Run the focused tests on all platforms; keep actual launchctl execution behind explicit CLI mutation. + +### Task 6: Build rollback-safe self-contained installation + +**Files:** +- Create: `tests/engine/installer.test.mjs` +- Create: `src/engine/installer.mjs` +- Modify: `.gitignore` + +**Steps:** +1. Write failing temporary-directory tests for required runtime files, runnable-theme copying, owner permissions, atomic `current` switch, same-version idempotence, and cleanup after copy failure. +2. Confirm the failure. +3. Implement installation using `fs.cp`, canonical source paths, a staging release, and dependency-injected LaunchAgent operations. +4. Run focused tests and inspect the installed fixture tree. + +### Task 7: Add the persistent supervisor state machine + +**Files:** +- Create: `tests/engine/agent.test.mjs` +- Create: `src/engine/agent.mjs` +- Modify: `src/engine/session.mjs` + +**Steps:** +1. Write failing tests for disabled, app-absent, managed-app, unmanaged-app, renderer-reload, app-exit, best-effort-version, theme-switch, and signal-stop states. +2. Confirm each behavior fails for the intended missing implementation. +3. Implement one deterministic `agentStep` plus a small loop; reuse existing app discovery, port ownership, apply, and removal functions. +4. Ensure unmanaged running Codex returns `restart-required` and never calls a terminate dependency. +5. Run agent, session, macOS, CDP, and injection tests. + +### Task 8: Expose persistent lifecycle commands + +**Files:** +- Modify: `tests/cli/main.test.mjs` +- Modify: `src/cli/main.mjs` +- Modify: `bin/awesome-codex-themes` + +**Steps:** +1. Write failing CLI tests for `install-agent`, `switch`, `pause`, `resume`, `status`, `uninstall-agent`, and internal `_agent` validation. +2. Confirm stable error/output assertions fail before implementation. +3. Wire commands to installer/config/agent dependencies and preserve session-scoped `start/apply/restore` compatibility. +4. Run CLI tests and verify help text is entirely English. + +### Task 9: Add conflict diagnostics and migration guidance + +**Files:** +- Modify: `tests/engine/macos.test.mjs` +- Modify: `src/engine/macos.mjs` +- Modify: `tests/cli/main.test.mjs` +- Modify: `src/cli/main.mjs` +- Create: `docs/MIGRATION.md` + +**Steps:** +1. Write failing tests that detect known theme LaunchAgents/processes without deleting them. +2. Implement read-only diagnostics and surface them through `doctor`. +3. Document manual migration from older injectors, including restore and `launchctl bootout`, without machine-specific paths. +4. Run focused tests. + +### Task 10: Rewrite public documentation for the English global project + +**Files:** +- Modify: `README.md` +- Modify: `docs/ARCHITECTURE.md` +- Modify: `docs/SAFETY.md` +- Modify: `docs/STATUS.md` +- Modify: `CONTRIBUTING.md` +- Modify: `SECURITY.md` +- Modify: `CHANGELOG.md` +- Modify: `.github/ISSUE_TEMPLATE/*.yml` +- Modify: `.github/pull_request_template.md` +- Create: `docs/INSTALL.md` +- Create: `docs/THEME_AUTHORING.md` + +**Steps:** +1. Add documentation assertions to existing tests for current commands, Satoru Gojo naming, recovery, twelve-slot truth, and absence of private or internal-iteration claims. +2. Confirm failures. +3. Rewrite documentation in English with exact commands, lifecycle limits, manual recovery, compatibility boundaries, and native-name policy. +4. Run documentation and catalog tests. + +### Task 11: Verify static and macOS lifecycle behavior + +**Files:** +- Modify: `docs/STATUS.md` +- Create: `docs/verification/persistent-agent-macos.md` + +**Steps:** +1. Run `pnpm check` and record exact counts. +2. Run `git diff --check` and inspect the complete diff for unrelated changes or private content. +3. On macOS, dry-run installation into a temporary home and inspect the plist/runtime copy. +4. With explicit live-session safety, verify renderer reapply and current Satoru Gojo markers without closing the user's active Codex. +5. Do not load the new LaunchAgent or replace the existing agent until the user explicitly approves that machine-level migration. +6. Report implemented, tested, live-verified, committed, pushed, and released states separately. diff --git a/index.html b/index.html index 5d2f169..81d0de6 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ - + Awesome Codex Themes diff --git a/package.json b/package.json index e7b091f..842db01 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,26 @@ { "name": "awesome-codex-themes", - "version": "0.2.0", + "version": "0.4.2", "packageManager": "pnpm@10.32.1", "private": false, "type": "module", "description": "A safe, open theme engine and gallery for the official Codex Desktop app.", "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/erickkkyt/Awesome-codex-themes.git" + }, + "homepage": "https://github.com/erickkkyt/Awesome-codex-themes#readme", + "bugs": { + "url": "https://github.com/erickkkyt/Awesome-codex-themes/issues" + }, + "keywords": [ + "codex", + "codex-desktop", + "macos", + "theme", + "cdp" + ], "bin": { "awesome-codex-themes": "bin/awesome-codex-themes" }, @@ -19,7 +34,8 @@ "test:watch": "vitest", "typecheck": "tsc --noEmit", "themes:validate": "node scripts/validate-themes.mjs", - "check": "pnpm test && pnpm themes:validate && pnpm typecheck && pnpm build" + "check": "pnpm test && pnpm themes:validate && pnpm typecheck && pnpm build", + "release:check": "pnpm check && pnpm audit --prod --audit-level moderate && git diff --check" }, "dependencies": { "@fontsource-variable/newsreader": "^5.2.8", diff --git a/public/theme-assets/arctic-signal/preview.svg b/public/theme-assets/arctic-signal/preview.svg deleted file mode 100644 index ecd565e..0000000 --- a/public/theme-assets/arctic-signal/preview.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - ARCTIC SIGNAL - - LIVE CHANNEL / 07Design a safer local theme engine. - - RUN DIAGNOSTIC - Ask Codex to build, explain, or repair... - diff --git a/public/theme-assets/castle-archive/preview.jpg b/public/theme-assets/castle-archive/preview.jpg new file mode 100644 index 0000000..c7773dc Binary files /dev/null and b/public/theme-assets/castle-archive/preview.jpg differ diff --git a/public/theme-assets/foundling-garden/preview.jpg b/public/theme-assets/foundling-garden/preview.jpg new file mode 100644 index 0000000..18f8d0c Binary files /dev/null and b/public/theme-assets/foundling-garden/preview.jpg differ diff --git a/public/theme-assets/grand-line/preview.jpg b/public/theme-assets/grand-line/preview.jpg new file mode 100644 index 0000000..4e2d62f Binary files /dev/null and b/public/theme-assets/grand-line/preview.jpg differ diff --git a/public/theme-assets/mordor-runtime/preview.jpg b/public/theme-assets/mordor-runtime/preview.jpg new file mode 100644 index 0000000..645317b Binary files /dev/null and b/public/theme-assets/mordor-runtime/preview.jpg differ diff --git a/public/theme-assets/new-world-studio/preview.jpg b/public/theme-assets/new-world-studio/preview.jpg new file mode 100644 index 0000000..38f9f53 Binary files /dev/null and b/public/theme-assets/new-world-studio/preview.jpg differ diff --git a/public/theme-assets/night-city/preview.jpg b/public/theme-assets/night-city/preview.jpg new file mode 100644 index 0000000..fa3d4fa Binary files /dev/null and b/public/theme-assets/night-city/preview.jpg differ diff --git a/public/theme-assets/obsidian-bloom/preview.svg b/public/theme-assets/obsidian-bloom/preview.svg deleted file mode 100644 index 60341a6..0000000 --- a/public/theme-assets/obsidian-bloom/preview.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - OBSIDIAN BLOOM - WORKTREE / FOCUSMake the interface feel alive. - APPLY THEME - Ask Codex to build, explain, or repair... - diff --git a/public/theme-assets/overworld-realms/preview.jpg b/public/theme-assets/overworld-realms/preview.jpg new file mode 100644 index 0000000..697c4d1 Binary files /dev/null and b/public/theme-assets/overworld-realms/preview.jpg differ diff --git a/public/theme-assets/paper-circuit/preview.svg b/public/theme-assets/paper-circuit/preview.svg deleted file mode 100644 index 3f1584d..0000000 --- a/public/theme-assets/paper-circuit/preview.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - PAPER CIRCUIT - SPECIFICATION / 01Draw the system before wiring it. - OPEN BLUEPRINT - Ask Codex to build, explain, or repair... - diff --git a/public/theme-assets/saiyan-ukiyoe/preview.jpg b/public/theme-assets/saiyan-ukiyoe/preview.jpg new file mode 100644 index 0000000..aeab3fd Binary files /dev/null and b/public/theme-assets/saiyan-ukiyoe/preview.jpg differ diff --git a/public/theme-assets/limitless-six-eyes/preview.png b/public/theme-assets/satoru-gojo/preview.png similarity index 100% rename from public/theme-assets/limitless-six-eyes/preview.png rename to public/theme-assets/satoru-gojo/preview.png diff --git a/public/theme-assets/slingshot-lab/preview.jpg b/public/theme-assets/slingshot-lab/preview.jpg new file mode 100644 index 0000000..c28ec64 Binary files /dev/null and b/public/theme-assets/slingshot-lab/preview.jpg differ diff --git a/public/theme-assets/solar-archive/preview.svg b/public/theme-assets/solar-archive/preview.svg deleted file mode 100644 index 9202d89..0000000 --- a/public/theme-assets/solar-archive/preview.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - SOLAR ARCHIVE - ARCHIVE NOTE / 184Recover the shape of an old idea. - INDEX RECORD - Ask Codex to build, explain, or repair... - diff --git a/public/theme-assets/symbiote-sumi-e/preview.jpg b/public/theme-assets/symbiote-sumi-e/preview.jpg new file mode 100644 index 0000000..ccc7674 Binary files /dev/null and b/public/theme-assets/symbiote-sumi-e/preview.jpg differ diff --git a/public/theme-assets/zaun-workshop/preview.jpg b/public/theme-assets/zaun-workshop/preview.jpg new file mode 100644 index 0000000..27b6104 Binary files /dev/null and b/public/theme-assets/zaun-workshop/preview.jpg differ diff --git a/scripts/catalog.mjs b/scripts/catalog.mjs index 46cb3b2..c3fe82c 100644 --- a/scripts/catalog.mjs +++ b/scripts/catalog.mjs @@ -1,4 +1,4 @@ -import { copyFile, mkdir, readdir, writeFile } from 'node:fs/promises'; +import { copyFile, mkdir, readdir, rm, writeFile } from 'node:fs/promises'; import { basename, dirname, join } from 'node:path'; import { loadThemePackage } from '../src/engine/theme.mjs'; @@ -17,6 +17,9 @@ export async function buildThemeCatalog(themesRoot) { slug: manifest.slug, version: manifest.version, name: manifest.name, + ...(manifest.nativeName + ? { nativeName: manifest.nativeName, nativeLocale: manifest.nativeLocale } + : {}), description: manifest.description, author: manifest.author, license: manifest.license, @@ -27,7 +30,7 @@ export async function buildThemeCatalog(themesRoot) { palette: manifest.palette, ...(manifest.experience ? { experience: manifest.experience } : {}), preview: `/theme-assets/${manifest.slug}/${basename(manifest.files.preview)}`, - command: `awesome-codex-themes start ${manifest.slug}`, + command: `./bin/awesome-codex-themes install-agent ${manifest.slug}`, }); } @@ -38,6 +41,7 @@ export async function writeThemeCatalog({ themesRoot, jsonPath, publicRoot }) { const catalog = await buildThemeCatalog(themesRoot); await mkdir(dirname(jsonPath), { recursive: true }); await writeFile(jsonPath, `${JSON.stringify(catalog, null, 2)}\n`); + await rm(join(publicRoot, 'theme-assets'), { recursive: true, force: true }); for (const theme of catalog) { const previewName = theme.preview.split('/').at(-1); diff --git a/src/cli/main.mjs b/src/cli/main.mjs index d91759a..637cef2 100644 --- a/src/cli/main.mjs +++ b/src/cli/main.mjs @@ -1,5 +1,6 @@ import { execFile } from 'node:child_process'; -import { readdir, rm } from 'node:fs/promises'; +import { realpathSync } from 'node:fs'; +import { readFile, readdir, rm } from 'node:fs/promises'; import { createServer } from 'node:net'; import { homedir } from 'node:os'; import { dirname, join, resolve } from 'node:path'; @@ -8,6 +9,13 @@ import { promisify } from 'node:util'; import { spawn } from 'node:child_process'; import { evaluateOnRendererTargets, findAvailablePort } from '../engine/cdp.mjs'; +import { runPersistentAgent } from '../engine/agent.mjs'; +import { isActiveAgentStateLive, readAgentState, writeAgentState } from '../engine/agent-state.mjs'; +import { readAgentConfig, writeAgentConfig } from '../engine/config.mjs'; +import { inspectInjectorConflicts } from '../engine/conflicts.mjs'; +import { pausePersistentInstallation } from '../engine/control.mjs'; +import { installPersistentAgent, installationPaths, uninstallPersistentAgent } from '../engine/installer.mjs'; +import { isLaunchAgentLoaded, kickstartLaunchAgent } from '../engine/launch-agent.mjs'; import { assertOfficialPortOwner, discoverOfficialApp, @@ -28,7 +36,20 @@ const cliPath = fileURLToPath(import.meta.url); const projectRoot = resolve(dirname(cliPath), '../..'); const themesRoot = join(projectRoot, 'themes'); const statePath = join(homedir(), 'Library/Application Support/AwesomeCodexThemes/state.json'); -const RETRYABLE_CDP_ERRORS = new Set(['CDP_HTTP_FAILED', 'CDP_RENDERER_NOT_FOUND', 'CDP_CONNECTION_FAILED']); +const RETRYABLE_CDP_ERRORS = new Set([ + 'CDP_PORT_UNAVAILABLE', + 'CDP_HTTP_FAILED', + 'CDP_RENDERER_NOT_FOUND', + 'CDP_CONNECTION_FAILED', +]); + +async function projectVersion() { + return JSON.parse(await readFile(join(projectRoot, 'package.json'), 'utf8')).version; +} + +async function persistentPaths() { + return installationPaths({ home: homedir(), version: await projectVersion() }); +} const HELP = `Awesome Codex Themes @@ -36,9 +57,15 @@ Usage: awesome-codex-themes list awesome-codex-themes doctor awesome-codex-themes start + awesome-codex-themes install-agent + awesome-codex-themes upgrade-agent + awesome-codex-themes switch + awesome-codex-themes pause + awesome-codex-themes resume awesome-codex-themes apply --port awesome-codex-themes status awesome-codex-themes restore [--port ] + awesome-codex-themes uninstall-agent The engine only connects to CDP on 127.0.0.1 and never terminates the official app.`; @@ -65,7 +92,7 @@ function parsePort(value) { function requireTheme(args) { const theme = args[0]; - if (!theme || theme.startsWith('-')) cliFail('CLI_THEME_REQUIRED', 'Choose a theme slug, for example: obsidian-bloom.'); + if (!theme || theme.startsWith('-')) cliFail('CLI_THEME_REQUIRED', 'Choose a theme slug, for example: satoru-gojo.'); return theme; } @@ -94,7 +121,11 @@ export async function runCli(argv, dependencies, io = { out: console.log, error: return 0; } for (const theme of themes) { - io.out(`${theme.slug} ${theme.status} ${theme.categories.join(', ')} ${theme.name}`); + const displayName = theme.nativeName ? `${theme.name} / ${theme.nativeName}` : theme.name; + const compatibility = theme.strategy === 'best-effort-all' + ? `best effort: all numeric versions high compatibility: ${theme.verifiedAppVersions.join(', ')}` + : `high compatibility: ${theme.verifiedAppVersions.join(', ')}`; + io.out(`${theme.slug} ${theme.status} ${theme.categories.join(', ')} ${compatibility} ${displayName}`); } return 0; } @@ -105,6 +136,12 @@ export async function runCli(argv, dependencies, io = { out: console.log, error: io.out(`app: ${report.appPath}`); io.out(`version: ${report.version}`); io.out(`running: ${report.runningPids.length ? report.runningPids.join(', ') : 'no'}`); + if (report.conflicts?.length) { + for (const conflict of report.conflicts) { + io.out(`conflict: ${conflict.id}${conflict.path ? ` — ${conflict.path}` : ''}`); + } + io.out('warning: conflict diagnostics are read-only; no files were changed'); + } return 0; } @@ -126,10 +163,53 @@ export async function runCli(argv, dependencies, io = { out: console.log, error: return 0; } + if (command === 'install-agent') { + const theme = requireTheme(args); + rejectExtraArgs(args, 1, 'install-agent'); + const result = await dependencies.installAgent(theme); + io.out(`installed persistent theme ${result.theme} at ${result.supportRoot}`); + return 0; + } + + if (command === 'upgrade-agent') { + rejectExtraArgs(args, 0, 'upgrade-agent'); + const result = await dependencies.upgradeAgent(); + io.out( + `upgraded persistent agent to ${result.version} with ${result.theme} (${result.enabled ? 'enabled' : 'paused'})`, + ); + return 0; + } + + if (command === 'switch') { + const theme = requireTheme(args); + rejectExtraArgs(args, 1, 'switch'); + const result = await dependencies.switchTheme(theme); + io.out(`selected persistent theme ${result.theme}`); + return 0; + } + + if (command === 'pause') { + rejectExtraArgs(args, 0, 'pause'); + const result = await dependencies.pause(); + io.out(`paused persistent theme ${result.theme}`); + return 0; + } + + if (command === 'resume') { + rejectExtraArgs(args, 0, 'resume'); + const result = await dependencies.resume(); + io.out(`resumed persistent theme ${result.theme}`); + return 0; + } + if (command === 'status') { rejectExtraArgs(args, 0, 'status'); const result = await dependencies.status(); - if (result.stale) { + if (result.installed && result.status === 'active') { + io.out(`active — persistent ${result.theme} on 127.0.0.1:${result.port}`); + } else if (result.installed) { + io.out(`${result.status} — persistent ${result.theme}${result.enabled ? '' : ' (disabled)'}`); + } else if (result.stale) { io.out(`inactive — stale state for ${result.theme}; run restore to clean project-owned state`); } else if (!result.active) { io.out('inactive — official UI is not managed by Awesome Codex Themes'); @@ -148,7 +228,22 @@ export async function runCli(argv, dependencies, io = { out: console.log, error: port = parsePort(args[1]); } const result = await dependencies.restore(port); - io.out(`restored official UI${result.theme ? ` from ${result.theme}` : ''}`); + if (result.persistent) { + io.out( + result.liveRemoval === 'removed' + ? `paused persistent theme ${result.theme} and restored project-owned styling` + : `paused persistent theme ${result.theme}; project-owned styling will clear when the endpoint is reachable`, + ); + } else { + io.out(`restored official UI${result.theme ? ` from ${result.theme}` : ''}`); + } + return 0; + } + + if (command === 'uninstall-agent') { + rejectExtraArgs(args, 0, 'uninstall-agent'); + await dependencies.uninstallAgent(); + io.out('uninstalled persistent agent; official Codex data was not changed'); return 0; } @@ -161,6 +256,12 @@ export async function runCli(argv, dependencies, io = { out: console.log, error: return 0; } + if (command === '_agent') { + rejectExtraArgs(args, 0, '_agent'); + await dependencies.runAgent(); + return 0; + } + cliFail('CLI_COMMAND_UNKNOWN', `Unknown command: ${command}. Run help for usage.`); } catch (error) { io.error(`[${errorCode(error)}] ${error?.message ?? 'Unexpected failure.'}`); @@ -178,10 +279,20 @@ function portIsAvailable(port) { }); } -async function waitForRenderer(port, { attempts = 80, delayMs = 250 } = {}) { +export async function waitForRenderer( + port, + app, + { + attempts = 80, + delayMs = 250, + assertPortOwner = assertOfficialPortOwner, + evaluate = evaluateOnRendererTargets, + } = {}, +) { for (let attempt = 0; attempt < attempts; attempt += 1) { try { - await evaluateOnRendererTargets({ port, expression: '({ pass: true })' }); + await assertPortOwner(app, port); + await evaluate({ port, expression: '({ pass: true })' }); return; } catch (error) { if (!RETRYABLE_CDP_ERRORS.has(error?.code) || attempt === attempts - 1) throw error; @@ -261,6 +372,20 @@ export async function terminateProcess( } export function createDefaultDependencies() { + async function pauseInstalledTheme() { + const paths = await persistentPaths(); + return pausePersistentInstallation( + { configPath: paths.configPath, statePath: paths.agentStatePath }, + { + removeTheme: async ({ port }) => { + const app = await discoverOfficialApp(); + await assertOfficialPortOwner(app, port); + await removeThemeAtPort({ port }); + }, + }, + ); + } + return { async listThemes() { const entries = await readdir(themesRoot, { withFileTypes: true }); @@ -273,8 +398,11 @@ export function createDefaultDependencies() { .map((manifest) => ({ slug: manifest.slug, name: manifest.name, + nativeName: manifest.nativeName, categories: manifest.categories, status: manifest.compatibility.status, + strategy: manifest.compatibility.strategy, + verifiedAppVersions: manifest.compatibility.verifiedAppVersions, })) .sort((left, right) => left.slug.localeCompare(right.slug)); }, @@ -285,6 +413,7 @@ export function createDefaultDependencies() { appPath: app.appPath, version: app.version, runningPids: await listOfficialAppPids(app), + conflicts: await inspectInjectorConflicts(homedir()), }; }, @@ -318,6 +447,52 @@ export function createDefaultDependencies() { ); }, + async installAgent(themeSlug) { + await discoverOfficialApp(); + return installPersistentAgent({ + sourceRoot: projectRoot, + home: homedir(), + version: await projectVersion(), + themeSlug, + }); + }, + + async upgradeAgent() { + await discoverOfficialApp(); + const paths = await persistentPaths(); + const config = await readAgentConfig(paths.configPath); + const version = await projectVersion(); + const result = await installPersistentAgent({ + sourceRoot: projectRoot, + home: homedir(), + version, + themeSlug: config.themeSlug, + enabled: config.enabled, + }); + return { ...result, upgraded: true, enabled: config.enabled }; + }, + + async switchTheme(themeSlug) { + const paths = await persistentPaths(); + await loadThemePackage(join(paths.currentPath, 'themes', themeSlug)); + const config = await readAgentConfig(paths.configPath); + await writeAgentConfig(paths.configPath, { ...config, themeSlug }); + await kickstartLaunchAgent(); + return { switched: true, theme: themeSlug }; + }, + + async pause() { + return pauseInstalledTheme(); + }, + + async resume() { + const paths = await persistentPaths(); + const config = await readAgentConfig(paths.configPath); + await writeAgentConfig(paths.configPath, { ...config, enabled: true }); + await kickstartLaunchAgent(); + return { resumed: true, theme: config.themeSlug }; + }, + async apply(themeSlug, port) { const app = await discoverOfficialApp(); await assertOfficialPortOwner(app, port); @@ -325,6 +500,34 @@ export function createDefaultDependencies() { }, async status() { + try { + const paths = await persistentPaths(); + const config = await readAgentConfig(paths.configPath); + try { + const state = await readAgentState(paths.agentStatePath); + const live = state.status !== 'active' || await isActiveAgentStateLive(state, { + discoverApp: discoverOfficialApp, + listPids: listOfficialAppPids, + assertPortOwner: assertOfficialPortOwner, + isAgentLoaded: isLaunchAgentLoaded, + }); + return { + installed: true, + enabled: config.enabled, + status: live ? state.status : 'stale', + theme: config.themeSlug, + port: state.port, + errorCode: live ? state.errorCode : 'AGENT_STATE_STALE', + }; + } catch (error) { + if (error?.code === 'AGENT_STATE_READ_FAILED') { + return { installed: true, enabled: config.enabled, status: 'idle', theme: config.themeSlug, port: null }; + } + throw error; + } + } catch (error) { + if (error?.code !== 'CONFIG_READ_FAILED') throw error; + } try { const state = await readRuntimeState(statePath); try { @@ -358,6 +561,12 @@ export function createDefaultDependencies() { await assertOfficialPortOwner(app, port); return removeThemeAtPort({ port }); } + try { + const paused = await pauseInstalledTheme(); + return { ...paused, restored: paused.liveRemoval === 'removed', persistent: true }; + } catch (error) { + if (error?.code !== 'CONFIG_READ_FAILED') throw error; + } let expected; return restoreThemeSession( { statePath }, @@ -380,6 +589,27 @@ export function createDefaultDependencies() { ); }, + async uninstallAgent() { + const paths = await persistentPaths(); + try { + const state = await readAgentState(paths.agentStatePath); + if (state.port !== null) { + const app = await discoverOfficialApp(); + await assertOfficialPortOwner(app, state.port); + await removeThemeAtPort({ port: state.port }); + } + } catch (error) { + if ( + error?.code !== 'AGENT_STATE_READ_FAILED' && + !RETRYABLE_CDP_ERRORS.has(error?.code) && + error?.code !== 'CDP_PORT_OWNER_INVALID' + ) { + throw error; + } + } + return uninstallPersistentAgent({ home: homedir() }); + }, + async watch(themeSlug, port, appPid) { if (!Number.isInteger(appPid) || appPid < 1) cliFail('CLI_PID_INVALID', 'Internal watcher PID is invalid.'); const app = await discoverOfficialApp(); @@ -401,10 +631,67 @@ export function createDefaultDependencies() { await new Promise((resolveDelay) => setTimeout(resolveDelay, 1_500)); } }, + + async runAgent() { + const paths = await persistentPaths(); + let stopping = false; + const stop = () => { + stopping = true; + }; + process.once('SIGTERM', stop); + process.once('SIGINT', stop); + let initialRuntime = null; + try { + const state = await readAgentState(paths.agentStatePath); + if (state.appPid && state.port) { + initialRuntime = { appPid: state.appPid, port: state.port, themeSlug: state.themeSlug }; + } + } catch (error) { + if (error?.code !== 'AGENT_STATE_READ_FAILED') throw error; + } + + try { + return await runPersistentAgent( + { + configPath: paths.configPath, + statePath: paths.agentStatePath, + intervalMs: 1_500, + initialRuntime, + }, + { + writeState: writeAgentState, + stepDependencies: { + discoverApp: discoverOfficialApp, + listPids: listOfficialAppPids, + selectPort: () => findAvailablePort({ start: 9_341, end: 9_441, isAvailable: portIsAvailable }), + launchApp: launchOfficialApp, + waitForRenderer, + assertPortOwner: assertOfficialPortOwner, + applyTheme: ({ themeSlug, port, app }) => + applyThemeAtPort({ themesRoot, themeSlug, port, appVersion: app.version }), + removeTheme: ({ port }) => removeThemeAtPort({ port }), + }, + shouldStop: () => stopping, + }, + ); + } finally { + process.removeListener('SIGTERM', stop); + process.removeListener('SIGINT', stop); + } + }, }; } -if (process.argv[1] && resolve(process.argv[1]) === cliPath) { +function isDirectExecution(argvPath) { + if (!argvPath) return false; + try { + return realpathSync(argvPath) === realpathSync(cliPath); + } catch { + return resolve(argvPath) === cliPath; + } +} + +if (isDirectExecution(process.argv[1])) { const exitCode = await runCli(process.argv.slice(2), createDefaultDependencies()); process.exitCode = exitCode; } diff --git a/src/engine/adapter.mjs b/src/engine/adapter.mjs index 6e1d9f8..ea2cc29 100644 --- a/src/engine/adapter.mjs +++ b/src/engine/adapter.mjs @@ -9,12 +9,22 @@ export class AdapterError extends Error { } const ADAPTERS = Object.freeze([ + Object.freeze({ + id: 'codex-26.715', + appVersions: Object.freeze(['26.715.*']), + file: new URL('./adapters/codex-26.707.css', import.meta.url), + }), Object.freeze({ id: 'codex-26.707', appVersions: Object.freeze(['26.707.*']), file: new URL('./adapters/codex-26.707.css', import.meta.url), }), ]); +const BEST_EFFORT_ADAPTER = Object.freeze({ + id: 'codex-best-effort', + appVersions: Object.freeze(['*']), + file: new URL('./adapters/codex-26.707.css', import.meta.url), +}); function versionMatchesRange(appVersion, range) { const versionParts = appVersion.split('.'); @@ -30,15 +40,10 @@ export async function loadCodexAdapter(appVersion) { throw new AdapterError('THEME_ADAPTER_VERSION_INVALID', 'A numeric Codex app version is required.'); } - const adapter = ADAPTERS.find((candidate) => + const verifiedAdapter = ADAPTERS.find((candidate) => candidate.appVersions.some((range) => versionMatchesRange(appVersion, range)), ); - if (!adapter) { - throw new AdapterError( - 'THEME_ADAPTER_UNSUPPORTED', - `No full-theme adapter declares support for Codex ${appVersion}; official UI was left unchanged.`, - ); - } + const adapter = verifiedAdapter ?? BEST_EFFORT_ADAPTER; let css; try { @@ -51,5 +56,10 @@ export async function loadCodexAdapter(appVersion) { if (!css.includes('html.awesome-codex-theme')) { throw new AdapterError('THEME_ADAPTER_INVALID', `Built-in adapter ${adapter.id} is not namespaced.`); } - return { id: adapter.id, appVersions: [...adapter.appVersions], css }; + return { + id: adapter.id, + appVersions: [...adapter.appVersions], + verified: Boolean(verifiedAdapter), + css, + }; } diff --git a/src/engine/adapters/codex-26.707.css b/src/engine/adapters/codex-26.707.css index 02dd475..86276a7 100644 --- a/src/engine/adapters/codex-26.707.css +++ b/src/engine/adapters/codex-26.707.css @@ -1,5 +1,5 @@ /* - * Awesome Codex Themes — Codex Desktop 26.707 adapter + * Awesome Codex Themes — shared Codex Desktop 26.707 / 26.715 adapter mapping * Original MIT-licensed compatibility layer. Rules are scoped to the project marker. */ @@ -236,14 +236,6 @@ html.awesome-codex-theme :is(input, textarea)::placeholder { opacity: 1 !important; } -html.awesome-codex-theme :is(button, [role="button"], a) { - border-color: var(--act-border-subtle); -} - -html.awesome-codex-theme :is(button, [role="button"], a):hover { - border-color: var(--act-border) !important; -} - html.awesome-codex-theme :is(button, [role="button"], a, input, textarea, select, [contenteditable="true"]):focus-visible { outline: 2px solid var(--act-focus) !important; outline-offset: 2px !important; @@ -338,6 +330,23 @@ html.awesome-codex-theme :is( border-color: var(--act-border-subtle) !important; } +html.awesome-codex-theme[data-awesome-codex-artifact-panel-state="open"]:not([data-awesome-codex-theme="satoru-gojo"]) + [data-awesome-codex-artifact-panel-host="true"] { + background-color: color-mix(in srgb, var(--act-surface-elevated) 82%, transparent) !important; + background-image: + linear-gradient( + 180deg, + color-mix(in srgb, var(--act-surface-overlay) 48%, transparent), + color-mix(in srgb, var(--act-background) 68%, transparent) + ), + var(--act-asset-workspace-panel-safe) !important; + background-position: center !important; + background-repeat: no-repeat !important; + background-size: cover !important; + border-radius: 24px 0 0 24px; + box-shadow: inset 1px 0 0 var(--act-border-strong); +} + @media (prefers-reduced-motion: reduce) { html.awesome-codex-theme *, html.awesome-codex-theme *::before, diff --git a/src/engine/agent-state.mjs b/src/engine/agent-state.mjs new file mode 100644 index 0000000..48d58d2 --- /dev/null +++ b/src/engine/agent-state.mjs @@ -0,0 +1,99 @@ +import { chmod, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises'; +import { dirname } from 'node:path'; + +const SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/; +const STATUSES = new Set([ + 'idle', + 'starting', + 'active', + 'restart-required', + 'paused', + 'error', +]); + +export class AgentStateError extends Error { + constructor(code, message, options = {}) { + super(message, options); + this.name = 'AgentStateError'; + this.code = code; + } +} + +function fail(code, message, options) { + throw new AgentStateError(code, message, options); +} + +function nullableString(value, field) { + if (value === null) return null; + if (typeof value !== 'string' || !value.trim()) fail('AGENT_STATE_FIELD_INVALID', `${field} must be null or a non-empty string.`); + return value; +} + +export function validateAgentState(input) { + if (!input || typeof input !== 'object' || Array.isArray(input) || input.schemaVersion !== 1) { + fail('AGENT_STATE_SCHEMA_INVALID', 'Agent state schemaVersion must be 1.'); + } + if (!STATUSES.has(input.status)) fail('AGENT_STATE_STATUS_INVALID', 'Agent state status is unsupported.'); + if (typeof input.themeSlug !== 'string' || !SLUG_PATTERN.test(input.themeSlug)) { + fail('AGENT_STATE_THEME_INVALID', 'themeSlug must use lowercase kebab-case.'); + } + if (input.appPid !== null && (!Number.isInteger(input.appPid) || input.appPid < 1)) { + fail('AGENT_STATE_PID_INVALID', 'appPid must be null or a positive PID.'); + } + if (input.port !== null && (!Number.isInteger(input.port) || input.port < 1024 || input.port > 65_535)) { + fail('AGENT_STATE_PORT_INVALID', 'port must be null or a non-privileged TCP port.'); + } + return { + schemaVersion: 1, + status: input.status, + themeSlug: input.themeSlug, + appPid: input.appPid, + appVersion: nullableString(input.appVersion, 'appVersion'), + port: input.port, + errorCode: nullableString(input.errorCode, 'errorCode'), + updatedAt: nullableString(input.updatedAt, 'updatedAt'), + }; +} + +export async function isActiveAgentStateLive( + input, + { discoverApp, listPids, assertPortOwner, isAgentLoaded }, +) { + const state = validateAgentState(input); + if (state.status !== 'active' || state.appPid === null || state.port === null) return false; + try { + if (!(await isAgentLoaded())) return false; + const app = await discoverApp(); + if (app.version !== state.appVersion) return false; + const pids = await listPids(app); + if (!pids.includes(state.appPid)) return false; + await assertPortOwner(app, state.port); + return true; + } catch { + return false; + } +} + +export async function writeAgentState(path, input) { + const state = validateAgentState(input); + await mkdir(dirname(path), { recursive: true, mode: 0o700 }); + const temporaryPath = `${path}.${process.pid}.${Date.now()}.tmp`; + try { + await writeFile(temporaryPath, `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600 }); + await chmod(temporaryPath, 0o600); + await rename(temporaryPath, path); + await chmod(path, 0o600); + } catch (error) { + await rm(temporaryPath, { force: true }).catch(() => {}); + fail('AGENT_STATE_WRITE_FAILED', 'Unable to write agent state atomically.', { cause: error }); + } +} + +export async function readAgentState(path) { + try { + return validateAgentState(JSON.parse(await readFile(path, 'utf8'))); + } catch (error) { + if (error instanceof AgentStateError) throw error; + fail('AGENT_STATE_READ_FAILED', 'Agent state is missing or malformed.', { cause: error }); + } +} diff --git a/src/engine/agent.mjs b/src/engine/agent.mjs new file mode 100644 index 0000000..81b7edb --- /dev/null +++ b/src/engine/agent.mjs @@ -0,0 +1,177 @@ +import { readAgentConfig } from './config.mjs'; +import { writeAgentState } from './agent-state.mjs'; + +const LOST_MANAGED_ENDPOINT_ERRORS = new Set([ + 'CDP_PORT_UNAVAILABLE', + 'CDP_PORT_OWNER_INVALID', + 'CDP_LISTENER_UNSAFE', +]); + +function observedState({ status, themeSlug, appPid = null, appVersion = null, port = null, errorCode = null }, now) { + return { + schemaVersion: 1, + status, + themeSlug, + appPid, + appVersion, + port, + errorCode, + updatedAt: now(), + }; +} + +function failureStatus() { + return 'error'; +} + +export async function agentStep({ config, runtime }, dependencies) { + const { + discoverApp, + listPids, + selectPort, + launchApp, + waitForRenderer, + assertPortOwner, + applyTheme, + removeTheme, + now = () => new Date().toISOString(), + } = dependencies; + + const app = await discoverApp(); + const runningPids = await listPids(app); + + if (!config.enabled) { + let pausedRuntime = null; + if (runtime && runningPids.includes(runtime.appPid)) { + try { + await assertPortOwner(app, runtime.port); + await removeTheme({ port: runtime.port, app }); + pausedRuntime = { ...runtime, themeSlug: config.themeSlug }; + } catch { + // Pausing must not broaden process control when the recorded endpoint is gone. + } + } + return { + runtime: pausedRuntime, + state: observedState( + { + status: 'paused', + themeSlug: config.themeSlug, + appPid: pausedRuntime?.appPid ?? null, + appVersion: app.version, + port: pausedRuntime?.port ?? null, + }, + now, + ), + }; + } + + let managed = runtime && runningPids.includes(runtime.appPid) ? runtime : null; + if (!managed && runningPids.length > 0) { + return { + runtime: null, + state: observedState( + { + status: 'restart-required', + themeSlug: config.themeSlug, + appPid: runningPids[0], + appVersion: app.version, + errorCode: 'APP_RUNNING_WITHOUT_MANAGED_CDP', + }, + now, + ), + }; + } + + if (!managed) { + const port = await selectPort(); + const launched = await launchApp(app, port); + if (!Number.isInteger(launched?.pid) || launched.pid < 1) { + const error = Object.assign(new Error('The official app did not return a valid PID.'), { code: 'APP_LAUNCH_FAILED' }); + return { + runtime: null, + state: observedState( + { status: 'error', themeSlug: config.themeSlug, appVersion: app.version, errorCode: error.code }, + now, + ), + }; + } + managed = { appPid: launched.pid, port, themeSlug: config.themeSlug }; + try { + await waitForRenderer(port, app); + } catch (error) { + return { + runtime: managed, + state: observedState( + { + status: failureStatus(error), + themeSlug: config.themeSlug, + appPid: managed.appPid, + appVersion: app.version, + port, + errorCode: error?.code ?? 'UNEXPECTED_ERROR', + }, + now, + ), + }; + } + } + + try { + await assertPortOwner(app, managed.port); + await applyTheme({ themeSlug: config.themeSlug, port: managed.port, app }); + const nextRuntime = { ...managed, themeSlug: config.themeSlug }; + return { + runtime: nextRuntime, + state: observedState( + { + status: 'active', + themeSlug: config.themeSlug, + appPid: nextRuntime.appPid, + appVersion: app.version, + port: nextRuntime.port, + }, + now, + ), + }; + } catch (error) { + const nextRuntime = LOST_MANAGED_ENDPOINT_ERRORS.has(error?.code) + ? null + : { ...managed, themeSlug: config.themeSlug }; + return { + runtime: nextRuntime, + state: observedState( + { + status: failureStatus(error), + themeSlug: config.themeSlug, + appPid: managed.appPid, + appVersion: app.version, + port: managed.port, + errorCode: error?.code ?? 'UNEXPECTED_ERROR', + }, + now, + ), + }; + } +} + +export async function runPersistentAgent( + { configPath, statePath, intervalMs = 1_500, initialRuntime = null }, + { + readConfig = readAgentConfig, + writeState = writeAgentState, + stepDependencies, + delay = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)), + shouldStop = () => false, + }, +) { + let runtime = initialRuntime; + while (!shouldStop()) { + const config = await readConfig(configPath); + const result = await agentStep({ config, runtime }, stepDependencies); + runtime = result.runtime; + await writeState(statePath, result.state); + await delay(intervalMs); + } + return { stopped: true, runtime }; +} diff --git a/src/engine/config.mjs b/src/engine/config.mjs new file mode 100644 index 0000000..a8001b9 --- /dev/null +++ b/src/engine/config.mjs @@ -0,0 +1,58 @@ +import { chmod, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises'; +import { dirname } from 'node:path'; + +const SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/; + +export class AgentConfigError extends Error { + constructor(code, message, options = {}) { + super(message, options); + this.name = 'AgentConfigError'; + this.code = code; + } +} + +function fail(code, message, options) { + throw new AgentConfigError(code, message, options); +} + +export function validateAgentConfig(input) { + if (!input || typeof input !== 'object' || Array.isArray(input) || input.schemaVersion !== 1) { + fail('CONFIG_SCHEMA_INVALID', 'Agent configuration schemaVersion must be 1.'); + } + if (typeof input.enabled !== 'boolean' || typeof input.launchAtLogin !== 'boolean') { + fail('CONFIG_FIELD_INVALID', 'enabled and launchAtLogin must be booleans.'); + } + if (typeof input.themeSlug !== 'string' || !SLUG_PATTERN.test(input.themeSlug)) { + fail('CONFIG_THEME_INVALID', 'themeSlug must use lowercase kebab-case.'); + } + return { + schemaVersion: 1, + enabled: input.enabled, + themeSlug: input.themeSlug, + launchAtLogin: input.launchAtLogin, + }; +} + +export async function writeAgentConfig(path, input) { + const config = validateAgentConfig(input); + await mkdir(dirname(path), { recursive: true, mode: 0o700 }); + const temporaryPath = `${path}.${process.pid}.${Date.now()}.tmp`; + try { + await writeFile(temporaryPath, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 }); + await chmod(temporaryPath, 0o600); + await rename(temporaryPath, path); + await chmod(path, 0o600); + } catch (error) { + await rm(temporaryPath, { force: true }).catch(() => {}); + fail('CONFIG_WRITE_FAILED', 'Unable to write agent configuration atomically.', { cause: error }); + } +} + +export async function readAgentConfig(path) { + try { + return validateAgentConfig(JSON.parse(await readFile(path, 'utf8'))); + } catch (error) { + if (error instanceof AgentConfigError) throw error; + fail('CONFIG_READ_FAILED', 'Agent configuration is missing or malformed.', { cause: error }); + } +} diff --git a/src/engine/conflicts.mjs b/src/engine/conflicts.mjs new file mode 100644 index 0000000..7f89383 --- /dev/null +++ b/src/engine/conflicts.mjs @@ -0,0 +1,62 @@ +import { execFile } from 'node:child_process'; +import { readFile, readdir } from 'node:fs/promises'; +import { join } from 'node:path'; +import { promisify } from 'node:util'; + +const execFileAsync = promisify(execFile); + +export function detectInjectorConflicts({ launchAgents = [], processCommands = [] }) { + const conflicts = []; + for (const agent of launchAgents) { + const evidence = `${agent.path}\n${agent.content}`; + if (/com\.kkkk\.codex-nocturne|codex-nocturne\/theme_agent\.py/i.test(evidence)) { + conflicts.push({ + id: 'legacy-nocturne-launch-agent', + path: agent.path, + recovery: 'Boot out the legacy agent only after restoring its owned theme state.', + }); + } + } + + if ( + processCommands.some( + (command) => + /\/(?:ChatGPT|Codex)(?:\.app)?\//i.test(command) && + /--remote-allow-origins=\*/.test(command), + ) + ) { + conflicts.push({ + id: 'wildcard-cdp-origin', + recovery: 'Relaunch through the audited loopback-only launcher after explicit user approval.', + }); + } + return conflicts; +} + +export async function inspectInjectorConflicts( + home, + { + readDirectory = readdir, + readText = (path) => readFile(path, 'utf8'), + listCommands = async () => (await execFileAsync('/bin/ps', ['-axo', 'command='], { encoding: 'utf8' })).stdout.split('\n'), + } = {}, +) { + const directory = join(home, 'Library/LaunchAgents'); + let entries = []; + try { + entries = await readDirectory(directory, { withFileTypes: true }); + } catch { + entries = []; + } + const launchAgents = []; + for (const entry of entries) { + if (!entry.isFile() || !entry.name.endsWith('.plist')) continue; + const path = join(directory, entry.name); + try { + launchAgents.push({ path, content: await readText(path) }); + } catch { + // Unreadable unrelated agents are outside this project's ownership. + } + } + return detectInjectorConflicts({ launchAgents, processCommands: await listCommands() }); +} diff --git a/src/engine/control.mjs b/src/engine/control.mjs new file mode 100644 index 0000000..51dd7d4 --- /dev/null +++ b/src/engine/control.mjs @@ -0,0 +1,39 @@ +import { readAgentState } from './agent-state.mjs'; +import { readAgentConfig, writeAgentConfig } from './config.mjs'; +import { kickstartLaunchAgent } from './launch-agent.mjs'; + +export async function pausePersistentInstallation( + { configPath, statePath }, + { + readConfig = readAgentConfig, + writeConfig = writeAgentConfig, + readState = readAgentState, + kickstart = kickstartLaunchAgent, + removeTheme, + } = {}, +) { + const config = await readConfig(configPath); + await writeConfig(configPath, { ...config, enabled: false }); + + let state = null; + try { + state = await readState(statePath); + } catch (error) { + if (error?.code !== 'AGENT_STATE_READ_FAILED') throw error; + } + + await kickstart(); + + let liveRemoval = 'not-reachable'; + if (state?.port !== null && state?.port !== undefined && typeof removeTheme === 'function') { + try { + await removeTheme({ port: state.port, state }); + liveRemoval = 'removed'; + } catch { + // Persistence is already disabled. Never broaden process control just to + // turn an unreachable cleanup request into a success claim. + } + } + + return { paused: true, theme: config.themeSlug, liveRemoval }; +} diff --git a/src/engine/injection.mjs b/src/engine/injection.mjs index 6ca8e22..50d50a6 100644 --- a/src/engine/injection.mjs +++ b/src/engine/injection.mjs @@ -6,6 +6,8 @@ export const CHROME_ID = 'awesome-codex-theme-chrome'; export const EXPERIENCE_MARKER = 'awesome-codex-theme-experience'; const OWNER = 'awesome-codex-themes'; const SURFACE_OBSERVER_KEY = '__awesomeCodexThemeSurfaceObserver'; +const AUXILIARY_ASSET_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/; +const AUXILIARY_ASSET_VARIABLE_PREFIX = '--act-asset-'; function roleToVariable(role) { return `--act-${role.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}`; @@ -60,6 +62,19 @@ function runtimePayload(theme, adapter) { return [variable, value]; }), ); + const assetVariables = Object.fromEntries( + Object.entries(theme.assets ?? {}).map(([name, asset]) => { + if ( + !AUXILIARY_ASSET_NAME_PATTERN.test(name) || + !asset || + typeof asset.dataUrl !== 'string' || + !asset.dataUrl.startsWith('data:image/') + ) { + throw new TypeError(`Theme runtime asset is invalid: ${name}`); + } + return [`${AUXILIARY_ASSET_VARIABLE_PREFIX}${name}`, `url("${asset.dataUrl}")`]; + }), + ); return { slug: manifest.slug, colorScheme: manifest.mode === 'system' ? 'light dark' : manifest.mode, @@ -68,6 +83,7 @@ function runtimePayload(theme, adapter) { adapterCss: adapter.css, artworkDataUrl: artwork.dataUrl, variables, + assetVariables, experience: manifest.experience?.chrome ? { brand: manifest.experience.brand, @@ -107,9 +123,18 @@ export function buildApplyExpression(theme, adapter) { root.dataset.awesomeCodexAdapter = payload.adapterId; root.style.setProperty('--act-artwork', 'url("' + payload.artworkDataUrl + '")'); root.style.setProperty('--act-color-scheme', payload.colorScheme); + for (let index = root.style.length - 1; index >= 0; index -= 1) { + const variable = root.style.item(index); + if (variable.startsWith(${serializeForExpression(AUXILIARY_ASSET_VARIABLE_PREFIX)})) { + root.style.removeProperty(variable); + } + } for (const [variable, value] of Object.entries(payload.variables)) { root.style.setProperty(variable, value); } + for (const [variable, value] of Object.entries(payload.assetVariables)) { + root.style.setProperty(variable, value); + } if (payload.experience && document.body) { const chrome = currentChrome ?? document.createElement('div'); @@ -168,24 +193,391 @@ export function buildApplyExpression(theme, adapter) { const computed = getComputedStyle(node); return rect.width > 1 && rect.height > 1 && computed.display !== 'none' && computed.visibility !== 'hidden' && computed.opacity !== '0'; }; + const isEffectivelyVisible = (node) => { + if (!(node instanceof Element)) return false; + const rect = node.getBoundingClientRect(); + if ( + rect.width <= 1 || + rect.height <= 1 || + rect.right <= 0 || + rect.left >= window.innerWidth || + rect.bottom <= 0 || + rect.top >= window.innerHeight + ) return false; + for (let current = node; current && current !== document; current = current.parentElement) { + const computed = getComputedStyle(current); + if ( + computed.display === 'none' || + computed.visibility === 'hidden' || + Number.parseFloat(computed.opacity || '1') < 0.01 + ) return false; + } + return true; + }; + const findPluginSearchInput = () => { + for (const candidate of document.querySelectorAll('input[placeholder]')) { + const placeholder = candidate.getAttribute('placeholder') || ''; + if (/search plugins|搜索插件/i.test(placeholder)) return candidate; + } + return null; + }; + const findScheduledSearchInput = () => { + for (const candidate of document.querySelectorAll('input[placeholder]')) { + const placeholder = candidate.getAttribute('placeholder') || ''; + if (/search scheduled|搜索已安排/i.test(placeholder)) return candidate; + } + return null; + }; + const findSitesSearchInput = () => { + for (const candidate of document.querySelectorAll('input[placeholder]')) { + const placeholder = candidate.getAttribute('placeholder') || ''; + if (/search sites|搜索站点/i.test(placeholder)) return candidate; + } + return null; + }; + const findPullRequestsSearchInput = () => { + for (const candidate of document.querySelectorAll('input[placeholder]')) { + const placeholder = candidate.getAttribute('placeholder') || ''; + if (/pull requests?|拉取请求/i.test(placeholder)) return candidate; + } + return null; + }; + const findPullRequestsRoot = () => { + for (const heading of document.querySelectorAll('h1')) { + const copy = heading.textContent?.trim() || ''; + if (/^pull requests?$/i.test(copy) || /^拉取请求$/.test(copy)) { + return ( + heading.closest('[role="main"]') || + heading.closest('.app-shell-main-content-viewport') || + heading.closest('.app-shell-main-content-frame') || + document.body + ); + } + } + return null; + }; + const closestMainSurface = (candidate) => { + if (!candidate) return null; + return ( + candidate.closest?.('[role="main"]') || + candidate.closest?.('.app-shell-main-content-viewport') || + candidate.closest?.('.app-shell-main-content-frame') || + document.body + ); + }; + const updateComponentRoles = () => { + const roleKeys = [ + 'awesomeCodexArtifactPanel', + 'awesomeCodexArtifactPanelHost', + 'awesomeCodexArtifactHeader', + 'awesomeCodexPanelMore', + 'awesomeCodexCompletedSummary', + 'awesomeCodexActivityItem', + 'awesomeCodexActivitySummary', + 'awesomeCodexHeaderControl', + 'awesomeCodexSidebarBrand', + 'awesomeCodexSidebarNav', + 'awesomeCodexDomainHeader', + 'awesomeCodexProjectRow', + 'awesomeCodexDomainId', + 'awesomeCodexTaskRow', + 'awesomeCodexAccountTrigger', + 'awesomeCodexAccountIdentity', + 'awesomeCodexHelpTrigger', + 'awesomeCodexHomePrompt', + 'awesomeCodexDomainMarker', + 'awesomeCodexComposerControl', + 'awesomeCodexPluginSearch', + 'awesomeCodexPluginTab', + 'awesomeCodexPluginSectionHeader', + 'awesomeCodexPluginInstalled', + 'awesomeCodexPluginCard', + 'awesomeCodexPluginIcon', + 'awesomeCodexPluginAction', + 'awesomeCodexCollectionSearch', + 'awesomeCodexCollectionTab', + 'awesomeCodexCollectionSectionHeader', + 'awesomeCodexCollectionRow', + 'awesomeCodexCollectionEmpty', + 'awesomeCodexCollectionStatus', + 'awesomeCodexCollectionAction', + ]; + for (const roleKey of roleKeys) { + const attribute = 'data-' + roleKey.replace(/[A-Z]/g, (letter) => '-' + letter.toLowerCase()); + for (const candidate of document.querySelectorAll('[' + attribute + ']')) { + delete candidate.dataset[roleKey]; + } + } + + for (const candidate of document.querySelectorAll('.bg-token-dropdown-background:not(header)')) { + const headers = candidate.querySelectorAll('section > header.bg-token-dropdown-background'); + if (headers.length === 0) continue; + candidate.dataset.awesomeCodexArtifactPanel = 'true'; + const panelHost = + candidate.closest('[data-pip-obstacle="thread-summary-panel"]') || candidate.parentElement; + if (panelHost) panelHost.dataset.awesomeCodexArtifactPanelHost = 'true'; + for (const header of headers) header.dataset.awesomeCodexArtifactHeader = 'true'; + for (const panelMore of candidate.querySelectorAll('.text-token-conversation-summary-trailing')) { + panelMore.dataset.awesomeCodexPanelMore = 'true'; + } + } + + for (const candidate of document.querySelectorAll('button')) { + const isCompletedSummary = Boolean( + candidate.matches('.text-size-chat.inline-flex.items-center.gap-1') && + candidate.querySelector('.text-token-conversation-body'), + ); + if (isCompletedSummary) { + candidate.dataset.awesomeCodexCompletedSummary = 'true'; + } else if (candidate.classList.contains('group/activity-header')) { + candidate.dataset.awesomeCodexActivityItem = 'true'; + } + } + + for (const candidate of document.querySelectorAll('.app-header-tint :is(button, [role="button"])')) { + candidate.dataset.awesomeCodexHeaderControl = 'true'; + } + + for (const candidate of document.querySelectorAll('[class~="group/folder-row"]')) { + candidate.dataset.awesomeCodexProjectRow = 'true'; + } + for (const [index, candidate] of [ + ...document.querySelectorAll('[data-awesome-codex-project-row="true"]'), + ].entries()) { + candidate.dataset.awesomeCodexDomainId = 'D-' + String(index + 1).padStart(2, '0'); + } + for (const dragHandle of document.querySelectorAll('[class~="cursor-grab"]')) { + const taskRow = dragHandle.closest('div.group.relative') || dragHandle.parentElement; + if (taskRow) taskRow.dataset.awesomeCodexTaskRow = 'true'; + } + + for (const sidebar of document.querySelectorAll('.app-shell-left-panel')) { + const domainHeaders = [...sidebar.querySelectorAll('[class~="group/section-toggle"]')]; + for (const candidate of domainHeaders) { + candidate.dataset.awesomeCodexDomainHeader = 'true'; + } + const firstDomainHeader = domainHeaders[0] ?? null; + for (const candidate of sidebar.querySelectorAll('button')) { + const label = (candidate.getAttribute('aria-label') || '').toLocaleLowerCase(); + if (/switch mode|切换模式/.test(label)) { + candidate.dataset.awesomeCodexSidebarBrand = 'true'; + } + if (candidate.querySelector('img.rounded-full')) { + candidate.dataset.awesomeCodexAccountIdentity = 'true'; + candidate.dataset.awesomeCodexAccountTrigger = 'true'; + } + } + for (const candidate of sidebar.querySelectorAll('button')) { + if (candidate === firstDomainHeader) break; + if (candidate.classList.contains('h-[var(--height-token-row)]')) { + const currentIndex = sidebar.querySelectorAll('[data-awesome-codex-sidebar-nav]').length + 1; + candidate.dataset.awesomeCodexSidebarNav = String(currentIndex).padStart(2, '0'); + } + } + for (const candidate of sidebar.querySelectorAll('button[aria-label], [role="button"][aria-label]')) { + const label = (candidate.getAttribute('aria-label') || '').toLocaleLowerCase(); + if ( + label.includes('profile menu') || + label.includes('account menu') || + label.includes('个人资料菜单') || + label.includes('账户菜单') + ) { + candidate.dataset.awesomeCodexAccountTrigger = 'true'; + } else if (label.includes('help') || label.includes('帮助')) { + candidate.dataset.awesomeCodexHelpTrigger = 'true'; + } + } + } + + const homePrompt = document.querySelector( + '[data-awesome-codex-home="true"] [data-feature="game-source"]', + ); + if (homePrompt) { + homePrompt.dataset.awesomeCodexHomePrompt = 'true'; + const domainMarker = homePrompt.querySelector('button'); + if (domainMarker) domainMarker.dataset.awesomeCodexDomainMarker = 'true'; + } + + for (const composer of document.querySelectorAll('.composer-surface-chrome')) { + for (const candidate of composer.querySelectorAll('button')) { + const label = (candidate.getAttribute('aria-label') || '').toLocaleLowerCase(); + const isSquare = candidate.classList.contains('aspect-square'); + let control = ''; + if ( + candidate.matches('[data-testid="composer-send-button"], .composer-send-button') || + candidate.classList.contains('size-token-button-composer') || + candidate.classList.contains('bg-token-foreground') + ) { + control = 'primary'; + } else if (label.includes('dictation') || label.includes('听写')) { + control = 'dictation'; + } else if ( + label.includes('add files') || + label.includes('attach') || + label.includes('添加文件') || + label.includes('附件') + ) { + control = 'attachment'; + } else if (candidate.classList.contains('h-token-button-composer-sm')) { + control = 'access'; + } else if (candidate.classList.contains('h-token-button-composer') && !isSquare) { + control = 'model'; + } + if (control) candidate.dataset.awesomeCodexComposerControl = control; + } + } + + const pluginSearchInput = findPluginSearchInput(); + if (pluginSearchInput) { + const pluginRoot = closestMainSurface(pluginSearchInput); + const searchSurface = pluginSearchInput.parentElement; + if (searchSurface) { + searchSurface.dataset.awesomeCodexPluginSearch = 'true'; + searchSurface.dataset.awesomeCodexCollectionSearch = 'true'; + } + + for (const heading of pluginRoot.querySelectorAll('h2')) { + if (heading.parentElement) { + heading.parentElement.dataset.awesomeCodexPluginSectionHeader = 'true'; + heading.parentElement.dataset.awesomeCodexCollectionSectionHeader = 'true'; + } + } + + for (const installedRow of pluginRoot.querySelectorAll('[class~="group/plugin-row"]')) { + for (const button of installedRow.querySelectorAll(':scope > button')) { + button.dataset.awesomeCodexPluginInstalled = 'true'; + button.dataset.awesomeCodexCollectionRow = 'true'; + const icon = button.querySelector('img')?.parentElement; + if (icon) icon.dataset.awesomeCodexPluginIcon = 'true'; + } + } + + for (const candidate of pluginRoot.querySelectorAll('button.shrink-0')) { + if ( + candidate.classList.contains('h-token-button-composer') && + !candidate.closest('[data-awesome-codex-plugin-card="true"]') && + !candidate.closest('[class~="group/plugin-row"]') + ) { + candidate.dataset.awesomeCodexPluginTab = 'true'; + candidate.dataset.awesomeCodexCollectionTab = 'true'; + } + } + + for (const candidate of pluginRoot.querySelectorAll('div[role="button"][tabindex="0"]')) { + const iconImage = candidate.querySelector('img'); + const contentRow = candidate.querySelector(':scope > .flex.items-center.gap-3'); + if (!iconImage || !contentRow) continue; + candidate.dataset.awesomeCodexPluginCard = 'true'; + candidate.dataset.awesomeCodexCollectionRow = 'true'; + const icon = iconImage.parentElement; + if (icon) icon.dataset.awesomeCodexPluginIcon = 'true'; + for (const action of candidate.querySelectorAll('button')) { + action.dataset.awesomeCodexPluginAction = action.classList.contains('aspect-square') + ? 'menu' + : 'install'; + } + } + } + + const scheduledSearchInput = findScheduledSearchInput(); + if (scheduledSearchInput) { + const scheduledRoot = closestMainSurface(scheduledSearchInput); + if (scheduledSearchInput.parentElement) { + scheduledSearchInput.parentElement.dataset.awesomeCodexCollectionSearch = 'true'; + } + for (const candidate of scheduledRoot.querySelectorAll('button.shrink-0.h-token-button-composer')) { + candidate.dataset.awesomeCodexCollectionTab = 'true'; + } + for (const candidate of scheduledRoot.querySelectorAll('.automation-row')) { + candidate.dataset.awesomeCodexCollectionRow = 'true'; + } + for (const heading of scheduledRoot.querySelectorAll('h2')) { + if (heading.parentElement) { + heading.parentElement.dataset.awesomeCodexCollectionSectionHeader = 'true'; + } + } + } + + const sitesSearchInput = findSitesSearchInput(); + if (sitesSearchInput) { + const sitesRoot = closestMainSurface(sitesSearchInput); + if (sitesSearchInput.parentElement) { + sitesSearchInput.parentElement.dataset.awesomeCodexCollectionSearch = 'true'; + } + const emptyState = sitesRoot.querySelector('.max-w-xl.flex-col.items-center'); + if (emptyState) { + emptyState.dataset.awesomeCodexCollectionEmpty = 'true'; + const action = emptyState.querySelector('button'); + if (action) action.dataset.awesomeCodexCollectionAction = 'primary'; + } + } + + const pullRequestsSearchInput = findPullRequestsSearchInput(); + const pullRequestsRoot = + closestMainSurface(pullRequestsSearchInput) || findPullRequestsRoot(); + if (pullRequestsRoot) { + if (pullRequestsSearchInput?.parentElement) { + pullRequestsSearchInput.parentElement.dataset.awesomeCodexCollectionSearch = 'true'; + } + for (const candidate of pullRequestsRoot.querySelectorAll('button.shrink-0.h-token-button-composer')) { + candidate.dataset.awesomeCodexCollectionTab = 'true'; + } + const status = pullRequestsRoot.querySelector('[role="status"]'); + if (status) status.dataset.awesomeCodexCollectionStatus = 'true'; + const emptyState = pullRequestsRoot.querySelector('.max-w-xl.flex-col.items-center'); + if (emptyState) emptyState.dataset.awesomeCodexCollectionEmpty = 'true'; + } + }; const updateSurface = () => { + const pluginSearchInput = findPluginSearchInput(); + const scheduledSearchInput = findScheduledSearchInput(); + const sitesSearchInput = findSitesSearchInput(); + const pullRequestsSearchInput = findPullRequestsSearchInput(); + const pullRequestsRoot = findPullRequestsRoot(); let home = null; - for (const candidate of document.querySelectorAll('[role="main"]')) { - const icon = candidate.querySelector('[data-testid="home-icon"]'); - const source = candidate.querySelector('[data-feature="game-source"]'); - const suggestions = candidate.querySelector('[class~="group/home-suggestions"]'); - const hasIdentity = isVisible(icon) && isVisible(source); - const hasActions = isVisible(source) && isVisible(suggestions); - if (isVisible(candidate) && (hasIdentity || hasActions)) { - home = candidate; - break; + if ( + !pluginSearchInput && + !scheduledSearchInput && + !sitesSearchInput && + !pullRequestsSearchInput && + !pullRequestsRoot + ) { + for (const candidate of document.querySelectorAll('[role="main"]')) { + const icon = candidate.querySelector('[data-testid="home-icon"]'); + const source = candidate.querySelector('[data-feature="game-source"]'); + const suggestions = candidate.querySelector('[class~="group/home-suggestions"]'); + const hasIdentity = isVisible(icon) && isVisible(source); + const hasActions = isVisible(source) && isVisible(suggestions); + if (isVisible(candidate) && (hasIdentity || hasActions)) { + home = candidate; + break; + } } } for (const candidate of document.querySelectorAll('[data-awesome-codex-home]')) { if (candidate !== home) delete candidate.dataset.awesomeCodexHome; } if (home) home.dataset.awesomeCodexHome = 'true'; - root.dataset.awesomeCodexSurface = home ? 'home' : 'workspace'; + root.dataset.awesomeCodexSurface = pluginSearchInput + ? 'plugins' + : scheduledSearchInput + ? 'scheduled' + : sitesSearchInput + ? 'sites' + : pullRequestsSearchInput || pullRequestsRoot + ? 'pull-requests' + : home + ? 'home' + : 'workspace'; + updateComponentRoles(); + const artifactPanels = [...document.querySelectorAll('[data-awesome-codex-artifact-panel="true"]')]; + if (artifactPanels.length === 0) { + delete root.dataset.awesomeCodexArtifactPanelState; + } else { + root.dataset.awesomeCodexArtifactPanelState = artifactPanels.some(isEffectivelyVisible) + ? 'open' + : 'closed'; + } }; updateSurface(); const previousObserver = window[${serializeForExpression(SURFACE_OBSERVER_KEY)}]; @@ -200,7 +592,12 @@ export function buildApplyExpression(theme, adapter) { updateSurface(); }); }); - observer.observe(document.body, { childList: true, subtree: true }); + observer.observe(document.body, { + childList: true, + subtree: true, + attributes: true, + attributeFilter: ['class', 'style', 'aria-pressed'], + }); window[${serializeForExpression(SURFACE_OBSERVER_KEY)}] = observer; } @@ -243,11 +640,60 @@ export function buildRemoveExpression() { delete root.dataset.awesomeCodexTheme; delete root.dataset.awesomeCodexAdapter; delete root.dataset.awesomeCodexSurface; + delete root.dataset.awesomeCodexArtifactPanelState; for (const variable of ${variables}) root.style.removeProperty(variable); + for (let index = root.style.length - 1; index >= 0; index -= 1) { + const variable = root.style.item(index); + if (variable.startsWith(${serializeForExpression(AUXILIARY_ASSET_VARIABLE_PREFIX)})) { + root.style.removeProperty(variable); + } + } } for (const candidate of document.querySelectorAll('[data-awesome-codex-home]')) { delete candidate.dataset.awesomeCodexHome; } + const roleKeys = [ + 'awesomeCodexArtifactPanel', + 'awesomeCodexArtifactPanelHost', + 'awesomeCodexArtifactHeader', + 'awesomeCodexPanelMore', + 'awesomeCodexCompletedSummary', + 'awesomeCodexActivityItem', + 'awesomeCodexActivitySummary', + 'awesomeCodexHeaderControl', + 'awesomeCodexSidebarBrand', + 'awesomeCodexSidebarNav', + 'awesomeCodexDomainHeader', + 'awesomeCodexProjectRow', + 'awesomeCodexDomainId', + 'awesomeCodexTaskRow', + 'awesomeCodexAccountTrigger', + 'awesomeCodexAccountIdentity', + 'awesomeCodexHelpTrigger', + 'awesomeCodexHomePrompt', + 'awesomeCodexDomainMarker', + 'awesomeCodexComposerControl', + 'awesomeCodexPluginSearch', + 'awesomeCodexPluginTab', + 'awesomeCodexPluginSectionHeader', + 'awesomeCodexPluginInstalled', + 'awesomeCodexPluginCard', + 'awesomeCodexPluginIcon', + 'awesomeCodexPluginAction', + 'awesomeCodexCollectionSearch', + 'awesomeCodexCollectionTab', + 'awesomeCodexCollectionSectionHeader', + 'awesomeCodexCollectionRow', + 'awesomeCodexCollectionEmpty', + 'awesomeCodexCollectionStatus', + 'awesomeCodexCollectionAction', + ]; + for (const roleKey of roleKeys) { + const attribute = 'data-' + roleKey.replace(/[A-Z]/g, (letter) => '-' + letter.toLowerCase()); + for (const candidate of document.querySelectorAll('[' + attribute + ']')) { + delete candidate.dataset[roleKey]; + } + } return { pass: !document.getElementById(${serializeForExpression(STYLE_ID)}) && !document.getElementById(${serializeForExpression(CHROME_ID)}) && !root?.classList?.contains(${serializeForExpression(APPLY_MARKER)}), action: 'removed', diff --git a/src/engine/installer.mjs b/src/engine/installer.mjs new file mode 100644 index 0000000..96e2789 --- /dev/null +++ b/src/engine/installer.mjs @@ -0,0 +1,144 @@ +import { access, chmod, cp, mkdir, readdir, rename, rm, symlink } from 'node:fs/promises'; +import { isAbsolute, join } from 'node:path'; + +import { writeAgentConfig } from './config.mjs'; +import { + bootoutLaunchAgent, + bootstrapLaunchAgent, + buildLaunchAgentPlist, + kickstartLaunchAgent, + writeLaunchAgentPlist, +} from './launch-agent.mjs'; +import { loadThemePackage } from './theme.mjs'; + +const VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/; + +export class InstallerError extends Error { + constructor(code, message, options = {}) { + super(message, options); + this.name = 'InstallerError'; + this.code = code; + } +} + +function fail(code, message, options) { + throw new InstallerError(code, message, options); +} + +function requireAbsolute(value, field) { + if (typeof value !== 'string' || !isAbsolute(value)) fail('INSTALL_PATH_INVALID', `${field} must be an absolute path.`); + return value; +} + +export function installationPaths({ home, version }) { + const userHome = requireAbsolute(home, 'home'); + if (typeof version !== 'string' || !VERSION_PATTERN.test(version)) { + fail('INSTALL_VERSION_INVALID', 'version must use semantic versioning.'); + } + const supportRoot = join(userHome, 'Library/Application Support/AwesomeCodexThemes'); + const releasesRoot = join(supportRoot, 'releases'); + const releaseDir = join(releasesRoot, version); + const currentPath = join(supportRoot, 'current'); + return { + supportRoot, + releasesRoot, + releaseDir, + currentPath, + configPath: join(supportRoot, 'config.json'), + agentStatePath: join(supportRoot, 'agent-state.json'), + plistPath: join(userHome, 'Library/LaunchAgents/io.github.awesome-codex-themes.agent.plist'), + stdoutPath: join(userHome, 'Library/Logs/AwesomeCodexThemes.log'), + stderrPath: join(userHome, 'Library/Logs/AwesomeCodexThemes.error.log'), + }; +} + +async function exists(path) { + try { + await access(path); + return true; + } catch { + return false; + } +} + +async function validateRunnableThemes(sourceRoot, selectedTheme) { + const themesRoot = join(sourceRoot, 'themes'); + const entries = await readdir(themesRoot, { withFileTypes: true }); + const slugs = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort(); + if (!slugs.includes(selectedTheme)) fail('INSTALL_THEME_NOT_FOUND', `Theme ${selectedTheme} is not a runnable package.`); + await Promise.all(slugs.map((slug) => loadThemePackage(join(themesRoot, slug)))); + return slugs; +} + +export async function installPersistentAgent( + { sourceRoot, home, version, themeSlug, enabled = true }, + { + copy = (source, destination) => cp(source, destination, { recursive: true, force: true }), + bootout = bootoutLaunchAgent, + bootstrap = bootstrapLaunchAgent, + kickstart = kickstartLaunchAgent, + writeConfig = writeAgentConfig, + writePlist = writeLaunchAgentPlist, + } = {}, +) { + const source = requireAbsolute(sourceRoot, 'sourceRoot'); + const paths = installationPaths({ home, version }); + await validateRunnableThemes(source, themeSlug); + await mkdir(paths.releasesRoot, { recursive: true, mode: 0o700 }); + + if (!(await exists(paths.releaseDir))) { + const staging = `${paths.releaseDir}.staging-${process.pid}-${Date.now()}`; + await mkdir(staging, { recursive: true, mode: 0o700 }); + try { + await copy(join(source, 'package.json'), join(staging, 'package.json')); + await copy(join(source, 'bin'), join(staging, 'bin')); + await copy(join(source, 'src/cli'), join(staging, 'src/cli')); + await copy(join(source, 'src/engine'), join(staging, 'src/engine')); + await copy(join(source, 'themes'), join(staging, 'themes')); + await chmod(join(staging, 'bin/awesome-codex-themes'), 0o755); + await rename(staging, paths.releaseDir); + } catch (error) { + await rm(staging, { recursive: true, force: true }).catch(() => {}); + fail('INSTALL_COPY_FAILED', 'Unable to stage the self-contained runtime.', { cause: error }); + } + } + + await writeConfig(paths.configPath, { + schemaVersion: 1, + enabled, + themeSlug, + launchAtLogin: true, + }); + + const plist = buildLaunchAgentPlist({ + launcherPath: join(paths.currentPath, 'bin/awesome-codex-themes'), + stdoutPath: paths.stdoutPath, + stderrPath: paths.stderrPath, + }); + await writePlist(paths.plistPath, plist); + + const temporaryLink = `${paths.currentPath}.next-${process.pid}-${Date.now()}`; + try { + await symlink(paths.releaseDir, temporaryLink, 'dir'); + await rename(temporaryLink, paths.currentPath); + } catch (error) { + await rm(temporaryLink, { force: true }).catch(() => {}); + fail('INSTALL_ACTIVATE_FAILED', 'Unable to activate the installed runtime.', { cause: error }); + } + + await bootout({ ignoreMissing: true }); + await bootstrap(paths.plistPath); + await kickstart(); + return { installed: true, theme: themeSlug, version, supportRoot: paths.supportRoot, plistPath: paths.plistPath }; +} + +export async function uninstallPersistentAgent( + { home }, + { bootout = bootoutLaunchAgent, remove = rm } = {}, +) { + const paths = installationPaths({ home, version: '0.0.0' }); + await bootout({ ignoreMissing: true }); + await remove(paths.plistPath, { force: true }); + await remove(paths.supportRoot, { recursive: true, force: true }); + return { uninstalled: true }; +} diff --git a/src/engine/launch-agent.mjs b/src/engine/launch-agent.mjs new file mode 100644 index 0000000..fb26404 --- /dev/null +++ b/src/engine/launch-agent.mjs @@ -0,0 +1,167 @@ +import { execFile } from 'node:child_process'; +import { chmod, mkdir, rename, rm, writeFile } from 'node:fs/promises'; +import { dirname, isAbsolute } from 'node:path'; +import { promisify } from 'node:util'; + +export const LAUNCH_AGENT_LABEL = 'io.github.awesome-codex-themes.agent'; + +const execFileAsync = promisify(execFile); + +export class LaunchAgentError extends Error { + constructor(code, message, options = {}) { + super(message, options); + this.name = 'LaunchAgentError'; + this.code = code; + } +} + +function fail(code, message, options) { + throw new LaunchAgentError(code, message, options); +} + +function requireAbsolutePath(value, field) { + if (typeof value !== 'string' || !isAbsolute(value)) { + fail('LAUNCH_AGENT_PATH_INVALID', `${field} must be an absolute path.`); + } + return value; +} + +function xml(value) { + return value + .replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>') + .replaceAll('"', '"') + .replaceAll("'", '''); +} + +export function buildLaunchAgentPlist({ launcherPath, stdoutPath, stderrPath }) { + const launcher = xml(requireAbsolutePath(launcherPath, 'launcherPath')); + const stdout = xml(requireAbsolutePath(stdoutPath, 'stdoutPath')); + const stderr = xml(requireAbsolutePath(stderrPath, 'stderrPath')); + return ` + + + + Label + ${LAUNCH_AGENT_LABEL} + ProgramArguments + + ${launcher} + _agent + + RunAtLoad + + KeepAlive + + ProcessType + Background + ThrottleInterval + 10 + StandardOutPath + ${stdout} + StandardErrorPath + ${stderr} + + +`; +} + +export async function writeLaunchAgentPlist(path, plist) { + requireAbsolutePath(path, 'plist path'); + if (typeof plist !== 'string' || !plist.includes(`${LAUNCH_AGENT_LABEL}`)) { + fail('LAUNCH_AGENT_PLIST_INVALID', 'LaunchAgent plist content is invalid.'); + } + await mkdir(dirname(path), { recursive: true, mode: 0o700 }); + const temporaryPath = `${path}.${process.pid}.${Date.now()}.tmp`; + try { + await writeFile(temporaryPath, plist, { mode: 0o644 }); + await chmod(temporaryPath, 0o644); + await rename(temporaryPath, path); + await chmod(path, 0o644); + } catch (error) { + await rm(temporaryPath, { force: true }).catch(() => {}); + fail('LAUNCH_AGENT_WRITE_FAILED', 'Unable to write the LaunchAgent plist atomically.', { cause: error }); + } +} + +function domain(uid) { + if (!Number.isInteger(uid) || uid < 1) fail('LAUNCH_AGENT_UID_INVALID', 'A positive user ID is required.'); + return `gui/${uid}`; +} + +async function defaultRun(executable, args) { + return execFileAsync(executable, args, { encoding: 'utf8' }); +} + +function isTransientBootstrap(error) { + const detail = `${error?.message ?? ''}\n${error?.stderr ?? ''}`.toLowerCase(); + return error?.code === 5 && detail.includes('input/output error'); +} + +export async function bootstrapLaunchAgent( + plistPath, + { + uid = process.getuid?.(), + run = defaultRun, + delay = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)), + attempts = 20, + } = {}, +) { + const args = ['bootstrap', domain(uid), requireAbsolutePath(plistPath, 'plistPath')]; + let lastError; + for (let attempt = 1; attempt <= attempts; attempt += 1) { + try { + await run('/bin/launchctl', args); + return; + } catch (error) { + lastError = error; + if (!isTransientBootstrap(error) || attempt === attempts) break; + await delay(250); + } + } + fail('LAUNCH_AGENT_BOOTSTRAP_FAILED', 'Unable to bootstrap the Awesome Codex Themes LaunchAgent.', { + cause: lastError, + }); +} + +export async function kickstartLaunchAgent({ uid = process.getuid?.(), run = defaultRun } = {}) { + const target = `${domain(uid)}/${LAUNCH_AGENT_LABEL}`; + try { + await run('/bin/launchctl', ['kickstart', '-k', target]); + } catch (error) { + fail('LAUNCH_AGENT_KICKSTART_FAILED', 'Unable to kickstart the Awesome Codex Themes LaunchAgent.', { cause: error }); + } +} + +function isMissingService(error) { + const detail = `${error?.message ?? ''}\n${error?.stderr ?? ''}`.toLowerCase(); + return (error?.code === 3 || error?.code === 113) && ( + detail.includes('no such process') || + detail.includes('could not find specified service') || + detail.includes('could not find service') + ); +} + +export async function isLaunchAgentLoaded({ uid = process.getuid?.(), run = defaultRun } = {}) { + const target = `${domain(uid)}/${LAUNCH_AGENT_LABEL}`; + try { + await run('/bin/launchctl', ['print', target]); + return true; + } catch (error) { + if (isMissingService(error)) return false; + fail('LAUNCH_AGENT_STATUS_FAILED', 'Unable to inspect the Awesome Codex Themes LaunchAgent.', { cause: error }); + } +} + +export async function bootoutLaunchAgent( + { uid = process.getuid?.(), run = defaultRun, ignoreMissing = false } = {}, +) { + const target = `${domain(uid)}/${LAUNCH_AGENT_LABEL}`; + try { + await run('/bin/launchctl', ['bootout', target]); + } catch (error) { + if (ignoreMissing && isMissingService(error)) return; + fail('LAUNCH_AGENT_BOOTOUT_FAILED', 'Unable to boot out the Awesome Codex Themes LaunchAgent.', { cause: error }); + } +} diff --git a/src/engine/session.mjs b/src/engine/session.mjs index fde5eec..ec26866 100644 --- a/src/engine/session.mjs +++ b/src/engine/session.mjs @@ -86,7 +86,7 @@ export async function startThemeSession( if (!Number.isInteger(appPid) || appPid < 1) sessionFail('APP_LAUNCH_FAILED', 'The official app did not return a valid PID.'); const appStartedAt = await processStartedAt(appPid); - await waitForRenderer(port); + await waitForRenderer(port, app); let watcher; try { await applyTheme({ themeSlug, port, app }); diff --git a/src/engine/theme.mjs b/src/engine/theme.mjs index bc3a9b3..c5e2cfb 100644 --- a/src/engine/theme.mjs +++ b/src/engine/theme.mjs @@ -40,14 +40,19 @@ export const SEMANTIC_COLOR_ROLES = Object.freeze([ ]); export const DEFAULT_MAX_ASSET_BYTES = 10 * 1024 * 1024; export const DEFAULT_MAX_RUNTIME_ARTWORK_BYTES = 700 * 1024; +export const DEFAULT_MAX_RUNTIME_ASSET_BYTES = 256 * 1024; export const DEFAULT_MAX_CSS_BYTES = 256 * 1024; const SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/; +const AUXILIARY_ASSET_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/; +const MAX_AUXILIARY_ASSETS = 16; const VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/; const APP_VERSION_RANGE_PATTERN = /^\d+(?:\.\d+)+(?:\.\*)?$/; +const NATIVE_LOCALE_PATTERN = /^[a-z]{2,3}(?:-[A-Z][a-z]{3})?(?:-(?:[A-Z]{2}|\d{3}))?(?:-[A-Za-z0-9]{4,8})*$/; const HEX_COLOR_PATTERN = /^#[0-9A-Fa-f]{6}(?:[0-9A-Fa-f]{2})?$/; const SUPPORTED_PLATFORMS = new Set(['macos']); const SUPPORTED_STATUS = new Set(['experimental', 'verified']); +const SUPPORTED_COMPATIBILITY_STRATEGIES = new Set(['best-effort-all']); const SUPPORTED_MODES = new Set(['dark', 'light', 'system']); const EXPERIENCE_FIELD_LIMITS = Object.freeze({ brand: 48, @@ -106,6 +111,25 @@ function validateLocalPath(value, field) { return path; } +function normalizeAuxiliaryAssets(value) { + if (value === undefined) return {}; + if (!value || typeof value !== 'object' || Array.isArray(value)) { + fail('THEME_FIELD_INVALID', 'files.assets must be an object of named local image paths.'); + } + const entries = Object.entries(value); + if (entries.length > MAX_AUXILIARY_ASSETS) { + fail('THEME_ASSET_COUNT_EXCEEDED', `files.assets supports at most ${MAX_AUXILIARY_ASSETS} entries.`); + } + return Object.fromEntries( + entries.map(([name, path]) => { + if (!AUXILIARY_ASSET_NAME_PATTERN.test(name)) { + fail('THEME_ASSET_NAME_INVALID', 'files.assets keys must use lowercase kebab-case.'); + } + return [name, validateLocalPath(path, `files.assets.${name}`)]; + }), + ); +} + function validateColor(value, field) { if (typeof value !== 'string' || !HEX_COLOR_PATTERN.test(value)) { fail('THEME_COLOR_INVALID', `${field} must be a six- or eight-digit hex color.`); @@ -187,6 +211,22 @@ function normalizeExperience(value, schemaVersion) { }; } +function normalizeNativeDisplayName(input) { + const hasNativeName = input.nativeName !== undefined; + const hasNativeLocale = input.nativeLocale !== undefined; + if (hasNativeName !== hasNativeLocale) { + fail('THEME_NATIVE_NAME_INCOMPLETE', 'nativeName and nativeLocale must be provided together.'); + } + if (!hasNativeName) return undefined; + + const nativeName = requireString(input.nativeName, 'nativeName'); + const nativeLocale = requireString(input.nativeLocale, 'nativeLocale'); + if (!NATIVE_LOCALE_PATTERN.test(nativeLocale)) { + fail('THEME_NATIVE_LOCALE_INVALID', 'nativeLocale must use a canonical BCP 47 language tag such as ja-JP.'); + } + return { nativeName, nativeLocale }; +} + function validateManifestObject(input) { if (!input || typeof input !== 'object' || Array.isArray(input)) { fail('THEME_JSON_INVALID', 'theme.json must contain an object.'); @@ -246,17 +286,29 @@ function validateManifestObject(input) { fail('THEME_MODE_INVALID', 'mode must be dark, light, or system.'); } - const appVersions = requireStringArray(compatibility.appVersions, 'compatibility.appVersions'); - if (appVersions.some((range) => !APP_VERSION_RANGE_PATTERN.test(range))) { - fail('THEME_COMPATIBILITY_INVALID', 'compatibility.appVersions must contain exact versions or a trailing wildcard.'); + const strategy = compatibility.strategy ?? 'best-effort-all'; + if (!SUPPORTED_COMPATIBILITY_STRATEGIES.has(strategy)) { + fail('THEME_COMPATIBILITY_INVALID', 'compatibility.strategy must be best-effort-all.'); + } + const verifiedAppVersions = requireStringArray( + compatibility.verifiedAppVersions ?? compatibility.appVersions, + 'compatibility.verifiedAppVersions', + ); + if (verifiedAppVersions.some((range) => !APP_VERSION_RANGE_PATTERN.test(range))) { + fail( + 'THEME_COMPATIBILITY_INVALID', + 'compatibility.verifiedAppVersions must contain exact versions or a trailing wildcard.', + ); } const experience = normalizeExperience(input.experience, input.schemaVersion); + const nativeDisplayName = normalizeNativeDisplayName(input); return { schemaVersion: input.schemaVersion, slug, version, name: requireString(input.name, 'name'), + ...(nativeDisplayName ?? {}), description: requireString(input.description, 'description'), author: { name: requireString(author.name, 'author.name'), @@ -271,7 +323,8 @@ function validateManifestObject(input) { compatibility: { platforms, status: compatibility.status, - appVersions, + strategy, + verifiedAppVersions, }, mode, palette: normalizePalette(palette, input.schemaVersion), @@ -280,6 +333,10 @@ function validateManifestObject(input) { css: validateLocalPath(files.css, 'files.css'), artwork: validateLocalPath(files.artwork, 'files.artwork'), preview: validateLocalPath(files.preview, 'files.preview'), + ...(() => { + const assets = normalizeAuxiliaryAssets(files.assets); + return Object.keys(assets).length > 0 ? { assets } : {}; + })(), }, }; } @@ -306,13 +363,16 @@ export function assertThemeCompatibility(input, { platform, appVersion } = {}) { if (typeof appVersion !== 'string' || !/^\d+(?:\.\d+)+$/.test(appVersion)) { fail('THEME_APP_VERSION_INVALID', 'A numeric Codex app version is required.'); } - if (!manifest.compatibility.appVersions.some((range) => versionMatchesRange(appVersion, range))) { - fail( - 'THEME_APP_VERSION_UNSUPPORTED', - `Theme ${manifest.slug} does not declare support for Codex ${appVersion}; official UI was left unchanged.`, - ); - } - return { platform, appVersion, status: manifest.compatibility.status }; + const verified = manifest.compatibility.verifiedAppVersions.some((range) => + versionMatchesRange(appVersion, range), + ); + return { + platform, + appVersion, + status: manifest.compatibility.status, + strategy: manifest.compatibility.strategy, + verified, + }; } async function resolveThemeFile(root, relativePath) { @@ -357,7 +417,7 @@ function validateCss(css) { if (css.includes('\\')) { fail('THEME_CSS_UNSAFE_ESCAPE', 'Theme CSS cannot contain escape sequences that obscure executable or remote tokens.'); } - if (/\@import\s/i.test(css) || /https?:|\/\//i.test(css)) { + if (/\@import\s/i.test(css) || /https?:|(?:url|image-set)\s*\([^)]*\/\//i.test(css)) { fail('THEME_CSS_REMOTE_IMPORT', 'Theme CSS cannot load remote resources.'); } if (/url\s*\(/i.test(css) || /javascript\s*:|expression\s*\(/i.test(css)) { @@ -394,6 +454,7 @@ export async function loadThemePackage( { maxAssetBytes = DEFAULT_MAX_ASSET_BYTES, maxRuntimeArtworkBytes = DEFAULT_MAX_RUNTIME_ARTWORK_BYTES, + maxRuntimeAssetBytes = DEFAULT_MAX_RUNTIME_ASSET_BYTES, maxCssBytes = DEFAULT_MAX_CSS_BYTES, } = {}, ) { @@ -415,10 +476,19 @@ export async function loadThemePackage( const cssPath = await resolveThemeFile(canonicalRoot, manifest.files.css); const artworkPath = await resolveThemeFile(canonicalRoot, manifest.files.artwork); const previewPath = await resolveThemeFile(canonicalRoot, manifest.files.preview); - const [cssInfo, artworkInfo, previewInfo] = await Promise.all([ + const auxiliaryAssetEntries = await Promise.all( + Object.entries(manifest.files.assets ?? {}).map(async ([name, relativePath]) => [ + name, + await resolveThemeFile(canonicalRoot, relativePath), + ]), + ); + const [cssInfo, artworkInfo, previewInfo, auxiliaryAssetInfos] = await Promise.all([ stat(cssPath), stat(artworkPath), stat(previewPath), + Promise.all( + auxiliaryAssetEntries.map(async ([name, path]) => [name, path, await stat(path)]), + ), ]); if (cssInfo.size > maxCssBytes) { @@ -433,17 +503,46 @@ export async function loadThemePackage( `Runtime artwork exceeds ${maxRuntimeArtworkBytes} bytes and cannot be injected reliably.`, ); } + for (const [name, , info] of auxiliaryAssetInfos) { + if (info.size > maxAssetBytes) { + fail('THEME_ASSET_TOO_LARGE', `Theme asset ${name} exceeds ${maxAssetBytes} bytes.`); + } + if (info.size > maxRuntimeAssetBytes) { + fail( + 'THEME_RUNTIME_ASSET_TOO_LARGE', + `Runtime asset ${name} exceeds ${maxRuntimeAssetBytes} bytes and cannot be injected reliably.`, + ); + } + } - const [css, artworkBuffer, previewBuffer] = await Promise.all([ + const [css, artworkBuffer, previewBuffer, auxiliaryAssetBuffers] = await Promise.all([ readFile(cssPath, 'utf8'), readFile(artworkPath), readFile(previewPath), + Promise.all( + auxiliaryAssetEntries.map(async ([name, path]) => [name, path, await readFile(path)]), + ), ]); validateCss(css); const mime = mimeForArtwork(artworkPath); mimeForArtwork(previewPath); validateSvg(artworkBuffer, artworkPath); validateSvg(previewBuffer, previewPath); + const assets = Object.fromEntries( + auxiliaryAssetBuffers.map(([name, path, buffer]) => { + const assetMime = mimeForArtwork(path); + validateSvg(buffer, path); + return [ + name, + { + path, + mime: assetMime, + bytes: buffer.byteLength, + dataUrl: `data:${assetMime};base64,${buffer.toString('base64')}`, + }, + ]; + }), + ); return { root: canonicalRoot, @@ -455,6 +554,7 @@ export async function loadThemePackage( bytes: artworkBuffer.byteLength, dataUrl: `data:${mime};base64,${artworkBuffer.toString('base64')}`, }, + assets, previewPath, }; } diff --git a/src/gallery/App.tsx b/src/gallery/App.tsx index 33ec816..0ef2e9c 100644 --- a/src/gallery/App.tsx +++ b/src/gallery/App.tsx @@ -2,7 +2,10 @@ import { useMemo, useState } from 'react'; import catalog from '../generated/themes.json'; -type Theme = (typeof catalog)[number]; +type Theme = (typeof catalog)[number] & { + nativeName?: string; + nativeLocale?: string; +}; const themes = [...(catalog as Theme[])].sort((left, right) => { const featuredDifference = Number(right.tags.includes('featured')) - Number(left.tags.includes('featured')); @@ -35,6 +38,7 @@ function ThemeCard({ theme, onOpen }: { theme: Theme; onOpen: (theme: Theme) => {theme.compatibility.status}

{theme.name}

+ {theme.nativeName ?

{theme.nativeName}

: null}

{theme.description}

@@ -70,11 +74,13 @@ function ThemeDialog({ theme, onClose }: { theme: Theme; onClose: () => void })

Theme specimen / {theme.version}

{theme.name}

+ {theme.nativeName ?

{theme.nativeName}

: null}

{theme.description}

Status
{theme.compatibility.status}
Mode
{theme.mode}
-
Tested range
{theme.compatibility.appVersions.join(', ')}
+
Compatibility
Best effort on every numeric Codex Desktop version
+
Highly compatible
{theme.compatibility.verifiedAppVersions.join(', ')}
Artwork
{theme.license.artwork}
Coverage
{theme.tags.includes('full-workspace') ? 'Full workspace' : 'Legacy palette'}
@@ -83,14 +89,11 @@ function ThemeDialog({ theme, onClose }: { theme: Theme; onClose: () => void })
{theme.command} -
-

Experimental Mac-first runtime theme. Run doctor first and use restore to return to the official UI.

- {theme.license.artwork === 'PROTOTYPE-REFERENCE-ONLY' ? ( -

Private fan prototype artwork. Replace the character asset and naming before any public or commercial release.

- ) : null} +

Experimental Mac-first runtime theme. Run doctor first. If the interface looks wrong, run pause or restore to return to the official UI, then file a GitHub Issue with the Codex version and a privacy-safe screenshot.

@@ -107,7 +110,10 @@ export function App() { const normalized = query.trim().toLowerCase(); return themes.filter((theme) => { const matchesCategory = category === 'all' || theme.categories.includes(category); - const searchable = [theme.name, theme.description, ...theme.categories, ...theme.tags].join(' ').toLowerCase(); + const searchable = [theme.name, theme.nativeName, theme.description, ...theme.categories, ...theme.tags] + .filter(Boolean) + .join(' ') + .toLowerCase(); return matchesCategory && (!normalized || searchable.includes(normalized)); }); }, [category, query]); @@ -135,8 +141,8 @@ export function App() {

Open-source Codex skin system / macOS

-

Make Codex feel limitless.

-

A flagship full-interface theme, a versioned full-workspace adapter, local assets, diagnostics, and a restore path you can inspect. Atmosphere without surrendering control.

+

Make Codex feel like yours.

+

Twelve complete full-workspace themes, one persistent local agent, and an inspectable adapter with diagnostics, switching, and recovery built in.

Browse the collection Inspect the source ↗ @@ -150,15 +156,15 @@ export function App() { > {`${flagship.name}
- 05 + 12
- 1 flagship theme - 4 original studies + 12 complete themes + 1 featured flagship MIT engine · local packages
@@ -181,7 +187,7 @@ export function App() { type="search" value={query} onChange={(event) => setQuery(event.target.value)} - placeholder="Try “aurora” or “editorial”" + placeholder="Try “night city” or “ukiyoe”" aria-label="Search themes" /> @@ -204,7 +210,7 @@ export function App() {

No matching themes.

-

The atlas is small on purpose. Try another word or return to the full collection.

+

Try another word or return to the complete twelve-theme collection.

)} @@ -216,9 +222,9 @@ export function App() {

Decoration should never outrank recovery.

    -
  1. 01

    Local by construction

    Theme packages use local CSS, metadata, and original artwork. No remote JavaScript and no conversation access.

  2. -
  3. 02

    Fail closed

    The engine checks the signed app, loopback endpoint, renderer target, and supported versions before applying.

  4. -
  5. 03

    One owner, clean exit

    Injection is namespaced and idempotent. Restore removes only the state owned by Awesome Codex Themes.

  6. +
  7. 01

    Local by construction

    Theme packages use local CSS, metadata, and documented artwork. No remote JavaScript and no conversation access.

  8. +
  9. 02

    Verify the boundary

    The engine checks the signed app, loopback endpoint, and renderer target before attempting a verified or best-effort adapter.

  10. +
  11. 03

    One owner, clean exit

    Injection is namespaced and idempotent. Pause and uninstall remove only the state owned by Awesome Codex Themes.

diff --git a/src/gallery/styles.css b/src/gallery/styles.css index d051bde..05416d0 100644 --- a/src/gallery/styles.css +++ b/src/gallery/styles.css @@ -135,6 +135,7 @@ button { color: inherit; } .theme-card__meta span + span::before { content: '•'; margin-right: 16px; color: var(--theme-accent); } .theme-card h2 { margin: 12px 0 8px; font-size: clamp(36px, 4vw, 58px); line-height: .95; font-weight: 500; letter-spacing: -.04em; } .theme-card p { max-width: 600px; margin: 0; color: #55574f; font-size: 18px; line-height: 1.45; } +.theme-card .theme-card__native { margin: -2px 0 12px; color: var(--theme-accent); font-family: 'IBM Plex Mono', monospace; font-size: 13px; letter-spacing: .12em; } .theme-card__footer { margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--line); display: flex; align-items: center; justify-content: space-between; } .palette { display: flex; } .palette span { width: 23px; height: 23px; border: 2px solid var(--paper); border-radius: 50%; margin-left: -5px; box-shadow: 0 0 0 1px #aaa; } @@ -166,6 +167,7 @@ footer p { margin: 4px 0 0; color: var(--muted); line-height: 1.5; } .dialog-preview img { width: 100%; display: block; box-shadow: 0 18px 60px #0009; } .dialog-copy { padding: 68px 48px 48px; } .dialog-copy h2 { margin: 0; font-size: clamp(52px, 6vw, 84px); line-height: .88; font-weight: 460; letter-spacing: -.05em; } +.theme-dialog__native { margin: 14px 0 0; color: var(--orange); font-family: 'IBM Plex Mono', monospace; font-size: 14px; letter-spacing: .16em; } .dialog-description { margin: 28px 0; color: #4e5048; font-size: 20px; line-height: 1.45; } .detail-grid { display: grid; grid-template-columns: 1fr 1fr; margin: 0; border-top: 1px solid var(--ink); } .detail-grid div { padding: 16px 0; border-bottom: 1px solid var(--line); } diff --git a/src/generated/themes.json b/src/generated/themes.json index 3ce6fc7..77cf2d2 100644 --- a/src/generated/themes.json +++ b/src/generated/themes.json @@ -1,85 +1,673 @@ [ { - "slug": "arctic-signal", - "version": "1.0.0", - "name": "Arctic Signal", - "description": "A polar-night workspace cut by electric cyan telemetry and quiet aurora bands.", + "slug": "castle-archive", + "version": "0.1.1", + "name": "Castle Archive", + "description": "A monumental candlelit castle interior built from black columns, burgundy velvet, ivory fabric, and brass archival details.", "author": { "name": "Awesome Codex Themes" }, "license": { "code": "MIT", - "artwork": "CC0-1.0" + "artwork": "PROJECT-ASSET" }, "categories": [ "dark", - "technical" + "full-workspace", + "character-inspired" + ], + "tags": [ + "castle-archive", + "ebony-wood", + "tall-vertical-rails", + "macos" + ], + "compatibility": { + "platforms": [ + "macos" + ], + "status": "experimental", + "strategy": "best-effort-all", + "verifiedAppVersions": [ + "26.707.*", + "26.715.*" + ] + }, + "mode": "dark", + "palette": { + "background": "#080707", + "scrim": "#080707E8", + "surface": "#11100FDE", + "surfaceElevated": "#1A1715F2", + "surfaceOverlay": "#1A1715F7", + "input": "#151210F2", + "text": "#EEE6DB", + "textSecondary": "#C9B9A7", + "textMuted": "#8A796A", + "textDisabled": "#5D5148", + "icon": "#EEE6DB", + "iconSecondary": "#C9B9A7", + "iconMuted": "#8A796A", + "border": "#B58A583D", + "borderSubtle": "#B58A5824", + "borderStrong": "#B58A5878", + "accent": "#B58A58", + "accentHover": "#D0A674", + "selection": "#B58A5838", + "focus": "#8E3E49", + "link": "#C49A67", + "hover": "#B58A581A", + "active": "#8E3E492B", + "code": "#0E0D0CFA", + "terminal": "#070606FA", + "diffAdded": "#63856B40", + "diffRemoved": "#A8404740", + "success": "#63856B", + "warning": "#B48445", + "danger": "#A84047", + "scrollbar": "#B58A583D", + "scrollbarHover": "#B58A5875", + "composer": "#12100EF2" + }, + "experience": { + "brand": "CASTLE ARCHIVE", + "eyebrow": "MIDNIGHT INDEX", + "headline": "COMMAND THE ARCHIVE", + "tagline": "Quiet rooms. Exact execution.", + "status": "ARCHIVE // AWAKE", + "signature": "CASTLE VII", + "chrome": true + }, + "preview": "/theme-assets/castle-archive/preview.jpg", + "command": "./bin/awesome-codex-themes install-agent castle-archive" + }, + { + "slug": "foundling-garden", + "version": "0.1.1", + "name": "Foundling Garden", + "description": "A small foundling in a misty moss garden, reinterpreted by the concept art as a wider luminous sanctuary and orbital seed instrument.", + "author": { + "name": "Awesome Codex Themes" + }, + "license": { + "code": "MIT", + "artwork": "PROJECT-ASSET" + }, + "categories": [ + "dark", + "full-workspace", + "character-inspired" + ], + "tags": [ + "foundling-garden", + "moss", + "soft-stone-arches", + "macos" + ], + "compatibility": { + "platforms": [ + "macos" + ], + "status": "experimental", + "strategy": "best-effort-all", + "verifiedAppVersions": [ + "26.707.*", + "26.715.*" + ] + }, + "mode": "dark", + "palette": { + "background": "#101309", + "scrim": "#101309E8", + "surface": "#171C10DE", + "surfaceElevated": "#222819F2", + "surfaceOverlay": "#222819F7", + "input": "#1B2114F2", + "text": "#F2ECD2", + "textSecondary": "#C9C29E", + "textMuted": "#8B8D6D", + "textDisabled": "#5E614C", + "icon": "#F2ECD2", + "iconSecondary": "#C9C29E", + "iconMuted": "#8B8D6D", + "border": "#D4B85E3D", + "borderSubtle": "#D4B85E24", + "borderStrong": "#D4B85E78", + "accent": "#D4B85E", + "accentHover": "#E6CD78", + "selection": "#D4B85E38", + "focus": "#8DB66A", + "link": "#C6B25D", + "hover": "#D4B85E1A", + "active": "#8DB66A2B", + "code": "#0D1109FA", + "terminal": "#080B06FA", + "diffAdded": "#78A96840", + "diffRemoved": "#C45C4D40", + "success": "#78A968", + "warning": "#C99647", + "danger": "#C45C4D", + "scrollbar": "#D4B85E3D", + "scrollbarHover": "#D4B85E75", + "composer": "#161B0FF2" + }, + "experience": { + "brand": "FOUNDLING GARDEN", + "eyebrow": "SEEDLING ORDER", + "headline": "GROW THE QUIET SYSTEM", + "tagline": "Small steps. Living systems.", + "status": "GARDEN // LISTENING", + "signature": "GROVE 11", + "chrome": true + }, + "preview": "/theme-assets/foundling-garden/preview.jpg", + "command": "./bin/awesome-codex-themes install-agent foundling-garden" + }, + { + "slug": "grand-line", + "version": "0.1.1", + "name": "Grand Line", + "description": "A weathered black-sail navigation desk illuminated by brass chart marks, rope, smoke, and a high-energy captain portrait.", + "author": { + "name": "Awesome Codex Themes" + }, + "license": { + "code": "MIT", + "artwork": "PROJECT-ASSET" + }, + "categories": [ + "dark", + "full-workspace", + "character-inspired" + ], + "tags": [ + "grand-line", + "black-sailcloth", + "chart-ticks", + "macos" + ], + "compatibility": { + "platforms": [ + "macos" + ], + "status": "experimental", + "strategy": "best-effort-all", + "verifiedAppVersions": [ + "26.707.*", + "26.715.*" + ] + }, + "mode": "dark", + "palette": { + "background": "#0B0906", + "scrim": "#0B0906E8", + "surface": "#17120DDE", + "surfaceElevated": "#21190FF2", + "surfaceOverlay": "#21190FF7", + "input": "#1A140EF2", + "text": "#F1D8A4", + "textSecondary": "#C8AD78", + "textMuted": "#8F7A55", + "textDisabled": "#5F523D", + "icon": "#F1D8A4", + "iconSecondary": "#C8AD78", + "iconMuted": "#8F7A55", + "border": "#D59B363D", + "borderSubtle": "#D59B3624", + "borderStrong": "#D59B3678", + "accent": "#D59B36", + "accentHover": "#F0B957", + "selection": "#D59B3638", + "focus": "#B13E35", + "link": "#E1B45A", + "hover": "#D59B361A", + "active": "#B13E352B", + "code": "#100D09FA", + "terminal": "#080705FA", + "diffAdded": "#5E9B6340", + "diffRemoved": "#C34A3D40", + "success": "#5E9B63", + "warning": "#D59732", + "danger": "#C34A3D", + "scrollbar": "#D59B363D", + "scrollbarHover": "#D59B3675", + "composer": "#15100BF2" + }, + "experience": { + "brand": "GRAND LINE", + "eyebrow": "LOG POSE", + "headline": "CHART THE UNKNOWN", + "tagline": "Plot the route. Ship the voyage.", + "status": "COURSE // LOCKED", + "signature": "NAV LOG 06", + "chrome": true + }, + "preview": "/theme-assets/grand-line/preview.jpg", + "command": "./bin/awesome-codex-themes install-agent grand-line" + }, + { + "slug": "mordor-runtime", + "version": "0.1.1", + "name": "Mordor Runtime", + "description": "A storm-black mounted formation crossing basalt and ember fissures beneath a distant volcanic watchtower.", + "author": { + "name": "Awesome Codex Themes" + }, + "license": { + "code": "MIT", + "artwork": "PROJECT-ASSET" + }, + "categories": [ + "dark", + "full-workspace", + "character-inspired" + ], + "tags": [ + "mordor-runtime", + "black-iron", + "forged-notches", + "macos" + ], + "compatibility": { + "platforms": [ + "macos" + ], + "status": "experimental", + "strategy": "best-effort-all", + "verifiedAppVersions": [ + "26.707.*", + "26.715.*" + ] + }, + "mode": "dark", + "palette": { + "background": "#070809", + "scrim": "#070809E8", + "surface": "#101112DE", + "surfaceElevated": "#18191AF2", + "surfaceOverlay": "#18191AF7", + "input": "#131415F2", + "text": "#D9D4CA", + "textSecondary": "#ABA59B", + "textMuted": "#73716B", + "textDisabled": "#4A4A47", + "icon": "#D9D4CA", + "iconSecondary": "#ABA59B", + "iconMuted": "#73716B", + "border": "#F05A3B3D", + "borderSubtle": "#F05A3B24", + "borderStrong": "#F05A3B78", + "accent": "#F05A3B", + "accentHover": "#FF7658", + "selection": "#F05A3B38", + "focus": "#FF9A61", + "link": "#F37A5E", + "hover": "#F05A3B1A", + "active": "#FF9A612B", + "code": "#0A0B0CFA", + "terminal": "#050607FA", + "diffAdded": "#71876040", + "diffRemoved": "#E0443640", + "success": "#718760", + "warning": "#D78B3E", + "danger": "#E04436", + "scrollbar": "#F05A3B3D", + "scrollbarHover": "#F05A3B75", + "composer": "#0F1011F2" + }, + "experience": { + "brand": "MORDOR RUNTIME", + "eyebrow": "BLACK GATE", + "headline": "FORGE THE RUNTIME", + "tagline": "No noise survives the fire.", + "status": "WATCH // ACTIVE", + "signature": "RUNTIME IX", + "chrome": true + }, + "preview": "/theme-assets/mordor-runtime/preview.jpg", + "command": "./bin/awesome-codex-themes install-agent mordor-runtime" + }, + { + "slug": "new-world-studio", + "version": "0.1.1", + "name": "New World Studio", + "description": "A many-character ink-wash editorial composition over warm paper, black and red brush fields, brass flecks, blossoms, and ocean references.", + "author": { + "name": "Awesome Codex Themes" + }, + "license": { + "code": "MIT", + "artwork": "PROJECT-ASSET" + }, + "categories": [ + "light", + "full-workspace", + "character-inspired" + ], + "tags": [ + "new-world-studio", + "warm-kozo-paper", + "tidal-contour-lines", + "macos" + ], + "compatibility": { + "platforms": [ + "macos" + ], + "status": "experimental", + "strategy": "best-effort-all", + "verifiedAppVersions": [ + "26.707.*", + "26.715.*" + ] + }, + "mode": "light", + "palette": { + "background": "#F0E6D2", + "scrim": "#F0E6D2E8", + "surface": "#E8DCC5DE", + "surfaceElevated": "#FAF2E3F2", + "surfaceOverlay": "#FAF2E3F7", + "input": "#F4E9D5F2", + "text": "#252017", + "textSecondary": "#51483B", + "textMuted": "#7B7060", + "textDisabled": "#AAA08F", + "icon": "#252017", + "iconSecondary": "#51483B", + "iconMuted": "#7B7060", + "border": "#A77A2C3D", + "borderSubtle": "#A77A2C24", + "borderStrong": "#A77A2C78", + "accent": "#A77A2C", + "accentHover": "#C49443", + "selection": "#A77A2C38", + "focus": "#A34D3F", + "link": "#365F73", + "hover": "#A77A2C1A", + "active": "#A34D3F2B", + "code": "#171A1BFA", + "terminal": "#0C0F10FA", + "diffAdded": "#4E7D5D40", + "diffRemoved": "#A7473E40", + "success": "#4E7D5D", + "warning": "#B47828", + "danger": "#A7473E", + "scrollbar": "#A77A2C3D", + "scrollbarHover": "#A77A2C75", + "composer": "#F1E5D0F2" + }, + "experience": { + "brand": "NEW WORLD STUDIO", + "eyebrow": "TIDAL EDITION", + "headline": "MAKE A WORLD WORTH SAILING", + "tagline": "Sketch boldly. Navigate together.", + "status": "TIDE // FAVORABLE", + "signature": "STUDIO XII", + "chrome": true + }, + "preview": "/theme-assets/new-world-studio/preview.jpg", + "command": "./bin/awesome-codex-themes install-agent new-world-studio" + }, + { + "slug": "night-city", + "version": "0.1.1", + "name": "Night City", + "description": "A moonlit high-rise interior overlooking a rain-blue megacity, with a pale figure, glass reflections, and sparse magenta signal lights.", + "author": { + "name": "Awesome Codex Themes" + }, + "license": { + "code": "MIT", + "artwork": "PROJECT-ASSET" + }, + "categories": [ + "dark", + "full-workspace", + "character-inspired" + ], + "tags": [ + "night-city", + "tinted-glass", + "cut-corner-panes", + "macos" + ], + "compatibility": { + "platforms": [ + "macos" + ], + "status": "experimental", + "strategy": "best-effort-all", + "verifiedAppVersions": [ + "26.707.*", + "26.715.*" + ] + }, + "mode": "dark", + "palette": { + "background": "#050E1A", + "scrim": "#050E1AE8", + "surface": "#091827DE", + "surfaceElevated": "#102238F2", + "surfaceOverlay": "#102238F7", + "input": "#0B1C2DF2", + "text": "#EAF8FF", + "textSecondary": "#BAD5E2", + "textMuted": "#7798A8", + "textDisabled": "#4B6572", + "icon": "#EAF8FF", + "iconSecondary": "#BAD5E2", + "iconMuted": "#7798A8", + "border": "#20DFFF3D", + "borderSubtle": "#20DFFF24", + "borderStrong": "#20DFFF78", + "accent": "#20DFFF", + "accentHover": "#65ECFF", + "selection": "#20DFFF38", + "focus": "#F05DC7", + "link": "#48E4F5", + "hover": "#20DFFF1A", + "active": "#F05DC72B", + "code": "#06111DFA", + "terminal": "#030A12FA", + "diffAdded": "#6DDC9B40", + "diffRemoved": "#F05D7240", + "success": "#6DDC9B", + "warning": "#F0B451", + "danger": "#F05D72", + "scrollbar": "#20DFFF3D", + "scrollbarHover": "#20DFFF75", + "composer": "#081827F2" + }, + "experience": { + "brand": "NIGHT CITY", + "eyebrow": "LUNAR LINK", + "headline": "RUN THE CITY AFTER DARK", + "tagline": "Trace signals. Cut latency.", + "status": "NEURAL LINK // CLEAN", + "signature": "NC-10", + "chrome": true + }, + "preview": "/theme-assets/night-city/preview.jpg", + "command": "./bin/awesome-codex-themes install-agent night-city" + }, + { + "slug": "overworld-realms", + "version": "0.1.1", + "name": "Overworld Realms", + "description": "A cross-section of bright overworld village, deep mineral cave, lava seam, and violet portal rendered as an isometric voxel system.", + "author": { + "name": "Awesome Codex Themes" + }, + "license": { + "code": "MIT", + "artwork": "PROJECT-ASSET" + }, + "categories": [ + "dark", + "full-workspace", + "character-inspired" ], "tags": [ - "cyan", - "aurora", - "terminal", - "high-contrast" + "overworld-realms", + "deepslate", + "8px-voxel-grid", + "macos" ], "compatibility": { "platforms": [ "macos" ], "status": "experimental", - "appVersions": [ - "26.707.*" + "strategy": "best-effort-all", + "verifiedAppVersions": [ + "26.707.*", + "26.715.*" ] }, "mode": "dark", "palette": { - "background": "#071219", - "scrim": "#071219D9", - "surface": "#0D202BCC", - "surfaceElevated": "#0D202BF2", - "surfaceOverlay": "#071219F2", - "input": "#0D202BCC", - "text": "#E6FAFF", - "textSecondary": "#E6FAFFBF", - "textMuted": "#E6FAFF8F", - "textDisabled": "#E6FAFF66", - "icon": "#E6FAFF", - "iconSecondary": "#E6FAFFBF", - "iconMuted": "#E6FAFF8F", - "border": "#50E6FF38", - "borderSubtle": "#50E6FF1F", - "borderStrong": "#50E6FF70", - "accent": "#50E6FF", - "accentHover": "#50E6FF", - "selection": "#50E6FF4D", - "focus": "#50E6FF", - "link": "#50E6FF", - "hover": "#50E6FF1F", - "active": "#50E6FF38", - "code": "#071219F2", - "terminal": "#071219F2", - "diffAdded": "#2DAA6A42", - "diffRemoved": "#FF5F5242", - "success": "#40C977", - "warning": "#F6C85F", - "danger": "#FF6764", - "scrollbar": "#50E6FF45", - "scrollbarHover": "#50E6FF78", - "composer": "#0D202BCC" - }, - "preview": "/theme-assets/arctic-signal/preview.svg", - "command": "awesome-codex-themes start arctic-signal" + "background": "#0B1117", + "scrim": "#0B1117E8", + "surface": "#111A20DE", + "surfaceElevated": "#19242AF2", + "surfaceOverlay": "#19242AF7", + "input": "#131E24F2", + "text": "#E9F4F1", + "textSecondary": "#B9CBC5", + "textMuted": "#78908A", + "textDisabled": "#4E625E", + "icon": "#E9F4F1", + "iconSecondary": "#B9CBC5", + "iconMuted": "#78908A", + "border": "#27D7DA3D", + "borderSubtle": "#27D7DA24", + "borderStrong": "#27D7DA78", + "accent": "#27D7DA", + "accentHover": "#62F1EC", + "selection": "#27D7DA38", + "focus": "#B461E9", + "link": "#4DE6E2", + "hover": "#27D7DA1A", + "active": "#B461E92B", + "code": "#0A1116FA", + "terminal": "#060C10FA", + "diffAdded": "#71C85A40", + "diffRemoved": "#E3574F40", + "success": "#71C85A", + "warning": "#E89A36", + "danger": "#E3574F", + "scrollbar": "#27D7DA3D", + "scrollbarHover": "#27D7DA75", + "composer": "#10191EF2" + }, + "experience": { + "brand": "OVERWORLD REALMS", + "eyebrow": "CHUNK 08", + "headline": "BUILD THE NEXT REALM", + "tagline": "Mine context. Craft systems.", + "status": "REALM // LOADED", + "signature": "SEED 08", + "chrome": true + }, + "preview": "/theme-assets/overworld-realms/preview.jpg", + "command": "./bin/awesome-codex-themes install-agent overworld-realms" + }, + { + "slug": "saiyan-ukiyoe", + "version": "0.1.1", + "name": "Saiyan Ukiyo-e", + "description": "A parchment-and-indigo battle chronicle using ukiyo-e brush rhythm, orange ki, wave energy, and carved seal logic.", + "author": { + "name": "Awesome Codex Themes" + }, + "license": { + "code": "MIT", + "artwork": "PROJECT-ASSET" + }, + "categories": [ + "light", + "full-workspace", + "character-inspired" + ], + "tags": [ + "saiyan-ukiyoe", + "mulberry-paper", + "brush-rectangles", + "macos" + ], + "compatibility": { + "platforms": [ + "macos" + ], + "status": "experimental", + "strategy": "best-effort-all", + "verifiedAppVersions": [ + "26.707.*", + "26.715.*" + ] + }, + "mode": "light", + "palette": { + "background": "#EFE2C6", + "scrim": "#EFE2C6E8", + "surface": "#E7D5B2DE", + "surfaceElevated": "#FAEFD8F2", + "surfaceOverlay": "#FAEFD8F7", + "input": "#F3E4C5F2", + "text": "#12293A", + "textSecondary": "#314A59", + "textMuted": "#687A7E", + "textDisabled": "#9DA69E", + "icon": "#12293A", + "iconSecondary": "#314A59", + "iconMuted": "#687A7E", + "border": "#DF68183D", + "borderSubtle": "#DF681824", + "borderStrong": "#DF681878", + "accent": "#DF6818", + "accentHover": "#F2862F", + "selection": "#DF681838", + "focus": "#1E5C86", + "link": "#174D70", + "hover": "#DF68181A", + "active": "#1E5C862B", + "code": "#102330FA", + "terminal": "#091720FA", + "diffAdded": "#3F845E40", + "diffRemoved": "#B93F2D40", + "success": "#3F845E", + "warning": "#B77A19", + "danger": "#B93F2D", + "scrollbar": "#DF68183D", + "scrollbarHover": "#DF681875", + "composer": "#F1E1C0F2" + }, + "experience": { + "brand": "SAIYAN UKIYO-E", + "eyebrow": "KI IMPRESSION", + "headline": "BUILD BEYOND LIMITS", + "tagline": "Refine the form. Raise the power.", + "status": "KI // RISING", + "signature": "UKIYO-E 05", + "chrome": true + }, + "preview": "/theme-assets/saiyan-ukiyoe/preview.jpg", + "command": "./bin/awesome-codex-themes install-agent saiyan-ukiyoe" }, { - "slug": "limitless-six-eyes", - "version": "0.1.0", - "name": "Limitless Six Eyes", - "description": "A porcelain-white editorial workspace with iris-violet telemetry, technical geometry, and a midnight code cockpit.", + "slug": "satoru-gojo", + "version": "1.2.0", + "name": "Satoru Gojo", + "nativeName": "五条 悟", + "nativeLocale": "ja-JP", + "description": "A porcelain-white editorial workspace shaped by Limitless, Six Eyes, iris-violet telemetry, and a midnight code cockpit.", "author": { "name": "Awesome Codex Themes" }, "license": { "code": "MIT", - "artwork": "PROTOTYPE-REFERENCE-ONLY" + "artwork": "PROJECT-ASSET" }, "categories": [ "light", @@ -92,274 +680,312 @@ "editorial", "lavender", "glass", - "anime-prototype" + "anime", + "character" ], "compatibility": { "platforms": [ "macos" ], "status": "experimental", - "appVersions": [ - "26.707.*" + "strategy": "best-effort-all", + "verifiedAppVersions": [ + "26.707.*", + "26.715.*" ] }, "mode": "light", "palette": { - "background": "#F2F1F8", - "scrim": "#F2F1F8E8", - "surface": "#F8F7FCDD", + "background": "#EAF5FF", + "scrim": "#EAF5FFE8", + "surface": "#F8F9FDDE", "surfaceElevated": "#FFFFFFF2", - "surfaceOverlay": "#FCFBFFF7", - "input": "#F4F2FAF2", - "text": "#1C1930", - "textSecondary": "#494260", - "textMuted": "#77708E", - "textDisabled": "#AAA4B9", - "icon": "#2C2648", - "iconSecondary": "#675F81", - "iconMuted": "#958EA8", - "border": "#7567B83D", - "borderSubtle": "#7567B824", - "borderStrong": "#6654C47A", - "accent": "#5E4BD8", - "accentHover": "#7461EC", - "selection": "#9D8EFF52", - "focus": "#6E5AE6", - "link": "#5544C8", - "hover": "#7567B81C", - "active": "#7567B833", - "code": "#17152BFA", - "terminal": "#100F20FA", - "diffAdded": "#1E7E5740", - "diffRemoved": "#D94F7040", - "success": "#1E9A65", - "warning": "#B97B13", - "danger": "#D44761", - "scrollbar": "#7166A83D", - "scrollbarHover": "#6559AD75", - "composer": "#FFFFFFF2" + "surfaceOverlay": "#F8F9FDF7", + "input": "#F4F8FFF2", + "text": "#171B31", + "textSecondary": "#3E4A69", + "textMuted": "#74829F", + "textDisabled": "#ADB7C9", + "icon": "#202A4A", + "iconSecondary": "#61718E", + "iconMuted": "#92A0B7", + "border": "#718BC63D", + "borderSubtle": "#7891C624", + "borderStrong": "#5D73B078", + "accent": "#4C75E8", + "accentHover": "#658AF1", + "selection": "#4C75E838", + "focus": "#6754D9", + "link": "#3B62C9", + "hover": "#4C75E81A", + "active": "#6754D92B", + "code": "#111426FA", + "terminal": "#0A0D1BFA", + "diffAdded": "#59BFA440", + "diffRemoved": "#DB627640", + "success": "#59BFA4", + "warning": "#B77712", + "danger": "#DB6276", + "scrollbar": "#6B82B83D", + "scrollbarHover": "#566FAE75", + "composer": "#F8F9FDF2" }, "experience": { "brand": "LIMITLESS", "eyebrow": "SIX EYES", - "headline": "LIMITLESS WORKSPACE", - "tagline": "Plan beyond the visible.", - "status": "LIMITLESS ONLINE", - "signature": "SATORU GOJO", + "headline": "CODEX THEME SYSTEM", + "tagline": "Editorial workspace protocol.", + "status": "LIMITLESS // ACTIVE", + "signature": "SATORU GOJO // 五条 悟", "chrome": true }, - "preview": "/theme-assets/limitless-six-eyes/preview.png", - "command": "awesome-codex-themes start limitless-six-eyes" + "preview": "/theme-assets/satoru-gojo/preview.png", + "command": "./bin/awesome-codex-themes install-agent satoru-gojo" }, { - "slug": "obsidian-bloom", - "version": "1.1.0", - "name": "Obsidian Bloom", - "description": "A charcoal garden with ember-orange petals and softly layered glass surfaces.", + "slug": "slingshot-lab", + "version": "0.1.1", + "name": "Slingshot Lab", + "description": "A bright island construction lab where timber, stone, glass blocks, tension bands, and projectile arcs form the system language.", "author": { "name": "Awesome Codex Themes" }, "license": { "code": "MIT", - "artwork": "CC0-1.0" + "artwork": "PROJECT-ASSET" }, "categories": [ - "dark", - "organic" + "light", + "full-workspace", + "character-inspired" ], "tags": [ - "charcoal", - "orange", - "botanical", - "glass", - "full-workspace" + "slingshot-lab", + "sunlit-wood", + "square-timber-joints", + "macos" ], "compatibility": { "platforms": [ "macos" ], "status": "experimental", - "appVersions": [ - "26.707.*" + "strategy": "best-effort-all", + "verifiedAppVersions": [ + "26.707.*", + "26.715.*" ] }, - "mode": "dark", + "mode": "light", "palette": { - "background": "#100F0E", - "scrim": "#100F0ED9", - "surface": "#201D1ACC", - "surfaceElevated": "#29241FEE", - "surfaceOverlay": "#171411F2", - "input": "#24201CE6", - "text": "#F5EDE3", - "textSecondary": "#D8CFC4", - "textMuted": "#A79D91", - "textDisabled": "#756D64", - "icon": "#F5EDE3", - "iconSecondary": "#C6BCAF", - "iconMuted": "#8F867C", - "border": "#FFB18A38", - "borderSubtle": "#FFB18A1F", - "borderStrong": "#FFB18A70", - "accent": "#FF7849", - "accentHover": "#FF936C", - "selection": "#FF78494D", - "focus": "#FFB18A", - "link": "#FF9B75", - "hover": "#FFB18A1F", - "active": "#FF784938", - "code": "#0C0B0AF2", - "terminal": "#080706F2", - "diffAdded": "#2DAA6A42", - "diffRemoved": "#FF5F5242", - "success": "#58D68D", - "warning": "#F6C85F", - "danger": "#FF6B64", - "scrollbar": "#FFB18A45", - "scrollbarHover": "#FFB18A78", - "composer": "#191613F2" - }, - "preview": "/theme-assets/obsidian-bloom/preview.svg", - "command": "awesome-codex-themes start obsidian-bloom" + "background": "#E6F6FB", + "scrim": "#E6F6FBE8", + "surface": "#F0E0B8DE", + "surfaceElevated": "#FFF4D5F2", + "surfaceOverlay": "#FFF4D5F7", + "input": "#F8EBCBF2", + "text": "#2B2116", + "textSecondary": "#594631", + "textMuted": "#806B52", + "textDisabled": "#B2A187", + "icon": "#2B2116", + "iconSecondary": "#594631", + "iconMuted": "#806B52", + "border": "#D9382B3D", + "borderSubtle": "#D9382B24", + "borderStrong": "#D9382B78", + "accent": "#D9382B", + "accentHover": "#F05442", + "selection": "#D9382B38", + "focus": "#148DAD", + "link": "#0E7895", + "hover": "#D9382B1A", + "active": "#148DAD2B", + "code": "#17232AFA", + "terminal": "#0D171DFA", + "diffAdded": "#4E9B3B40", + "diffRemoved": "#C92F2F40", + "success": "#4E9B3B", + "warning": "#D88A1D", + "danger": "#C92F2F", + "scrollbar": "#D9382B3D", + "scrollbarHover": "#D9382B75", + "composer": "#F6E7C5F2" + }, + "experience": { + "brand": "SLINGSHOT LAB", + "eyebrow": "TRAJECTORY", + "headline": "AIM. BUILD. RELEASE.", + "tagline": "Turn intent into impact.", + "status": "FLIGHTPATH // CLEAR", + "signature": "LAB 04", + "chrome": true + }, + "preview": "/theme-assets/slingshot-lab/preview.jpg", + "command": "./bin/awesome-codex-themes install-agent slingshot-lab" }, { - "slug": "paper-circuit", - "version": "1.0.0", - "name": "Paper Circuit", - "description": "A bright drafting-table theme with graphite type, cobalt traces, and warm paper grain.", + "slug": "symbiote-sumi-e", + "version": "0.1.1", + "name": "Symbiote Sumi-e", + "description": "A monochrome symbiote emerging from rice-paper negative space, ink blooms, brush drag, and sparse gold repair seams.", "author": { "name": "Awesome Codex Themes" }, "license": { "code": "MIT", - "artwork": "CC0-1.0" + "artwork": "PROJECT-ASSET" }, "categories": [ "light", - "editorial" + "full-workspace", + "character-inspired" ], "tags": [ - "paper", - "cobalt", - "diagram", - "low-glare" + "symbiote-sumi-e", + "uncoated-washi", + "brush-cut-edges", + "macos" ], "compatibility": { "platforms": [ "macos" ], "status": "experimental", - "appVersions": [ - "26.707.*" + "strategy": "best-effort-all", + "verifiedAppVersions": [ + "26.707.*", + "26.715.*" ] }, "mode": "light", "palette": { - "background": "#F3EEDC", - "scrim": "#F3EEDCD9", - "surface": "#FFFDF5DD", - "surfaceElevated": "#FFFDF5F2", - "surfaceOverlay": "#F3EEDCF2", - "input": "#FFFDF5DD", - "text": "#242621", - "textSecondary": "#242621BF", - "textMuted": "#2426218F", - "textDisabled": "#24262166", - "icon": "#242621", - "iconSecondary": "#242621BF", - "iconMuted": "#2426218F", - "border": "#2455D638", - "borderSubtle": "#2455D61F", - "borderStrong": "#2455D670", - "accent": "#2455D6", - "accentHover": "#2455D6", - "selection": "#2455D64D", - "focus": "#2455D6", - "link": "#2455D6", - "hover": "#2455D61F", - "active": "#2455D638", - "code": "#F3EEDCF2", - "terminal": "#F3EEDCF2", - "diffAdded": "#2DAA6A42", - "diffRemoved": "#FF5F5242", - "success": "#40C977", - "warning": "#F6C85F", - "danger": "#FF6764", - "scrollbar": "#2455D645", - "scrollbarHover": "#2455D678", - "composer": "#FFFDF5DD" - }, - "preview": "/theme-assets/paper-circuit/preview.svg", - "command": "awesome-codex-themes start paper-circuit" + "background": "#F1EEE5", + "scrim": "#F1EEE5E8", + "surface": "#E9E4D9DE", + "surfaceElevated": "#FBF8F0F2", + "surfaceOverlay": "#FBF8F0F7", + "input": "#F6F1E7F2", + "text": "#171512", + "textSecondary": "#413B32", + "textMuted": "#756C5E", + "textDisabled": "#A79E90", + "icon": "#171512", + "iconSecondary": "#413B32", + "iconMuted": "#756C5E", + "border": "#A77C2D3D", + "borderSubtle": "#A77C2D24", + "borderStrong": "#A77C2D78", + "accent": "#A77C2D", + "accentHover": "#C69B48", + "selection": "#A77C2D38", + "focus": "#6E4E18", + "link": "#76551F", + "hover": "#A77C2D1A", + "active": "#6E4E182B", + "code": "#181714FA", + "terminal": "#0D0D0BFA", + "diffAdded": "#3D7B5C40", + "diffRemoved": "#A63D3240", + "success": "#3D7B5C", + "warning": "#A96A1F", + "danger": "#A63D32", + "scrollbar": "#A77C2D3D", + "scrollbarHover": "#A77C2D75", + "composer": "#F5F0E6F2" + }, + "experience": { + "brand": "SYMBIOTE SUMI-E", + "eyebrow": "INKBOND", + "headline": "LET THE INK ADAPT", + "tagline": "One surface. Two instincts.", + "status": "BOND // STABLE", + "signature": "SUMI PROTOCOL 03", + "chrome": true + }, + "preview": "/theme-assets/symbiote-sumi-e/preview.jpg", + "command": "./bin/awesome-codex-themes install-agent symbiote-sumi-e" }, { - "slug": "solar-archive", - "version": "1.0.0", - "name": "Solar Archive", - "description": "A midnight reading room illuminated by amber index marks and orbiting archival forms.", + "slug": "zaun-workshop", + "version": "0.1.1", + "name": "Zaun Workshop", + "description": "A volatile undercity workshop framed by fractured stained glass, bridge ironwork, chemical smoke, and kinetic diagonal motion.", "author": { "name": "Awesome Codex Themes" }, "license": { "code": "MIT", - "artwork": "CC0-1.0" + "artwork": "PROJECT-ASSET" }, "categories": [ "dark", - "editorial" + "full-workspace", + "character-inspired" ], "tags": [ - "amber", - "archive", - "warm", - "reading" + "zaun-workshop", + "smoked-glass", + "fractured-polygons", + "macos" ], "compatibility": { "platforms": [ "macos" ], "status": "experimental", - "appVersions": [ - "26.707.*" + "strategy": "best-effort-all", + "verifiedAppVersions": [ + "26.707.*", + "26.715.*" ] }, "mode": "dark", "palette": { - "background": "#15130E", - "scrim": "#15130ED9", - "surface": "#292419D9", - "surfaceElevated": "#292419F2", - "surfaceOverlay": "#15130EF2", - "input": "#292419D9", - "text": "#FFF4D6", - "textSecondary": "#FFF4D6BF", - "textMuted": "#FFF4D68F", - "textDisabled": "#FFF4D666", - "icon": "#FFF4D6", - "iconSecondary": "#FFF4D6BF", - "iconMuted": "#FFF4D68F", - "border": "#F5B94238", - "borderSubtle": "#F5B9421F", - "borderStrong": "#F5B94270", - "accent": "#F5B942", - "accentHover": "#F5B942", - "selection": "#F5B9424D", - "focus": "#F5B942", - "link": "#F5B942", - "hover": "#F5B9421F", - "active": "#F5B94238", - "code": "#15130EF2", - "terminal": "#15130EF2", - "diffAdded": "#2DAA6A42", - "diffRemoved": "#FF5F5242", - "success": "#40C977", - "warning": "#F6C85F", - "danger": "#FF6764", - "scrollbar": "#F5B94245", - "scrollbarHover": "#F5B94278", - "composer": "#292419D9" - }, - "preview": "/theme-assets/solar-archive/preview.svg", - "command": "awesome-codex-themes start solar-archive" + "background": "#080817", + "scrim": "#080817E8", + "surface": "#101125DE", + "surfaceElevated": "#17182FF2", + "surfaceOverlay": "#17182FF7", + "input": "#111329F2", + "text": "#F5F4FF", + "textSecondary": "#C7C6D9", + "textMuted": "#8E8AA8", + "textDisabled": "#5D596F", + "icon": "#F5F4FF", + "iconSecondary": "#C7C6D9", + "iconMuted": "#8E8AA8", + "border": "#00DCEB3D", + "borderSubtle": "#00DCEB24", + "borderStrong": "#00DCEB78", + "accent": "#00DCEB", + "accentHover": "#52F4FF", + "selection": "#00DCEB38", + "focus": "#B864FF", + "link": "#45E7F3", + "hover": "#00DCEB1A", + "active": "#B864FF2B", + "code": "#080913FA", + "terminal": "#05060DFA", + "diffAdded": "#73E35F40", + "diffRemoved": "#FF416740", + "success": "#73E35F", + "warning": "#FFB83E", + "danger": "#FF4167", + "scrollbar": "#00DCEB3D", + "scrollbarHover": "#00DCEB75", + "composer": "#0E1022F2" + }, + "experience": { + "brand": "ZAUN WORKSHOP", + "eyebrow": "SHIMMER CIRCUIT", + "headline": "MAKE CHAOS USEFUL", + "tagline": "Build loud. Ship precise.", + "status": "REACTOR // ONLINE", + "signature": "WORKSHOP 02", + "chrome": true + }, + "preview": "/theme-assets/zaun-workshop/preview.jpg", + "command": "./bin/awesome-codex-themes install-agent zaun-workshop" } ] diff --git a/tests/cli/installed-entry.test.mjs b/tests/cli/installed-entry.test.mjs new file mode 100644 index 0000000..a6539fa --- /dev/null +++ b/tests/cli/installed-entry.test.mjs @@ -0,0 +1,30 @@ +import { execFile } from 'node:child_process'; +import { mkdtemp, rm, symlink } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join, resolve } from 'node:path'; +import { promisify } from 'node:util'; +import { afterEach, describe, expect, test } from 'vitest'; + +const execFileAsync = promisify(execFile); +const repoRoot = resolve(import.meta.dirname, '../..'); +const tempDirs = []; + +afterEach(async () => { + await Promise.all(tempDirs.splice(0).map((directory) => rm(directory, { recursive: true, force: true }))); +}); + +describe('installed CLI entry', () => { + test('runs when invoked through the installed current symlink', async () => { + const home = await mkdtemp(join(tmpdir(), 'awesome-codex-entry-')); + tempDirs.push(home); + const current = join(home, 'current'); + await symlink(repoRoot, current, 'dir'); + + const { stdout } = await execFileAsync(process.execPath, [join(current, 'src/cli/main.mjs'), 'help'], { + encoding: 'utf8', + }); + + expect(stdout).toContain('Awesome Codex Themes'); + expect(stdout).toContain('install-agent '); + }); +}); diff --git a/tests/cli/launcher-security.test.mjs b/tests/cli/launcher-security.test.mjs new file mode 100644 index 0000000..ef30eac --- /dev/null +++ b/tests/cli/launcher-security.test.mjs @@ -0,0 +1,23 @@ +import { readFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; +import { describe, expect, test } from 'vitest'; + +const launcherPath = resolve(import.meta.dirname, '../../bin/awesome-codex-themes'); + +describe('shell launcher trust boundary', () => { + test('verifies the official bundle before executing its bundled Node runtime', async () => { + const launcher = await readFile(launcherPath, 'utf8'); + const identityCheck = launcher.indexOf('CFBundleIdentifier'); + const signatureCheck = launcher.indexOf('/usr/bin/codesign --verify --deep --strict'); + const teamCheck = launcher.indexOf('TeamIdentifier'); + const nodeExecution = launcher.indexOf('exec "$NODE_PATH"'); + + expect(identityCheck).toBeGreaterThan(0); + expect(signatureCheck).toBeGreaterThan(identityCheck); + expect(teamCheck).toBeGreaterThan(signatureCheck); + expect(nodeExecution).toBeGreaterThan(teamCheck); + expect(launcher).toContain('com.openai.codex'); + expect(launcher).toContain('2DC432GLL2'); + expect(launcher).not.toMatch(/\beval\b|--remote-allow-origins=\*/); + }); +}); diff --git a/tests/cli/main.test.mjs b/tests/cli/main.test.mjs index bc5cdbb..4387ca5 100644 --- a/tests/cli/main.test.mjs +++ b/tests/cli/main.test.mjs @@ -1,6 +1,6 @@ import { describe, expect, test, vi } from 'vitest'; -import { CliError, runCli, terminateProcess } from '../../src/cli/main.mjs'; +import { CliError, runCli, terminateProcess, waitForRenderer } from '../../src/cli/main.mjs'; function createIo() { const stdout = []; @@ -18,11 +18,18 @@ function createIo() { function dependencies(overrides = {}) { return { listThemes: async () => [], - doctor: async () => ({ appPath: '/Applications/ChatGPT.app', version: '26.707.72221', runningPids: [] }), + doctor: async () => ({ appPath: '/Applications/ChatGPT.app', version: '26.707.72221', runningPids: [], conflicts: [] }), start: async () => ({ theme: 'obsidian-bloom', port: 9341 }), apply: async () => ({ theme: 'obsidian-bloom', renderers: 1 }), status: async () => ({ active: false }), restore: async () => ({ restored: true, theme: 'obsidian-bloom' }), + installAgent: async () => ({ installed: true, theme: 'satoru-gojo', supportRoot: '/support' }), + upgradeAgent: async () => ({ upgraded: true, theme: 'satoru-gojo', version: '0.3.0', enabled: false }), + switchTheme: async () => ({ switched: true, theme: 'satoru-gojo' }), + pause: async () => ({ paused: true, theme: 'satoru-gojo' }), + resume: async () => ({ resumed: true, theme: 'satoru-gojo' }), + uninstallAgent: async () => ({ uninstalled: true }), + runAgent: async () => ({ stopped: true }), watch: async () => {}, ...overrides, }; @@ -34,22 +41,38 @@ describe('runCli', () => { await expect(runCli(['help'], dependencies(), io)).resolves.toBe(0); expect(stdout.join('\n')).toContain('start '); + expect(stdout.join('\n')).toContain('install-agent '); + expect(stdout.join('\n')).toContain('upgrade-agent'); + expect(stdout.join('\n')).toContain('switch '); + expect(stdout.join('\n')).toContain('uninstall-agent'); expect(stdout.join('\n')).toContain('restore'); expect(stdout.join('\n')).not.toContain('_watch'); + expect(stdout.join('\n')).not.toContain('_agent'); }); test('lists themes with category and compatibility metadata', async () => { const { io, stdout } = createIo(); const deps = dependencies({ listThemes: async () => [ - { slug: 'obsidian-bloom', name: 'Obsidian Bloom', categories: ['dark', 'organic'], status: 'verified' }, + { + slug: 'satoru-gojo', + name: 'Satoru Gojo', + nativeName: '五条 悟', + categories: ['light', 'editorial'], + status: 'experimental', + strategy: 'best-effort-all', + verifiedAppVersions: ['26.707.*', '26.715.*'], + }, ], }); await expect(runCli(['list'], deps, io)).resolves.toBe(0); - expect(stdout.join('\n')).toContain('obsidian-bloom'); - expect(stdout.join('\n')).toContain('dark, organic'); - expect(stdout.join('\n')).toContain('verified'); + expect(stdout.join('\n')).toContain('satoru-gojo'); + expect(stdout.join('\n')).toContain('light, editorial'); + expect(stdout.join('\n')).toContain('experimental'); + expect(stdout.join('\n')).toContain('best effort: all numeric versions'); + expect(stdout.join('\n')).toContain('high compatibility: 26.707.*, 26.715.*'); + expect(stdout.join('\n')).toContain('Satoru Gojo / 五条 悟'); }); test('reports doctor evidence without changing application state', async () => { @@ -62,6 +85,28 @@ describe('runCli', () => { expect(stdout.join('\n')).toContain('26.707.72221'); }); + test('reports injector conflicts as read-only migration warnings', async () => { + const { io, stdout } = createIo(); + + await expect( + runCli( + ['doctor'], + dependencies({ + doctor: async () => ({ + appPath: '/Applications/ChatGPT.app', + version: '26.707.72221', + runningPids: [123], + conflicts: [{ id: 'legacy-nocturne-launch-agent', path: '/Users/test/Library/LaunchAgents/legacy.plist' }], + }), + }), + io, + ), + ).resolves.toBe(0); + + expect(stdout.join('\n')).toContain('conflict: legacy-nocturne-launch-agent'); + expect(stdout.join('\n')).toContain('no files were changed'); + }); + test('requires an explicit theme for start and apply', async () => { const { io, stderr } = createIo(); @@ -105,6 +150,85 @@ describe('runCli', () => { expect(stdout.join('\n')).toContain('run restore'); }); + test('installs persistent mode for an explicit validated theme', async () => { + const { io, stdout } = createIo(); + const installAgent = vi.fn(dependencies().installAgent); + + await expect(runCli(['install-agent', 'satoru-gojo'], dependencies({ installAgent }), io)).resolves.toBe(0); + + expect(installAgent).toHaveBeenCalledWith('satoru-gojo'); + expect(stdout.join('\n')).toContain('installed persistent theme satoru-gojo'); + expect(stdout.join('\n')).toContain('/support'); + }); + + test('switches the saved persistent theme without accepting extra arguments', async () => { + const { io, stdout } = createIo(); + const switchTheme = vi.fn(dependencies().switchTheme); + + await expect(runCli(['switch', 'satoru-gojo'], dependencies({ switchTheme }), io)).resolves.toBe(0); + + expect(switchTheme).toHaveBeenCalledWith('satoru-gojo'); + expect(stdout.join('\n')).toContain('selected persistent theme satoru-gojo'); + }); + + test('upgrades the installed agent without changing its saved enabled state', async () => { + const { io, stdout } = createIo(); + const upgradeAgent = vi.fn(dependencies().upgradeAgent); + + await expect(runCli(['upgrade-agent'], dependencies({ upgradeAgent }), io)).resolves.toBe(0); + + expect(upgradeAgent).toHaveBeenCalledOnce(); + expect(stdout).toEqual(['upgraded persistent agent to 0.3.0 with satoru-gojo (paused)']); + }); + + test.each([ + ['pause', 'pause', 'paused persistent theme satoru-gojo'], + ['resume', 'resume', 'resumed persistent theme satoru-gojo'], + ])('runs the %s lifecycle command', async (command, dependencyName, output) => { + const { io, stdout } = createIo(); + const operation = vi.fn(dependencies()[dependencyName]); + + await expect(runCli([command], dependencies({ [dependencyName]: operation }), io)).resolves.toBe(0); + + expect(operation).toHaveBeenCalledOnce(); + expect(stdout).toEqual([output]); + }); + + test('reports installed persistent state separately from a session watcher', async () => { + const { io, stdout } = createIo(); + + await expect( + runCli( + ['status'], + dependencies({ + status: async () => ({ installed: true, enabled: true, status: 'active', theme: 'satoru-gojo', port: 9341 }), + }), + io, + ), + ).resolves.toBe(0); + + expect(stdout).toEqual(['active — persistent satoru-gojo on 127.0.0.1:9341']); + }); + + test('uninstalls only the project-owned persistent service', async () => { + const { io, stdout } = createIo(); + const uninstallAgent = vi.fn(dependencies().uninstallAgent); + + await expect(runCli(['uninstall-agent'], dependencies({ uninstallAgent }), io)).resolves.toBe(0); + + expect(uninstallAgent).toHaveBeenCalledOnce(); + expect(stdout).toEqual(['uninstalled persistent agent; official Codex data was not changed']); + }); + + test('runs the internal persistent agent only with no public arguments', async () => { + const { io } = createIo(); + const runAgent = vi.fn(dependencies().runAgent); + + await expect(runCli(['_agent'], dependencies({ runAgent }), io)).resolves.toBe(0); + + expect(runAgent).toHaveBeenCalledOnce(); + }); + test('runs restore without accepting a theme argument', async () => { const { io, stdout } = createIo(); const restore = vi.fn(dependencies().restore); @@ -114,6 +238,28 @@ describe('runCli', () => { expect(stdout.join('\n')).toContain('restored official UI'); }); + test('reports that restore paused an installed persistent theme', async () => { + const { io, stdout } = createIo(); + + await expect( + runCli( + ['restore'], + dependencies({ + restore: async () => ({ + restored: true, + persistent: true, + paused: true, + theme: 'satoru-gojo', + liveRemoval: 'removed', + }), + }), + io, + ), + ).resolves.toBe(0); + + expect(stdout).toEqual(['paused persistent theme satoru-gojo and restored project-owned styling']); + }); + test('restores a one-shot apply using the same validated explicit port', async () => { const { io, stdout } = createIo(); const restore = vi.fn(dependencies().restore); @@ -146,3 +292,32 @@ describe('terminateProcess', () => { ).rejects.toEqual(expect.objectContaining({ code: 'INJECTOR_STOP_TIMEOUT' })); }); }); + +describe('managed renderer readiness', () => { + test('verifies official port ownership before every renderer query', async () => { + const events = []; + const unavailable = Object.assign(new Error('not listening yet'), { code: 'CDP_PORT_UNAVAILABLE' }); + const assertPortOwner = vi + .fn() + .mockImplementationOnce(async () => { + events.push('owner'); + throw unavailable; + }) + .mockImplementationOnce(async () => { + events.push('owner'); + }); + const evaluate = vi.fn(async () => { + events.push('renderer'); + return [{ pass: true }]; + }); + + await waitForRenderer(9341, { appPath: '/Applications/ChatGPT.app' }, { + attempts: 2, + delayMs: 0, + assertPortOwner, + evaluate, + }); + + expect(events).toEqual(['owner', 'owner', 'renderer']); + }); +}); diff --git a/tests/docs/public-docs.test.mjs b/tests/docs/public-docs.test.mjs new file mode 100644 index 0000000..2529b0f --- /dev/null +++ b/tests/docs/public-docs.test.mjs @@ -0,0 +1,104 @@ +import { readFile, readdir } from 'node:fs/promises'; +import { join, resolve } from 'node:path'; +import { describe, expect, test } from 'vitest'; + +const repoRoot = resolve(import.meta.dirname, '../..'); + +async function text(path) { + return readFile(join(repoRoot, path), 'utf8'); +} + +describe('public English-first documentation', () => { + test('documents the persistent Satoru Gojo lifecycle without internal iteration labels', async () => { + const readme = await text('README.md'); + + for (const command of [ + 'install-agent satoru-gojo', + 'upgrade-agent', + 'switch satoru-gojo', + 'pause', + 'resume', + 'uninstall-agent', + ]) { + expect(readme, command).toContain(command); + } + expect(readme).toContain('Satoru Gojo'); + expect(readme).toContain('五条 悟'); + expect(readme).toContain('Twelve full-workspace visual themes'); + expect(readme).toContain('Satoru Gojo (五条 悟) — Featured'); + expect(readme).toContain('New World Studio'); + expect(readme).toContain('Best-effort injection is attempted on every numeric Codex Desktop version'); + expect(readme).toContain('Highly compatible and live-verified: `26.707.*` and `26.715.*`'); + expect(readme).toContain('GitHub Issue'); + expect(readme).not.toContain('hard version allowlist'); + expect(readme).not.toMatch(/limitless-six-eyes|Limitless Six Eyes|\bV5(?:\.6)?\b|private flagship/i); + }); + + test('ships install, migration, and authoring recovery documentation', async () => { + const [install, migration, authoring, releasing] = await Promise.all([ + text('docs/INSTALL.md'), + text('docs/MIGRATION.md'), + text('docs/THEME_AUTHORING.md'), + text('docs/RELEASING.md'), + ]); + + expect(install).toContain('install-agent satoru-gojo'); + expect(install).toContain('restart-required'); + expect(install).toContain('launchctl bootout'); + expect(migration).toContain('legacy injector'); + expect(migration).toContain('doctor'); + expect(authoring).toContain('English primary name'); + expect(authoring).toContain('nativeName'); + expect(releasing).toContain('Public visibility gate'); + expect(releasing).toContain('pnpm check'); + expect(releasing).toContain('macOS lifecycle evidence'); + expect(releasing).toContain('v0.4.2'); + }); + + test('publishes only complete runnable theme previews', async () => { + const themeDirectories = (await readdir(join(repoRoot, 'themes'), { withFileTypes: true })) + .filter((entry) => entry.isDirectory()) + .map((entry) => entry.name) + .sort(); + const publicDirectories = (await readdir(join(repoRoot, 'public/theme-assets'), { withFileTypes: true })) + .filter((entry) => entry.isDirectory()) + .map((entry) => entry.name) + .sort(); + + const expected = [ + 'castle-archive', + 'foundling-garden', + 'grand-line', + 'mordor-runtime', + 'new-world-studio', + 'night-city', + 'overworld-realms', + 'saiyan-ukiyoe', + 'satoru-gojo', + 'slingshot-lab', + 'symbiote-sumi-e', + 'zaun-workshop', + ]; + expect(themeDirectories).toEqual(expected); + expect(publicDirectories).toEqual(expected); + }); + + test('uses a rights declaration instead of a categorical character-theme ban', async () => { + const [contributing, submission, notice] = await Promise.all([ + text('CONTRIBUTING.md'), + text('.github/ISSUE_TEMPLATE/theme_submission.yml'), + text('NOTICE'), + ]); + const combined = `${contributing}\n${submission}\n${notice}`; + + expect(combined).toContain('necessary rights'); + expect(combined).not.toContain('no unlicensed character, celebrity, game, anime, logo, or brand artwork'); + expect(notice).toContain('All twelve'); + expect(notice).not.toMatch(/private prototype|must be replaced|Limitless Six Eyes/i); + }); + + test('bumps the platform for the persistent-agent release line', async () => { + const packageJson = JSON.parse(await text('package.json')); + expect(packageJson.version).toBe('0.4.2'); + }); +}); diff --git a/tests/docs/release-preflight.test.mjs b/tests/docs/release-preflight.test.mjs new file mode 100644 index 0000000..adf1d84 --- /dev/null +++ b/tests/docs/release-preflight.test.mjs @@ -0,0 +1,68 @@ +import { readFile, readdir } from 'node:fs/promises'; +import { join, resolve } from 'node:path'; +import { describe, expect, test } from 'vitest'; + +const repoRoot = resolve(import.meta.dirname, '../..'); + +async function collectTextFiles(path) { + const entries = await readdir(path, { withFileTypes: true }); + const files = []; + + for (const entry of entries) { + const entryPath = join(path, entry.name); + if (entry.isDirectory()) { + files.push(...(await collectTextFiles(entryPath))); + } else if (/\.(?:css|html|json|md|mjs|ts|tsx|yml)$/.test(entry.name)) { + files.push(entryPath); + } + } + + return files; +} + +describe('public release preflight', () => { + test('has one command that runs the complete portable release gate', async () => { + const packageJson = JSON.parse(await readFile(join(repoRoot, 'package.json'), 'utf8')); + const workflow = await readFile(join(repoRoot, '.github/workflows/ci.yml'), 'utf8'); + const readme = await readFile(join(repoRoot, 'README.md'), 'utf8'); + + expect(packageJson.scripts['release:check']).toBe( + 'pnpm check && pnpm audit --prod --audit-level moderate && git diff --check', + ); + expect(workflow).toContain('run: pnpm release:check'); + expect(readme).toContain('pnpm release:check'); + }); + + test('contains no maintainer home path or internal V5.6 label in public release files', async () => { + const roots = [ + '.github', + 'docs', + 'src', + 'themes', + ]; + const rootFiles = [ + 'CHANGELOG.md', + 'CONTRIBUTING.md', + 'NOTICE', + 'README.md', + 'SECURITY.md', + 'index.html', + 'package.json', + ]; + const nestedFiles = (await Promise.all(roots.map((root) => collectTextFiles(join(repoRoot, root))))).flat(); + const files = [...rootFiles.map((file) => join(repoRoot, file)), ...nestedFiles]; + + for (const file of files) { + const source = await readFile(file, 'utf8'); + expect(source, file).not.toMatch(/\/Users\/kkkk\//); + expect(source, file).not.toMatch(/\bv\s*5\.6\b/i); + } + }); + + test('does not present the private 0.2 prototype as a published release', async () => { + const changelog = await readFile(join(repoRoot, 'CHANGELOG.md'), 'utf8'); + + expect(changelog).toContain('Private prototype history (not publicly released)'); + expect(changelog).not.toContain('/releases/tag/v0.2.0'); + }); +}); diff --git a/tests/engine/adapter.test.mjs b/tests/engine/adapter.test.mjs index e326e35..352dd1d 100644 --- a/tests/engine/adapter.test.mjs +++ b/tests/engine/adapter.test.mjs @@ -8,9 +8,19 @@ describe('loadCodexAdapter', () => { expect(adapter.id).toBe('codex-26.707'); expect(adapter.appVersions).toEqual(['26.707.*']); + expect(adapter.verified).toBe(true); expect(adapter.css.length).toBeGreaterThan(4_000); }); + test('loads a separately identified adapter for the live-verified 26.715 family', async () => { + const adapter = await loadCodexAdapter('26.715.21425'); + + expect(adapter.id).toBe('codex-26.715'); + expect(adapter.appVersions).toEqual(['26.715.*']); + expect(adapter.verified).toBe(true); + expect(adapter.css).toContain('.composer-surface-chrome'); + }); + test('contains a representative rule for every full-theme coverage group', async () => { const { css } = await loadCodexAdapter('26.707.72221'); const coverage = { @@ -35,9 +45,25 @@ describe('loadCodexAdapter', () => { } }); - test('fails closed when no adapter declares the app version', async () => { - await expect(loadCodexAdapter('26.708.1')).rejects.toEqual( - expect.objectContaining({ code: 'THEME_ADAPTER_UNSUPPORTED' }), + test('does not force visible borders onto every native interactive control', async () => { + const { css } = await loadCodexAdapter('26.707.72221'); + + expect(css).not.toMatch( + /html\.awesome-codex-theme\s+:is\(button,\s*\[role="button"\],\s*a\)\s*\{[^}]*border-color/s, + ); + expect(css).not.toMatch( + /html\.awesome-codex-theme\s+:is\(button,\s*\[role="button"\],\s*a\):hover\s*\{[^}]*border-color/s, + ); + }); + + test('uses the shared mapping as a best-effort adapter for every numeric version', async () => { + await expect(loadCodexAdapter('26.716.1')).resolves.toEqual( + expect.objectContaining({ + id: 'codex-best-effort', + appVersions: ['*'], + verified: false, + css: expect.stringContaining('.composer-surface-chrome'), + }), ); }); diff --git a/tests/engine/agent-state.test.mjs b/tests/engine/agent-state.test.mjs new file mode 100644 index 0000000..48a163b --- /dev/null +++ b/tests/engine/agent-state.test.mjs @@ -0,0 +1,96 @@ +import { mkdtemp, rm, stat } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { afterEach, describe, expect, test, vi } from 'vitest'; + +import { + AgentStateError, + isActiveAgentStateLive, + readAgentState, + validateAgentState, + writeAgentState, +} from '../../src/engine/agent-state.mjs'; + +const tempDirs = []; + +function validState(overrides = {}) { + return { + schemaVersion: 1, + status: 'active', + themeSlug: 'satoru-gojo', + appPid: 123, + appVersion: '26.707.91948', + port: 9341, + errorCode: null, + updatedAt: '2026-07-17T00:00:00.000Z', + ...overrides, + }; +} + +afterEach(async () => { + await Promise.all(tempDirs.splice(0).map((directory) => rm(directory, { recursive: true, force: true }))); +}); + +describe('agent observed state', () => { + test('accepts active and paused state shapes', () => { + expect(validateAgentState(validState()).status).toBe('active'); + expect(validateAgentState(validState({ status: 'paused', appPid: null, appVersion: null, port: null })).status).toBe('paused'); + }); + + test.each([ + [{ status: 'unknown' }, 'AGENT_STATE_STATUS_INVALID'], + [{ status: 'unsupported-version' }, 'AGENT_STATE_STATUS_INVALID'], + [{ appPid: -1 }, 'AGENT_STATE_PID_INVALID'], + [{ port: 80 }, 'AGENT_STATE_PORT_INVALID'], + [{ themeSlug: '../escape' }, 'AGENT_STATE_THEME_INVALID'], + ])('rejects malformed observed state', (overrides, code) => { + expect(() => validateAgentState(validState(overrides))).toThrowError(expect.objectContaining({ code })); + }); + + test('persists state with owner-only permissions', async () => { + const root = await mkdtemp(join(tmpdir(), 'awesome-codex-agent-state-')); + tempDirs.push(root); + const path = join(root, 'agent-state.json'); + + await writeAgentState(path, validState()); + + expect(await readAgentState(path)).toEqual(validState()); + expect((await stat(path)).mode & 0o777).toBe(0o600); + }); + + test('uses a typed error when the file is missing', async () => { + await expect(readAgentState('/missing/agent-state.json')).rejects.toBeInstanceOf(AgentStateError); + }); + + test('calls an active state live only when version, PID, and port ownership are current', async () => { + const app = { version: '26.707.91948' }; + const assertPortOwner = vi.fn(async () => {}); + const dependencies = { + discoverApp: async () => app, + listPids: async () => [123], + assertPortOwner, + isAgentLoaded: async () => true, + }; + + await expect(isActiveAgentStateLive(validState(), dependencies)).resolves.toBe(true); + expect(assertPortOwner).toHaveBeenCalledWith(app, 9341); + + await expect( + isActiveAgentStateLive(validState({ appVersion: '26.706.1' }), dependencies), + ).resolves.toBe(false); + await expect( + isActiveAgentStateLive(validState(), { ...dependencies, listPids: async () => [] }), + ).resolves.toBe(false); + await expect( + isActiveAgentStateLive(validState(), { ...dependencies, isAgentLoaded: async () => false }), + ).resolves.toBe(false); + await expect( + isActiveAgentStateLive(validState(), { + ...dependencies, + assertPortOwner: async () => { + throw Object.assign(new Error('stale'), { code: 'CDP_PORT_UNAVAILABLE' }); + }, + }), + ).resolves.toBe(false); + }); +}); diff --git a/tests/engine/agent.test.mjs b/tests/engine/agent.test.mjs new file mode 100644 index 0000000..21ea4ef --- /dev/null +++ b/tests/engine/agent.test.mjs @@ -0,0 +1,172 @@ +import { describe, expect, test, vi } from 'vitest'; + +import { agentStep, runPersistentAgent } from '../../src/engine/agent.mjs'; + +function config(overrides = {}) { + return { schemaVersion: 1, enabled: true, themeSlug: 'satoru-gojo', launchAtLogin: true, ...overrides }; +} + +function app() { + return { appPath: '/Applications/ChatGPT.app', version: '26.707.91948', executable: '/Applications/ChatGPT.app/Contents/MacOS/ChatGPT' }; +} + +function dependencies(overrides = {}) { + return { + discoverApp: vi.fn(async () => app()), + listPids: vi.fn(async () => []), + selectPort: vi.fn(async () => 9341), + launchApp: vi.fn(async () => ({ pid: 123 })), + waitForRenderer: vi.fn(async () => {}), + assertPortOwner: vi.fn(async () => {}), + applyTheme: vi.fn(async ({ themeSlug }) => ({ theme: themeSlug, renderers: 1 })), + removeTheme: vi.fn(async () => ({ restored: true })), + now: () => '2026-07-17T00:00:00.000Z', + ...overrides, + }; +} + +describe('persistent agent state machine', () => { + test('launches an absent official app with CDP and applies the selected theme', async () => { + const deps = dependencies(); + + const result = await agentStep({ config: config(), runtime: null }, deps); + + expect(deps.launchApp).toHaveBeenCalledWith(app(), 9341); + expect(deps.assertPortOwner).toHaveBeenCalledWith(app(), 9341); + expect(deps.applyTheme).toHaveBeenCalledWith({ themeSlug: 'satoru-gojo', port: 9341, app: app() }); + expect(result.runtime).toEqual({ appPid: 123, port: 9341, themeSlug: 'satoru-gojo' }); + expect(result.state).toEqual({ + schemaVersion: 1, + status: 'active', + themeSlug: 'satoru-gojo', + appPid: 123, + appVersion: '26.707.91948', + port: 9341, + errorCode: null, + updatedAt: '2026-07-17T00:00:00.000Z', + }); + }); + + test('reapplies idempotently to a managed renderer after reload', async () => { + const deps = dependencies({ listPids: vi.fn(async () => [123]) }); + const runtime = { appPid: 123, port: 9341, themeSlug: 'satoru-gojo' }; + + const result = await agentStep({ config: config(), runtime }, deps); + + expect(deps.launchApp).not.toHaveBeenCalled(); + expect(deps.applyTheme).toHaveBeenCalledOnce(); + expect(result.state.status).toBe('active'); + }); + + test('switches the selected theme on the existing managed renderer', async () => { + const deps = dependencies({ listPids: vi.fn(async () => [123]) }); + const runtime = { appPid: 123, port: 9341, themeSlug: 'old-theme' }; + + const result = await agentStep({ config: config({ themeSlug: 'satoru-gojo' }), runtime }, deps); + + expect(deps.applyTheme).toHaveBeenCalledWith({ themeSlug: 'satoru-gojo', port: 9341, app: app() }); + expect(result.runtime.themeSlug).toBe('satoru-gojo'); + }); + + test('reports restart required for an unmanaged running app and never terminates it', async () => { + const terminate = vi.fn(); + const deps = dependencies({ listPids: vi.fn(async () => [777]), terminate }); + + const result = await agentStep({ config: config(), runtime: null }, deps); + + expect(result.state.status).toBe('restart-required'); + expect(result.runtime).toBeNull(); + expect(deps.launchApp).not.toHaveBeenCalled(); + expect(terminate).not.toHaveBeenCalled(); + }); + + test('pauses without launching, removes owned styling, and retains a reachable managed endpoint for resume', async () => { + const deps = dependencies({ listPids: vi.fn(async () => [123]) }); + const runtime = { appPid: 123, port: 9341, themeSlug: 'satoru-gojo' }; + + const result = await agentStep({ config: config({ enabled: false }), runtime }, deps); + + expect(deps.removeTheme).toHaveBeenCalledWith({ port: 9341, app: app() }); + expect(deps.launchApp).not.toHaveBeenCalled(); + expect(result).toEqual({ + runtime, + state: expect.objectContaining({ + status: 'paused', + appPid: 123, + port: 9341, + }), + }); + + const resumed = await agentStep({ config: config({ enabled: true }), runtime: result.runtime }, deps); + expect(resumed.state.status).toBe('active'); + expect(deps.launchApp).not.toHaveBeenCalled(); + }); + + test('reports a normal typed error when a best-effort renderer cannot be verified', async () => { + const unverified = Object.assign(new Error('unverified'), { code: 'THEME_APPLY_UNVERIFIED' }); + const deps = dependencies({ applyTheme: vi.fn(async () => { throw unverified; }) }); + + const result = await agentStep({ config: config(), runtime: null }, deps); + + expect(result.state.status).toBe('error'); + expect(result.state.errorCode).toBe('THEME_APPLY_UNVERIFIED'); + expect(result.runtime).toEqual({ appPid: 123, port: 9341, themeSlug: 'satoru-gojo' }); + }); + + test('starts a new managed process after the previous app exits', async () => { + const deps = dependencies({ launchApp: vi.fn(async () => ({ pid: 456 })) }); + const runtime = { appPid: 123, port: 9341, themeSlug: 'satoru-gojo' }; + + const result = await agentStep({ config: config(), runtime }, deps); + + expect(deps.launchApp).toHaveBeenCalledOnce(); + expect(result.runtime.appPid).toBe(456); + }); + + test('forgets a stale persisted endpoint and transitions to restart-required', async () => { + const staleEndpoint = Object.assign(new Error('listener is gone'), { code: 'CDP_PORT_UNAVAILABLE' }); + const deps = dependencies({ + listPids: vi.fn(async () => [123]), + assertPortOwner: vi.fn(async () => { throw staleEndpoint; }), + }); + const runtime = { appPid: 123, port: 9341, themeSlug: 'satoru-gojo' }; + + const failed = await agentStep({ config: config(), runtime }, deps); + expect(failed.runtime).toBeNull(); + expect(failed.state).toEqual(expect.objectContaining({ status: 'error', errorCode: 'CDP_PORT_UNAVAILABLE' })); + + const recovered = await agentStep({ config: config(), runtime: failed.runtime }, deps); + expect(recovered.runtime).toBeNull(); + expect(recovered.state.status).toBe('restart-required'); + expect(deps.launchApp).not.toHaveBeenCalled(); + }); +}); + +describe('persistent agent loop', () => { + test('reloads desired configuration and writes observed state on every iteration', async () => { + const readConfig = vi + .fn() + .mockResolvedValueOnce(config()) + .mockResolvedValueOnce(config({ enabled: false })); + const writeState = vi.fn(async () => {}); + const delay = vi.fn(async () => {}); + let checks = 0; + + await runPersistentAgent( + { configPath: '/config.json', statePath: '/state.json', intervalMs: 10 }, + { + readConfig, + writeState, + stepDependencies: dependencies(), + delay, + shouldStop: () => checks++ >= 2, + }, + ); + + expect(readConfig).toHaveBeenCalledTimes(2); + expect(writeState).toHaveBeenCalledTimes(2); + expect(writeState.mock.calls[0][1].status).toBe('active'); + expect(writeState.mock.calls[1][1].status).toBe('paused'); + expect(delay).toHaveBeenCalledTimes(2); + }); +}); diff --git a/tests/engine/config.test.mjs b/tests/engine/config.test.mjs new file mode 100644 index 0000000..1158aa7 --- /dev/null +++ b/tests/engine/config.test.mjs @@ -0,0 +1,63 @@ +import { mkdtemp, readFile, rm, stat } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { afterEach, describe, expect, test } from 'vitest'; + +import { + AgentConfigError, + readAgentConfig, + validateAgentConfig, + writeAgentConfig, +} from '../../src/engine/config.mjs'; + +const tempDirs = []; + +function validConfig(overrides = {}) { + return { + schemaVersion: 1, + enabled: true, + themeSlug: 'satoru-gojo', + launchAtLogin: true, + ...overrides, + }; +} + +afterEach(async () => { + await Promise.all(tempDirs.splice(0).map((directory) => rm(directory, { recursive: true, force: true }))); +}); + +describe('agent configuration', () => { + test('normalizes a complete English-slug desired configuration', () => { + expect(validateAgentConfig(validConfig())).toEqual(validConfig()); + }); + + test.each([ + [{ schemaVersion: 2 }, 'CONFIG_SCHEMA_INVALID'], + [{ enabled: 'yes' }, 'CONFIG_FIELD_INVALID'], + [{ launchAtLogin: 'yes' }, 'CONFIG_FIELD_INVALID'], + [{ themeSlug: '../escape' }, 'CONFIG_THEME_INVALID'], + ])('rejects malformed desired configuration', (overrides, code) => { + expect(() => validateAgentConfig(validConfig(overrides))).toThrowError(expect.objectContaining({ code })); + }); + + test('writes atomically with owner-only permissions and reads it back', async () => { + const root = await mkdtemp(join(tmpdir(), 'awesome-codex-config-')); + tempDirs.push(root); + const path = join(root, 'nested/config.json'); + + await writeAgentConfig(path, validConfig()); + + expect(await readAgentConfig(path)).toEqual(validConfig()); + expect((await stat(path)).mode & 0o777).toBe(0o600); + expect(JSON.parse(await readFile(path, 'utf8')).schemaVersion).toBe(1); + }); + + test('uses a typed error for malformed JSON', async () => { + const root = await mkdtemp(join(tmpdir(), 'awesome-codex-config-')); + tempDirs.push(root); + const path = join(root, 'config.json'); + await import('node:fs/promises').then(({ writeFile }) => writeFile(path, '{bad')); + + await expect(readAgentConfig(path)).rejects.toBeInstanceOf(AgentConfigError); + }); +}); diff --git a/tests/engine/conflicts.test.mjs b/tests/engine/conflicts.test.mjs new file mode 100644 index 0000000..b1da0d2 --- /dev/null +++ b/tests/engine/conflicts.test.mjs @@ -0,0 +1,40 @@ +import { describe, expect, test } from 'vitest'; + +import { detectInjectorConflicts } from '../../src/engine/conflicts.mjs'; + +describe('theme injector conflict diagnostics', () => { + test('reports the known legacy Nocturne agent and wildcard CDP launch without mutating them', () => { + const conflicts = detectInjectorConflicts({ + launchAgents: [ + { + path: '/Users/test/Library/LaunchAgents/com.kkkk.codex-nocturne.plist', + content: '/Users/test/.local/share/codex-nocturne/theme_agent.py', + }, + ], + processCommands: [ + '/Applications/ChatGPT.app/Contents/MacOS/ChatGPT --remote-debugging-address=127.0.0.1 --remote-allow-origins=*', + ], + }); + + expect(conflicts).toEqual([ + expect.objectContaining({ id: 'legacy-nocturne-launch-agent', path: expect.stringContaining('com.kkkk.codex-nocturne.plist') }), + expect.objectContaining({ id: 'wildcard-cdp-origin' }), + ]); + }); + + test('does not report the project-owned agent or a loopback-only managed launch', () => { + expect( + detectInjectorConflicts({ + launchAgents: [ + { + path: '/Users/test/Library/LaunchAgents/io.github.awesome-codex-themes.agent.plist', + content: 'io.github.awesome-codex-themes.agent', + }, + ], + processCommands: [ + '/Applications/ChatGPT.app/Contents/MacOS/ChatGPT --remote-debugging-address=127.0.0.1 --remote-debugging-port=9341', + ], + }), + ).toEqual([]); + }); +}); diff --git a/tests/engine/control.test.mjs b/tests/engine/control.test.mjs new file mode 100644 index 0000000..e6abcc1 --- /dev/null +++ b/tests/engine/control.test.mjs @@ -0,0 +1,46 @@ +import { describe, expect, test, vi } from 'vitest'; + +import { pausePersistentInstallation } from '../../src/engine/control.mjs'; + +describe('persistent lifecycle control', () => { + test('disables persistence before requesting live owned-style removal', async () => { + const events = []; + const writeConfig = vi.fn(async (_path, config) => events.push(['write', config.enabled])); + const kickstart = vi.fn(async () => events.push(['kickstart'])); + const removeTheme = vi.fn(async ({ port }) => events.push(['remove', port])); + + const result = await pausePersistentInstallation( + { configPath: '/support/config.json', statePath: '/support/agent-state.json' }, + { + readConfig: async () => ({ schemaVersion: 1, enabled: true, themeSlug: 'satoru-gojo', launchAtLogin: true }), + writeConfig, + readState: async () => ({ themeSlug: 'satoru-gojo', port: 9341 }), + kickstart, + removeTheme, + }, + ); + + expect(events).toEqual([['write', false], ['kickstart'], ['remove', 9341]]); + expect(result).toEqual({ paused: true, theme: 'satoru-gojo', liveRemoval: 'removed' }); + }); + + test('still pauses safely when no observed state or reachable endpoint exists', async () => { + const removeTheme = vi.fn(); + + await expect( + pausePersistentInstallation( + { configPath: '/support/config.json', statePath: '/support/agent-state.json' }, + { + readConfig: async () => ({ schemaVersion: 1, enabled: true, themeSlug: 'satoru-gojo', launchAtLogin: true }), + writeConfig: async () => {}, + readState: async () => { + throw Object.assign(new Error('missing'), { code: 'AGENT_STATE_READ_FAILED' }); + }, + kickstart: async () => {}, + removeTheme, + }, + ), + ).resolves.toEqual({ paused: true, theme: 'satoru-gojo', liveRemoval: 'not-reachable' }); + expect(removeTheme).not.toHaveBeenCalled(); + }); +}); diff --git a/tests/engine/injection.test.mjs b/tests/engine/injection.test.mjs index f1df7e0..97f7424 100644 --- a/tests/engine/injection.test.mjs +++ b/tests/engine/injection.test.mjs @@ -66,6 +66,14 @@ function runtimeTheme(overrides = {}) { artwork: { dataUrl: 'data:image/svg+xml;base64,PHN2Zy8+', }, + assets: { + 'six-eyes-emblem': { + dataUrl: 'data:image/png;base64,c2l4LWV5ZXM=', + }, + 'send-infinity': { + dataUrl: 'data:image/png;base64,aW5maW5pdHk=', + }, + }, ...overrides, }; } @@ -100,6 +108,8 @@ describe('theme injection expressions', () => { expect(root.dataset.awesomeCodexAdapter).toBe('codex-26.707'); expect(root.dataset.awesomeCodexSurface).toBe('workspace'); expect(root.style.getPropertyValue('--act-artwork')).toContain('data:image/svg+xml'); + expect(root.style.getPropertyValue('--act-asset-six-eyes-emblem')).toContain('data:image/png'); + expect(root.style.getPropertyValue('--act-asset-send-infinity')).toContain('data:image/png'); expect(root.style.getPropertyValue('--act-surface-overlay')).toBe('#0B1220F2'); expect(root.style.getPropertyValue('--act-text-secondary')).toBe('#F4F7FBBF'); expect(root.style.getPropertyValue('--act-scrollbar-hover')).toBe('#73E2FF78'); @@ -151,6 +161,407 @@ describe('theme injection expressions', () => { expect(home.dataset.awesomeCodexHome).toBe('true'); }); + test('marks stable component roles without reading conversation copy', async () => { + const dom = createDom(); + const appHeader = dom.window.document.createElement('header'); + appHeader.className = 'app-header-tint'; + const headerControl = dom.window.document.createElement('button'); + headerControl.setAttribute('aria-label', 'Localized layout control'); + appHeader.appendChild(headerControl); + + const sidebar = dom.window.document.createElement('aside'); + sidebar.className = 'app-shell-left-panel'; + const brandTrigger = dom.window.document.createElement('button'); + brandTrigger.setAttribute('aria-label', 'Switch mode, current mode: Codex'); + brandTrigger.textContent = 'Codex'; + const navItem = dom.window.document.createElement('button'); + navItem.className = + 'relative h-[var(--height-token-row)] px-[var(--padding-row-cell-x,var(--padding-row-x))]'; + navItem.textContent = 'Localized primary destination'; + const domainHeader = dom.window.document.createElement('button'); + domainHeader.className = 'group/section-toggle'; + domainHeader.textContent = 'Localized projects'; + const projectRow = dom.window.document.createElement('div'); + projectRow.className = 'group/folder-row'; + projectRow.setAttribute('aria-label', 'Localized project'); + const taskRow = dom.window.document.createElement('div'); + taskRow.className = 'group relative h-[var(--height-token-row)]'; + const taskDragHandle = dom.window.document.createElement('div'); + taskDragHandle.className = 'cursor-grab active:cursor-grabbing'; + taskRow.appendChild(taskDragHandle); + const accountTrigger = dom.window.document.createElement('button'); + accountTrigger.setAttribute('aria-label', 'Open profile menu'); + const similarlyNamedProjectControl = dom.window.document.createElement('button'); + similarlyNamedProjectControl.setAttribute('aria-label', 'Personal-Profile project actions'); + const helpTrigger = dom.window.document.createElement('button'); + helpTrigger.setAttribute('aria-label', 'Open help menu'); + const accountIdentity = dom.window.document.createElement('button'); + const avatar = dom.window.document.createElement('img'); + avatar.className = 'icon-sm shrink-0 rounded-full'; + const accountName = dom.window.document.createElement('span'); + accountName.textContent = 'Localized account'; + accountIdentity.append(avatar, accountName); + sidebar.append( + brandTrigger, + navItem, + domainHeader, + projectRow, + taskRow, + accountTrigger, + similarlyNamedProjectControl, + accountIdentity, + helpTrigger, + ); + + const artifactPanelHost = dom.window.document.createElement('div'); + artifactPanelHost.dataset.pipObstacle = 'thread-summary-panel'; + const artifactPanel = dom.window.document.createElement('div'); + artifactPanel.className = 'rounded-3xl bg-token-dropdown-background'; + const panelScroller = dom.window.document.createElement('div'); + const section = dom.window.document.createElement('section'); + const artifactHeader = dom.window.document.createElement('header'); + artifactHeader.className = 'sticky bg-token-dropdown-background'; + artifactHeader.textContent = 'Localized panel label'; + const panelMore = dom.window.document.createElement('button'); + panelMore.className = 'text-token-conversation-summary-trailing'; + panelMore.textContent = 'Localized additional items'; + section.append(artifactHeader, panelMore); + panelScroller.appendChild(section); + artifactPanel.appendChild(panelScroller); + artifactPanelHost.appendChild(artifactPanel); + + const completedSummary = dom.window.document.createElement('button'); + completedSummary.className = + 'text-size-chat inline-flex items-center gap-1 rounded-md group/activity-header'; + const completedCopy = dom.window.document.createElement('span'); + const completedBody = dom.window.document.createElement('span'); + completedBody.className = 'text-token-conversation-body'; + completedBody.textContent = 'Localized completed state'; + completedCopy.appendChild(completedBody); + completedSummary.appendChild(completedCopy); + + const runningSummary = dom.window.document.createElement('button'); + runningSummary.className = + 'group/activity-header inline-flex min-w-0 max-w-full self-start items-center gap-1'; + runningSummary.textContent = 'Localized running state'; + + const composer = dom.window.document.createElement('div'); + composer.className = 'composer-surface-chrome'; + const attachment = dom.window.document.createElement('button'); + attachment.className = 'aspect-square h-token-button-composer'; + attachment.setAttribute('aria-label', 'Add files and more'); + const access = dom.window.document.createElement('button'); + access.className = 'h-token-button-composer-sm'; + const model = dom.window.document.createElement('button'); + model.className = 'h-token-button-composer'; + const dictation = dom.window.document.createElement('button'); + dictation.className = 'aspect-square h-token-button-composer'; + dictation.setAttribute('aria-label', 'Dictation'); + const primary = dom.window.document.createElement('button'); + primary.className = 'size-token-button-composer bg-token-foreground'; + composer.append(attachment, access, model, dictation, primary); + + dom.window.document.body.append( + appHeader, + sidebar, + artifactPanelHost, + completedSummary, + runningSummary, + composer, + ); + dom.window.eval(buildApplyExpression(runtimeTheme(), runtimeAdapter())); + await new Promise((resolve) => dom.window.setTimeout(resolve, 0)); + + expect(headerControl.dataset.awesomeCodexHeaderControl).toBe('true'); + expect(brandTrigger.dataset.awesomeCodexSidebarBrand).toBe('true'); + expect(navItem.dataset.awesomeCodexSidebarNav).toBe('01'); + expect(domainHeader.dataset.awesomeCodexDomainHeader).toBe('true'); + expect(projectRow.dataset.awesomeCodexProjectRow).toBe('true'); + expect(projectRow.dataset.awesomeCodexDomainId).toBe('D-01'); + expect(taskRow.dataset.awesomeCodexTaskRow).toBe('true'); + expect(accountTrigger.dataset.awesomeCodexAccountTrigger).toBe('true'); + expect(similarlyNamedProjectControl.dataset.awesomeCodexAccountTrigger).toBeUndefined(); + expect(accountIdentity.dataset.awesomeCodexAccountIdentity).toBe('true'); + expect(helpTrigger.dataset.awesomeCodexHelpTrigger).toBe('true'); + expect(artifactPanel.dataset.awesomeCodexArtifactPanel).toBe('true'); + expect(artifactPanelHost.dataset.awesomeCodexArtifactPanelHost).toBe('true'); + expect(artifactHeader.dataset.awesomeCodexArtifactHeader).toBe('true'); + expect(panelMore.dataset.awesomeCodexPanelMore).toBe('true'); + expect(completedSummary.dataset.awesomeCodexCompletedSummary).toBe('true'); + expect(runningSummary.dataset.awesomeCodexActivityItem).toBe('true'); + expect(attachment.dataset.awesomeCodexComposerControl).toBe('attachment'); + expect(access.dataset.awesomeCodexComposerControl).toBe('access'); + expect(model.dataset.awesomeCodexComposerControl).toBe('model'); + expect(dictation.dataset.awesomeCodexComposerControl).toBe('dictation'); + expect(primary.dataset.awesomeCodexComposerControl).toBe('primary'); + }); + + test('tracks whether the Codex artifact panel is actually visible', async () => { + const dom = createDom(); + const transitionWrapper = dom.window.document.createElement('div'); + transitionWrapper.style.opacity = '1'; + transitionWrapper.style.transform = 'none'; + const artifactPanel = dom.window.document.createElement('div'); + artifactPanel.className = 'rounded-3xl bg-token-dropdown-background'; + Object.defineProperty(artifactPanel, 'getBoundingClientRect', { + value: () => ({ width: 300, height: 300, top: 50, left: 700, right: 1000, bottom: 350 }), + }); + const section = dom.window.document.createElement('section'); + const header = dom.window.document.createElement('header'); + header.className = 'bg-token-dropdown-background'; + section.appendChild(header); + artifactPanel.appendChild(section); + transitionWrapper.appendChild(artifactPanel); + dom.window.document.body.appendChild(transitionWrapper); + + dom.window.eval(buildApplyExpression(runtimeTheme(), runtimeAdapter())); + await new Promise((resolve) => dom.window.setTimeout(resolve, 0)); + + const root = dom.window.document.documentElement; + expect(root.dataset.awesomeCodexArtifactPanelState).toBe('open'); + + transitionWrapper.style.opacity = '0'; + await new Promise((resolve) => dom.window.setTimeout(resolve, 0)); + expect(root.dataset.awesomeCodexArtifactPanelState).toBe('closed'); + + transitionWrapper.style.opacity = '1'; + await new Promise((resolve) => dom.window.setTimeout(resolve, 0)); + expect(root.dataset.awesomeCodexArtifactPanelState).toBe('open'); + + dom.window.eval(buildRemoveExpression()); + expect(root.dataset.awesomeCodexArtifactPanelState).toBeUndefined(); + }); + + test('marks the home prompt and inline project as a domain identity', async () => { + const dom = createDom(); + const home = dom.window.document.createElement('main'); + home.setAttribute('role', 'main'); + const icon = dom.window.document.createElement('div'); + icon.dataset.testid = 'home-icon'; + const prompt = dom.window.document.createElement('div'); + prompt.dataset.feature = 'game-source'; + const project = dom.window.document.createElement('button'); + project.textContent = 'Localized project'; + prompt.appendChild(project); + const suggestions = dom.window.document.createElement('div'); + suggestions.className = 'group/home-suggestions'; + for (const candidate of [home, icon, prompt, suggestions]) { + Object.defineProperty(candidate, 'getBoundingClientRect', { + value: () => ({ width: 600, height: 120, top: 80, left: 300, right: 900, bottom: 200 }), + }); + } + home.append(icon, prompt, suggestions); + dom.window.document.body.appendChild(home); + + dom.window.eval(buildApplyExpression(runtimeTheme(), runtimeAdapter())); + await new Promise((resolve) => dom.window.setTimeout(resolve, 0)); + + expect(dom.window.document.documentElement.dataset.awesomeCodexSurface).toBe('home'); + expect(prompt.dataset.awesomeCodexHomePrompt).toBe('true'); + expect(project.dataset.awesomeCodexDomainMarker).toBe('true'); + + dom.window.eval(buildRemoveExpression()); + expect(prompt.dataset.awesomeCodexHomePrompt).toBeUndefined(); + expect(project.dataset.awesomeCodexDomainMarker).toBeUndefined(); + }); + + test('classifies the plugin marketplace and its interactive surfaces', async () => { + const dom = createDom(); + const pluginMarket = dom.window.document.createElement('main'); + pluginMarket.setAttribute('role', 'main'); + + const search = dom.window.document.createElement('div'); + search.className = + 'no-drag flex items-center gap-2 border border-token-input-border rounded-full bg-token-input-background/90'; + const searchInput = dom.window.document.createElement('input'); + searchInput.placeholder = '搜索插件'; + search.appendChild(searchInput); + + const installedSection = dom.window.document.createElement('section'); + installedSection.className = 'flex flex-col gap-1'; + const installedHeader = dom.window.document.createElement('div'); + const installedTitle = dom.window.document.createElement('h2'); + installedHeader.appendChild(installedTitle); + const installedRow = dom.window.document.createElement('div'); + installedRow.className = 'group/plugin-row flex h-11 gap-1 overflow-visible'; + const installedButton = dom.window.document.createElement('button'); + const installedIcon = dom.window.document.createElement('span'); + installedIcon.className = 'rounded-lg border border-token-border-default'; + installedIcon.appendChild(dom.window.document.createElement('img')); + installedButton.appendChild(installedIcon); + installedRow.appendChild(installedButton); + installedSection.append(installedHeader, installedRow); + + const tabs = dom.window.document.createElement('div'); + const publicTab = dom.window.document.createElement('button'); + publicTab.className = 'shrink-0 h-token-button-composer bg-token-foreground/5'; + const personalTab = dom.window.document.createElement('button'); + personalTab.className = 'shrink-0 h-token-button-composer'; + tabs.append(publicTab, personalTab); + + const section = dom.window.document.createElement('section'); + section.className = 'flex flex-col gap-4'; + const sectionHeader = dom.window.document.createElement('div'); + sectionHeader.className = 'border-b border-token-border-light'; + sectionHeader.appendChild(dom.window.document.createElement('h2')); + const grid = dom.window.document.createElement('div'); + grid.className = 'grid gap-x-7 gap-y-4'; + const card = dom.window.document.createElement('div'); + card.className = + 'flex flex-col gap-2.5 border-token-border/40 rounded-2xl border p-2.5 hover:bg-token-foreground/5'; + card.setAttribute('role', 'button'); + card.tabIndex = 0; + const cardRow = dom.window.document.createElement('div'); + cardRow.className = 'flex items-center gap-3'; + const cardIcon = dom.window.document.createElement('span'); + cardIcon.className = + 'flex h-10 w-10 items-center justify-center rounded-lg border border-token-border-default'; + cardIcon.appendChild(dom.window.document.createElement('img')); + const cardContent = dom.window.document.createElement('div'); + cardContent.className = 'flex min-w-0 flex-1 items-center gap-3'; + const more = dom.window.document.createElement('button'); + more.className = 'aspect-square'; + const install = dom.window.document.createElement('button'); + install.className = 'h-token-button-composer border rounded-lg'; + cardContent.append(more, install); + cardRow.append(cardIcon, cardContent); + card.appendChild(cardRow); + grid.appendChild(card); + section.append(sectionHeader, grid); + + pluginMarket.append(search, installedSection, tabs, section); + dom.window.document.body.appendChild(pluginMarket); + dom.window.eval(buildApplyExpression(runtimeTheme(), runtimeAdapter())); + await new Promise((resolve) => dom.window.setTimeout(resolve, 0)); + + expect(dom.window.document.documentElement.dataset.awesomeCodexSurface).toBe('plugins'); + expect(search.dataset.awesomeCodexPluginSearch).toBe('true'); + expect(publicTab.dataset.awesomeCodexPluginTab).toBe('true'); + expect(personalTab.dataset.awesomeCodexPluginTab).toBe('true'); + expect(sectionHeader.dataset.awesomeCodexPluginSectionHeader).toBe('true'); + expect(installedHeader.dataset.awesomeCodexPluginSectionHeader).toBe('true'); + expect(installedButton.dataset.awesomeCodexPluginInstalled).toBe('true'); + expect(installedIcon.dataset.awesomeCodexPluginIcon).toBe('true'); + expect(card.dataset.awesomeCodexPluginCard).toBe('true'); + expect(cardIcon.dataset.awesomeCodexPluginIcon).toBe('true'); + expect(more.dataset.awesomeCodexPluginAction).toBe('menu'); + expect(install.dataset.awesomeCodexPluginAction).toBe('install'); + + dom.window.eval(buildRemoveExpression()); + expect(card.dataset.awesomeCodexPluginCard).toBeUndefined(); + expect(install.dataset.awesomeCodexPluginAction).toBeUndefined(); + }); + + test('classifies scheduled tasks and marks shared collection roles', async () => { + const dom = createDom(); + const main = dom.window.document.createElement('main'); + main.setAttribute('role', 'main'); + + const search = dom.window.document.createElement('div'); + const searchInput = dom.window.document.createElement('input'); + searchInput.placeholder = '搜索已安排任务'; + search.appendChild(searchInput); + + const tabs = dom.window.document.createElement('div'); + tabs.className = 'hide-scrollbar overflow-x-auto'; + const activeTab = dom.window.document.createElement('button'); + activeTab.className = 'shrink-0 h-token-button-composer bg-token-foreground/5'; + const pausedTab = dom.window.document.createElement('button'); + pausedTab.className = 'shrink-0 h-token-button-composer'; + tabs.append(activeTab, pausedTab); + + const row = dom.window.document.createElement('div'); + row.className = 'automation-row opacity-60'; + const rowButton = dom.window.document.createElement('button'); + row.appendChild(rowButton); + main.append(search, tabs, row); + dom.window.document.body.appendChild(main); + + dom.window.eval(buildApplyExpression(runtimeTheme(), runtimeAdapter())); + await new Promise((resolve) => dom.window.setTimeout(resolve, 0)); + + expect(dom.window.document.documentElement.dataset.awesomeCodexSurface).toBe('scheduled'); + expect(search.dataset.awesomeCodexCollectionSearch).toBe('true'); + expect(activeTab.dataset.awesomeCodexCollectionTab).toBe('true'); + expect(pausedTab.dataset.awesomeCodexCollectionTab).toBe('true'); + expect(row.dataset.awesomeCodexCollectionRow).toBe('true'); + }); + + test('classifies sites and marks its empty state and primary action', async () => { + const dom = createDom(); + const main = dom.window.document.createElement('main'); + main.setAttribute('role', 'main'); + + const search = dom.window.document.createElement('div'); + const searchInput = dom.window.document.createElement('input'); + searchInput.placeholder = '搜索站点'; + search.appendChild(searchInput); + + const emptyState = dom.window.document.createElement('div'); + emptyState.className = 'flex w-full max-w-xl flex-col items-center justify-center text-center gap-3'; + const createButton = dom.window.document.createElement('button'); + createButton.textContent = '创建新站点'; + emptyState.appendChild(createButton); + main.append(search, emptyState); + dom.window.document.body.appendChild(main); + + dom.window.eval(buildApplyExpression(runtimeTheme(), runtimeAdapter())); + await new Promise((resolve) => dom.window.setTimeout(resolve, 0)); + + expect(dom.window.document.documentElement.dataset.awesomeCodexSurface).toBe('sites'); + expect(search.dataset.awesomeCodexCollectionSearch).toBe('true'); + expect(emptyState.dataset.awesomeCodexCollectionEmpty).toBe('true'); + expect(createButton.dataset.awesomeCodexCollectionAction).toBe('primary'); + }); + + test('classifies pull requests and marks the loading status', async () => { + const dom = createDom(); + const main = dom.window.document.createElement('main'); + main.setAttribute('role', 'main'); + const heading = dom.window.document.createElement('h1'); + heading.textContent = 'Pull Request'; + const status = dom.window.document.createElement('div'); + status.setAttribute('role', 'status'); + status.textContent = 'Checking GitHub access'; + main.append(heading, status); + dom.window.document.body.appendChild(main); + + dom.window.eval(buildApplyExpression(runtimeTheme(), runtimeAdapter())); + await new Promise((resolve) => dom.window.setTimeout(resolve, 0)); + + expect(dom.window.document.documentElement.dataset.awesomeCodexSurface).toBe('pull-requests'); + expect(status.dataset.awesomeCodexCollectionStatus).toBe('true'); + + dom.window.eval(buildRemoveExpression()); + expect(status.dataset.awesomeCodexCollectionStatus).toBeUndefined(); + }); + + test('classifies a loaded pull-request collection from its search surface', async () => { + const dom = createDom(); + const search = dom.window.document.createElement('div'); + const searchInput = dom.window.document.createElement('input'); + searchInput.placeholder = '搜索 Pull Request'; + search.appendChild(searchInput); + const tabs = dom.window.document.createElement('div'); + tabs.className = 'hide-scrollbar overflow-x-auto'; + const activeTab = dom.window.document.createElement('button'); + activeTab.className = 'shrink-0 h-token-button-composer bg-token-foreground/5'; + tabs.appendChild(activeTab); + const emptyState = dom.window.document.createElement('div'); + emptyState.className = + 'flex w-full max-w-xl flex-col items-center justify-center text-center gap-3 opacity-60'; + emptyState.textContent = '未找到 Pull Request'; + dom.window.document.body.append(search, tabs, emptyState); + + dom.window.eval(buildApplyExpression(runtimeTheme(), runtimeAdapter())); + await new Promise((resolve) => dom.window.setTimeout(resolve, 0)); + + expect(dom.window.document.documentElement.dataset.awesomeCodexSurface).toBe('pull-requests'); + expect(search.dataset.awesomeCodexCollectionSearch).toBe('true'); + expect(activeTab.dataset.awesomeCodexCollectionTab).toBe('true'); + expect(emptyState.dataset.awesomeCodexCollectionEmpty).toBe('true'); + }); + test('is idempotent and replaces the prior payload', () => { const dom = createDom(); dom.window.eval(buildApplyExpression(runtimeTheme(), runtimeAdapter())); @@ -175,6 +586,28 @@ describe('theme injection expressions', () => { expect(dom.window.document.documentElement.dataset.awesomeCodexTheme).toBe('second-theme'); }); + test('reapply removes auxiliary asset variables that the replacement theme no longer declares', () => { + const dom = createDom(); + dom.window.eval(buildApplyExpression(runtimeTheme(), runtimeAdapter())); + + dom.window.eval( + buildApplyExpression( + runtimeTheme({ + assets: { + 'six-eyes-emblem': { + dataUrl: 'data:image/png;base64,b25seS1lbWJsZW0=', + }, + }, + }), + runtimeAdapter(), + ), + ); + + const root = dom.window.document.documentElement; + expect(root.style.getPropertyValue('--act-asset-six-eyes-emblem')).toContain('data:image/png'); + expect(root.style.getPropertyValue('--act-asset-send-infinity')).toBe(''); + }); + test('removes the experience layer when a plain theme replaces it', () => { const dom = createDom(); dom.window.eval(buildApplyExpression(runtimeTheme(), runtimeAdapter())); @@ -258,6 +691,14 @@ describe('theme injection expressions', () => { test('removes only project-owned state', () => { const dom = createDom(); dom.window.eval(buildApplyExpression(runtimeTheme(), runtimeAdapter())); + const header = dom.window.document.createElement('header'); + header.className = 'app-header-tint'; + const headerControl = dom.window.document.createElement('button'); + headerControl.dataset.awesomeCodexHeaderControl = 'true'; + header.appendChild(headerControl); + const legacyActivityMarker = dom.window.document.createElement('button'); + legacyActivityMarker.dataset.awesomeCodexActivitySummary = 'true'; + dom.window.document.body.append(header, legacyActivityMarker); const result = dom.window.eval(buildRemoveExpression()); @@ -270,11 +711,33 @@ describe('theme injection expressions', () => { expect(root.dataset.awesomeCodexAdapter).toBeUndefined(); expect(root.dataset.awesomeCodexSurface).toBeUndefined(); expect(root.style.getPropertyValue('--act-artwork')).toBe(''); + expect(root.style.getPropertyValue('--act-asset-six-eyes-emblem')).toBe(''); + expect(root.style.getPropertyValue('--act-asset-send-infinity')).toBe(''); expect(root.style.getPropertyValue('--act-surface-overlay')).toBe(''); expect(root.style.getPropertyValue('--act-composer')).toBe(''); expect(dom.window.document.getElementById(STYLE_ID)).toBeNull(); expect(dom.window.document.getElementById(CHROME_ID)).toBeNull(); expect(dom.window.document.getElementById('keep-style')).not.toBeNull(); + expect(dom.window.document.querySelector('[data-awesome-codex-artifact-panel]')).toBeNull(); + expect(dom.window.document.querySelector('[data-awesome-codex-artifact-panel-host]')).toBeNull(); + expect(dom.window.document.querySelector('[data-awesome-codex-artifact-header]')).toBeNull(); + expect(dom.window.document.querySelector('[data-awesome-codex-panel-more]')).toBeNull(); + expect(dom.window.document.querySelector('[data-awesome-codex-completed-summary]')).toBeNull(); + expect(dom.window.document.querySelector('[data-awesome-codex-activity-item]')).toBeNull(); + expect(dom.window.document.querySelector('[data-awesome-codex-header-control]')).toBeNull(); + expect(dom.window.document.querySelector('[data-awesome-codex-sidebar-brand]')).toBeNull(); + expect(dom.window.document.querySelector('[data-awesome-codex-sidebar-nav]')).toBeNull(); + expect(dom.window.document.querySelector('[data-awesome-codex-domain-header]')).toBeNull(); + expect(dom.window.document.querySelector('[data-awesome-codex-project-row]')).toBeNull(); + expect(dom.window.document.querySelector('[data-awesome-codex-domain-id]')).toBeNull(); + expect(dom.window.document.querySelector('[data-awesome-codex-task-row]')).toBeNull(); + expect(dom.window.document.querySelector('[data-awesome-codex-account-trigger]')).toBeNull(); + expect(dom.window.document.querySelector('[data-awesome-codex-account-identity]')).toBeNull(); + expect(dom.window.document.querySelector('[data-awesome-codex-help-trigger]')).toBeNull(); + expect(dom.window.document.querySelector('[data-awesome-codex-home-prompt]')).toBeNull(); + expect(dom.window.document.querySelector('[data-awesome-codex-domain-marker]')).toBeNull(); + expect(dom.window.document.querySelector('[data-awesome-codex-composer-control]')).toBeNull(); + expect(dom.window.document.querySelector('[data-awesome-codex-activity-summary]')).toBeNull(); }); test('restore leaves an unrelated theme injector untouched', () => { diff --git a/tests/engine/installer.test.mjs b/tests/engine/installer.test.mjs new file mode 100644 index 0000000..4d4bfc4 --- /dev/null +++ b/tests/engine/installer.test.mjs @@ -0,0 +1,115 @@ +import { access, lstat, mkdtemp, readFile, readlink, readdir, rm, stat } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join, resolve } from 'node:path'; +import { afterEach, describe, expect, test, vi } from 'vitest'; + +import { + installPersistentAgent, + installationPaths, + uninstallPersistentAgent, +} from '../../src/engine/installer.mjs'; + +const repoRoot = resolve(import.meta.dirname, '../..'); +const tempDirs = []; + +afterEach(async () => { + await Promise.all(tempDirs.splice(0).map((directory) => rm(directory, { recursive: true, force: true }))); +}); + +describe('self-contained persistent installation', () => { + test('copies the audited runtime and runnable themes outside the Git clone', async () => { + const home = await mkdtemp(join(tmpdir(), 'awesome-codex-install-')); + tempDirs.push(home); + const bootstrap = vi.fn(async () => {}); + const kickstart = vi.fn(async () => {}); + const bootout = vi.fn(async () => {}); + + const result = await installPersistentAgent( + { sourceRoot: repoRoot, home, version: '0.3.0', themeSlug: 'satoru-gojo' }, + { bootout, bootstrap, kickstart }, + ); + const paths = installationPaths({ home, version: '0.3.0' }); + + expect(result).toEqual(expect.objectContaining({ installed: true, theme: 'satoru-gojo' })); + await expect(access(join(paths.releaseDir, 'bin/awesome-codex-themes'))).resolves.toBeUndefined(); + await expect(access(join(paths.releaseDir, 'src/cli/main.mjs'))).resolves.toBeUndefined(); + await expect(access(join(paths.releaseDir, 'src/engine/cdp.mjs'))).resolves.toBeUndefined(); + await expect(access(join(paths.releaseDir, 'themes/satoru-gojo/theme.json'))).resolves.toBeUndefined(); + expect((await stat(join(paths.releaseDir, 'bin/awesome-codex-themes'))).mode & 0o111).not.toBe(0); + expect((await lstat(paths.currentPath)).isSymbolicLink()).toBe(true); + expect(await readlink(paths.currentPath)).toBe(paths.releaseDir); + + const config = JSON.parse(await readFile(paths.configPath, 'utf8')); + expect(config).toEqual({ schemaVersion: 1, enabled: true, themeSlug: 'satoru-gojo', launchAtLogin: true }); + expect((await stat(paths.configPath)).mode & 0o777).toBe(0o600); + expect(await readFile(paths.plistPath, 'utf8')).toContain(join(paths.currentPath, 'bin/awesome-codex-themes')); + expect(bootout).toHaveBeenCalledWith({ ignoreMissing: true }); + expect(bootstrap).toHaveBeenCalledWith(paths.plistPath); + expect(kickstart).toHaveBeenCalledOnce(); + }); + + test('is idempotent for the same release and refreshes desired configuration', async () => { + const home = await mkdtemp(join(tmpdir(), 'awesome-codex-install-')); + tempDirs.push(home); + const dependencies = { + bootout: vi.fn(async () => {}), + bootstrap: vi.fn(async () => {}), + kickstart: vi.fn(async () => {}), + }; + + await installPersistentAgent({ sourceRoot: repoRoot, home, version: '0.3.0', themeSlug: 'satoru-gojo' }, dependencies); + await expect( + installPersistentAgent({ + sourceRoot: repoRoot, + home, + version: '0.3.0', + themeSlug: 'satoru-gojo', + enabled: false, + }, dependencies), + ).resolves.toEqual(expect.objectContaining({ installed: true })); + + const paths = installationPaths({ home, version: '0.3.0' }); + expect((await readdir(paths.releasesRoot)).filter((name) => name.includes('staging'))).toEqual([]); + expect(dependencies.bootout).toHaveBeenCalledTimes(2); + expect(dependencies.bootstrap).toHaveBeenCalledTimes(2); + expect(dependencies.kickstart).toHaveBeenCalledTimes(2); + expect(JSON.parse(await readFile(paths.configPath, 'utf8')).enabled).toBe(false); + }); + + test('cleans a staging release when copying fails', async () => { + const home = await mkdtemp(join(tmpdir(), 'awesome-codex-install-')); + tempDirs.push(home); + const copy = vi.fn(async () => { + throw new Error('disk full'); + }); + + await expect( + installPersistentAgent( + { sourceRoot: repoRoot, home, version: '0.3.0', themeSlug: 'satoru-gojo' }, + { copy, bootstrap: vi.fn(), kickstart: vi.fn() }, + ), + ).rejects.toEqual(expect.objectContaining({ code: 'INSTALL_COPY_FAILED' })); + + const paths = installationPaths({ home, version: '0.3.0' }); + expect((await readdir(paths.releasesRoot)).filter((name) => name.includes('staging'))).toEqual([]); + }); + + test('boots out and removes only project-owned installation paths', async () => { + const home = await mkdtemp(join(tmpdir(), 'awesome-codex-install-')); + tempDirs.push(home); + const dependencies = { + bootout: vi.fn(async () => {}), + bootstrap: vi.fn(async () => {}), + kickstart: vi.fn(async () => {}), + }; + await installPersistentAgent({ sourceRoot: repoRoot, home, version: '0.3.0', themeSlug: 'satoru-gojo' }, dependencies); + const bootout = vi.fn(async () => {}); + + await uninstallPersistentAgent({ home }, { bootout }); + + const paths = installationPaths({ home, version: '0.3.0' }); + expect(bootout).toHaveBeenCalledWith({ ignoreMissing: true }); + await expect(access(paths.supportRoot)).rejects.toThrow(); + await expect(access(paths.plistPath)).rejects.toThrow(); + }); +}); diff --git a/tests/engine/launch-agent.test.mjs b/tests/engine/launch-agent.test.mjs new file mode 100644 index 0000000..4f639eb --- /dev/null +++ b/tests/engine/launch-agent.test.mjs @@ -0,0 +1,155 @@ +import { mkdtemp, readFile, rm, stat } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { afterEach, describe, expect, test, vi } from 'vitest'; + +import { + LAUNCH_AGENT_LABEL, + bootoutLaunchAgent, + bootstrapLaunchAgent, + buildLaunchAgentPlist, + kickstartLaunchAgent, + writeLaunchAgentPlist, +} from '../../src/engine/launch-agent.mjs'; + +const tempDirs = []; + +afterEach(async () => { + await Promise.all(tempDirs.splice(0).map((directory) => rm(directory, { recursive: true, force: true }))); +}); + +describe('LaunchAgent definition', () => { + test('generates a stable escaped user agent with login persistence', () => { + const plist = buildLaunchAgentPlist({ + launcherPath: '/Users/test/Awesome & Themes/current/bin/awesome-codex-themes', + stdoutPath: '/Users/test/Library/Logs/AwesomeCodexThemes.log', + stderrPath: '/Users/test/Library/Logs/AwesomeCodexThemes.error.log', + }); + + expect(LAUNCH_AGENT_LABEL).toBe('io.github.awesome-codex-themes.agent'); + expect(plist).toContain('io.github.awesome-codex-themes.agent'); + expect(plist).toContain('/Users/test/Awesome & Themes/current/bin/awesome-codex-themes'); + expect(plist).toContain('_agent'); + expect(plist).toContain('RunAtLoad\n '); + expect(plist).toContain('KeepAlive\n '); + expect(plist).not.toContain('--remote-allow-origins'); + }); + + test('rejects relative installed paths', () => { + expect(() => buildLaunchAgentPlist({ + launcherPath: 'bin/awesome-codex-themes', + stdoutPath: '/tmp/out.log', + stderrPath: '/tmp/error.log', + })).toThrowError(expect.objectContaining({ code: 'LAUNCH_AGENT_PATH_INVALID' })); + }); + + test('writes a readable plist atomically', async () => { + const root = await mkdtemp(join(tmpdir(), 'awesome-codex-plist-')); + tempDirs.push(root); + const path = join(root, 'LaunchAgents/agent.plist'); + const plist = buildLaunchAgentPlist({ + launcherPath: '/opt/awesome/bin/awesome-codex-themes', + stdoutPath: '/tmp/out.log', + stderrPath: '/tmp/error.log', + }); + + await writeLaunchAgentPlist(path, plist); + + expect(await readFile(path, 'utf8')).toBe(plist); + expect((await stat(path)).mode & 0o777).toBe(0o644); + }); +}); + +describe('launchctl operations', () => { + test('reports whether the user service is actually loaded', async () => { + const launchAgent = await import('../../src/engine/launch-agent.mjs'); + expect(launchAgent.isLaunchAgentLoaded).toBeTypeOf('function'); + if (typeof launchAgent.isLaunchAgentLoaded !== 'function') return; + + const run = vi.fn(async () => ({ stdout: '', stderr: '' })); + await expect(launchAgent.isLaunchAgentLoaded({ uid: 501, run })).resolves.toBe(true); + expect(run).toHaveBeenCalledWith('/bin/launchctl', [ + 'print', + 'gui/501/io.github.awesome-codex-themes.agent', + ]); + + const missing = Object.assign(new Error('Could not find service'), { + code: 113, + stderr: 'Could not find service "io.github.awesome-codex-themes.agent" in domain for user gui: 501', + }); + run.mockRejectedValueOnce(missing); + await expect(launchAgent.isLaunchAgentLoaded({ uid: 501, run })).resolves.toBe(false); + + const denied = Object.assign(new Error('Operation not permitted'), { code: 1, stderr: 'Operation not permitted' }); + run.mockRejectedValueOnce(denied); + await expect(launchAgent.isLaunchAgentLoaded({ uid: 501, run })).rejects.toEqual( + expect.objectContaining({ code: 'LAUNCH_AGENT_STATUS_FAILED', cause: denied }), + ); + }); + + test('uses argument arrays for bootstrap, kickstart, and bootout', async () => { + const run = vi.fn(async () => ({ stdout: '', stderr: '' })); + + await bootstrapLaunchAgent('/Users/test/Library/LaunchAgents/agent.plist', { uid: 501, run }); + await kickstartLaunchAgent({ uid: 501, run }); + await bootoutLaunchAgent({ uid: 501, run }); + + expect(run.mock.calls).toEqual([ + ['/bin/launchctl', ['bootstrap', 'gui/501', '/Users/test/Library/LaunchAgents/agent.plist']], + ['/bin/launchctl', ['kickstart', '-k', 'gui/501/io.github.awesome-codex-themes.agent']], + ['/bin/launchctl', ['bootout', 'gui/501/io.github.awesome-codex-themes.agent']], + ]); + }); + + test('ignores only a missing service when bootout is used for replacement', async () => { + const missing = Object.assign(new Error('Boot-out failed: 3: No such process'), { + code: 3, + stderr: 'Boot-out failed: 3: No such process', + }); + const run = vi.fn().mockRejectedValueOnce(missing); + + await expect(bootoutLaunchAgent({ uid: 501, run, ignoreMissing: true })).resolves.toBeUndefined(); + + const denied = Object.assign(new Error('Operation not permitted'), { code: 1, stderr: 'Operation not permitted' }); + run.mockRejectedValueOnce(denied); + await expect(bootoutLaunchAgent({ uid: 501, run, ignoreMissing: true })).rejects.toEqual( + expect.objectContaining({ code: 'LAUNCH_AGENT_BOOTOUT_FAILED', cause: denied }), + ); + }); + + test('retries the transient launchd unload window before bootstrap succeeds', async () => { + const transient = Object.assign(new Error('Bootstrap failed: 5: Input/output error'), { + code: 5, + stderr: 'Bootstrap failed: 5: Input/output error', + }); + const run = vi.fn() + .mockRejectedValueOnce(transient) + .mockRejectedValueOnce(transient) + .mockResolvedValueOnce({ stdout: '', stderr: '' }); + const delay = vi.fn(async () => {}); + + await expect( + bootstrapLaunchAgent('/Users/test/Library/LaunchAgents/agent.plist', { + uid: 501, + run, + delay, + attempts: 3, + }), + ).resolves.toBeUndefined(); + + expect(run).toHaveBeenCalledTimes(3); + expect(delay).toHaveBeenCalledTimes(2); + }); + + test('wraps launchctl failures in stable public error codes', async () => { + const failure = new Error('launchctl failed'); + const run = vi.fn(async () => { throw failure; }); + + await expect( + bootstrapLaunchAgent('/Users/test/Library/LaunchAgents/agent.plist', { uid: 501, run }), + ).rejects.toEqual(expect.objectContaining({ code: 'LAUNCH_AGENT_BOOTSTRAP_FAILED', cause: failure })); + await expect(kickstartLaunchAgent({ uid: 501, run })).rejects.toEqual( + expect.objectContaining({ code: 'LAUNCH_AGENT_KICKSTART_FAILED', cause: failure }), + ); + }); +}); diff --git a/tests/engine/session.test.mjs b/tests/engine/session.test.mjs index 1c54ac3..366e4aa 100644 --- a/tests/engine/session.test.mjs +++ b/tests/engine/session.test.mjs @@ -61,7 +61,12 @@ async function createThemeRoot() { license: { code: 'MIT', artwork: 'CC0-1.0' }, categories: ['dark'], tags: ['test'], - compatibility: { platforms: ['macos'], status: 'experimental', appVersions: ['26.707.*'] }, + compatibility: { + platforms: ['macos'], + status: 'experimental', + strategy: 'best-effort-all', + verifiedAppVersions: ['26.707.*'], + }, palette: { background: '#000000', surface: '#111111CC', text: '#FFFFFF', accent: '#00FFAA' }, files: { css: 'theme.css', artwork: 'background.svg', preview: 'preview.svg' }, }), @@ -103,6 +108,24 @@ describe('applyThemeAtPort', () => { }), ).rejects.toEqual(expect.objectContaining({ code: 'THEME_APPLY_UNVERIFIED' })); }); + + test('attempts the shared best-effort adapter on an unverified numeric app version', async () => { + const themesRoot = await createThemeRoot(); + const evaluate = vi.fn(async ({ expression }) => { + expect(expression).toContain('codex-best-effort'); + return [{ pass: true, theme: 'test-theme', adapter: 'codex-best-effort' }]; + }); + + await expect( + applyThemeAtPort({ + themesRoot, + themeSlug: 'test-theme', + port: 9341, + appVersion: '99.1.2', + evaluate, + }), + ).resolves.toEqual({ theme: 'test-theme', adapter: 'codex-best-effort', renderers: 1 }); + }); }); describe('startThemeSession', () => { diff --git a/tests/engine/theme.test.mjs b/tests/engine/theme.test.mjs index 63bbe65..9c2bdc9 100644 --- a/tests/engine/theme.test.mjs +++ b/tests/engine/theme.test.mjs @@ -27,7 +27,8 @@ function validManifest(overrides = {}) { compatibility: { platforms: ['macos'], status: 'experimental', - appVersions: ['26.707.*'], + strategy: 'best-effort-all', + verifiedAppVersions: ['26.707.*'], }, palette: { background: '#07111F', @@ -120,12 +121,54 @@ describe('validateThemeManifest', () => { expect(manifest.tags).toEqual(['blue', 'focus']); }); + test('keeps English primary and normalizes an optional native display name pair', () => { + const manifest = validateThemeManifest( + validManifest({ + name: 'Satoru Gojo', + nativeName: ' 五条 悟 ', + nativeLocale: 'ja-JP', + }), + ); + + expect(manifest.name).toBe('Satoru Gojo'); + expect(manifest.nativeName).toBe('五条 悟'); + expect(manifest.nativeLocale).toBe('ja-JP'); + }); + + test.each([ + [{ nativeName: '五条 悟' }, 'THEME_NATIVE_NAME_INCOMPLETE'], + [{ nativeLocale: 'ja-JP' }, 'THEME_NATIVE_NAME_INCOMPLETE'], + [{ nativeName: '五条 悟', nativeLocale: 'japanese' }, 'THEME_NATIVE_LOCALE_INVALID'], + ])('rejects incomplete or invalid native display metadata', (overrides, code) => { + expect(() => validateThemeManifest(validManifest(overrides))).toThrowError( + expect.objectContaining({ code }), + ); + }); + test.each([ [{ slug: '../escape' }, 'THEME_SLUG_INVALID'], [{ version: 'latest' }, 'THEME_VERSION_INVALID'], [{ palette: { ...validManifest().palette, accent: 'blue' } }, 'THEME_COLOR_INVALID'], [{ compatibility: { ...validManifest().compatibility, platforms: ['windows'] } }, 'THEME_PLATFORM_UNSUPPORTED'], [{ files: { ...validManifest().files, artwork: 'https://example.com/art.svg' } }, 'THEME_PATH_UNSAFE'], + [ + { + files: { + ...validManifest().files, + assets: { 'Six Eyes': 'assets/six-eyes.png' }, + }, + }, + 'THEME_ASSET_NAME_INVALID', + ], + [ + { + files: { + ...validManifest().files, + assets: { 'six-eyes': '../six-eyes.png' }, + }, + }, + 'THEME_PATH_UNSAFE', + ], ])('rejects unsafe or unsupported metadata with a stable code', (overrides, code) => { expect(() => validateThemeManifest(validManifest(overrides))).toThrowError( expect.objectContaining({ code }), @@ -138,6 +181,13 @@ describe('validateThemeManifest', () => { schemaVersion: 2, palette: semanticPalette(), experience: experience({ brand: ' LIMITLESS ' }), + files: { + ...validManifest().files, + assets: { + 'six-eyes-emblem': 'assets/six-eyes-emblem.png', + 'send-infinity': 'assets/send-infinity.png', + }, + }, }), ); @@ -146,6 +196,10 @@ describe('validateThemeManifest', () => { expect(manifest.palette.surfaceOverlay).toBe('#171411F2'); expect(manifest.palette.scrollbarHover).toBe('#FFB18A78'); expect(manifest.experience).toEqual(experience()); + expect(manifest.files.assets).toEqual({ + 'six-eyes-emblem': 'assets/six-eyes-emblem.png', + 'send-infinity': 'assets/send-infinity.png', + }); }); test('rejects a schema v2 palette missing a required semantic role', () => { @@ -201,19 +255,37 @@ describe('validateThemeManifest', () => { }); describe('assertThemeCompatibility', () => { - test('accepts an app version covered by a declared wildcard range', () => { + test('identifies an app version covered by a live-verified wildcard range', () => { expect( assertThemeCompatibility(validManifest(), { platform: 'macos', appVersion: '26.707.72221' }), - ).toEqual({ platform: 'macos', appVersion: '26.707.72221', status: 'experimental' }); + ).toEqual({ + platform: 'macos', + appVersion: '26.707.72221', + status: 'experimental', + strategy: 'best-effort-all', + verified: true, + }); }); - test.each([ - [{ platform: 'macos', appVersion: '26.708.1' }, 'THEME_APP_VERSION_UNSUPPORTED'], - [{ platform: 'windows', appVersion: '26.707.72221' }, 'THEME_PLATFORM_UNSUPPORTED'], - ])('fails closed outside the declared compatibility boundary', (runtime, code) => { - expect(() => assertThemeCompatibility(validManifest(), runtime)).toThrowError( - expect.objectContaining({ code }), - ); + test('allows every numeric Codex version as best effort and reports unverified status', () => { + expect( + assertThemeCompatibility(validManifest(), { platform: 'macos', appVersion: '99.1.2' }), + ).toEqual({ + platform: 'macos', + appVersion: '99.1.2', + status: 'experimental', + strategy: 'best-effort-all', + verified: false, + }); + }); + + test('still rejects unsupported platforms', () => { + expect(() => + assertThemeCompatibility(validManifest(), { + platform: 'windows', + appVersion: '26.707.72221', + }), + ).toThrowError(expect.objectContaining({ code: 'THEME_PLATFORM_UNSUPPORTED' })); }); }); @@ -234,6 +306,52 @@ describe('loadThemePackage', () => { expect(theme.artwork.mime).toBe('image/svg+xml'); expect(theme.artwork.dataUrl).toMatch(/^data:image\/svg\+xml;base64,/); expect(theme.artwork.bytes).toBeGreaterThan(0); + expect(theme.assets).toEqual({}); + }); + + test('loads declared auxiliary UI assets into named runtime-safe payloads', async () => { + const manifest = validManifest({ + schemaVersion: 2, + palette: semanticPalette(), + files: { + ...validManifest().files, + assets: { + 'six-eyes-emblem': 'assets/six-eyes-emblem.png', + 'send-infinity': 'assets/send-infinity.png', + }, + }, + }); + const root = await createTheme(manifest); + await mkdir(join(root, 'assets'), { recursive: true }); + await writeFile(join(root, 'assets/six-eyes-emblem.png'), Buffer.from('89504e470d0a1a0a', 'hex')); + await writeFile(join(root, 'assets/send-infinity.png'), Buffer.from('89504e470d0a1a0a', 'hex')); + + const theme = await loadThemePackage(root); + + expect(Object.keys(theme.assets)).toEqual(['six-eyes-emblem', 'send-infinity']); + expect(theme.assets['six-eyes-emblem']).toEqual( + expect.objectContaining({ + mime: 'image/png', + bytes: 8, + dataUrl: expect.stringMatching(/^data:image\/png;base64,/), + }), + ); + }); + + test('rejects an oversized auxiliary runtime asset with a stable code', async () => { + const manifest = validManifest({ + files: { + ...validManifest().files, + assets: { 'six-eyes-emblem': 'assets/six-eyes-emblem.png' }, + }, + }); + const root = await createTheme(manifest); + await mkdir(join(root, 'assets'), { recursive: true }); + await writeFile(join(root, 'assets/six-eyes-emblem.png'), Buffer.alloc(32, 1)); + + await expect( + loadThemePackage(root, { maxRuntimeAssetBytes: 16 }), + ).rejects.toEqual(expect.objectContaining({ code: 'THEME_RUNTIME_ASSET_TOO_LARGE' })); }); test('rejects path traversal before reading outside the theme directory', async () => { diff --git a/tests/gallery/app.test.tsx b/tests/gallery/app.test.tsx index d9beff4..d9084b2 100644 --- a/tests/gallery/app.test.tsx +++ b/tests/gallery/app.test.tsx @@ -9,26 +9,25 @@ describe('theme gallery', () => { test('presents the flagship-led collection and project boundary', () => { render(); - expect(screen.getByRole('heading', { name: /make codex feel limitless/i })).toBeInTheDocument(); - expect(screen.getByText('1 flagship theme')).toBeInTheDocument(); - expect(screen.getByText('4 original studies')).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: /make codex feel like yours/i })).toBeInTheDocument(); + expect(screen.getByText('12 complete themes')).toBeInTheDocument(); + expect(screen.getByText('1 featured flagship')).toBeInTheDocument(); expect(screen.getByText(/unofficial and not affiliated/i)).toBeInTheDocument(); - expect(screen.getByRole('heading', { name: 'Limitless Six Eyes' })).toBeInTheDocument(); - expect(screen.getByRole('heading', { name: 'Obsidian Bloom' })).toBeInTheDocument(); - expect(screen.getByRole('heading', { name: 'Paper Circuit' })).toBeInTheDocument(); - expect(screen.getByText(/versioned full-workspace adapter/i)).toBeInTheDocument(); - expect(screen.getByLabelText('Obsidian Bloom color palette').querySelectorAll('span')).toHaveLength(5); - expect(screen.getAllByRole('button', { name: /^view /i })[0]).toHaveAccessibleName('View Limitless Six Eyes'); - }); + expect(screen.getByRole('heading', { name: 'Satoru Gojo' })).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: 'Night City' })).toBeInTheDocument(); + expect(screen.getByText('五条 悟')).toBeInTheDocument(); + expect(screen.getByText(/inspectable adapter/i)).toBeInTheDocument(); + expect(screen.getByLabelText('Satoru Gojo color palette').querySelectorAll('span')).toHaveLength(5); + expect(screen.getAllByRole('button', { name: /^view /i })[0]).toHaveAccessibleName('View Satoru Gojo'); + }, 15_000); test('searches across names, descriptions, categories, and tags', async () => { const user = userEvent.setup(); render(); - await user.type(screen.getByRole('searchbox', { name: /search themes/i }), 'aurora'); + await user.type(screen.getByRole('searchbox', { name: /search themes/i }), '五条'); - expect(screen.getByRole('heading', { name: 'Arctic Signal' })).toBeInTheDocument(); - expect(screen.queryByRole('heading', { name: 'Solar Archive' })).not.toBeInTheDocument(); + expect(screen.getByRole('heading', { name: 'Satoru Gojo' })).toBeInTheDocument(); expect(screen.getByText('1 theme shown')).toBeInTheDocument(); }); @@ -37,14 +36,12 @@ describe('theme gallery', () => { render(); await user.click(screen.getByRole('button', { name: 'Editorial' })); - expect(screen.getByRole('heading', { name: 'Paper Circuit' })).toBeInTheDocument(); - expect(screen.getByRole('heading', { name: 'Solar Archive' })).toBeInTheDocument(); - expect(screen.queryByRole('heading', { name: 'Arctic Signal' })).not.toBeInTheDocument(); + expect(screen.getByRole('heading', { name: 'Satoru Gojo' })).toBeInTheDocument(); await user.type(screen.getByRole('searchbox', { name: /search themes/i }), 'nonexistent'); expect(screen.getByRole('heading', { name: /no matching themes/i })).toBeInTheDocument(); await user.click(screen.getByRole('button', { name: /clear search and filters/i })); - expect(screen.getByRole('heading', { name: 'Arctic Signal' })).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: 'Satoru Gojo' })).toBeInTheDocument(); }); test('opens structured details and copies the exact CLI command', async () => { @@ -56,26 +53,31 @@ describe('theme gallery', () => { }); render(); - await user.click(screen.getByRole('button', { name: /view obsidian bloom/i })); - const dialog = screen.getByRole('dialog', { name: 'Obsidian Bloom' }); + await user.click(screen.getByRole('button', { name: /view satoru gojo/i })); + const dialog = screen.getByRole('dialog', { name: 'Satoru Gojo' }); + expect(dialog).toHaveTextContent('Best effort on every numeric Codex Desktop version'); + expect(dialog).toHaveTextContent('Highly compatible'); expect(dialog).toHaveTextContent('26.707.*'); - expect(dialog).toHaveTextContent('CC0-1.0'); + expect(dialog).toHaveTextContent('26.715.*'); + expect(dialog).toHaveTextContent('PROJECT-ASSET'); expect(dialog).toHaveTextContent('Full workspace'); - await user.click(screen.getByRole('button', { name: /copy apply command/i })); + expect(dialog).toHaveTextContent(/pause.*restore.*GitHub Issue/i); + await user.click(screen.getByRole('button', { name: /copy install command/i })); - expect(writeText).toHaveBeenCalledWith('awesome-codex-themes start obsidian-bloom'); + expect(writeText).toHaveBeenCalledWith('./bin/awesome-codex-themes install-agent satoru-gojo'); expect(screen.getByRole('button', { name: /command copied/i })).toBeInTheDocument(); }); - test('labels private prototype artwork without weakening the theme coverage claim', async () => { + test('shows the approved project artwork without a prototype restriction warning', async () => { const user = userEvent.setup(); render(); - await user.click(screen.getByRole('button', { name: /view limitless six eyes/i })); + await user.click(screen.getByRole('button', { name: /view satoru gojo/i })); - const dialog = screen.getByRole('dialog', { name: 'Limitless Six Eyes' }); - expect(dialog).toHaveTextContent('PROTOTYPE-REFERENCE-ONLY'); + const dialog = screen.getByRole('dialog', { name: 'Satoru Gojo' }); + expect(dialog).toHaveTextContent('五条 悟'); + expect(dialog).toHaveTextContent('PROJECT-ASSET'); expect(dialog).toHaveTextContent('Full workspace'); - expect(dialog).toHaveTextContent(/private fan prototype/i); + expect(dialog).not.toHaveTextContent(/private fan|must be replaced|public or commercial release/i); }); }); diff --git a/tests/themes/castle-archive.test.mjs b/tests/themes/castle-archive.test.mjs new file mode 100644 index 0000000..b1aeec7 --- /dev/null +++ b/tests/themes/castle-archive.test.mjs @@ -0,0 +1,56 @@ +import { readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { describe, expect, test } from 'vitest'; + +import { SEMANTIC_COLOR_ROLES, loadThemePackage } from '../../src/engine/theme.mjs'; + +const repoRoot = new URL('../..', import.meta.url).pathname; +const themeRoot = join(repoRoot, 'themes/castle-archive'); + +describe('castle-archive theme package', () => { + test('loads as a complete experimental Schema 2 package', async () => { + const theme = await loadThemePackage(themeRoot); + expect(theme.manifest.schemaVersion).toBe(2); + expect(theme.manifest.slug).toBe('castle-archive'); + expect(theme.manifest.version).toBe('0.1.1'); + expect(theme.manifest.mode).toBe('dark'); + expect(theme.manifest.compatibility).toEqual({ + platforms: ['macos'], + status: 'experimental', + strategy: 'best-effort-all', + verifiedAppVersions: ['26.707.*', '26.715.*'], + }); + expect(Object.keys(theme.manifest.palette)).toEqual(SEMANTIC_COLOR_ROLES); + expect(theme.artwork.bytes).toBeLessThanOrEqual(700 * 1024); + }); + + test('declares a bounded original component-asset system', async () => { + const theme = await loadThemePackage(themeRoot); + const assets = Object.entries(theme.assets); + expect(assets.length).toBeGreaterThanOrEqual(5); + expect(assets.length).toBeLessThanOrEqual(16); + expect(theme.manifest.files.assets).toHaveProperty('keyhole-index'); + for (const [name, asset] of assets) { + expect(name).toMatch(/^[a-z0-9]+(?:-[a-z0-9]+)*$/); + expect(asset.bytes).toBeLessThanOrEqual(256 * 1024); + expect(asset.dataUrl).toMatch(/^data:image\//); + } + }); + + test('scopes visual rules to this theme and excludes the Satoru Gojo visual language', async () => { + const theme = await loadThemePackage(themeRoot); + expect(theme.css).toContain("html.awesome-codex-theme[data-awesome-codex-theme=\"castle-archive\"]"); + expect(theme.css).toContain('--batch-theme-signature: "castle-archive"'); + expect(theme.css).toContain('--act-asset-keyhole-index'); + expect(theme.css).not.toMatch(/six-eyes|blindfold|send-infinity|limitless-violet|lavender|satoru-gojo/i); + expect(theme.css).not.toMatch(/(^|[^-])url\s*\(/i); + }); + + test('records project asset sources without an internal-only restriction', async () => { + const notice = await readFile(join(themeRoot, 'ASSET_LICENSE.md'), 'utf8'); + expect(notice).toContain('Asset identifier: `PROJECT-ASSET`'); + expect(notice).not.toMatch(/internal prototype|not approved for public release|must be replaced/i); + expect(notice).toContain('07-lady-dimitrescu.png'); + expect(notice).toContain('07-lady-dimitrescu-standard-codex-imagegen-v1.png'); + }); +}); diff --git a/tests/themes/catalog.test.mjs b/tests/themes/catalog.test.mjs index 97535f0..1e2dddf 100644 --- a/tests/themes/catalog.test.mjs +++ b/tests/themes/catalog.test.mjs @@ -15,62 +15,55 @@ afterEach(async () => { }); describe('theme catalog', () => { - test('loads the five themes in deterministic package order', async () => { + test('loads only complete runnable release themes', async () => { const catalog = await buildThemeCatalog(themesRoot); expect(catalog.map((theme) => theme.slug)).toEqual([ - 'arctic-signal', - 'limitless-six-eyes', - 'obsidian-bloom', - 'paper-circuit', - 'solar-archive', + 'castle-archive', + 'foundling-garden', + 'grand-line', + 'mordor-runtime', + 'new-world-studio', + 'night-city', + 'overworld-realms', + 'saiyan-ukiyoe', + 'satoru-gojo', + 'slingshot-lab', + 'symbiote-sumi-e', + 'zaun-workshop', ]); + expect(catalog).toHaveLength(12); expect(catalog.every((theme) => theme.compatibility.status === 'experimental')).toBe(true); - expect(catalog.every((theme) => theme.command.startsWith('awesome-codex-themes start '))).toBe(true); + expect(catalog.every((theme) => theme.command.startsWith('./bin/awesome-codex-themes install-agent '))).toBe(true); expect(catalog.every((theme) => theme.preview.startsWith('/theme-assets/'))).toBe(true); }); - test('ships Obsidian Bloom as a schema-v2 full semantic theme', async () => { - const manifest = JSON.parse(await readFile(join(themesRoot, 'obsidian-bloom', 'theme.json'), 'utf8')); - - expect(manifest.schemaVersion).toBe(2); - expect(Object.keys(manifest.palette)).toEqual(SEMANTIC_COLOR_ROLES); - expect(manifest.version).toBe('1.1.0'); - expect(manifest.tags).toContain('full-workspace'); - }); - - test('publishes Limitless Six Eyes metadata for a flagship experience', async () => { + test('publishes English-first Satoru Gojo metadata for a flagship experience', async () => { const catalog = await buildThemeCatalog(themesRoot); - const theme = catalog.find((item) => item.slug === 'limitless-six-eyes'); + const theme = catalog.find((item) => item.slug === 'satoru-gojo'); + expect(theme?.name).toBe('Satoru Gojo'); + expect(theme?.nativeName).toBe('五条 悟'); + expect(theme?.nativeLocale).toBe('ja-JP'); expect(theme?.tags).toContain('featured'); expect(theme?.tags).toContain('full-workspace'); - expect(theme?.license.artwork).toBe('PROTOTYPE-REFERENCE-ONLY'); + expect(theme?.license.artwork).toBe('PROJECT-ASSET'); expect(theme?.experience).toEqual( expect.objectContaining({ brand: 'LIMITLESS', - status: 'LIMITLESS ONLINE', + status: 'LIMITLESS // ACTIVE', chrome: true, }), ); }); - test('keeps the other launch themes on the legacy schema until they are tuned', async () => { - for (const slug of ['arctic-signal', 'paper-circuit', 'solar-archive']) { - const manifest = JSON.parse(await readFile(join(themesRoot, slug, 'theme.json'), 'utf8')); - expect(manifest.schemaVersion, slug).toBe(1); - expect(manifest.version, slug).toBe('1.0.0'); - expect(Object.keys(manifest.palette), slug).toEqual(['background', 'surface', 'text', 'accent']); - expect(manifest.tags, slug).not.toContain('full-workspace'); - } - }); - - test('identifies Obsidian Bloom as a full-workspace theme for the first live check', async () => { + test('keeps exactly one Featured theme and project assets across the collection', async () => { const catalog = await buildThemeCatalog(themesRoot); - const theme = catalog.find((item) => item.slug === 'obsidian-bloom'); - expect(theme?.tags).toContain('full-workspace'); - expect(theme?.compatibility.status).toBe('experimental'); + expect(catalog.filter((theme) => theme.tags.includes('featured')).map((theme) => theme.slug)).toEqual([ + 'satoru-gojo', + ]); + expect(catalog.every((theme) => theme.license.artwork === 'PROJECT-ASSET')).toBe(true); }); test('writes gallery metadata and only the declared local preview assets', async () => { @@ -78,6 +71,9 @@ describe('theme catalog', () => { tempDirs.push(outputRoot); const jsonPath = join(outputRoot, 'generated/themes.json'); const publicRoot = join(outputRoot, 'public'); + const stalePreview = join(publicRoot, 'theme-assets/removed-theme/preview.svg'); + await mkdir(join(publicRoot, 'theme-assets/removed-theme'), { recursive: true }); + await writeFile(stalePreview, ''); const catalog = await writeThemeCatalog({ themesRoot, jsonPath, publicRoot }); const written = JSON.parse(await readFile(jsonPath, 'utf8')); @@ -87,6 +83,7 @@ describe('theme catalog', () => { await expect(access(join(publicRoot, theme.preview))).resolves.toBeUndefined(); expect(theme.preview).not.toMatch(/\.\.|https?:/); } + await expect(access(stalePreview)).rejects.toThrow(); }); test('copies a declared preview from a nested safe package path', async () => { @@ -98,14 +95,21 @@ describe('theme catalog', () => { await writeFile(join(themeRoot, 'theme.json'), JSON.stringify({ schemaVersion: 1, slug: 'nested-preview', - version: '1.0.0', + version: '1.1.0', name: 'Nested Preview', + nativeName: 'プレビュー', + nativeLocale: 'ja-JP', description: 'Tests a nested preview path.', author: { name: 'Tests' }, license: { code: 'MIT', artwork: 'CC0-1.0' }, categories: ['test'], tags: ['nested'], - compatibility: { platforms: ['macos'], status: 'experimental', appVersions: ['26.707.*'] }, + compatibility: { + platforms: ['macos'], + status: 'experimental', + strategy: 'best-effort-all', + verifiedAppVersions: ['26.707.*', '26.715.*'], + }, palette: { background: '#111111', surface: '#222222CC', text: '#FFFFFF', accent: '#00AAFF' }, files: { css: 'theme.css', artwork: 'background.svg', preview: 'assets/preview.svg' }, })); @@ -121,6 +125,8 @@ describe('theme catalog', () => { }); expect(catalog[0].preview).toBe('/theme-assets/nested-preview/preview.svg'); + expect(catalog[0].nativeName).toBe('プレビュー'); + expect(catalog[0].nativeLocale).toBe('ja-JP'); await expect(readFile(join(outputRoot, 'public/theme-assets/nested-preview/preview.svg'), 'utf8')).resolves.toContain(' { + test('ships exactly twelve runnable packages with paired README marketing assets', async () => { + const catalog = await buildThemeCatalog(join(repoRoot, 'themes')); + + expect(catalog).toHaveLength(12); + expect(catalog.filter((theme) => theme.tags.includes('featured')).map((theme) => theme.slug)).toEqual([ + 'satoru-gojo', + ]); + + for (const theme of catalog) { + await expect(access(join(repoRoot, 'themes', theme.slug, 'background.jpg'))).resolves.toBeUndefined(); + await expect(access(join(repoRoot, 'docs', 'gallery', theme.slug, 'marketing.jpg'))).resolves.toBeUndefined(); + } + }); + + test('contains no legacy example catalog or temporary intake slots', async () => { + const roots = (await readdir(repoRoot, { withFileTypes: true })) + .filter((entry) => entry.isDirectory()) + .map((entry) => entry.name); + + expect(roots).not.toContain('examples'); + expect(roots).not.toContain('theme-slots'); + }); +}); diff --git a/tests/themes/foundling-garden.test.mjs b/tests/themes/foundling-garden.test.mjs new file mode 100644 index 0000000..33330a1 --- /dev/null +++ b/tests/themes/foundling-garden.test.mjs @@ -0,0 +1,56 @@ +import { readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { describe, expect, test } from 'vitest'; + +import { SEMANTIC_COLOR_ROLES, loadThemePackage } from '../../src/engine/theme.mjs'; + +const repoRoot = new URL('../..', import.meta.url).pathname; +const themeRoot = join(repoRoot, 'themes/foundling-garden'); + +describe('foundling-garden theme package', () => { + test('loads as a complete experimental Schema 2 package', async () => { + const theme = await loadThemePackage(themeRoot); + expect(theme.manifest.schemaVersion).toBe(2); + expect(theme.manifest.slug).toBe('foundling-garden'); + expect(theme.manifest.version).toBe('0.1.1'); + expect(theme.manifest.mode).toBe('dark'); + expect(theme.manifest.compatibility).toEqual({ + platforms: ['macos'], + status: 'experimental', + strategy: 'best-effort-all', + verifiedAppVersions: ['26.707.*', '26.715.*'], + }); + expect(Object.keys(theme.manifest.palette)).toEqual(SEMANTIC_COLOR_ROLES); + expect(theme.artwork.bytes).toBeLessThanOrEqual(700 * 1024); + }); + + test('declares a bounded original component-asset system', async () => { + const theme = await loadThemePackage(themeRoot); + const assets = Object.entries(theme.assets); + expect(assets.length).toBeGreaterThanOrEqual(5); + expect(assets.length).toBeLessThanOrEqual(16); + expect(theme.manifest.files.assets).toHaveProperty('seed-compass'); + for (const [name, asset] of assets) { + expect(name).toMatch(/^[a-z0-9]+(?:-[a-z0-9]+)*$/); + expect(asset.bytes).toBeLessThanOrEqual(256 * 1024); + expect(asset.dataUrl).toMatch(/^data:image\//); + } + }); + + test('scopes visual rules to this theme and excludes the Satoru Gojo visual language', async () => { + const theme = await loadThemePackage(themeRoot); + expect(theme.css).toContain("html.awesome-codex-theme[data-awesome-codex-theme=\"foundling-garden\"]"); + expect(theme.css).toContain('--batch-theme-signature: "foundling-garden"'); + expect(theme.css).toContain('--act-asset-seed-compass'); + expect(theme.css).not.toMatch(/six-eyes|blindfold|send-infinity|limitless-violet|lavender|satoru-gojo/i); + expect(theme.css).not.toMatch(/(^|[^-])url\s*\(/i); + }); + + test('records project asset sources without an internal-only restriction', async () => { + const notice = await readFile(join(themeRoot, 'ASSET_LICENSE.md'), 'utf8'); + expect(notice).toContain('Asset identifier: `PROJECT-ASSET`'); + expect(notice).not.toMatch(/internal prototype|not approved for public release|must be replaced/i); + expect(notice).toContain('11-grogu-official.png'); + expect(notice).toContain('11-grogu-standard-codex-imagegen-v1.png'); + }); +}); diff --git a/tests/themes/grand-line.test.mjs b/tests/themes/grand-line.test.mjs new file mode 100644 index 0000000..f94b2e1 --- /dev/null +++ b/tests/themes/grand-line.test.mjs @@ -0,0 +1,56 @@ +import { readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { describe, expect, test } from 'vitest'; + +import { SEMANTIC_COLOR_ROLES, loadThemePackage } from '../../src/engine/theme.mjs'; + +const repoRoot = new URL('../..', import.meta.url).pathname; +const themeRoot = join(repoRoot, 'themes/grand-line'); + +describe('grand-line theme package', () => { + test('loads as a complete experimental Schema 2 package', async () => { + const theme = await loadThemePackage(themeRoot); + expect(theme.manifest.schemaVersion).toBe(2); + expect(theme.manifest.slug).toBe('grand-line'); + expect(theme.manifest.version).toBe('0.1.1'); + expect(theme.manifest.mode).toBe('dark'); + expect(theme.manifest.compatibility).toEqual({ + platforms: ['macos'], + status: 'experimental', + strategy: 'best-effort-all', + verifiedAppVersions: ['26.707.*', '26.715.*'], + }); + expect(Object.keys(theme.manifest.palette)).toEqual(SEMANTIC_COLOR_ROLES); + expect(theme.artwork.bytes).toBeLessThanOrEqual(700 * 1024); + }); + + test('declares a bounded original component-asset system', async () => { + const theme = await loadThemePackage(themeRoot); + const assets = Object.entries(theme.assets); + expect(assets.length).toBeGreaterThanOrEqual(5); + expect(assets.length).toBeLessThanOrEqual(16); + expect(theme.manifest.files.assets).toHaveProperty('compass-wedge'); + for (const [name, asset] of assets) { + expect(name).toMatch(/^[a-z0-9]+(?:-[a-z0-9]+)*$/); + expect(asset.bytes).toBeLessThanOrEqual(256 * 1024); + expect(asset.dataUrl).toMatch(/^data:image\//); + } + }); + + test('scopes visual rules to this theme and excludes the Satoru Gojo visual language', async () => { + const theme = await loadThemePackage(themeRoot); + expect(theme.css).toContain("html.awesome-codex-theme[data-awesome-codex-theme=\"grand-line\"]"); + expect(theme.css).toContain('--batch-theme-signature: "grand-line"'); + expect(theme.css).toContain('--act-asset-compass-wedge'); + expect(theme.css).not.toMatch(/six-eyes|blindfold|send-infinity|limitless-violet|lavender|satoru-gojo/i); + expect(theme.css).not.toMatch(/(^|[^-])url\s*\(/i); + }); + + test('records project asset sources without an internal-only restriction', async () => { + const notice = await readFile(join(themeRoot, 'ASSET_LICENSE.md'), 'utf8'); + expect(notice).toContain('Asset identifier: `PROJECT-ASSET`'); + expect(notice).not.toMatch(/internal prototype|not approved for public release|must be replaced/i); + expect(notice).toContain('06-luffy.png'); + expect(notice).toContain('06-luffy-standard-codex-imagegen-v1.png'); + }); +}); diff --git a/tests/themes/limitless-six-eyes.test.mjs b/tests/themes/limitless-six-eyes.test.mjs deleted file mode 100644 index 3ec41cd..0000000 --- a/tests/themes/limitless-six-eyes.test.mjs +++ /dev/null @@ -1,69 +0,0 @@ -import { readFile, stat } from 'node:fs/promises'; -import { join, resolve } from 'node:path'; -import { describe, expect, test } from 'vitest'; - -import { loadThemePackage, SEMANTIC_COLOR_ROLES } from '../../src/engine/theme.mjs'; - -const repoRoot = resolve(import.meta.dirname, '../..'); -const themeRoot = join(repoRoot, 'themes/limitless-six-eyes'); - -describe('Limitless Six Eyes flagship theme', () => { - test('ships a complete schema-v2 experience manifest', async () => { - const loaded = await loadThemePackage(themeRoot); - - expect(loaded.manifest.slug).toBe('limitless-six-eyes'); - expect(loaded.manifest.mode).toBe('light'); - expect(Object.keys(loaded.manifest.palette)).toEqual(SEMANTIC_COLOR_ROLES); - expect(loaded.manifest.tags).toEqual( - expect.arrayContaining(['featured', 'full-workspace', 'composer', 'editorial']), - ); - expect(loaded.manifest.experience).toEqual({ - brand: 'LIMITLESS', - eyebrow: 'SIX EYES', - headline: 'LIMITLESS WORKSPACE', - tagline: 'Plan beyond the visible.', - status: 'LIMITLESS ONLINE', - signature: 'SATORU GOJO', - chrome: true, - }); - }); - - test('covers the native workspace, home, interaction, code, and ambient layers', async () => { - const css = await readFile(join(themeRoot, 'theme.css'), 'utf8'); - - for (const marker of [ - '.app-shell-left-panel', - '[data-awesome-codex-home="true"]', - '[data-feature="game-source"]', - '[role="menu"]', - 'input', - ':hover', - ':focus-visible', - '::selection', - '.monaco-editor', - '.composer-root', - '::-webkit-scrollbar-thumb', - '.act-experience__brand', - '.act-experience__orbit', - '.act-experience__particle', - '@media (prefers-reduced-motion: reduce)', - ]) { - expect(css, marker).toContain(marker); - } - }); - - test('keeps prototype artwork outside the repository MIT grant', async () => { - const [license, backgroundInfo, previewInfo] = await Promise.all([ - readFile(join(themeRoot, 'ASSET_LICENSE.md'), 'utf8'), - stat(join(themeRoot, 'background.jpg')), - stat(join(themeRoot, 'preview.png')), - ]); - - expect(license).toContain('PROTOTYPE-REFERENCE-ONLY'); - expect(license).toMatch(/not covered by the repository MIT license/i); - expect(license).toMatch(/must be replaced/i); - expect(backgroundInfo.size).toBeGreaterThan(100_000); - expect(backgroundInfo.size).toBeLessThan(700 * 1024); - expect(previewInfo.size).toBeGreaterThan(100_000); - }); -}); diff --git a/tests/themes/mordor-runtime.test.mjs b/tests/themes/mordor-runtime.test.mjs new file mode 100644 index 0000000..fec304c --- /dev/null +++ b/tests/themes/mordor-runtime.test.mjs @@ -0,0 +1,56 @@ +import { readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { describe, expect, test } from 'vitest'; + +import { SEMANTIC_COLOR_ROLES, loadThemePackage } from '../../src/engine/theme.mjs'; + +const repoRoot = new URL('../..', import.meta.url).pathname; +const themeRoot = join(repoRoot, 'themes/mordor-runtime'); + +describe('mordor-runtime theme package', () => { + test('loads as a complete experimental Schema 2 package', async () => { + const theme = await loadThemePackage(themeRoot); + expect(theme.manifest.schemaVersion).toBe(2); + expect(theme.manifest.slug).toBe('mordor-runtime'); + expect(theme.manifest.version).toBe('0.1.1'); + expect(theme.manifest.mode).toBe('dark'); + expect(theme.manifest.compatibility).toEqual({ + platforms: ['macos'], + status: 'experimental', + strategy: 'best-effort-all', + verifiedAppVersions: ['26.707.*', '26.715.*'], + }); + expect(Object.keys(theme.manifest.palette)).toEqual(SEMANTIC_COLOR_ROLES); + expect(theme.artwork.bytes).toBeLessThanOrEqual(700 * 1024); + }); + + test('declares a bounded original component-asset system', async () => { + const theme = await loadThemePackage(themeRoot); + const assets = Object.entries(theme.assets); + expect(assets.length).toBeGreaterThanOrEqual(5); + expect(assets.length).toBeLessThanOrEqual(16); + expect(theme.manifest.files.assets).toHaveProperty('forge-notch'); + for (const [name, asset] of assets) { + expect(name).toMatch(/^[a-z0-9]+(?:-[a-z0-9]+)*$/); + expect(asset.bytes).toBeLessThanOrEqual(256 * 1024); + expect(asset.dataUrl).toMatch(/^data:image\//); + } + }); + + test('scopes visual rules to this theme and excludes the Satoru Gojo visual language', async () => { + const theme = await loadThemePackage(themeRoot); + expect(theme.css).toContain("html.awesome-codex-theme[data-awesome-codex-theme=\"mordor-runtime\"]"); + expect(theme.css).toContain('--batch-theme-signature: "mordor-runtime"'); + expect(theme.css).toContain('--act-asset-forge-notch'); + expect(theme.css).not.toMatch(/six-eyes|blindfold|send-infinity|limitless-violet|lavender|satoru-gojo/i); + expect(theme.css).not.toMatch(/(^|[^-])url\s*\(/i); + }); + + test('records project asset sources without an internal-only restriction', async () => { + const notice = await readFile(join(themeRoot, 'ASSET_LICENSE.md'), 'utf8'); + expect(notice).toContain('Asset identifier: `PROJECT-ASSET`'); + expect(notice).not.toMatch(/internal prototype|not approved for public release|must be replaced/i); + expect(notice).toContain('09-nazgul-ringwraiths.png'); + expect(notice).toContain('09-nazgul-standard-codex-imagegen-v1.png'); + }); +}); diff --git a/tests/themes/new-world-studio.test.mjs b/tests/themes/new-world-studio.test.mjs new file mode 100644 index 0000000..63b749a --- /dev/null +++ b/tests/themes/new-world-studio.test.mjs @@ -0,0 +1,56 @@ +import { readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { describe, expect, test } from 'vitest'; + +import { SEMANTIC_COLOR_ROLES, loadThemePackage } from '../../src/engine/theme.mjs'; + +const repoRoot = new URL('../..', import.meta.url).pathname; +const themeRoot = join(repoRoot, 'themes/new-world-studio'); + +describe('new-world-studio theme package', () => { + test('loads as a complete experimental Schema 2 package', async () => { + const theme = await loadThemePackage(themeRoot); + expect(theme.manifest.schemaVersion).toBe(2); + expect(theme.manifest.slug).toBe('new-world-studio'); + expect(theme.manifest.version).toBe('0.1.1'); + expect(theme.manifest.mode).toBe('light'); + expect(theme.manifest.compatibility).toEqual({ + platforms: ['macos'], + status: 'experimental', + strategy: 'best-effort-all', + verifiedAppVersions: ['26.707.*', '26.715.*'], + }); + expect(Object.keys(theme.manifest.palette)).toEqual(SEMANTIC_COLOR_ROLES); + expect(theme.artwork.bytes).toBeLessThanOrEqual(700 * 1024); + }); + + test('declares a bounded original component-asset system', async () => { + const theme = await loadThemePackage(themeRoot); + const assets = Object.entries(theme.assets); + expect(assets.length).toBeGreaterThanOrEqual(5); + expect(assets.length).toBeLessThanOrEqual(16); + expect(theme.manifest.files.assets).toHaveProperty('tidal-contour'); + for (const [name, asset] of assets) { + expect(name).toMatch(/^[a-z0-9]+(?:-[a-z0-9]+)*$/); + expect(asset.bytes).toBeLessThanOrEqual(256 * 1024); + expect(asset.dataUrl).toMatch(/^data:image\//); + } + }); + + test('scopes visual rules to this theme and excludes the Satoru Gojo visual language', async () => { + const theme = await loadThemePackage(themeRoot); + expect(theme.css).toContain("html.awesome-codex-theme[data-awesome-codex-theme=\"new-world-studio\"]"); + expect(theme.css).toContain('--batch-theme-signature: "new-world-studio"'); + expect(theme.css).toContain('--act-asset-tidal-contour'); + expect(theme.css).not.toMatch(/six-eyes|blindfold|send-infinity|limitless-violet|lavender|satoru-gojo/i); + expect(theme.css).not.toMatch(/(^|[^-])url\s*\(/i); + }); + + test('records project asset sources without an internal-only restriction', async () => { + const notice = await readFile(join(themeRoot, 'ASSET_LICENSE.md'), 'utf8'); + expect(notice).toContain('Asset identifier: `PROJECT-ASSET`'); + expect(notice).not.toMatch(/internal prototype|not approved for public release|must be replaced/i); + expect(notice).toContain('12-one-piece-women.png'); + expect(notice).toContain('12-one-piece-women-standard-codex-imagegen-v1.png'); + }); +}); diff --git a/tests/themes/night-city.test.mjs b/tests/themes/night-city.test.mjs new file mode 100644 index 0000000..6775916 --- /dev/null +++ b/tests/themes/night-city.test.mjs @@ -0,0 +1,56 @@ +import { readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { describe, expect, test } from 'vitest'; + +import { SEMANTIC_COLOR_ROLES, loadThemePackage } from '../../src/engine/theme.mjs'; + +const repoRoot = new URL('../..', import.meta.url).pathname; +const themeRoot = join(repoRoot, 'themes/night-city'); + +describe('night-city theme package', () => { + test('loads as a complete experimental Schema 2 package', async () => { + const theme = await loadThemePackage(themeRoot); + expect(theme.manifest.schemaVersion).toBe(2); + expect(theme.manifest.slug).toBe('night-city'); + expect(theme.manifest.version).toBe('0.1.1'); + expect(theme.manifest.mode).toBe('dark'); + expect(theme.manifest.compatibility).toEqual({ + platforms: ['macos'], + status: 'experimental', + strategy: 'best-effort-all', + verifiedAppVersions: ['26.707.*', '26.715.*'], + }); + expect(Object.keys(theme.manifest.palette)).toEqual(SEMANTIC_COLOR_ROLES); + expect(theme.artwork.bytes).toBeLessThanOrEqual(700 * 1024); + }); + + test('declares a bounded original component-asset system', async () => { + const theme = await loadThemePackage(themeRoot); + const assets = Object.entries(theme.assets); + expect(assets.length).toBeGreaterThanOrEqual(5); + expect(assets.length).toBeLessThanOrEqual(16); + expect(theme.manifest.files.assets).toHaveProperty('lunar-phase'); + for (const [name, asset] of assets) { + expect(name).toMatch(/^[a-z0-9]+(?:-[a-z0-9]+)*$/); + expect(asset.bytes).toBeLessThanOrEqual(256 * 1024); + expect(asset.dataUrl).toMatch(/^data:image\//); + } + }); + + test('scopes visual rules to this theme and excludes the Satoru Gojo visual language', async () => { + const theme = await loadThemePackage(themeRoot); + expect(theme.css).toContain("html.awesome-codex-theme[data-awesome-codex-theme=\"night-city\"]"); + expect(theme.css).toContain('--batch-theme-signature: "night-city"'); + expect(theme.css).toContain('--act-asset-lunar-phase'); + expect(theme.css).not.toMatch(/six-eyes|blindfold|send-infinity|limitless-violet|lavender|satoru-gojo/i); + expect(theme.css).not.toMatch(/(^|[^-])url\s*\(/i); + }); + + test('records project asset sources without an internal-only restriction', async () => { + const notice = await readFile(join(themeRoot, 'ASSET_LICENSE.md'), 'utf8'); + expect(notice).toContain('Asset identifier: `PROJECT-ASSET`'); + expect(notice).not.toMatch(/internal prototype|not approved for public release|must be replaced/i); + expect(notice).toContain('10-lucy.png'); + expect(notice).toContain('10-lucy-standard-codex-imagegen-v1.png'); + }); +}); diff --git a/tests/themes/overworld-realms.test.mjs b/tests/themes/overworld-realms.test.mjs new file mode 100644 index 0000000..2b9ae7d --- /dev/null +++ b/tests/themes/overworld-realms.test.mjs @@ -0,0 +1,56 @@ +import { readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { describe, expect, test } from 'vitest'; + +import { SEMANTIC_COLOR_ROLES, loadThemePackage } from '../../src/engine/theme.mjs'; + +const repoRoot = new URL('../..', import.meta.url).pathname; +const themeRoot = join(repoRoot, 'themes/overworld-realms'); + +describe('overworld-realms theme package', () => { + test('loads as a complete experimental Schema 2 package', async () => { + const theme = await loadThemePackage(themeRoot); + expect(theme.manifest.schemaVersion).toBe(2); + expect(theme.manifest.slug).toBe('overworld-realms'); + expect(theme.manifest.version).toBe('0.1.1'); + expect(theme.manifest.mode).toBe('dark'); + expect(theme.manifest.compatibility).toEqual({ + platforms: ['macos'], + status: 'experimental', + strategy: 'best-effort-all', + verifiedAppVersions: ['26.707.*', '26.715.*'], + }); + expect(Object.keys(theme.manifest.palette)).toEqual(SEMANTIC_COLOR_ROLES); + expect(theme.artwork.bytes).toBeLessThanOrEqual(700 * 1024); + }); + + test('declares a bounded original component-asset system', async () => { + const theme = await loadThemePackage(themeRoot); + const assets = Object.entries(theme.assets); + expect(assets.length).toBeGreaterThanOrEqual(5); + expect(assets.length).toBeLessThanOrEqual(16); + expect(theme.manifest.files.assets).toHaveProperty('voxel-cube'); + for (const [name, asset] of assets) { + expect(name).toMatch(/^[a-z0-9]+(?:-[a-z0-9]+)*$/); + expect(asset.bytes).toBeLessThanOrEqual(256 * 1024); + expect(asset.dataUrl).toMatch(/^data:image\//); + } + }); + + test('scopes visual rules to this theme and excludes the Satoru Gojo visual language', async () => { + const theme = await loadThemePackage(themeRoot); + expect(theme.css).toContain("html.awesome-codex-theme[data-awesome-codex-theme=\"overworld-realms\"]"); + expect(theme.css).toContain('--batch-theme-signature: "overworld-realms"'); + expect(theme.css).toContain('--act-asset-voxel-cube'); + expect(theme.css).not.toMatch(/six-eyes|blindfold|send-infinity|limitless-violet|lavender|satoru-gojo/i); + expect(theme.css).not.toMatch(/(^|[^-])url\s*\(/i); + }); + + test('records project asset sources without an internal-only restriction', async () => { + const notice = await readFile(join(themeRoot, 'ASSET_LICENSE.md'), 'utf8'); + expect(notice).toContain('Asset identifier: `PROJECT-ASSET`'); + expect(notice).not.toMatch(/internal prototype|not approved for public release|must be replaced/i); + expect(notice).toContain('08-minecraft-realms.png'); + expect(notice).toContain('08-minecraft-standard-codex-imagegen-v1.png'); + }); +}); diff --git a/tests/themes/saiyan-ukiyoe.test.mjs b/tests/themes/saiyan-ukiyoe.test.mjs new file mode 100644 index 0000000..ffe459b --- /dev/null +++ b/tests/themes/saiyan-ukiyoe.test.mjs @@ -0,0 +1,56 @@ +import { readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { describe, expect, test } from 'vitest'; + +import { SEMANTIC_COLOR_ROLES, loadThemePackage } from '../../src/engine/theme.mjs'; + +const repoRoot = new URL('../..', import.meta.url).pathname; +const themeRoot = join(repoRoot, 'themes/saiyan-ukiyoe'); + +describe('saiyan-ukiyoe theme package', () => { + test('loads as a complete experimental Schema 2 package', async () => { + const theme = await loadThemePackage(themeRoot); + expect(theme.manifest.schemaVersion).toBe(2); + expect(theme.manifest.slug).toBe('saiyan-ukiyoe'); + expect(theme.manifest.version).toBe('0.1.1'); + expect(theme.manifest.mode).toBe('light'); + expect(theme.manifest.compatibility).toEqual({ + platforms: ['macos'], + status: 'experimental', + strategy: 'best-effort-all', + verifiedAppVersions: ['26.707.*', '26.715.*'], + }); + expect(Object.keys(theme.manifest.palette)).toEqual(SEMANTIC_COLOR_ROLES); + expect(theme.artwork.bytes).toBeLessThanOrEqual(700 * 1024); + }); + + test('declares a bounded original component-asset system', async () => { + const theme = await loadThemePackage(themeRoot); + const assets = Object.entries(theme.assets); + expect(assets.length).toBeGreaterThanOrEqual(5); + expect(assets.length).toBeLessThanOrEqual(16); + expect(theme.manifest.files.assets).toHaveProperty('brush-sun'); + for (const [name, asset] of assets) { + expect(name).toMatch(/^[a-z0-9]+(?:-[a-z0-9]+)*$/); + expect(asset.bytes).toBeLessThanOrEqual(256 * 1024); + expect(asset.dataUrl).toMatch(/^data:image\//); + } + }); + + test('scopes visual rules to this theme and excludes the Satoru Gojo visual language', async () => { + const theme = await loadThemePackage(themeRoot); + expect(theme.css).toContain("html.awesome-codex-theme[data-awesome-codex-theme=\"saiyan-ukiyoe\"]"); + expect(theme.css).toContain('--batch-theme-signature: "saiyan-ukiyoe"'); + expect(theme.css).toContain('--act-asset-brush-sun'); + expect(theme.css).not.toMatch(/six-eyes|blindfold|send-infinity|limitless-violet|lavender|satoru-gojo/i); + expect(theme.css).not.toMatch(/(^|[^-])url\s*\(/i); + }); + + test('records project asset sources without an internal-only restriction', async () => { + const notice = await readFile(join(themeRoot, 'ASSET_LICENSE.md'), 'utf8'); + expect(notice).toContain('Asset identifier: `PROJECT-ASSET`'); + expect(notice).not.toMatch(/internal prototype|not approved for public release|must be replaced/i); + expect(notice).toContain('05-son-goku-ukiyoe.png'); + expect(notice).toContain('05-goku-standard-codex-imagegen-v1.png'); + }); +}); diff --git a/tests/themes/satoru-gojo.test.mjs b/tests/themes/satoru-gojo.test.mjs new file mode 100644 index 0000000..a70b655 --- /dev/null +++ b/tests/themes/satoru-gojo.test.mjs @@ -0,0 +1,221 @@ +import { readFile, stat } from 'node:fs/promises'; +import { join, resolve } from 'node:path'; +import { describe, expect, test } from 'vitest'; + +import { loadThemePackage, SEMANTIC_COLOR_ROLES } from '../../src/engine/theme.mjs'; + +const repoRoot = resolve(import.meta.dirname, '../..'); +const themeRoot = join(repoRoot, 'themes/satoru-gojo'); + +describe('Satoru Gojo flagship theme', () => { + test('ships a complete schema-v2 experience manifest', async () => { + const loaded = await loadThemePackage(themeRoot); + + expect(loaded.manifest.slug).toBe('satoru-gojo'); + expect(loaded.manifest.version).toBe('1.2.0'); + expect(loaded.manifest.name).toBe('Satoru Gojo'); + expect(loaded.manifest.nativeName).toBe('五条 悟'); + expect(loaded.manifest.nativeLocale).toBe('ja-JP'); + expect(loaded.manifest.mode).toBe('light'); + expect(loaded.manifest.compatibility.strategy).toBe('best-effort-all'); + expect(loaded.manifest.compatibility.verifiedAppVersions).toEqual(['26.707.*', '26.715.*']); + expect(Object.keys(loaded.manifest.palette)).toEqual(SEMANTIC_COLOR_ROLES); + expect(loaded.manifest.tags).toEqual( + expect.arrayContaining(['featured', 'full-workspace', 'composer', 'editorial']), + ); + expect(loaded.manifest.experience).toEqual({ + brand: 'LIMITLESS', + eyebrow: 'SIX EYES', + headline: 'CODEX THEME SYSTEM', + tagline: 'Editorial workspace protocol.', + status: 'LIMITLESS // ACTIVE', + signature: 'SATORU GOJO // 五条 悟', + chrome: true, + }); + expect(loaded.manifest.files.assets).toEqual({ + 'six-eyes-emblem': 'assets/six-eyes-emblem.png', + 'send-infinity': 'assets/send-infinity.png', + 'panel-reticle': 'assets/panel-reticle.png', + 'blindfold-stripe': 'assets/blindfold-stripe.png', + 'six-dot-sequence': 'assets/six-dot-sequence.png', + 'workspace-panel-safe': 'assets/workspace-panel-safe.jpg', + }); + expect(Object.keys(loaded.assets)).toEqual([ + 'six-eyes-emblem', + 'send-infinity', + 'panel-reticle', + 'blindfold-stripe', + 'six-dot-sequence', + 'workspace-panel-safe', + ]); + }); + + test('covers the native workspace, home, interaction, code, and ambient layers', async () => { + const css = await readFile(join(themeRoot, 'theme.css'), 'utf8'); + + for (const marker of [ + '.app-shell-left-panel', + '[data-awesome-codex-home="true"]', + '[data-feature="game-source"]', + '[role="menu"]', + 'input', + ':hover', + ':focus-visible', + '::selection', + '.monaco-editor', + '.composer-root', + '::-webkit-scrollbar-thumb', + '[data-awesome-codex-artifact-panel="true"]', + '[data-awesome-codex-artifact-header="true"]', + '[data-awesome-codex-panel-more="true"]', + '[data-awesome-codex-completed-summary="true"]', + '[data-awesome-codex-activity-item="true"]', + '[data-awesome-codex-header-control="true"]', + '[data-awesome-codex-project-row="true"]', + '[data-awesome-codex-task-row="true"]', + '[data-awesome-codex-sidebar-brand="true"]', + '[data-awesome-codex-sidebar-nav]', + '[data-awesome-codex-domain-header="true"]', + '[data-awesome-codex-domain-id]', + '[data-awesome-codex-account-trigger="true"]', + '[data-awesome-codex-account-identity="true"]', + '[data-awesome-codex-help-trigger="true"]', + '[data-awesome-codex-home-prompt="true"]', + '[data-awesome-codex-domain-marker="true"]', + '[data-awesome-codex-composer-control="attachment"]', + '[data-awesome-codex-composer-control="access"]', + '[data-awesome-codex-composer-control="model"]', + '[data-awesome-codex-composer-control="dictation"]', + '[data-awesome-codex-composer-control="primary"]', + '[data-awesome-codex-surface="plugins"]', + '[data-awesome-codex-plugin-search="true"]', + '[data-awesome-codex-plugin-tab="true"]', + '[data-awesome-codex-plugin-section-header="true"]', + '[data-awesome-codex-plugin-installed="true"]', + '[data-awesome-codex-plugin-card="true"]', + '[data-awesome-codex-plugin-icon="true"]', + '[data-awesome-codex-plugin-action="menu"]', + '[data-awesome-codex-plugin-action="install"]', + '[data-awesome-codex-surface="scheduled"]', + '[data-awesome-codex-surface="sites"]', + '[data-awesome-codex-surface="pull-requests"]', + '[data-awesome-codex-collection-search="true"]', + '[data-awesome-codex-collection-tab="true"]', + '[data-awesome-codex-collection-row="true"]', + '[data-awesome-codex-collection-empty="true"]', + '[data-awesome-codex-collection-status="true"]', + '[data-awesome-codex-collection-action="primary"]', + '--act-asset-six-eyes-emblem', + '--act-asset-send-infinity', + '--act-asset-panel-reticle', + '--act-asset-blindfold-stripe', + '--act-asset-six-dot-sequence', + '--act-asset-workspace-panel-safe', + 'DOMAIN CONTROL DECK // SIX EYES', + 'LIMITLESS // SIX EYES', + '[data-awesome-codex-surface="workspace"] #awesome-codex-theme-chrome .act-experience__status', + '.act-experience__brand', + '.act-experience__orbit', + '.act-experience__particle', + '@media (prefers-reduced-motion: reduce)', + ]) { + expect(css, marker).toContain(marker); + } + }); + + test('keeps the flagship artwork and ice-blue atmosphere visible in active workspaces', async () => { + const css = await readFile(join(themeRoot, 'theme.css'), 'utf8'); + + for (const marker of [ + '[data-awesome-codex-surface="workspace"] #root', + '--limitless-workspace-art-opacity', + 'var(--act-artwork)', + '[data-awesome-codex-surface="workspace"] .thread-scroll-container', + '[data-awesome-codex-surface="workspace"] .composer-surface-chrome', + '--limitless-radius-card', + 'html.electron-dark.awesome-codex-theme', + ]) { + expect(css, marker).toContain(marker); + } + + expect(css).not.toContain('.app-shell-main-content-top-fade,\n header'); + expect(css).not.toContain('.dialog-layout,\n .bg-token-dropdown-background'); + expect(css).not.toContain('background-blend-mode'); + expect(css).toContain( + '[class*="bg-token-foreground/5"]:not([data-awesome-codex-header-control="true"])', + ); + expect(css).not.toMatch( + /:is\(\s*button,\s*\[role="button"\],\s*a\s*\):hover\s*\{[^}]*border-color/s, + ); + expect(css).toMatch( + /\[data-awesome-codex-plugin-card="true"\][^{]*\{[^}]*border:\s*0\s*!important/s, + ); + expect(css).toMatch( + /\[data-awesome-codex-composer-control="access"\]::before\s*\{[^}]*border:\s*0\s*!important/s, + ); + expect(css).toMatch( + /\.act-experience__status\s*\{[^}]*border-radius:\s*0\s*;/s, + ); + expect(css).toMatch( + /\[data-awesome-codex-artifact-panel="true"\]\s*\{[^}]*border:\s*0\s*!important/s, + ); + expect(css).toMatch( + /\[data-awesome-codex-domain-marker="true"\]\s*\{[^}]*margin-inline:\s*0\.16em/s, + ); + expect(css).toMatch( + /\[data-awesome-codex-sidebar-brand="true"\]\s*\{[^}]*var\(--act-asset-six-eyes-emblem\)/s, + ); + expect(css).toMatch( + /\[data-awesome-codex-account-identity="true"\]\s*\{[^}]*var\(--act-asset-six-dot-sequence\)/s, + ); + expect(css).toMatch( + /:has\(\[data-awesome-codex-artifact-panel="true"\]\)\s*#root::before\s*\{[^}]*background-image:\s*var\(--act-asset-workspace-panel-safe\)\s*!important;[^}]*background-position:\s*center\s*!important;[^}]*background-size:\s*cover\s*!important;/s, + ); + expect(css).toMatch( + /\[data-awesome-codex-domain-header="true"\]\s*>\s*svg\s*\{[^}]*width:\s*11px\s*!important;[^}]*margin-left:\s*2px\s*!important;/s, + ); + expect(css).toMatch( + /\[data-awesome-codex-task-row="true"\]:has\(\.animate-spin\)::after\s*\{[^}]*display:\s*none;/s, + ); + expect(css).toMatch( + /\[data-awesome-codex-domain-marker="true"\]\s*\{[^}]*margin-inline:\s*0\.16em\s*!important;[^}]*padding:\s*0\.06em 0\.32em 0\.06em 0\.58em\s*!important;/s, + ); + expect(css).toContain('background-size: auto, auto 205%'); + expect(css).not.toMatch( + /\[class~="bg-token-list-hover-background"\]\s*\{[^}]*var\(--act-asset-blindfold-stripe\)/s, + ); + }); + + test('ships bounded transparent component assets for the editorial system', async () => { + const loaded = await loadThemePackage(themeRoot); + + for (const [name, asset] of Object.entries(loaded.assets)) { + expect(asset.mime, name).toMatch(/^image\/(?:png|jpeg)$/); + expect(asset.bytes, name).toBeGreaterThan(1_000); + expect(asset.bytes, name).toBeLessThan(256 * 1024); + expect(asset.dataUrl, name).toMatch(/^data:image\/(?:png|jpeg);base64,/); + } + }); + + test('ships the approved public project artwork without prototype restrictions', async () => { + const [license, backgroundInfo, previewInfo] = await Promise.all([ + readFile(join(themeRoot, 'ASSET_LICENSE.md'), 'utf8'), + stat(join(themeRoot, 'background.jpg')), + stat(join(themeRoot, 'preview.png')), + ]); + + expect(license).toContain('PROJECT-ASSET'); + expect(license).not.toMatch(/private fan|prototype-reference-only|not covered|must be replaced|not offered/i); + expect(backgroundInfo.size).toBeGreaterThan(100_000); + expect(backgroundInfo.size).toBeLessThan(700 * 1024); + expect(previewInfo.size).toBeGreaterThan(100_000); + }); + + test('keeps internal iteration labels out of public package text', async () => { + const loaded = await loadThemePackage(themeRoot); + const license = await readFile(join(themeRoot, 'ASSET_LICENSE.md'), 'utf8'); + const publicText = JSON.stringify(loaded.manifest) + license; + + expect(publicText).not.toMatch(/\bV5(?:\.6)?\b/i); + }); +}); diff --git a/tests/themes/slingshot-lab.test.mjs b/tests/themes/slingshot-lab.test.mjs new file mode 100644 index 0000000..a7b49c3 --- /dev/null +++ b/tests/themes/slingshot-lab.test.mjs @@ -0,0 +1,56 @@ +import { readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { describe, expect, test } from 'vitest'; + +import { SEMANTIC_COLOR_ROLES, loadThemePackage } from '../../src/engine/theme.mjs'; + +const repoRoot = new URL('../..', import.meta.url).pathname; +const themeRoot = join(repoRoot, 'themes/slingshot-lab'); + +describe('slingshot-lab theme package', () => { + test('loads as a complete experimental Schema 2 package', async () => { + const theme = await loadThemePackage(themeRoot); + expect(theme.manifest.schemaVersion).toBe(2); + expect(theme.manifest.slug).toBe('slingshot-lab'); + expect(theme.manifest.version).toBe('0.1.1'); + expect(theme.manifest.mode).toBe('light'); + expect(theme.manifest.compatibility).toEqual({ + platforms: ['macos'], + status: 'experimental', + strategy: 'best-effort-all', + verifiedAppVersions: ['26.707.*', '26.715.*'], + }); + expect(Object.keys(theme.manifest.palette)).toEqual(SEMANTIC_COLOR_ROLES); + expect(theme.artwork.bytes).toBeLessThanOrEqual(700 * 1024); + }); + + test('declares a bounded original component-asset system', async () => { + const theme = await loadThemePackage(themeRoot); + const assets = Object.entries(theme.assets); + expect(assets.length).toBeGreaterThanOrEqual(5); + expect(assets.length).toBeLessThanOrEqual(16); + expect(theme.manifest.files.assets).toHaveProperty('forked-slingshot'); + for (const [name, asset] of assets) { + expect(name).toMatch(/^[a-z0-9]+(?:-[a-z0-9]+)*$/); + expect(asset.bytes).toBeLessThanOrEqual(256 * 1024); + expect(asset.dataUrl).toMatch(/^data:image\//); + } + }); + + test('scopes visual rules to this theme and excludes the Satoru Gojo visual language', async () => { + const theme = await loadThemePackage(themeRoot); + expect(theme.css).toContain("html.awesome-codex-theme[data-awesome-codex-theme=\"slingshot-lab\"]"); + expect(theme.css).toContain('--batch-theme-signature: "slingshot-lab"'); + expect(theme.css).toContain('--act-asset-forked-slingshot'); + expect(theme.css).not.toMatch(/six-eyes|blindfold|send-infinity|limitless-violet|lavender|satoru-gojo/i); + expect(theme.css).not.toMatch(/(^|[^-])url\s*\(/i); + }); + + test('records project asset sources without an internal-only restriction', async () => { + const notice = await readFile(join(themeRoot, 'ASSET_LICENSE.md'), 'utf8'); + expect(notice).toContain('Asset identifier: `PROJECT-ASSET`'); + expect(notice).not.toMatch(/internal prototype|not approved for public release|must be replaced/i); + expect(notice).toContain('04-angry-birds.png'); + expect(notice).toContain('04-angry-birds-standard-codex-imagegen-v1.png'); + }); +}); diff --git a/tests/themes/symbiote-sumi-e.test.mjs b/tests/themes/symbiote-sumi-e.test.mjs new file mode 100644 index 0000000..d657536 --- /dev/null +++ b/tests/themes/symbiote-sumi-e.test.mjs @@ -0,0 +1,56 @@ +import { readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { describe, expect, test } from 'vitest'; + +import { SEMANTIC_COLOR_ROLES, loadThemePackage } from '../../src/engine/theme.mjs'; + +const repoRoot = new URL('../..', import.meta.url).pathname; +const themeRoot = join(repoRoot, 'themes/symbiote-sumi-e'); + +describe('symbiote-sumi-e theme package', () => { + test('loads as a complete experimental Schema 2 package', async () => { + const theme = await loadThemePackage(themeRoot); + expect(theme.manifest.schemaVersion).toBe(2); + expect(theme.manifest.slug).toBe('symbiote-sumi-e'); + expect(theme.manifest.version).toBe('0.1.1'); + expect(theme.manifest.mode).toBe('light'); + expect(theme.manifest.compatibility).toEqual({ + platforms: ['macos'], + status: 'experimental', + strategy: 'best-effort-all', + verifiedAppVersions: ['26.707.*', '26.715.*'], + }); + expect(Object.keys(theme.manifest.palette)).toEqual(SEMANTIC_COLOR_ROLES); + expect(theme.artwork.bytes).toBeLessThanOrEqual(700 * 1024); + }); + + test('declares a bounded original component-asset system', async () => { + const theme = await loadThemePackage(themeRoot); + const assets = Object.entries(theme.assets); + expect(assets.length).toBeGreaterThanOrEqual(5); + expect(assets.length).toBeLessThanOrEqual(16); + expect(theme.manifest.files.assets).toHaveProperty('ink-drop-mon'); + for (const [name, asset] of assets) { + expect(name).toMatch(/^[a-z0-9]+(?:-[a-z0-9]+)*$/); + expect(asset.bytes).toBeLessThanOrEqual(256 * 1024); + expect(asset.dataUrl).toMatch(/^data:image\//); + } + }); + + test('scopes visual rules to this theme and excludes the Satoru Gojo visual language', async () => { + const theme = await loadThemePackage(themeRoot); + expect(theme.css).toContain("html.awesome-codex-theme[data-awesome-codex-theme=\"symbiote-sumi-e\"]"); + expect(theme.css).toContain('--batch-theme-signature: "symbiote-sumi-e"'); + expect(theme.css).toContain('--act-asset-ink-drop-mon'); + expect(theme.css).not.toMatch(/six-eyes|blindfold|send-infinity|limitless-violet|lavender|satoru-gojo/i); + expect(theme.css).not.toMatch(/(^|[^-])url\s*\(/i); + }); + + test('records project asset sources without an internal-only restriction', async () => { + const notice = await readFile(join(themeRoot, 'ASSET_LICENSE.md'), 'utf8'); + expect(notice).toContain('Asset identifier: `PROJECT-ASSET`'); + expect(notice).not.toMatch(/internal prototype|not approved for public release|must be replaced/i); + expect(notice).toContain('03-venom-sumi-e.png'); + expect(notice).toContain('03-venom-standard-codex-imagegen-v1.png'); + }); +}); diff --git a/tests/themes/zaun-workshop.test.mjs b/tests/themes/zaun-workshop.test.mjs new file mode 100644 index 0000000..c0cab97 --- /dev/null +++ b/tests/themes/zaun-workshop.test.mjs @@ -0,0 +1,56 @@ +import { readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { describe, expect, test } from 'vitest'; + +import { SEMANTIC_COLOR_ROLES, loadThemePackage } from '../../src/engine/theme.mjs'; + +const repoRoot = new URL('../..', import.meta.url).pathname; +const themeRoot = join(repoRoot, 'themes/zaun-workshop'); + +describe('zaun-workshop theme package', () => { + test('loads as a complete experimental Schema 2 package', async () => { + const theme = await loadThemePackage(themeRoot); + expect(theme.manifest.schemaVersion).toBe(2); + expect(theme.manifest.slug).toBe('zaun-workshop'); + expect(theme.manifest.version).toBe('0.1.1'); + expect(theme.manifest.mode).toBe('dark'); + expect(theme.manifest.compatibility).toEqual({ + platforms: ['macos'], + status: 'experimental', + strategy: 'best-effort-all', + verifiedAppVersions: ['26.707.*', '26.715.*'], + }); + expect(Object.keys(theme.manifest.palette)).toEqual(SEMANTIC_COLOR_ROLES); + expect(theme.artwork.bytes).toBeLessThanOrEqual(700 * 1024); + }); + + test('declares a bounded original component-asset system', async () => { + const theme = await loadThemePackage(themeRoot); + const assets = Object.entries(theme.assets); + expect(assets.length).toBeGreaterThanOrEqual(5); + expect(assets.length).toBeLessThanOrEqual(16); + expect(theme.manifest.files.assets).toHaveProperty('fractured-hex-mark'); + for (const [name, asset] of assets) { + expect(name).toMatch(/^[a-z0-9]+(?:-[a-z0-9]+)*$/); + expect(asset.bytes).toBeLessThanOrEqual(256 * 1024); + expect(asset.dataUrl).toMatch(/^data:image\//); + } + }); + + test('scopes visual rules to this theme and excludes the Satoru Gojo visual language', async () => { + const theme = await loadThemePackage(themeRoot); + expect(theme.css).toContain("html.awesome-codex-theme[data-awesome-codex-theme=\"zaun-workshop\"]"); + expect(theme.css).toContain('--batch-theme-signature: "zaun-workshop"'); + expect(theme.css).toContain('--act-asset-fractured-hex-mark'); + expect(theme.css).not.toMatch(/six-eyes|blindfold|send-infinity|limitless-violet|lavender|satoru-gojo/i); + expect(theme.css).not.toMatch(/(^|[^-])url\s*\(/i); + }); + + test('records project asset sources without an internal-only restriction', async () => { + const notice = await readFile(join(themeRoot, 'ASSET_LICENSE.md'), 'utf8'); + expect(notice).toContain('Asset identifier: `PROJECT-ASSET`'); + expect(notice).not.toMatch(/internal prototype|not approved for public release|must be replaced/i); + expect(notice).toContain('02-jinx-arcane.png'); + expect(notice).toContain('02-jinx-standard-codex-imagegen-v2.png'); + }); +}); diff --git a/themes/arctic-signal/ASSET_LICENSE.md b/themes/arctic-signal/ASSET_LICENSE.md deleted file mode 100644 index 13b1555..0000000 --- a/themes/arctic-signal/ASSET_LICENSE.md +++ /dev/null @@ -1,3 +0,0 @@ -# Arctic Signal asset license - -`background.svg` and `preview.svg` were created specifically for Awesome Codex Themes and are released under CC0 1.0. The theme CSS is covered by the repository MIT license. diff --git a/themes/arctic-signal/background.svg b/themes/arctic-signal/background.svg deleted file mode 100644 index c76f16c..0000000 --- a/themes/arctic-signal/background.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/themes/arctic-signal/preview.svg b/themes/arctic-signal/preview.svg deleted file mode 100644 index ecd565e..0000000 --- a/themes/arctic-signal/preview.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - ARCTIC SIGNAL - - LIVE CHANNEL / 07Design a safer local theme engine. - - RUN DIAGNOSTIC - Ask Codex to build, explain, or repair... - diff --git a/themes/arctic-signal/theme.css b/themes/arctic-signal/theme.css deleted file mode 100644 index 603375c..0000000 --- a/themes/arctic-signal/theme.css +++ /dev/null @@ -1,24 +0,0 @@ -html.awesome-codex-theme body { - color: var(--act-text); - background-color: var(--act-background); - background-image: linear-gradient(115deg, #071219E8 18%, #0B2631A8 64%, #071219F2), var(--act-artwork); - background-position: center; - background-size: cover; - background-attachment: fixed; -} - -html.awesome-codex-theme :is(aside, header, nav, [role="dialog"]) { - background-color: color-mix(in srgb, var(--act-surface) 86%, transparent); - border-color: color-mix(in srgb, var(--act-accent) 22%, transparent); - backdrop-filter: blur(22px) saturate(118%); -} - -html.awesome-codex-theme :is(textarea, input, pre, code) { - border-color: color-mix(in srgb, var(--act-accent) 18%, transparent); - caret-color: var(--act-accent); -} - -html.awesome-codex-theme :is(button, [role="button"]):focus-visible { - outline: 2px solid var(--act-accent); - outline-offset: 2px; -} diff --git a/themes/arctic-signal/theme.json b/themes/arctic-signal/theme.json deleted file mode 100644 index 16a61d7..0000000 --- a/themes/arctic-signal/theme.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "schemaVersion": 1, - "slug": "arctic-signal", - "version": "1.0.0", - "name": "Arctic Signal", - "description": "A polar-night workspace cut by electric cyan telemetry and quiet aurora bands.", - "author": { "name": "Awesome Codex Themes" }, - "license": { "code": "MIT", "artwork": "CC0-1.0" }, - "categories": ["dark", "technical"], - "tags": ["cyan", "aurora", "terminal", "high-contrast"], - "compatibility": { - "platforms": ["macos"], - "status": "experimental", - "appVersions": ["26.707.*"] - }, - "mode": "dark", - "palette": { - "background": "#071219", - "surface": "#0D202BCC", - "text": "#E6FAFF", - "accent": "#50E6FF" - }, - "files": { "css": "theme.css", "artwork": "background.svg", "preview": "preview.svg" } -} diff --git a/themes/castle-archive/ASSET_LICENSE.md b/themes/castle-archive/ASSET_LICENSE.md new file mode 100644 index 0000000..3f7937a --- /dev/null +++ b/themes/castle-archive/ASSET_LICENSE.md @@ -0,0 +1,11 @@ +# Castle Archive assets + +Asset identifier: `PROJECT-ASSET` + +The project maintainer supplied and approved the artwork and generated UI assets included in this theme package. + +- Background source: `07-lady-dimitrescu.png` +- Marketing source: `07-lady-dimitrescu-standard-codex-imagegen-v1.png` +- Runtime files: `background.jpg`, `preview.jpg`, and the local files under `assets/` + +Code and CSS remain covered by the repository MIT license. diff --git a/themes/castle-archive/README.md b/themes/castle-archive/README.md new file mode 100644 index 0000000..9530b74 --- /dev/null +++ b/themes/castle-archive/README.md @@ -0,0 +1,11 @@ +# Castle Archive + +Schema 2 installable package for Codex Desktop on macOS. + +- Version: 0.1.1 +- Compatibility: experimental for 26.707.* and 26.715.* +- Runtime artwork: background.jpg +- Right-panel-safe artwork: assets/workspace-panel-safe.jpg +- Original component assets: keyhole-index, chandelier-drop, stair-rune, wax-seal, thorn-send-spear +- Live verification: passed on Codex Desktop 26.715.21425; compatibility remains experimental +- Asset record: maintainer-supplied `PROJECT-ASSET`; see `ASSET_LICENSE.md` diff --git a/themes/castle-archive/assets/chandelier-drop.svg b/themes/castle-archive/assets/chandelier-drop.svg new file mode 100644 index 0000000..f587e2c --- /dev/null +++ b/themes/castle-archive/assets/chandelier-drop.svg @@ -0,0 +1,4 @@ + +chandelier drop + + diff --git a/themes/castle-archive/assets/keyhole-index.svg b/themes/castle-archive/assets/keyhole-index.svg new file mode 100644 index 0000000..753add0 --- /dev/null +++ b/themes/castle-archive/assets/keyhole-index.svg @@ -0,0 +1,4 @@ + +keyhole index + + diff --git a/themes/castle-archive/assets/stair-rune.svg b/themes/castle-archive/assets/stair-rune.svg new file mode 100644 index 0000000..ecd32a0 --- /dev/null +++ b/themes/castle-archive/assets/stair-rune.svg @@ -0,0 +1,4 @@ + +stair rune + + diff --git a/themes/castle-archive/assets/thorn-send-spear.svg b/themes/castle-archive/assets/thorn-send-spear.svg new file mode 100644 index 0000000..27860ec --- /dev/null +++ b/themes/castle-archive/assets/thorn-send-spear.svg @@ -0,0 +1,4 @@ + +thorn send spear + + diff --git a/themes/castle-archive/assets/wax-seal.svg b/themes/castle-archive/assets/wax-seal.svg new file mode 100644 index 0000000..318fa18 --- /dev/null +++ b/themes/castle-archive/assets/wax-seal.svg @@ -0,0 +1,4 @@ + +wax seal + + diff --git a/themes/castle-archive/assets/workspace-panel-safe.jpg b/themes/castle-archive/assets/workspace-panel-safe.jpg new file mode 100644 index 0000000..914b400 Binary files /dev/null and b/themes/castle-archive/assets/workspace-panel-safe.jpg differ diff --git a/themes/castle-archive/background.jpg b/themes/castle-archive/background.jpg new file mode 100644 index 0000000..e0a45b1 Binary files /dev/null and b/themes/castle-archive/background.jpg differ diff --git a/themes/castle-archive/preview.jpg b/themes/castle-archive/preview.jpg new file mode 100644 index 0000000..c7773dc Binary files /dev/null and b/themes/castle-archive/preview.jpg differ diff --git a/themes/castle-archive/theme.css b/themes/castle-archive/theme.css new file mode 100644 index 0000000..8658de1 --- /dev/null +++ b/themes/castle-archive/theme.css @@ -0,0 +1,175 @@ +html.awesome-codex-theme[data-awesome-codex-theme="castle-archive"] { + --batch-theme-signature: "castle-archive"; + --batch-radius: 1px; + --batch-radius-lg: 3px; + --batch-edge: polygon(0 0, 100% 0, 100% 96%, 98% 100%, 2% 100%, 0 96%); + --batch-border-width: 1px; + --batch-display-font: Didot, Bodoni 72, Baskerville, Georgia, serif; + --batch-code-font: SFMono-Regular, Menlo, Consolas, monospace; + --batch-mark: var(--act-asset-keyhole-index); + background-position: center !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="castle-archive"][data-awesome-codex-artifact-panel-state="open"] { + background-image: linear-gradient(115deg, var(--act-scrim), color-mix(in srgb, var(--act-scrim) 54%, transparent)), var(--act-asset-workspace-panel-safe) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="castle-archive"] :is(h1, h2, h3, [data-awesome-codex-sidebar-brand="true"], #awesome-codex-theme-chrome) { + font-family: var(--batch-display-font) !important; + letter-spacing: 0.14em; + text-transform: uppercase; +} + +html.awesome-codex-theme[data-awesome-codex-theme="castle-archive"] :is(pre, code, kbd, samp, .monaco-editor, .terminal) { + font-family: var(--batch-code-font) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="castle-archive"] .app-shell-left-panel { + border-inline-end: var(--batch-border-width) solid var(--act-border-strong) !important; + box-shadow: 0 18px 60px color-mix(in srgb, #000000 70%, transparent) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="castle-archive"] [data-awesome-codex-sidebar-brand="true"] { + background-image: var(--act-asset-keyhole-index) !important; + background-position: right 10px center !important; + background-repeat: no-repeat !important; + background-size: 24px 24px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="castle-archive"] :is([data-awesome-codex-sidebar-nav], [data-awesome-codex-domain-header], [data-awesome-codex-project-row], [data-awesome-codex-task-row]) { + border-radius: var(--batch-radius) !important; + transition: background-color 140ms ease, color 140ms ease, transform 140ms ease; +} + +html.awesome-codex-theme[data-awesome-codex-theme="castle-archive"] :is([data-awesome-codex-sidebar-nav], [data-awesome-codex-project-row], [data-awesome-codex-task-row]):is(:hover, [aria-current="page"], [data-state="active"]) { + background: linear-gradient(90deg, var(--act-active), var(--act-hover)) !important; + box-shadow: inset 3px 0 0 var(--act-accent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="castle-archive"] [data-awesome-codex-domain-header]::after { + content: ""; + display: inline-block; + width: 18px; + height: 18px; + margin-inline-start: auto; + background: var(--act-asset-chandelier-drop) center / contain no-repeat; + opacity: 0.82; +} + +html.awesome-codex-theme[data-awesome-codex-theme="castle-archive"] [data-awesome-codex-home="true"] { + position: relative; + isolation: isolate; +} + +html.awesome-codex-theme[data-awesome-codex-theme="castle-archive"] [data-awesome-codex-home-prompt="true"] { + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); + background: color-mix(in srgb, var(--act-surface-elevated) 88%, transparent) !important; + box-shadow: 0 18px 60px color-mix(in srgb, #000000 70%, transparent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="castle-archive"] :is(.composer-root, .composer-surface-chrome) { + border-width: var(--batch-border-width) !important; + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); +} + +html.awesome-codex-theme[data-awesome-codex-theme="castle-archive"] [data-awesome-codex-composer-control] { + min-width: 32px; + min-height: 32px; + border-radius: var(--batch-radius) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="castle-archive"] [data-awesome-codex-composer-control="attachment"] { + background-image: var(--act-asset-stair-rune) !important; + background-position: center !important; + background-repeat: no-repeat !important; + background-size: 15px 15px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="castle-archive"] [data-awesome-codex-composer-control="primary"] { + color: var(--act-background) !important; + background-color: var(--act-accent) !important; + background-image: var(--act-asset-thorn-send-spear) !important; + background-position: center !important; + background-repeat: no-repeat !important; + background-size: 19px 19px !important; + box-shadow: 0 18px 60px color-mix(in srgb, #000000 70%, transparent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="castle-archive"] [data-awesome-codex-artifact-panel="true"] { + background-color: color-mix(in srgb, var(--act-surface-elevated) 94%, transparent) !important; + background-image: linear-gradient(180deg, color-mix(in srgb, var(--act-surface-overlay) 92%, transparent), var(--act-surface-elevated)), var(--act-asset-workspace-panel-safe) !important; + background-position: center !important; + background-size: cover !important; + border-inline-start: var(--batch-border-width) solid var(--act-border-strong) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="castle-archive"] [data-awesome-codex-artifact-header="true"] { + border-block-end: var(--batch-border-width) solid var(--act-border) !important; + background-image: var(--act-asset-wax-seal) !important; + background-position: right 14px center !important; + background-repeat: no-repeat !important; + background-size: 44px 10px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="castle-archive"] :is([role="dialog"], [role="menu"], [role="listbox"], [role="tooltip"], [data-awesome-codex-plugin-card], [data-awesome-codex-collection-row]) { + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); +} + +html.awesome-codex-theme[data-awesome-codex-theme="castle-archive"] :is([data-awesome-codex-plugin-card], [data-awesome-codex-collection-row]):hover { + border-color: var(--act-border-strong) !important; + background-color: var(--act-hover) !important; + transform: translateY(-1px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="castle-archive"] :is(input, textarea, select, [contenteditable="true"]):focus-visible { + outline: 2px solid var(--act-focus) !important; + outline-offset: 2px !important; + box-shadow: 0 0 0 4px color-mix(in srgb, var(--act-focus) 18%, transparent) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="castle-archive"] :is(.monaco-editor, .terminal, .xterm-viewport, pre, code) { + border-radius: var(--batch-radius) !important; + border-color: var(--act-border-subtle) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="castle-archive"] :is([data-awesome-codex-activity-item], [data-awesome-codex-completed-summary])::before { + content: ""; + display: inline-block; + width: 16px; + height: 16px; + margin-inline-end: 7px; + background: var(--act-asset-chandelier-drop) center / contain no-repeat; + vertical-align: -3px; +} + +html.awesome-codex-theme[data-awesome-codex-theme="castle-archive"] :is(button, [role="button"]):active { + transform: translateY(1px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="castle-archive"] *::-webkit-scrollbar-thumb { + border-radius: var(--batch-radius) !important; +} + +@media (max-width: 1120px) { + html.awesome-codex-theme[data-awesome-codex-theme="castle-archive"] :is([data-awesome-codex-project-row], [data-awesome-codex-task-row], [data-awesome-codex-collection-row]) { + min-width: 0; + } + html.awesome-codex-theme[data-awesome-codex-theme="castle-archive"] :is([data-awesome-codex-project-row], [data-awesome-codex-task-row], [data-awesome-codex-collection-row]) :is(span, p) { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} + +@media (prefers-reduced-motion: reduce) { + html.awesome-codex-theme[data-awesome-codex-theme="castle-archive"] *, + html.awesome-codex-theme[data-awesome-codex-theme="castle-archive"] *::before, + html.awesome-codex-theme[data-awesome-codex-theme="castle-archive"] *::after { + transition-duration: 0.01ms !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + } +} diff --git a/themes/castle-archive/theme.json b/themes/castle-archive/theme.json new file mode 100644 index 0000000..203f643 --- /dev/null +++ b/themes/castle-archive/theme.json @@ -0,0 +1,93 @@ +{ + "schemaVersion": 2, + "slug": "castle-archive", + "version": "0.1.1", + "name": "Castle Archive", + "description": "A monumental candlelit castle interior built from black columns, burgundy velvet, ivory fabric, and brass archival details.", + "author": { + "name": "Awesome Codex Themes" + }, + "license": { + "code": "MIT", + "artwork": "PROJECT-ASSET" + }, + "categories": [ + "dark", + "full-workspace", + "character-inspired" + ], + "tags": [ + "castle-archive", + "ebony-wood", + "tall-vertical-rails", + "macos" + ], + "compatibility": { + "platforms": [ + "macos" + ], + "status": "experimental", + "appVersions": [ + "26.707.*", + "26.715.*" + ] + }, + "mode": "dark", + "palette": { + "background": "#080707", + "scrim": "#080707E8", + "surface": "#11100FDE", + "surfaceElevated": "#1A1715F2", + "surfaceOverlay": "#1A1715F7", + "input": "#151210F2", + "text": "#EEE6DB", + "textSecondary": "#C9B9A7", + "textMuted": "#8A796A", + "textDisabled": "#5D5148", + "icon": "#EEE6DB", + "iconSecondary": "#C9B9A7", + "iconMuted": "#8A796A", + "border": "#B58A583D", + "borderSubtle": "#B58A5824", + "borderStrong": "#B58A5878", + "accent": "#B58A58", + "accentHover": "#D0A674", + "selection": "#B58A5838", + "focus": "#8E3E49", + "link": "#C49A67", + "hover": "#B58A581A", + "active": "#8E3E492B", + "code": "#0E0D0CFA", + "terminal": "#070606FA", + "diffAdded": "#63856B40", + "diffRemoved": "#A8404740", + "success": "#63856B", + "warning": "#B48445", + "danger": "#A84047", + "scrollbar": "#B58A583D", + "scrollbarHover": "#B58A5875", + "composer": "#12100EF2" + }, + "experience": { + "brand": "CASTLE ARCHIVE", + "eyebrow": "MIDNIGHT INDEX", + "headline": "COMMAND THE ARCHIVE", + "tagline": "Quiet rooms. Exact execution.", + "status": "ARCHIVE // AWAKE", + "signature": "CASTLE VII", + "chrome": true + }, + "files": { + "css": "theme.css", + "artwork": "background.jpg", + "preview": "preview.jpg", + "assets": { + "workspace-panel-safe": "assets/workspace-panel-safe.jpg", + "keyhole-index": "assets/keyhole-index.svg", + "chandelier-drop": "assets/chandelier-drop.svg", + "stair-rune": "assets/stair-rune.svg", + "wax-seal": "assets/wax-seal.svg", + "thorn-send-spear": "assets/thorn-send-spear.svg" + } + } +} diff --git a/themes/foundling-garden/ASSET_LICENSE.md b/themes/foundling-garden/ASSET_LICENSE.md new file mode 100644 index 0000000..7ead70e --- /dev/null +++ b/themes/foundling-garden/ASSET_LICENSE.md @@ -0,0 +1,11 @@ +# Foundling Garden assets + +Asset identifier: `PROJECT-ASSET` + +The project maintainer supplied and approved the artwork and generated UI assets included in this theme package. + +- Background source: `11-grogu-official.png` +- Marketing source: `11-grogu-standard-codex-imagegen-v1.png` +- Runtime files: `background.jpg`, `preview.jpg`, and the local files under `assets/` + +Code and CSS remain covered by the repository MIT license. diff --git a/themes/foundling-garden/README.md b/themes/foundling-garden/README.md new file mode 100644 index 0000000..6092958 --- /dev/null +++ b/themes/foundling-garden/README.md @@ -0,0 +1,11 @@ +# Foundling Garden + +Schema 2 installable package for Codex Desktop on macOS. + +- Version: 0.1.1 +- Compatibility: experimental for 26.707.* and 26.715.* +- Runtime artwork: background.jpg +- Right-panel-safe artwork: assets/workspace-panel-safe.jpg +- Original component assets: seed-compass, leaf-pair, stone-arch, firefly-status, orbital-pod-send-mark +- Live verification: passed on Codex Desktop 26.715.21425; compatibility remains experimental +- Asset record: maintainer-supplied `PROJECT-ASSET`; see `ASSET_LICENSE.md` diff --git a/themes/foundling-garden/assets/firefly-status.svg b/themes/foundling-garden/assets/firefly-status.svg new file mode 100644 index 0000000..94a1aa2 --- /dev/null +++ b/themes/foundling-garden/assets/firefly-status.svg @@ -0,0 +1,4 @@ + +firefly status + + diff --git a/themes/foundling-garden/assets/leaf-pair.svg b/themes/foundling-garden/assets/leaf-pair.svg new file mode 100644 index 0000000..6f93cb1 --- /dev/null +++ b/themes/foundling-garden/assets/leaf-pair.svg @@ -0,0 +1,4 @@ + +leaf pair + + diff --git a/themes/foundling-garden/assets/orbital-pod-send-mark.svg b/themes/foundling-garden/assets/orbital-pod-send-mark.svg new file mode 100644 index 0000000..76486a1 --- /dev/null +++ b/themes/foundling-garden/assets/orbital-pod-send-mark.svg @@ -0,0 +1,4 @@ + +orbital pod send mark + + diff --git a/themes/foundling-garden/assets/seed-compass.svg b/themes/foundling-garden/assets/seed-compass.svg new file mode 100644 index 0000000..b66f21f --- /dev/null +++ b/themes/foundling-garden/assets/seed-compass.svg @@ -0,0 +1,4 @@ + +seed compass + + diff --git a/themes/foundling-garden/assets/stone-arch.svg b/themes/foundling-garden/assets/stone-arch.svg new file mode 100644 index 0000000..7a4fc8c --- /dev/null +++ b/themes/foundling-garden/assets/stone-arch.svg @@ -0,0 +1,4 @@ + +stone arch + + diff --git a/themes/foundling-garden/assets/workspace-panel-safe.jpg b/themes/foundling-garden/assets/workspace-panel-safe.jpg new file mode 100644 index 0000000..dd304e1 Binary files /dev/null and b/themes/foundling-garden/assets/workspace-panel-safe.jpg differ diff --git a/themes/foundling-garden/background.jpg b/themes/foundling-garden/background.jpg new file mode 100644 index 0000000..ed73f82 Binary files /dev/null and b/themes/foundling-garden/background.jpg differ diff --git a/themes/foundling-garden/preview.jpg b/themes/foundling-garden/preview.jpg new file mode 100644 index 0000000..18f8d0c Binary files /dev/null and b/themes/foundling-garden/preview.jpg differ diff --git a/themes/foundling-garden/theme.css b/themes/foundling-garden/theme.css new file mode 100644 index 0000000..e921cdc --- /dev/null +++ b/themes/foundling-garden/theme.css @@ -0,0 +1,175 @@ +html.awesome-codex-theme[data-awesome-codex-theme="foundling-garden"] { + --batch-theme-signature: "foundling-garden"; + --batch-radius: 12px; + --batch-radius-lg: 24px; + --batch-edge: none; + --batch-border-width: 1px; + --batch-display-font: Avenir Next, Optima, Helvetica Neue, sans-serif; + --batch-code-font: SFMono-Regular, Menlo, Consolas, monospace; + --batch-mark: var(--act-asset-seed-compass); + background-position: center !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="foundling-garden"][data-awesome-codex-artifact-panel-state="open"] { + background-image: linear-gradient(115deg, var(--act-scrim), color-mix(in srgb, var(--act-scrim) 54%, transparent)), var(--act-asset-workspace-panel-safe) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="foundling-garden"] :is(h1, h2, h3, [data-awesome-codex-sidebar-brand="true"], #awesome-codex-theme-chrome) { + font-family: var(--batch-display-font) !important; + letter-spacing: 0.03em; + text-transform: none; +} + +html.awesome-codex-theme[data-awesome-codex-theme="foundling-garden"] :is(pre, code, kbd, samp, .monaco-editor, .terminal) { + font-family: var(--batch-code-font) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="foundling-garden"] .app-shell-left-panel { + border-inline-end: var(--batch-border-width) solid var(--act-border-strong) !important; + box-shadow: 0 14px 45px color-mix(in srgb, var(--act-text) 10%, transparent) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="foundling-garden"] [data-awesome-codex-sidebar-brand="true"] { + background-image: var(--act-asset-seed-compass) !important; + background-position: right 10px center !important; + background-repeat: no-repeat !important; + background-size: 24px 24px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="foundling-garden"] :is([data-awesome-codex-sidebar-nav], [data-awesome-codex-domain-header], [data-awesome-codex-project-row], [data-awesome-codex-task-row]) { + border-radius: var(--batch-radius) !important; + transition: background-color 140ms ease, color 140ms ease, transform 140ms ease; +} + +html.awesome-codex-theme[data-awesome-codex-theme="foundling-garden"] :is([data-awesome-codex-sidebar-nav], [data-awesome-codex-project-row], [data-awesome-codex-task-row]):is(:hover, [aria-current="page"], [data-state="active"]) { + background: linear-gradient(90deg, var(--act-active), var(--act-hover)) !important; + box-shadow: inset 3px 0 0 var(--act-accent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="foundling-garden"] [data-awesome-codex-domain-header]::after { + content: ""; + display: inline-block; + width: 18px; + height: 18px; + margin-inline-start: auto; + background: var(--act-asset-leaf-pair) center / contain no-repeat; + opacity: 0.82; +} + +html.awesome-codex-theme[data-awesome-codex-theme="foundling-garden"] [data-awesome-codex-home="true"] { + position: relative; + isolation: isolate; +} + +html.awesome-codex-theme[data-awesome-codex-theme="foundling-garden"] [data-awesome-codex-home-prompt="true"] { + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); + background: color-mix(in srgb, var(--act-surface-elevated) 88%, transparent) !important; + box-shadow: 0 14px 45px color-mix(in srgb, var(--act-text) 10%, transparent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="foundling-garden"] :is(.composer-root, .composer-surface-chrome) { + border-width: var(--batch-border-width) !important; + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); +} + +html.awesome-codex-theme[data-awesome-codex-theme="foundling-garden"] [data-awesome-codex-composer-control] { + min-width: 32px; + min-height: 32px; + border-radius: var(--batch-radius) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="foundling-garden"] [data-awesome-codex-composer-control="attachment"] { + background-image: var(--act-asset-stone-arch) !important; + background-position: center !important; + background-repeat: no-repeat !important; + background-size: 15px 15px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="foundling-garden"] [data-awesome-codex-composer-control="primary"] { + color: var(--act-background) !important; + background-color: var(--act-accent) !important; + background-image: var(--act-asset-orbital-pod-send-mark) !important; + background-position: center !important; + background-repeat: no-repeat !important; + background-size: 19px 19px !important; + box-shadow: 0 14px 45px color-mix(in srgb, var(--act-text) 10%, transparent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="foundling-garden"] [data-awesome-codex-artifact-panel="true"] { + background-color: color-mix(in srgb, var(--act-surface-elevated) 94%, transparent) !important; + background-image: linear-gradient(180deg, color-mix(in srgb, var(--act-surface-overlay) 92%, transparent), var(--act-surface-elevated)), var(--act-asset-workspace-panel-safe) !important; + background-position: center !important; + background-size: cover !important; + border-inline-start: var(--batch-border-width) solid var(--act-border-strong) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="foundling-garden"] [data-awesome-codex-artifact-header="true"] { + border-block-end: var(--batch-border-width) solid var(--act-border) !important; + background-image: var(--act-asset-firefly-status) !important; + background-position: right 14px center !important; + background-repeat: no-repeat !important; + background-size: 44px 10px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="foundling-garden"] :is([role="dialog"], [role="menu"], [role="listbox"], [role="tooltip"], [data-awesome-codex-plugin-card], [data-awesome-codex-collection-row]) { + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); +} + +html.awesome-codex-theme[data-awesome-codex-theme="foundling-garden"] :is([data-awesome-codex-plugin-card], [data-awesome-codex-collection-row]):hover { + border-color: var(--act-border-strong) !important; + background-color: var(--act-hover) !important; + transform: translateY(-1px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="foundling-garden"] :is(input, textarea, select, [contenteditable="true"]):focus-visible { + outline: 2px solid var(--act-focus) !important; + outline-offset: 2px !important; + box-shadow: 0 0 0 4px color-mix(in srgb, var(--act-focus) 18%, transparent) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="foundling-garden"] :is(.monaco-editor, .terminal, .xterm-viewport, pre, code) { + border-radius: var(--batch-radius) !important; + border-color: var(--act-border-subtle) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="foundling-garden"] :is([data-awesome-codex-activity-item], [data-awesome-codex-completed-summary])::before { + content: ""; + display: inline-block; + width: 16px; + height: 16px; + margin-inline-end: 7px; + background: var(--act-asset-leaf-pair) center / contain no-repeat; + vertical-align: -3px; +} + +html.awesome-codex-theme[data-awesome-codex-theme="foundling-garden"] :is(button, [role="button"]):active { + transform: translateY(1px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="foundling-garden"] *::-webkit-scrollbar-thumb { + border-radius: var(--batch-radius) !important; +} + +@media (max-width: 1120px) { + html.awesome-codex-theme[data-awesome-codex-theme="foundling-garden"] :is([data-awesome-codex-project-row], [data-awesome-codex-task-row], [data-awesome-codex-collection-row]) { + min-width: 0; + } + html.awesome-codex-theme[data-awesome-codex-theme="foundling-garden"] :is([data-awesome-codex-project-row], [data-awesome-codex-task-row], [data-awesome-codex-collection-row]) :is(span, p) { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} + +@media (prefers-reduced-motion: reduce) { + html.awesome-codex-theme[data-awesome-codex-theme="foundling-garden"] *, + html.awesome-codex-theme[data-awesome-codex-theme="foundling-garden"] *::before, + html.awesome-codex-theme[data-awesome-codex-theme="foundling-garden"] *::after { + transition-duration: 0.01ms !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + } +} diff --git a/themes/foundling-garden/theme.json b/themes/foundling-garden/theme.json new file mode 100644 index 0000000..7f2f4d0 --- /dev/null +++ b/themes/foundling-garden/theme.json @@ -0,0 +1,93 @@ +{ + "schemaVersion": 2, + "slug": "foundling-garden", + "version": "0.1.1", + "name": "Foundling Garden", + "description": "A small foundling in a misty moss garden, reinterpreted by the concept art as a wider luminous sanctuary and orbital seed instrument.", + "author": { + "name": "Awesome Codex Themes" + }, + "license": { + "code": "MIT", + "artwork": "PROJECT-ASSET" + }, + "categories": [ + "dark", + "full-workspace", + "character-inspired" + ], + "tags": [ + "foundling-garden", + "moss", + "soft-stone-arches", + "macos" + ], + "compatibility": { + "platforms": [ + "macos" + ], + "status": "experimental", + "appVersions": [ + "26.707.*", + "26.715.*" + ] + }, + "mode": "dark", + "palette": { + "background": "#101309", + "scrim": "#101309E8", + "surface": "#171C10DE", + "surfaceElevated": "#222819F2", + "surfaceOverlay": "#222819F7", + "input": "#1B2114F2", + "text": "#F2ECD2", + "textSecondary": "#C9C29E", + "textMuted": "#8B8D6D", + "textDisabled": "#5E614C", + "icon": "#F2ECD2", + "iconSecondary": "#C9C29E", + "iconMuted": "#8B8D6D", + "border": "#D4B85E3D", + "borderSubtle": "#D4B85E24", + "borderStrong": "#D4B85E78", + "accent": "#D4B85E", + "accentHover": "#E6CD78", + "selection": "#D4B85E38", + "focus": "#8DB66A", + "link": "#C6B25D", + "hover": "#D4B85E1A", + "active": "#8DB66A2B", + "code": "#0D1109FA", + "terminal": "#080B06FA", + "diffAdded": "#78A96840", + "diffRemoved": "#C45C4D40", + "success": "#78A968", + "warning": "#C99647", + "danger": "#C45C4D", + "scrollbar": "#D4B85E3D", + "scrollbarHover": "#D4B85E75", + "composer": "#161B0FF2" + }, + "experience": { + "brand": "FOUNDLING GARDEN", + "eyebrow": "SEEDLING ORDER", + "headline": "GROW THE QUIET SYSTEM", + "tagline": "Small steps. Living systems.", + "status": "GARDEN // LISTENING", + "signature": "GROVE 11", + "chrome": true + }, + "files": { + "css": "theme.css", + "artwork": "background.jpg", + "preview": "preview.jpg", + "assets": { + "workspace-panel-safe": "assets/workspace-panel-safe.jpg", + "seed-compass": "assets/seed-compass.svg", + "leaf-pair": "assets/leaf-pair.svg", + "stone-arch": "assets/stone-arch.svg", + "firefly-status": "assets/firefly-status.svg", + "orbital-pod-send-mark": "assets/orbital-pod-send-mark.svg" + } + } +} diff --git a/themes/grand-line/ASSET_LICENSE.md b/themes/grand-line/ASSET_LICENSE.md new file mode 100644 index 0000000..8989bdb --- /dev/null +++ b/themes/grand-line/ASSET_LICENSE.md @@ -0,0 +1,11 @@ +# Grand Line assets + +Asset identifier: `PROJECT-ASSET` + +The project maintainer supplied and approved the artwork and generated UI assets included in this theme package. + +- Background source: `06-luffy.png` +- Marketing source: `06-luffy-standard-codex-imagegen-v1.png` +- Runtime files: `background.jpg`, `preview.jpg`, and the local files under `assets/` + +Code and CSS remain covered by the repository MIT license. diff --git a/themes/grand-line/README.md b/themes/grand-line/README.md new file mode 100644 index 0000000..7a98f1e --- /dev/null +++ b/themes/grand-line/README.md @@ -0,0 +1,11 @@ +# Grand Line + +Schema 2 installable package for Codex Desktop on macOS. + +- Version: 0.1.1 +- Compatibility: experimental for 26.707.* and 26.715.* +- Runtime artwork: background.jpg +- Right-panel-safe artwork: assets/workspace-panel-safe.jpg +- Original component assets: compass-wedge, rope-knot, sextant-tick, tide-arrow, pennant-send-mark +- Live verification: passed on Codex Desktop 26.715.21425; compatibility remains experimental +- Asset record: maintainer-supplied `PROJECT-ASSET`; see `ASSET_LICENSE.md` diff --git a/themes/grand-line/assets/compass-wedge.svg b/themes/grand-line/assets/compass-wedge.svg new file mode 100644 index 0000000..40a3828 --- /dev/null +++ b/themes/grand-line/assets/compass-wedge.svg @@ -0,0 +1,4 @@ + +compass wedge + + diff --git a/themes/grand-line/assets/pennant-send-mark.svg b/themes/grand-line/assets/pennant-send-mark.svg new file mode 100644 index 0000000..84d4549 --- /dev/null +++ b/themes/grand-line/assets/pennant-send-mark.svg @@ -0,0 +1,4 @@ + +pennant send mark + + diff --git a/themes/grand-line/assets/rope-knot.svg b/themes/grand-line/assets/rope-knot.svg new file mode 100644 index 0000000..8143c83 --- /dev/null +++ b/themes/grand-line/assets/rope-knot.svg @@ -0,0 +1,4 @@ + +rope knot + + diff --git a/themes/grand-line/assets/sextant-tick.svg b/themes/grand-line/assets/sextant-tick.svg new file mode 100644 index 0000000..19829d9 --- /dev/null +++ b/themes/grand-line/assets/sextant-tick.svg @@ -0,0 +1,4 @@ + +sextant tick + + diff --git a/themes/grand-line/assets/tide-arrow.svg b/themes/grand-line/assets/tide-arrow.svg new file mode 100644 index 0000000..54a6a63 --- /dev/null +++ b/themes/grand-line/assets/tide-arrow.svg @@ -0,0 +1,4 @@ + +tide arrow + + diff --git a/themes/grand-line/assets/workspace-panel-safe.jpg b/themes/grand-line/assets/workspace-panel-safe.jpg new file mode 100644 index 0000000..b0ea9e2 Binary files /dev/null and b/themes/grand-line/assets/workspace-panel-safe.jpg differ diff --git a/themes/grand-line/background.jpg b/themes/grand-line/background.jpg new file mode 100644 index 0000000..bb306ac Binary files /dev/null and b/themes/grand-line/background.jpg differ diff --git a/themes/grand-line/preview.jpg b/themes/grand-line/preview.jpg new file mode 100644 index 0000000..4e2d62f Binary files /dev/null and b/themes/grand-line/preview.jpg differ diff --git a/themes/grand-line/theme.css b/themes/grand-line/theme.css new file mode 100644 index 0000000..1b74fcf --- /dev/null +++ b/themes/grand-line/theme.css @@ -0,0 +1,175 @@ +html.awesome-codex-theme[data-awesome-codex-theme="grand-line"] { + --batch-theme-signature: "grand-line"; + --batch-radius: 2px; + --batch-radius-lg: 6px; + --batch-edge: polygon(0 0, 98% 0, 100% 8%, 100% 100%, 2% 100%, 0 92%); + --batch-border-width: 1px; + --batch-display-font: Baskerville, Iowan Old Style, Georgia, serif; + --batch-code-font: SFMono-Regular, Menlo, Consolas, monospace; + --batch-mark: var(--act-asset-compass-wedge); + background-position: center !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="grand-line"][data-awesome-codex-artifact-panel-state="open"] { + background-image: linear-gradient(115deg, var(--act-scrim), color-mix(in srgb, var(--act-scrim) 54%, transparent)), var(--act-asset-workspace-panel-safe) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="grand-line"] :is(h1, h2, h3, [data-awesome-codex-sidebar-brand="true"], #awesome-codex-theme-chrome) { + font-family: var(--batch-display-font) !important; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +html.awesome-codex-theme[data-awesome-codex-theme="grand-line"] :is(pre, code, kbd, samp, .monaco-editor, .terminal) { + font-family: var(--batch-code-font) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="grand-line"] .app-shell-left-panel { + border-inline-end: var(--batch-border-width) solid var(--act-border-strong) !important; + box-shadow: 0 12px 35px color-mix(in srgb, #000000 55%, transparent) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="grand-line"] [data-awesome-codex-sidebar-brand="true"] { + background-image: var(--act-asset-compass-wedge) !important; + background-position: right 10px center !important; + background-repeat: no-repeat !important; + background-size: 24px 24px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="grand-line"] :is([data-awesome-codex-sidebar-nav], [data-awesome-codex-domain-header], [data-awesome-codex-project-row], [data-awesome-codex-task-row]) { + border-radius: var(--batch-radius) !important; + transition: background-color 140ms ease, color 140ms ease, transform 140ms ease; +} + +html.awesome-codex-theme[data-awesome-codex-theme="grand-line"] :is([data-awesome-codex-sidebar-nav], [data-awesome-codex-project-row], [data-awesome-codex-task-row]):is(:hover, [aria-current="page"], [data-state="active"]) { + background: linear-gradient(90deg, var(--act-active), var(--act-hover)) !important; + box-shadow: inset 3px 0 0 var(--act-accent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="grand-line"] [data-awesome-codex-domain-header]::after { + content: ""; + display: inline-block; + width: 18px; + height: 18px; + margin-inline-start: auto; + background: var(--act-asset-rope-knot) center / contain no-repeat; + opacity: 0.82; +} + +html.awesome-codex-theme[data-awesome-codex-theme="grand-line"] [data-awesome-codex-home="true"] { + position: relative; + isolation: isolate; +} + +html.awesome-codex-theme[data-awesome-codex-theme="grand-line"] [data-awesome-codex-home-prompt="true"] { + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); + background: color-mix(in srgb, var(--act-surface-elevated) 88%, transparent) !important; + box-shadow: 0 12px 35px color-mix(in srgb, #000000 55%, transparent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="grand-line"] :is(.composer-root, .composer-surface-chrome) { + border-width: var(--batch-border-width) !important; + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); +} + +html.awesome-codex-theme[data-awesome-codex-theme="grand-line"] [data-awesome-codex-composer-control] { + min-width: 32px; + min-height: 32px; + border-radius: var(--batch-radius) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="grand-line"] [data-awesome-codex-composer-control="attachment"] { + background-image: var(--act-asset-sextant-tick) !important; + background-position: center !important; + background-repeat: no-repeat !important; + background-size: 15px 15px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="grand-line"] [data-awesome-codex-composer-control="primary"] { + color: var(--act-background) !important; + background-color: var(--act-accent) !important; + background-image: var(--act-asset-pennant-send-mark) !important; + background-position: center !important; + background-repeat: no-repeat !important; + background-size: 19px 19px !important; + box-shadow: 0 12px 35px color-mix(in srgb, #000000 55%, transparent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="grand-line"] [data-awesome-codex-artifact-panel="true"] { + background-color: color-mix(in srgb, var(--act-surface-elevated) 94%, transparent) !important; + background-image: linear-gradient(180deg, color-mix(in srgb, var(--act-surface-overlay) 92%, transparent), var(--act-surface-elevated)), var(--act-asset-workspace-panel-safe) !important; + background-position: center !important; + background-size: cover !important; + border-inline-start: var(--batch-border-width) solid var(--act-border-strong) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="grand-line"] [data-awesome-codex-artifact-header="true"] { + border-block-end: var(--batch-border-width) solid var(--act-border) !important; + background-image: var(--act-asset-tide-arrow) !important; + background-position: right 14px center !important; + background-repeat: no-repeat !important; + background-size: 44px 10px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="grand-line"] :is([role="dialog"], [role="menu"], [role="listbox"], [role="tooltip"], [data-awesome-codex-plugin-card], [data-awesome-codex-collection-row]) { + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); +} + +html.awesome-codex-theme[data-awesome-codex-theme="grand-line"] :is([data-awesome-codex-plugin-card], [data-awesome-codex-collection-row]):hover { + border-color: var(--act-border-strong) !important; + background-color: var(--act-hover) !important; + transform: translateY(-1px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="grand-line"] :is(input, textarea, select, [contenteditable="true"]):focus-visible { + outline: 2px solid var(--act-focus) !important; + outline-offset: 2px !important; + box-shadow: 0 0 0 4px color-mix(in srgb, var(--act-focus) 18%, transparent) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="grand-line"] :is(.monaco-editor, .terminal, .xterm-viewport, pre, code) { + border-radius: var(--batch-radius) !important; + border-color: var(--act-border-subtle) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="grand-line"] :is([data-awesome-codex-activity-item], [data-awesome-codex-completed-summary])::before { + content: ""; + display: inline-block; + width: 16px; + height: 16px; + margin-inline-end: 7px; + background: var(--act-asset-rope-knot) center / contain no-repeat; + vertical-align: -3px; +} + +html.awesome-codex-theme[data-awesome-codex-theme="grand-line"] :is(button, [role="button"]):active { + transform: translateY(1px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="grand-line"] *::-webkit-scrollbar-thumb { + border-radius: var(--batch-radius) !important; +} + +@media (max-width: 1120px) { + html.awesome-codex-theme[data-awesome-codex-theme="grand-line"] :is([data-awesome-codex-project-row], [data-awesome-codex-task-row], [data-awesome-codex-collection-row]) { + min-width: 0; + } + html.awesome-codex-theme[data-awesome-codex-theme="grand-line"] :is([data-awesome-codex-project-row], [data-awesome-codex-task-row], [data-awesome-codex-collection-row]) :is(span, p) { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} + +@media (prefers-reduced-motion: reduce) { + html.awesome-codex-theme[data-awesome-codex-theme="grand-line"] *, + html.awesome-codex-theme[data-awesome-codex-theme="grand-line"] *::before, + html.awesome-codex-theme[data-awesome-codex-theme="grand-line"] *::after { + transition-duration: 0.01ms !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + } +} diff --git a/themes/grand-line/theme.json b/themes/grand-line/theme.json new file mode 100644 index 0000000..11cbd42 --- /dev/null +++ b/themes/grand-line/theme.json @@ -0,0 +1,93 @@ +{ + "schemaVersion": 2, + "slug": "grand-line", + "version": "0.1.1", + "name": "Grand Line", + "description": "A weathered black-sail navigation desk illuminated by brass chart marks, rope, smoke, and a high-energy captain portrait.", + "author": { + "name": "Awesome Codex Themes" + }, + "license": { + "code": "MIT", + "artwork": "PROJECT-ASSET" + }, + "categories": [ + "dark", + "full-workspace", + "character-inspired" + ], + "tags": [ + "grand-line", + "black-sailcloth", + "chart-ticks", + "macos" + ], + "compatibility": { + "platforms": [ + "macos" + ], + "status": "experimental", + "appVersions": [ + "26.707.*", + "26.715.*" + ] + }, + "mode": "dark", + "palette": { + "background": "#0B0906", + "scrim": "#0B0906E8", + "surface": "#17120DDE", + "surfaceElevated": "#21190FF2", + "surfaceOverlay": "#21190FF7", + "input": "#1A140EF2", + "text": "#F1D8A4", + "textSecondary": "#C8AD78", + "textMuted": "#8F7A55", + "textDisabled": "#5F523D", + "icon": "#F1D8A4", + "iconSecondary": "#C8AD78", + "iconMuted": "#8F7A55", + "border": "#D59B363D", + "borderSubtle": "#D59B3624", + "borderStrong": "#D59B3678", + "accent": "#D59B36", + "accentHover": "#F0B957", + "selection": "#D59B3638", + "focus": "#B13E35", + "link": "#E1B45A", + "hover": "#D59B361A", + "active": "#B13E352B", + "code": "#100D09FA", + "terminal": "#080705FA", + "diffAdded": "#5E9B6340", + "diffRemoved": "#C34A3D40", + "success": "#5E9B63", + "warning": "#D59732", + "danger": "#C34A3D", + "scrollbar": "#D59B363D", + "scrollbarHover": "#D59B3675", + "composer": "#15100BF2" + }, + "experience": { + "brand": "GRAND LINE", + "eyebrow": "LOG POSE", + "headline": "CHART THE UNKNOWN", + "tagline": "Plot the route. Ship the voyage.", + "status": "COURSE // LOCKED", + "signature": "NAV LOG 06", + "chrome": true + }, + "files": { + "css": "theme.css", + "artwork": "background.jpg", + "preview": "preview.jpg", + "assets": { + "workspace-panel-safe": "assets/workspace-panel-safe.jpg", + "compass-wedge": "assets/compass-wedge.svg", + "rope-knot": "assets/rope-knot.svg", + "sextant-tick": "assets/sextant-tick.svg", + "tide-arrow": "assets/tide-arrow.svg", + "pennant-send-mark": "assets/pennant-send-mark.svg" + } + } +} diff --git a/themes/limitless-six-eyes/ASSET_LICENSE.md b/themes/limitless-six-eyes/ASSET_LICENSE.md deleted file mode 100644 index 4c67737..0000000 --- a/themes/limitless-six-eyes/ASSET_LICENSE.md +++ /dev/null @@ -1,11 +0,0 @@ -# Limitless Six Eyes prototype asset notice - -License identifier: `PROTOTYPE-REFERENCE-ONLY` - -`background.jpg` and `preview.png` are private fan-prototype assets generated from a user-supplied character reference. They are committed only so the owner of this private repository can evaluate the theme system. - -These two image files are not covered by the repository MIT license, are not offered for public or commercial redistribution, and do not grant any rights in the depicted character or source franchise. - -Before this repository or theme is made public, both image files and all character-specific naming must be replaced with original or commercially licensed artwork and branding. The replacement must be documented here with its actual license. - -The engine, manifest structure, and original theme CSS remain covered by the repository MIT license. diff --git a/themes/limitless-six-eyes/theme.css b/themes/limitless-six-eyes/theme.css deleted file mode 100644 index c400653..0000000 --- a/themes/limitless-six-eyes/theme.css +++ /dev/null @@ -1,703 +0,0 @@ -/* - * Limitless Six Eyes - * Private fan prototype for the Codex 26.707 adapter. - * The code is MIT licensed. See ASSET_LICENSE.md for artwork restrictions. - */ - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] { - --limitless-ink: #1c1930; - --limitless-violet: #5e4bd8; - --limitless-violet-soft: #ece9fb; - --limitless-paper: #f7f6fb; - --limitless-code-ink: #e9e6ff; - --limitless-radius-control: 9px; - --limitless-radius-panel: 13px; - --limitless-radius-composer: 15px; - --limitless-radius-hero: 18px; - --limitless-shadow-panel: 0 14px 38px #31284d14; - --limitless-shadow-floating: 0 18px 48px #2e24491f; - background-color: var(--act-background) !important; - background-image: - radial-gradient(circle at 78% 5%, #9586e91a, transparent 30%), - radial-gradient(circle at 45% 90%, #ffffffd9, transparent 38%), - linear-gradient(135deg, #f8f7fb, #efedf7) !important; - background-position: center !important; - background-size: cover !important; - background-repeat: no-repeat !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] body { - font-family: "Avenir Next", "Helvetica Neue", sans-serif; - letter-spacing: -0.006em; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] #root { - background: - linear-gradient(90deg, #faf9fce6 0 18%, transparent 31%), - radial-gradient(circle at 67% 0, #ffffffb8, transparent 34%) !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] :is( - .app-shell-main, - .main-surface, - .thread-root, - .thread-scroll-container, - .app-shell-main-content-frame, - .app-shell-main-content-viewport -) { - background-color: transparent !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] .app-shell-left-panel { - position: relative; - color: var(--act-text) !important; - background: - linear-gradient(180deg, #fdfcfff5, #f5f3faf0), - var(--act-surface) !important; - border-right: 1px solid #7567b826 !important; - box-shadow: - 8px 0 28px #39305f0c, - inset -1px 0 #ffffffb8 !important; - -webkit-backdrop-filter: blur(22px) saturate(112%); - backdrop-filter: blur(22px) saturate(112%); -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] .app-shell-left-panel::after { - content: ""; - position: absolute; - inset: 48px 14px auto; - height: 1px; - pointer-events: none; - background: linear-gradient(90deg, transparent, #6654c42b, transparent); -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] .app-shell-left-panel :is(button, a) { - border-radius: var(--limitless-radius-control) !important; - transition: - color 140ms ease, - background-color 140ms ease, - border-color 140ms ease, - transform 140ms ease !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] .app-shell-left-panel :is(button, a):hover { - color: var(--act-text) !important; - background: #7567b814 !important; - border-color: transparent !important; - transform: translateX(1px); -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] .app-shell-left-panel :is( - [aria-current="page"], - [class~="bg-token-list-hover-background"] -) { - color: #2d2559 !important; - background: - linear-gradient(90deg, #e9e5fb, #f0edfc), - var(--act-surface-elevated) !important; - border-color: #7567b826 !important; - box-shadow: - inset 3px 0 var(--act-accent), - 0 5px 14px #4f408e12 !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] :is( - .app-header-tint, - .app-shell-main-content-top-fade, - header -) { - background: linear-gradient(180deg, #ffffffeb, #f8f7fbc7 76%, transparent) !important; - border-color: #7567b81f !important; - -webkit-backdrop-filter: blur(16px) saturate(108%); - backdrop-filter: blur(16px) saturate(108%); -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] header :is(button, [role="button"]) { - border-radius: var(--limitless-radius-control) !important; - border-color: #7567b824 !important; - box-shadow: 0 3px 10px #463c6810 !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] :is( - [data-message-author-role], - [role="listitem"], - [data-browser-comment-editor-surface] -) { - border-color: #7567b81f !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] [class*="rounded-lg"][class*="bg-token-dropdown-background"] { - background-color: #ffffff8f !important; - border-color: #7567b824 !important; - border-radius: var(--limitless-radius-panel) !important; - box-shadow: 0 8px 24px #3d325a0c !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] :is( - [role="dialog"], - [role="menu"], - [role="listbox"], - [role="tooltip"], - [data-radix-menu-content], - .dialog-layout, - .bg-token-dropdown-background -) { - color: var(--act-text) !important; - background: - linear-gradient(145deg, #fffffffa, #f5f3fcf7), - var(--act-surface-overlay) !important; - border: 1px solid #7567b830 !important; - border-radius: var(--limitless-radius-panel) !important; - box-shadow: - var(--limitless-shadow-floating), - inset 0 1px #ffffff !important; - -webkit-backdrop-filter: blur(24px) saturate(116%); - backdrop-filter: blur(24px) saturate(116%); -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] :is( - [role="menuitem"], - [role="option"], - [data-radix-menu-item] -) { - border-radius: 8px !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] :is( - [role="menuitem"], - [role="option"], - [data-radix-menu-item] -):hover { - color: var(--act-text) !important; - background: #7567b817 !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] :is( - input, - textarea, - select -) { - color: var(--act-text) !important; - caret-color: var(--act-accent) !important; - background: - linear-gradient(180deg, #ffffffed, #f5f3fae8), - var(--act-input) !important; - border: 1px solid #7567b82e !important; - border-radius: var(--limitless-radius-control) !important; - box-shadow: - inset 0 1px 2px #4437670a, - 0 1px #ffffff !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] :is(input, textarea)::placeholder { - color: var(--act-text-muted) !important; - opacity: 0.78 !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] :is( - button, - [role="button"], - a -) { - transition: - color 140ms ease, - background-color 140ms ease, - border-color 140ms ease, - box-shadow 140ms ease, - transform 140ms ease !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] :is( - button, - [role="button"], - a -):hover { - border-color: #7567b83d !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] :is( - button, - [role="button"], - a, - input, - textarea, - select, - [contenteditable="true"] -):focus-visible { - outline: 2px solid var(--act-focus) !important; - outline-offset: 2px !important; - box-shadow: 0 0 0 4px #7d68eb1c !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] :is( - pre, - code, - kbd, - samp, - .vscode-markdown pre, - .vscode-markdown code -) { - color: var(--limitless-code-ink) !important; - background-color: var(--act-code) !important; - border-color: #958be02b !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] pre { - border-radius: 12px !important; - box-shadow: - 0 12px 32px #1612281c, - inset 0 1px #ffffff0a !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] :is( - .monaco-editor, - .monaco-editor-background, - .margin, - .terminal, - .xterm, - .xterm-viewport -) { - color: var(--limitless-code-ink) !important; - background-color: var(--act-terminal) !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] :is( - .monaco-editor, - .terminal, - .xterm -) { - border-color: #8c80d530 !important; - border-radius: 12px !important; - box-shadow: inset 0 1px #ffffff08 !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] .composer-root { - color: var(--act-text) !important; - background: transparent !important; - border: 0 !important; - border-radius: 0 !important; - box-shadow: none !important; - -webkit-backdrop-filter: none !important; - backdrop-filter: none !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] .composer-surface-chrome { - position: relative; - color: var(--act-text) !important; - background: - linear-gradient(160deg, #fffffffa, #f5f3fbf4), - var(--act-composer) !important; - border: 1px solid #7567b83d !important; - border-radius: var(--limitless-radius-composer) !important; - box-shadow: - 0 16px 38px #32274f1a, - inset 0 1px #ffffff !important; - -webkit-backdrop-filter: blur(22px) saturate(112%); - backdrop-filter: blur(22px) saturate(112%); -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] .composer-surface-chrome:has(.ProseMirror:focus) { - border-color: #6e5ae67a !important; - box-shadow: - 0 0 0 3px #7d68eb14, - 0 16px 38px #32274f1a, - inset 0 1px #ffffff !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] :is( - .composer-root, - .composer-surface-chrome -) .ProseMirror { - color: var(--act-text) !important; - caret-color: var(--act-accent) !important; - background: transparent !important; - border: 0 !important; - border-radius: 0 !important; - outline: 0 !important; - box-shadow: none !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] :is( - button[data-testid="composer-send-button"], - .composer-send-button -) { - color: #ffffff !important; - background: - linear-gradient(145deg, #7461e8, #5140c5), - var(--act-accent) !important; - border-color: #8372ec !important; - box-shadow: - 0 6px 16px #513ac136, - inset 0 1px #ffffff4a !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] :is( - button[data-testid="composer-send-button"], - .composer-send-button -):not(:disabled):hover { - background: - linear-gradient(145deg, #8371f0, #604ed5), - var(--act-accent-hover) !important; - transform: translateY(-1px); -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] ::selection { - color: var(--act-text) !important; - background: var(--act-selection) !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] * { - scrollbar-color: var(--act-scrollbar) transparent; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] *::-webkit-scrollbar { - width: 8px; - height: 8px; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] *::-webkit-scrollbar-track { - background: transparent; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] *::-webkit-scrollbar-thumb { - background: var(--act-scrollbar) !important; - border: 2px solid transparent; - border-radius: 999px; - background-clip: padding-box !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] *::-webkit-scrollbar-thumb:hover { - background: var(--act-scrollbar-hover) !important; - background-clip: padding-box !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] [data-awesome-codex-home="true"] { - --thread-content-max-width: min(1080px, calc(100cqw - 44px)) !important; - position: relative; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] [data-awesome-codex-home="true"] > div:first-child > div:first-child > div:first-child { - position: relative; - min-height: 278px !important; - overflow: hidden; - background: - linear-gradient(90deg, #fbfafff7 0%, #f8f6fce8 38%, #f3effba3 56%, transparent 77%), - var(--act-artwork) !important; - background-position: center, center !important; - background-size: cover, cover !important; - border: 1px solid #7567b82b !important; - border-radius: var(--limitless-radius-hero) !important; - box-shadow: - 0 18px 46px #362b5d17, - inset 0 1px #ffffff !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] [data-awesome-codex-home="true"] > div:first-child > div:first-child > div:first-child::after { - content: ""; - position: absolute; - inset: 0; - pointer-events: none; - background: - linear-gradient(90deg, #6654c429 1px, transparent 1px) 0 0 / 88px 100%, - linear-gradient(#6654c416 1px, transparent 1px) 0 0 / 100% 88px; - -webkit-mask-image: linear-gradient(90deg, #0005, transparent 58%); - mask-image: linear-gradient(90deg, #0005, transparent 58%); - opacity: 0.22; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] [data-awesome-codex-home="true"] [data-testid="home-icon"] { - opacity: 0 !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] [data-awesome-codex-home="true"] [data-feature="game-source"] { - color: var(--act-text) !important; - font-family: "Avenir Next", "Helvetica Neue", sans-serif !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] [data-awesome-codex-home="true"] [data-feature="game-source"] button { - color: #ffffff !important; - background: linear-gradient(135deg, #7160e9, #5140c5) !important; - border-color: #8170eb !important; - border-radius: var(--limitless-radius-control) !important; - box-shadow: 0 7px 18px #503ac32e !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] [data-awesome-codex-home="true"] [class~="group/home-suggestions"] { - overflow: visible !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] [data-awesome-codex-home="true"] [class~="group/home-suggestions"] button { - min-height: 106px !important; - color: var(--act-text) !important; - background: - linear-gradient(145deg, #ffffffed, #f4f2faf0), - var(--act-surface-elevated) !important; - border: 1px solid #7567b82b !important; - border-radius: var(--limitless-radius-panel) !important; - box-shadow: - 0 9px 24px #4034630f, - inset 0 1px #ffffff !important; -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] [data-awesome-codex-home="true"] [class~="group/home-suggestions"] button:hover { - background: - linear-gradient(145deg, #ffffff, #efecf9), - var(--act-surface-elevated) !important; - border-color: #7567b852 !important; - box-shadow: - 0 14px 30px #4c3b801a, - 0 0 0 3px #806ee410 !important; - transform: translateY(-2px); -} - -html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] [data-awesome-codex-home="true"] [class~="group/project-selector"] > button { - color: var(--act-text) !important; - background: #ffffffeb !important; - border-color: #7567b82b !important; - border-radius: var(--limitless-radius-control) !important; - box-shadow: 0 6px 18px #3b315b0f !important; -} - -#awesome-codex-theme-chrome.act-experience { - position: fixed; - z-index: 2; - inset: 0; - overflow: hidden; - pointer-events: none; - color: var(--act-text); - font-family: "Avenir Next", "Helvetica Neue", sans-serif; -} - -#awesome-codex-theme-chrome .act-experience__identity { - position: absolute; - top: 112px; - left: clamp(330px, 24vw, 410px); - width: min(370px, 30vw); - padding: 16px 20px; - opacity: 0; - transform: translateY(8px); - transition: - opacity 260ms ease, - transform 340ms cubic-bezier(0.16, 1, 0.3, 1); -} - -html.awesome-codex-theme-experience[data-awesome-codex-surface="home"] #awesome-codex-theme-chrome .act-experience__identity { - opacity: 1; - transform: translateY(0); -} - -#awesome-codex-theme-chrome .act-experience__eyebrow, -#awesome-codex-theme-chrome .act-experience__tagline, -#awesome-codex-theme-chrome .act-experience__signature, -#awesome-codex-theme-chrome .act-experience__status-copy { - display: block; - font-family: "SFMono-Regular", Consolas, monospace; - text-transform: uppercase; - letter-spacing: 0.16em; -} - -#awesome-codex-theme-chrome .act-experience__eyebrow { - margin-bottom: 6px; - color: var(--act-accent); - font-size: 9px; - font-weight: 720; -} - -#awesome-codex-theme-chrome .act-experience__brand { - display: block; - color: #4d3f9f; - font-family: "Avenir Next Condensed", "Avenir Next", sans-serif; - font-size: clamp(48px, 5vw, 72px); - font-weight: 800; - letter-spacing: -0.07em; - line-height: 0.82; - text-transform: uppercase; - text-shadow: 0 1px #ffffff; -} - -#awesome-codex-theme-chrome .act-experience__headline { - display: block; - margin-top: 12px; - color: var(--act-text); - font-family: "Avenir Next Condensed", "Avenir Next", sans-serif; - font-size: clamp(15px, 1.5vw, 21px); - font-weight: 680; - letter-spacing: 0.16em; - line-height: 1; - text-transform: uppercase; -} - -#awesome-codex-theme-chrome .act-experience__tagline { - margin-top: 12px; - color: var(--act-text-secondary); - font-size: 8px; -} - -#awesome-codex-theme-chrome .act-experience__signature { - margin-top: 12px; - color: var(--act-text-muted); - font-size: 7px; -} - -#awesome-codex-theme-chrome .act-experience__status { - position: absolute; - top: 17px; - right: 118px; - display: flex; - align-items: center; - gap: 7px; - padding: 6px 10px; - color: var(--act-text-secondary); - background: #ffffffd1; - border: 1px solid #7567b824; - border-radius: 999px; - box-shadow: 0 5px 16px #4034630f; - opacity: 0; - transform: translateY(-4px); - transition: - opacity 180ms ease, - transform 180ms ease; - -webkit-backdrop-filter: blur(12px); - backdrop-filter: blur(12px); -} - -html.awesome-codex-theme-experience[data-awesome-codex-surface="home"] #awesome-codex-theme-chrome .act-experience__status { - opacity: 1; - transform: translateY(0); -} - -#awesome-codex-theme-chrome .act-experience__status-dot { - width: 6px; - height: 6px; - border-radius: 50%; - background: var(--act-success); - box-shadow: 0 0 0 3px #1e9a6512; - animation: limitless-status 2.8s ease-in-out infinite; -} - -#awesome-codex-theme-chrome .act-experience__status-copy { - font-size: 7px; - font-weight: 650; -} - -#awesome-codex-theme-chrome .act-experience__orbit { - position: absolute; - top: 132px; - right: 8%; - width: 134px; - height: 134px; - border: 1px solid #6857c63d; - border-radius: 50%; - opacity: 0; -} - -html.awesome-codex-theme-experience[data-awesome-codex-surface="home"] #awesome-codex-theme-chrome .act-experience__orbit { - opacity: 0.48; - animation: limitless-orbit 28s linear infinite; -} - -#awesome-codex-theme-chrome .act-experience__orbit::before, -#awesome-codex-theme-chrome .act-experience__orbit::after { - content: ""; - position: absolute; - border: 1px solid #8070d82e; - border-radius: 50%; -} - -#awesome-codex-theme-chrome .act-experience__orbit::before { - inset: 20px -30px; - transform: rotate(24deg); -} - -#awesome-codex-theme-chrome .act-experience__orbit::after { - inset: -24px 38px; - transform: rotate(-27deg); -} - -#awesome-codex-theme-chrome .act-experience__particles { - position: absolute; - inset: 0; - opacity: 0; -} - -html.awesome-codex-theme-experience[data-awesome-codex-surface="home"] #awesome-codex-theme-chrome .act-experience__particles { - opacity: 0.42; -} - -#awesome-codex-theme-chrome .act-experience__particle { - --act-particle-x: calc(43% + var(--act-particle-index) * 7%); - --act-particle-y: calc(14% + var(--act-particle-index) * 8%); - position: absolute; - top: var(--act-particle-y); - left: var(--act-particle-x); - width: calc(2px + var(--act-particle-index) * 0.35px); - height: calc(2px + var(--act-particle-index) * 0.35px); - border-radius: 50%; - background: var(--act-accent); - box-shadow: 0 0 10px #6752df61; - animation: limitless-particle 6s ease-in-out infinite alternate; - animation-delay: calc(var(--act-particle-index) * -0.8s); -} - -@keyframes limitless-status { - 50% { - opacity: 0.55; - transform: scale(0.86); - } -} - -@keyframes limitless-orbit { - to { - transform: rotate(360deg); - } -} - -@keyframes limitless-particle { - to { - transform: translate3d(12px, -14px, 0) scale(1.3); - opacity: 0.2; - } -} - -@media (max-width: 1100px) { - #awesome-codex-theme-chrome .act-experience__identity { - left: 300px; - width: 330px; - } - - #awesome-codex-theme-chrome .act-experience__brand { - font-size: 52px; - } - - #awesome-codex-theme-chrome .act-experience__orbit { - display: none; - } -} - -@media (max-width: 820px) { - #awesome-codex-theme-chrome .act-experience__identity, - #awesome-codex-theme-chrome .act-experience__status { - display: none; - } - - html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] [data-awesome-codex-home="true"] > div:first-child > div:first-child > div:first-child { - min-height: 218px !important; - background: - linear-gradient(90deg, #fbfafff7, #f6f3fbd4 52%, transparent 88%), - var(--act-artwork) !important; - } -} - -@media (prefers-reduced-motion: reduce) { - html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] *, - html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] *::before, - html.awesome-codex-theme[data-awesome-codex-theme="limitless-six-eyes"] *::after, - #awesome-codex-theme-chrome *, - #awesome-codex-theme-chrome *::before, - #awesome-codex-theme-chrome *::after { - scroll-behavior: auto !important; - transition-duration: 0.01ms !important; - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - } -} diff --git a/themes/limitless-six-eyes/theme.json b/themes/limitless-six-eyes/theme.json deleted file mode 100644 index 59a4256..0000000 --- a/themes/limitless-six-eyes/theme.json +++ /dev/null @@ -1,86 +0,0 @@ -{ - "schemaVersion": 2, - "slug": "limitless-six-eyes", - "version": "0.1.0", - "name": "Limitless Six Eyes", - "description": "A porcelain-white editorial workspace with iris-violet telemetry, technical geometry, and a midnight code cockpit.", - "author": { - "name": "Awesome Codex Themes" - }, - "license": { - "code": "MIT", - "artwork": "PROTOTYPE-REFERENCE-ONLY" - }, - "categories": [ - "light", - "editorial" - ], - "tags": [ - "featured", - "full-workspace", - "composer", - "editorial", - "lavender", - "glass", - "anime-prototype" - ], - "compatibility": { - "platforms": [ - "macos" - ], - "status": "experimental", - "appVersions": [ - "26.707.*" - ] - }, - "mode": "light", - "palette": { - "background": "#F2F1F8", - "scrim": "#F2F1F8E8", - "surface": "#F8F7FCDD", - "surfaceElevated": "#FFFFFFF2", - "surfaceOverlay": "#FCFBFFF7", - "input": "#F4F2FAF2", - "text": "#1C1930", - "textSecondary": "#494260", - "textMuted": "#77708E", - "textDisabled": "#AAA4B9", - "icon": "#2C2648", - "iconSecondary": "#675F81", - "iconMuted": "#958EA8", - "border": "#7567B83D", - "borderSubtle": "#7567B824", - "borderStrong": "#6654C47A", - "accent": "#5E4BD8", - "accentHover": "#7461EC", - "selection": "#9D8EFF52", - "focus": "#6E5AE6", - "link": "#5544C8", - "hover": "#7567B81C", - "active": "#7567B833", - "code": "#17152BFA", - "terminal": "#100F20FA", - "diffAdded": "#1E7E5740", - "diffRemoved": "#D94F7040", - "success": "#1E9A65", - "warning": "#B97B13", - "danger": "#D44761", - "scrollbar": "#7166A83D", - "scrollbarHover": "#6559AD75", - "composer": "#FFFFFFF2" - }, - "experience": { - "brand": "LIMITLESS", - "eyebrow": "SIX EYES", - "headline": "LIMITLESS WORKSPACE", - "tagline": "Plan beyond the visible.", - "status": "LIMITLESS ONLINE", - "signature": "SATORU GOJO", - "chrome": true - }, - "files": { - "css": "theme.css", - "artwork": "background.jpg", - "preview": "preview.png" - } -} diff --git a/themes/mordor-runtime/ASSET_LICENSE.md b/themes/mordor-runtime/ASSET_LICENSE.md new file mode 100644 index 0000000..615cac9 --- /dev/null +++ b/themes/mordor-runtime/ASSET_LICENSE.md @@ -0,0 +1,11 @@ +# Mordor Runtime assets + +Asset identifier: `PROJECT-ASSET` + +The project maintainer supplied and approved the artwork and generated UI assets included in this theme package. + +- Background source: `09-nazgul-ringwraiths.png` +- Marketing source: `09-nazgul-standard-codex-imagegen-v1.png` +- Runtime files: `background.jpg`, `preview.jpg`, and the local files under `assets/` + +Code and CSS remain covered by the repository MIT license. diff --git a/themes/mordor-runtime/README.md b/themes/mordor-runtime/README.md new file mode 100644 index 0000000..310f5c7 --- /dev/null +++ b/themes/mordor-runtime/README.md @@ -0,0 +1,11 @@ +# Mordor Runtime + +Schema 2 installable package for Codex Desktop on macOS. + +- Version: 0.1.1 +- Compatibility: experimental for 26.707.* and 26.715.* +- Runtime artwork: background.jpg +- Right-panel-safe artwork: assets/workspace-panel-safe.jpg +- Original component assets: forge-notch, crown-spike, ember-slash, chain-link, gate-send-chevron +- Live verification: passed on Codex Desktop 26.715.21425; compatibility remains experimental +- Asset record: maintainer-supplied `PROJECT-ASSET`; see `ASSET_LICENSE.md` diff --git a/themes/mordor-runtime/assets/chain-link.svg b/themes/mordor-runtime/assets/chain-link.svg new file mode 100644 index 0000000..26bd3d2 --- /dev/null +++ b/themes/mordor-runtime/assets/chain-link.svg @@ -0,0 +1,4 @@ + +chain link + + diff --git a/themes/mordor-runtime/assets/crown-spike.svg b/themes/mordor-runtime/assets/crown-spike.svg new file mode 100644 index 0000000..802c38e --- /dev/null +++ b/themes/mordor-runtime/assets/crown-spike.svg @@ -0,0 +1,4 @@ + +crown spike + + diff --git a/themes/mordor-runtime/assets/ember-slash.svg b/themes/mordor-runtime/assets/ember-slash.svg new file mode 100644 index 0000000..1d4629a --- /dev/null +++ b/themes/mordor-runtime/assets/ember-slash.svg @@ -0,0 +1,4 @@ + +ember slash + + diff --git a/themes/mordor-runtime/assets/forge-notch.svg b/themes/mordor-runtime/assets/forge-notch.svg new file mode 100644 index 0000000..c176333 --- /dev/null +++ b/themes/mordor-runtime/assets/forge-notch.svg @@ -0,0 +1,4 @@ + +forge notch + + diff --git a/themes/mordor-runtime/assets/gate-send-chevron.svg b/themes/mordor-runtime/assets/gate-send-chevron.svg new file mode 100644 index 0000000..d5482aa --- /dev/null +++ b/themes/mordor-runtime/assets/gate-send-chevron.svg @@ -0,0 +1,4 @@ + +gate send chevron + + diff --git a/themes/mordor-runtime/assets/workspace-panel-safe.jpg b/themes/mordor-runtime/assets/workspace-panel-safe.jpg new file mode 100644 index 0000000..9a8ac6c Binary files /dev/null and b/themes/mordor-runtime/assets/workspace-panel-safe.jpg differ diff --git a/themes/mordor-runtime/background.jpg b/themes/mordor-runtime/background.jpg new file mode 100644 index 0000000..b1e1e70 Binary files /dev/null and b/themes/mordor-runtime/background.jpg differ diff --git a/themes/mordor-runtime/preview.jpg b/themes/mordor-runtime/preview.jpg new file mode 100644 index 0000000..645317b Binary files /dev/null and b/themes/mordor-runtime/preview.jpg differ diff --git a/themes/mordor-runtime/theme.css b/themes/mordor-runtime/theme.css new file mode 100644 index 0000000..cee8cc0 --- /dev/null +++ b/themes/mordor-runtime/theme.css @@ -0,0 +1,175 @@ +html.awesome-codex-theme[data-awesome-codex-theme="mordor-runtime"] { + --batch-theme-signature: "mordor-runtime"; + --batch-radius: 0; + --batch-radius-lg: 2px; + --batch-edge: polygon(2% 0, 98% 0, 100% 18%, 98% 100%, 2% 100%, 0 82%); + --batch-border-width: 1px; + --batch-display-font: Copperplate, Avenir Next Condensed, Helvetica Neue, sans-serif; + --batch-code-font: SFMono-Regular, Menlo, Consolas, monospace; + --batch-mark: var(--act-asset-forge-notch); + background-position: center !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="mordor-runtime"][data-awesome-codex-artifact-panel-state="open"] { + background-image: linear-gradient(115deg, var(--act-scrim), color-mix(in srgb, var(--act-scrim) 54%, transparent)), var(--act-asset-workspace-panel-safe) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="mordor-runtime"] :is(h1, h2, h3, [data-awesome-codex-sidebar-brand="true"], #awesome-codex-theme-chrome) { + font-family: var(--batch-display-font) !important; + letter-spacing: 0.13em; + text-transform: uppercase; +} + +html.awesome-codex-theme[data-awesome-codex-theme="mordor-runtime"] :is(pre, code, kbd, samp, .monaco-editor, .terminal) { + font-family: var(--batch-code-font) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="mordor-runtime"] .app-shell-left-panel { + border-inline-end: var(--batch-border-width) solid var(--act-border-strong) !important; + box-shadow: 0 0 26px color-mix(in srgb, var(--act-danger) 24%, transparent) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="mordor-runtime"] [data-awesome-codex-sidebar-brand="true"] { + background-image: var(--act-asset-forge-notch) !important; + background-position: right 10px center !important; + background-repeat: no-repeat !important; + background-size: 24px 24px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="mordor-runtime"] :is([data-awesome-codex-sidebar-nav], [data-awesome-codex-domain-header], [data-awesome-codex-project-row], [data-awesome-codex-task-row]) { + border-radius: var(--batch-radius) !important; + transition: background-color 140ms ease, color 140ms ease, transform 140ms ease; +} + +html.awesome-codex-theme[data-awesome-codex-theme="mordor-runtime"] :is([data-awesome-codex-sidebar-nav], [data-awesome-codex-project-row], [data-awesome-codex-task-row]):is(:hover, [aria-current="page"], [data-state="active"]) { + background: linear-gradient(90deg, var(--act-active), var(--act-hover)) !important; + box-shadow: inset 3px 0 0 var(--act-accent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="mordor-runtime"] [data-awesome-codex-domain-header]::after { + content: ""; + display: inline-block; + width: 18px; + height: 18px; + margin-inline-start: auto; + background: var(--act-asset-crown-spike) center / contain no-repeat; + opacity: 0.82; +} + +html.awesome-codex-theme[data-awesome-codex-theme="mordor-runtime"] [data-awesome-codex-home="true"] { + position: relative; + isolation: isolate; +} + +html.awesome-codex-theme[data-awesome-codex-theme="mordor-runtime"] [data-awesome-codex-home-prompt="true"] { + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); + background: color-mix(in srgb, var(--act-surface-elevated) 88%, transparent) !important; + box-shadow: 0 0 26px color-mix(in srgb, var(--act-danger) 24%, transparent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="mordor-runtime"] :is(.composer-root, .composer-surface-chrome) { + border-width: var(--batch-border-width) !important; + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); +} + +html.awesome-codex-theme[data-awesome-codex-theme="mordor-runtime"] [data-awesome-codex-composer-control] { + min-width: 32px; + min-height: 32px; + border-radius: var(--batch-radius) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="mordor-runtime"] [data-awesome-codex-composer-control="attachment"] { + background-image: var(--act-asset-ember-slash) !important; + background-position: center !important; + background-repeat: no-repeat !important; + background-size: 15px 15px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="mordor-runtime"] [data-awesome-codex-composer-control="primary"] { + color: var(--act-background) !important; + background-color: var(--act-accent) !important; + background-image: var(--act-asset-gate-send-chevron) !important; + background-position: center !important; + background-repeat: no-repeat !important; + background-size: 19px 19px !important; + box-shadow: 0 0 26px color-mix(in srgb, var(--act-danger) 24%, transparent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="mordor-runtime"] [data-awesome-codex-artifact-panel="true"] { + background-color: color-mix(in srgb, var(--act-surface-elevated) 94%, transparent) !important; + background-image: linear-gradient(180deg, color-mix(in srgb, var(--act-surface-overlay) 92%, transparent), var(--act-surface-elevated)), var(--act-asset-workspace-panel-safe) !important; + background-position: center !important; + background-size: cover !important; + border-inline-start: var(--batch-border-width) solid var(--act-border-strong) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="mordor-runtime"] [data-awesome-codex-artifact-header="true"] { + border-block-end: var(--batch-border-width) solid var(--act-border) !important; + background-image: var(--act-asset-chain-link) !important; + background-position: right 14px center !important; + background-repeat: no-repeat !important; + background-size: 44px 10px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="mordor-runtime"] :is([role="dialog"], [role="menu"], [role="listbox"], [role="tooltip"], [data-awesome-codex-plugin-card], [data-awesome-codex-collection-row]) { + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); +} + +html.awesome-codex-theme[data-awesome-codex-theme="mordor-runtime"] :is([data-awesome-codex-plugin-card], [data-awesome-codex-collection-row]):hover { + border-color: var(--act-border-strong) !important; + background-color: var(--act-hover) !important; + transform: translateY(-1px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="mordor-runtime"] :is(input, textarea, select, [contenteditable="true"]):focus-visible { + outline: 2px solid var(--act-focus) !important; + outline-offset: 2px !important; + box-shadow: 0 0 0 4px color-mix(in srgb, var(--act-focus) 18%, transparent) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="mordor-runtime"] :is(.monaco-editor, .terminal, .xterm-viewport, pre, code) { + border-radius: var(--batch-radius) !important; + border-color: var(--act-border-subtle) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="mordor-runtime"] :is([data-awesome-codex-activity-item], [data-awesome-codex-completed-summary])::before { + content: ""; + display: inline-block; + width: 16px; + height: 16px; + margin-inline-end: 7px; + background: var(--act-asset-crown-spike) center / contain no-repeat; + vertical-align: -3px; +} + +html.awesome-codex-theme[data-awesome-codex-theme="mordor-runtime"] :is(button, [role="button"]):active { + transform: translateY(1px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="mordor-runtime"] *::-webkit-scrollbar-thumb { + border-radius: var(--batch-radius) !important; +} + +@media (max-width: 1120px) { + html.awesome-codex-theme[data-awesome-codex-theme="mordor-runtime"] :is([data-awesome-codex-project-row], [data-awesome-codex-task-row], [data-awesome-codex-collection-row]) { + min-width: 0; + } + html.awesome-codex-theme[data-awesome-codex-theme="mordor-runtime"] :is([data-awesome-codex-project-row], [data-awesome-codex-task-row], [data-awesome-codex-collection-row]) :is(span, p) { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} + +@media (prefers-reduced-motion: reduce) { + html.awesome-codex-theme[data-awesome-codex-theme="mordor-runtime"] *, + html.awesome-codex-theme[data-awesome-codex-theme="mordor-runtime"] *::before, + html.awesome-codex-theme[data-awesome-codex-theme="mordor-runtime"] *::after { + transition-duration: 0.01ms !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + } +} diff --git a/themes/mordor-runtime/theme.json b/themes/mordor-runtime/theme.json new file mode 100644 index 0000000..d220f9f --- /dev/null +++ b/themes/mordor-runtime/theme.json @@ -0,0 +1,93 @@ +{ + "schemaVersion": 2, + "slug": "mordor-runtime", + "version": "0.1.1", + "name": "Mordor Runtime", + "description": "A storm-black mounted formation crossing basalt and ember fissures beneath a distant volcanic watchtower.", + "author": { + "name": "Awesome Codex Themes" + }, + "license": { + "code": "MIT", + "artwork": "PROJECT-ASSET" + }, + "categories": [ + "dark", + "full-workspace", + "character-inspired" + ], + "tags": [ + "mordor-runtime", + "black-iron", + "forged-notches", + "macos" + ], + "compatibility": { + "platforms": [ + "macos" + ], + "status": "experimental", + "appVersions": [ + "26.707.*", + "26.715.*" + ] + }, + "mode": "dark", + "palette": { + "background": "#070809", + "scrim": "#070809E8", + "surface": "#101112DE", + "surfaceElevated": "#18191AF2", + "surfaceOverlay": "#18191AF7", + "input": "#131415F2", + "text": "#D9D4CA", + "textSecondary": "#ABA59B", + "textMuted": "#73716B", + "textDisabled": "#4A4A47", + "icon": "#D9D4CA", + "iconSecondary": "#ABA59B", + "iconMuted": "#73716B", + "border": "#F05A3B3D", + "borderSubtle": "#F05A3B24", + "borderStrong": "#F05A3B78", + "accent": "#F05A3B", + "accentHover": "#FF7658", + "selection": "#F05A3B38", + "focus": "#FF9A61", + "link": "#F37A5E", + "hover": "#F05A3B1A", + "active": "#FF9A612B", + "code": "#0A0B0CFA", + "terminal": "#050607FA", + "diffAdded": "#71876040", + "diffRemoved": "#E0443640", + "success": "#718760", + "warning": "#D78B3E", + "danger": "#E04436", + "scrollbar": "#F05A3B3D", + "scrollbarHover": "#F05A3B75", + "composer": "#0F1011F2" + }, + "experience": { + "brand": "MORDOR RUNTIME", + "eyebrow": "BLACK GATE", + "headline": "FORGE THE RUNTIME", + "tagline": "No noise survives the fire.", + "status": "WATCH // ACTIVE", + "signature": "RUNTIME IX", + "chrome": true + }, + "files": { + "css": "theme.css", + "artwork": "background.jpg", + "preview": "preview.jpg", + "assets": { + "workspace-panel-safe": "assets/workspace-panel-safe.jpg", + "forge-notch": "assets/forge-notch.svg", + "crown-spike": "assets/crown-spike.svg", + "ember-slash": "assets/ember-slash.svg", + "chain-link": "assets/chain-link.svg", + "gate-send-chevron": "assets/gate-send-chevron.svg" + } + } +} diff --git a/themes/new-world-studio/ASSET_LICENSE.md b/themes/new-world-studio/ASSET_LICENSE.md new file mode 100644 index 0000000..f6a0e54 --- /dev/null +++ b/themes/new-world-studio/ASSET_LICENSE.md @@ -0,0 +1,11 @@ +# New World Studio assets + +Asset identifier: `PROJECT-ASSET` + +The project maintainer supplied and approved the artwork and generated UI assets included in this theme package. + +- Background source: `12-one-piece-women.png` +- Marketing source: `12-one-piece-women-standard-codex-imagegen-v1.png` +- Runtime files: `background.jpg`, `preview.jpg`, and the local files under `assets/` + +Code and CSS remain covered by the repository MIT license. diff --git a/themes/new-world-studio/README.md b/themes/new-world-studio/README.md new file mode 100644 index 0000000..0d0b816 --- /dev/null +++ b/themes/new-world-studio/README.md @@ -0,0 +1,11 @@ +# New World Studio + +Schema 2 installable package for Codex Desktop on macOS. + +- Version: 0.1.1 +- Compatibility: experimental for 26.707.* and 26.715.* +- Runtime artwork: background.jpg +- Right-panel-safe artwork: assets/workspace-panel-safe.jpg +- Original component assets: tidal-contour, shell-fan, cartographic-star, wave-bracket, sail-send-mark +- Live verification: passed on Codex Desktop 26.715.21425; compatibility remains experimental +- Asset record: maintainer-supplied `PROJECT-ASSET`; see `ASSET_LICENSE.md` diff --git a/themes/new-world-studio/assets/cartographic-star.svg b/themes/new-world-studio/assets/cartographic-star.svg new file mode 100644 index 0000000..07afdde --- /dev/null +++ b/themes/new-world-studio/assets/cartographic-star.svg @@ -0,0 +1,4 @@ + +cartographic star + + diff --git a/themes/new-world-studio/assets/sail-send-mark.svg b/themes/new-world-studio/assets/sail-send-mark.svg new file mode 100644 index 0000000..9c2ee68 --- /dev/null +++ b/themes/new-world-studio/assets/sail-send-mark.svg @@ -0,0 +1,4 @@ + +sail send mark + + diff --git a/themes/new-world-studio/assets/shell-fan.svg b/themes/new-world-studio/assets/shell-fan.svg new file mode 100644 index 0000000..8150014 --- /dev/null +++ b/themes/new-world-studio/assets/shell-fan.svg @@ -0,0 +1,4 @@ + +shell fan + + diff --git a/themes/new-world-studio/assets/tidal-contour.svg b/themes/new-world-studio/assets/tidal-contour.svg new file mode 100644 index 0000000..5081827 --- /dev/null +++ b/themes/new-world-studio/assets/tidal-contour.svg @@ -0,0 +1,4 @@ + +tidal contour + + diff --git a/themes/new-world-studio/assets/wave-bracket.svg b/themes/new-world-studio/assets/wave-bracket.svg new file mode 100644 index 0000000..822288a --- /dev/null +++ b/themes/new-world-studio/assets/wave-bracket.svg @@ -0,0 +1,4 @@ + +wave bracket + + diff --git a/themes/new-world-studio/assets/workspace-panel-safe.jpg b/themes/new-world-studio/assets/workspace-panel-safe.jpg new file mode 100644 index 0000000..620d6d1 Binary files /dev/null and b/themes/new-world-studio/assets/workspace-panel-safe.jpg differ diff --git a/themes/new-world-studio/background.jpg b/themes/new-world-studio/background.jpg new file mode 100644 index 0000000..3fa6806 Binary files /dev/null and b/themes/new-world-studio/background.jpg differ diff --git a/themes/new-world-studio/preview.jpg b/themes/new-world-studio/preview.jpg new file mode 100644 index 0000000..38f9f53 Binary files /dev/null and b/themes/new-world-studio/preview.jpg differ diff --git a/themes/new-world-studio/theme.css b/themes/new-world-studio/theme.css new file mode 100644 index 0000000..38d0904 --- /dev/null +++ b/themes/new-world-studio/theme.css @@ -0,0 +1,175 @@ +html.awesome-codex-theme[data-awesome-codex-theme="new-world-studio"] { + --batch-theme-signature: "new-world-studio"; + --batch-radius: 5px; + --batch-radius-lg: 12px 4px 12px 4px; + --batch-edge: polygon(0 0, 97% 0, 100% 10%, 100% 100%, 3% 100%, 0 90%); + --batch-border-width: 1px; + --batch-display-font: Iowan Old Style, Baskerville, Songti SC, Georgia, serif; + --batch-code-font: SFMono-Regular, Menlo, Consolas, monospace; + --batch-mark: var(--act-asset-tidal-contour); + background-position: center !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="new-world-studio"][data-awesome-codex-artifact-panel-state="open"] { + background-image: linear-gradient(115deg, var(--act-scrim), color-mix(in srgb, var(--act-scrim) 54%, transparent)), var(--act-asset-workspace-panel-safe) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="new-world-studio"] :is(h1, h2, h3, [data-awesome-codex-sidebar-brand="true"], #awesome-codex-theme-chrome) { + font-family: var(--batch-display-font) !important; + letter-spacing: 0.09em; + text-transform: uppercase; +} + +html.awesome-codex-theme[data-awesome-codex-theme="new-world-studio"] :is(pre, code, kbd, samp, .monaco-editor, .terminal) { + font-family: var(--batch-code-font) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="new-world-studio"] .app-shell-left-panel { + border-inline-end: var(--batch-border-width) solid var(--act-border-strong) !important; + box-shadow: 8px 10px 0 color-mix(in srgb, var(--act-accent) 12%, transparent) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="new-world-studio"] [data-awesome-codex-sidebar-brand="true"] { + background-image: var(--act-asset-tidal-contour) !important; + background-position: right 10px center !important; + background-repeat: no-repeat !important; + background-size: 24px 24px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="new-world-studio"] :is([data-awesome-codex-sidebar-nav], [data-awesome-codex-domain-header], [data-awesome-codex-project-row], [data-awesome-codex-task-row]) { + border-radius: var(--batch-radius) !important; + transition: background-color 140ms ease, color 140ms ease, transform 140ms ease; +} + +html.awesome-codex-theme[data-awesome-codex-theme="new-world-studio"] :is([data-awesome-codex-sidebar-nav], [data-awesome-codex-project-row], [data-awesome-codex-task-row]):is(:hover, [aria-current="page"], [data-state="active"]) { + background: linear-gradient(90deg, var(--act-active), var(--act-hover)) !important; + box-shadow: inset 3px 0 0 var(--act-accent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="new-world-studio"] [data-awesome-codex-domain-header]::after { + content: ""; + display: inline-block; + width: 18px; + height: 18px; + margin-inline-start: auto; + background: var(--act-asset-shell-fan) center / contain no-repeat; + opacity: 0.82; +} + +html.awesome-codex-theme[data-awesome-codex-theme="new-world-studio"] [data-awesome-codex-home="true"] { + position: relative; + isolation: isolate; +} + +html.awesome-codex-theme[data-awesome-codex-theme="new-world-studio"] [data-awesome-codex-home-prompt="true"] { + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); + background: color-mix(in srgb, var(--act-surface-elevated) 88%, transparent) !important; + box-shadow: 8px 10px 0 color-mix(in srgb, var(--act-accent) 12%, transparent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="new-world-studio"] :is(.composer-root, .composer-surface-chrome) { + border-width: var(--batch-border-width) !important; + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); +} + +html.awesome-codex-theme[data-awesome-codex-theme="new-world-studio"] [data-awesome-codex-composer-control] { + min-width: 32px; + min-height: 32px; + border-radius: var(--batch-radius) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="new-world-studio"] [data-awesome-codex-composer-control="attachment"] { + background-image: var(--act-asset-cartographic-star) !important; + background-position: center !important; + background-repeat: no-repeat !important; + background-size: 15px 15px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="new-world-studio"] [data-awesome-codex-composer-control="primary"] { + color: var(--act-background) !important; + background-color: var(--act-accent) !important; + background-image: var(--act-asset-sail-send-mark) !important; + background-position: center !important; + background-repeat: no-repeat !important; + background-size: 19px 19px !important; + box-shadow: 8px 10px 0 color-mix(in srgb, var(--act-accent) 12%, transparent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="new-world-studio"] [data-awesome-codex-artifact-panel="true"] { + background-color: color-mix(in srgb, var(--act-surface-elevated) 94%, transparent) !important; + background-image: linear-gradient(180deg, color-mix(in srgb, var(--act-surface-overlay) 92%, transparent), var(--act-surface-elevated)), var(--act-asset-workspace-panel-safe) !important; + background-position: center !important; + background-size: cover !important; + border-inline-start: var(--batch-border-width) solid var(--act-border-strong) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="new-world-studio"] [data-awesome-codex-artifact-header="true"] { + border-block-end: var(--batch-border-width) solid var(--act-border) !important; + background-image: var(--act-asset-wave-bracket) !important; + background-position: right 14px center !important; + background-repeat: no-repeat !important; + background-size: 44px 10px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="new-world-studio"] :is([role="dialog"], [role="menu"], [role="listbox"], [role="tooltip"], [data-awesome-codex-plugin-card], [data-awesome-codex-collection-row]) { + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); +} + +html.awesome-codex-theme[data-awesome-codex-theme="new-world-studio"] :is([data-awesome-codex-plugin-card], [data-awesome-codex-collection-row]):hover { + border-color: var(--act-border-strong) !important; + background-color: var(--act-hover) !important; + transform: translateY(-1px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="new-world-studio"] :is(input, textarea, select, [contenteditable="true"]):focus-visible { + outline: 2px solid var(--act-focus) !important; + outline-offset: 2px !important; + box-shadow: 0 0 0 4px color-mix(in srgb, var(--act-focus) 18%, transparent) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="new-world-studio"] :is(.monaco-editor, .terminal, .xterm-viewport, pre, code) { + border-radius: var(--batch-radius) !important; + border-color: var(--act-border-subtle) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="new-world-studio"] :is([data-awesome-codex-activity-item], [data-awesome-codex-completed-summary])::before { + content: ""; + display: inline-block; + width: 16px; + height: 16px; + margin-inline-end: 7px; + background: var(--act-asset-shell-fan) center / contain no-repeat; + vertical-align: -3px; +} + +html.awesome-codex-theme[data-awesome-codex-theme="new-world-studio"] :is(button, [role="button"]):active { + transform: translateY(1px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="new-world-studio"] *::-webkit-scrollbar-thumb { + border-radius: var(--batch-radius) !important; +} + +@media (max-width: 1120px) { + html.awesome-codex-theme[data-awesome-codex-theme="new-world-studio"] :is([data-awesome-codex-project-row], [data-awesome-codex-task-row], [data-awesome-codex-collection-row]) { + min-width: 0; + } + html.awesome-codex-theme[data-awesome-codex-theme="new-world-studio"] :is([data-awesome-codex-project-row], [data-awesome-codex-task-row], [data-awesome-codex-collection-row]) :is(span, p) { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} + +@media (prefers-reduced-motion: reduce) { + html.awesome-codex-theme[data-awesome-codex-theme="new-world-studio"] *, + html.awesome-codex-theme[data-awesome-codex-theme="new-world-studio"] *::before, + html.awesome-codex-theme[data-awesome-codex-theme="new-world-studio"] *::after { + transition-duration: 0.01ms !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + } +} diff --git a/themes/new-world-studio/theme.json b/themes/new-world-studio/theme.json new file mode 100644 index 0000000..2ce0072 --- /dev/null +++ b/themes/new-world-studio/theme.json @@ -0,0 +1,93 @@ +{ + "schemaVersion": 2, + "slug": "new-world-studio", + "version": "0.1.1", + "name": "New World Studio", + "description": "A many-character ink-wash editorial composition over warm paper, black and red brush fields, brass flecks, blossoms, and ocean references.", + "author": { + "name": "Awesome Codex Themes" + }, + "license": { + "code": "MIT", + "artwork": "PROJECT-ASSET" + }, + "categories": [ + "light", + "full-workspace", + "character-inspired" + ], + "tags": [ + "new-world-studio", + "warm-kozo-paper", + "tidal-contour-lines", + "macos" + ], + "compatibility": { + "platforms": [ + "macos" + ], + "status": "experimental", + "appVersions": [ + "26.707.*", + "26.715.*" + ] + }, + "mode": "light", + "palette": { + "background": "#F0E6D2", + "scrim": "#F0E6D2E8", + "surface": "#E8DCC5DE", + "surfaceElevated": "#FAF2E3F2", + "surfaceOverlay": "#FAF2E3F7", + "input": "#F4E9D5F2", + "text": "#252017", + "textSecondary": "#51483B", + "textMuted": "#7B7060", + "textDisabled": "#AAA08F", + "icon": "#252017", + "iconSecondary": "#51483B", + "iconMuted": "#7B7060", + "border": "#A77A2C3D", + "borderSubtle": "#A77A2C24", + "borderStrong": "#A77A2C78", + "accent": "#A77A2C", + "accentHover": "#C49443", + "selection": "#A77A2C38", + "focus": "#A34D3F", + "link": "#365F73", + "hover": "#A77A2C1A", + "active": "#A34D3F2B", + "code": "#171A1BFA", + "terminal": "#0C0F10FA", + "diffAdded": "#4E7D5D40", + "diffRemoved": "#A7473E40", + "success": "#4E7D5D", + "warning": "#B47828", + "danger": "#A7473E", + "scrollbar": "#A77A2C3D", + "scrollbarHover": "#A77A2C75", + "composer": "#F1E5D0F2" + }, + "experience": { + "brand": "NEW WORLD STUDIO", + "eyebrow": "TIDAL EDITION", + "headline": "MAKE A WORLD WORTH SAILING", + "tagline": "Sketch boldly. Navigate together.", + "status": "TIDE // FAVORABLE", + "signature": "STUDIO XII", + "chrome": true + }, + "files": { + "css": "theme.css", + "artwork": "background.jpg", + "preview": "preview.jpg", + "assets": { + "workspace-panel-safe": "assets/workspace-panel-safe.jpg", + "tidal-contour": "assets/tidal-contour.svg", + "shell-fan": "assets/shell-fan.svg", + "cartographic-star": "assets/cartographic-star.svg", + "wave-bracket": "assets/wave-bracket.svg", + "sail-send-mark": "assets/sail-send-mark.svg" + } + } +} diff --git a/themes/night-city/ASSET_LICENSE.md b/themes/night-city/ASSET_LICENSE.md new file mode 100644 index 0000000..0637abb --- /dev/null +++ b/themes/night-city/ASSET_LICENSE.md @@ -0,0 +1,11 @@ +# Night City assets + +Asset identifier: `PROJECT-ASSET` + +The project maintainer supplied and approved the artwork and generated UI assets included in this theme package. + +- Background source: `10-lucy.png` +- Marketing source: `10-lucy-standard-codex-imagegen-v1.png` +- Runtime files: `background.jpg`, `preview.jpg`, and the local files under `assets/` + +Code and CSS remain covered by the repository MIT license. diff --git a/themes/night-city/README.md b/themes/night-city/README.md new file mode 100644 index 0000000..81c89a0 --- /dev/null +++ b/themes/night-city/README.md @@ -0,0 +1,11 @@ +# Night City + +Schema 2 installable package for Codex Desktop on macOS. + +- Version: 0.1.1 +- Compatibility: experimental for 26.707.* and 26.715.* +- Runtime artwork: background.jpg +- Right-panel-safe artwork: assets/workspace-panel-safe.jpg +- Original component assets: lunar-phase, fiber-trace, cut-corner-reticle, rain-tick, pulse-send-wedge +- Live verification: passed on Codex Desktop 26.715.21425; compatibility remains experimental +- Asset record: maintainer-supplied `PROJECT-ASSET`; see `ASSET_LICENSE.md` diff --git a/themes/night-city/assets/cut-corner-reticle.svg b/themes/night-city/assets/cut-corner-reticle.svg new file mode 100644 index 0000000..3a25573 --- /dev/null +++ b/themes/night-city/assets/cut-corner-reticle.svg @@ -0,0 +1,4 @@ + +cut-corner reticle + + diff --git a/themes/night-city/assets/fiber-trace.svg b/themes/night-city/assets/fiber-trace.svg new file mode 100644 index 0000000..db67d41 --- /dev/null +++ b/themes/night-city/assets/fiber-trace.svg @@ -0,0 +1,4 @@ + +fiber trace + + diff --git a/themes/night-city/assets/lunar-phase.svg b/themes/night-city/assets/lunar-phase.svg new file mode 100644 index 0000000..15b0020 --- /dev/null +++ b/themes/night-city/assets/lunar-phase.svg @@ -0,0 +1,4 @@ + +lunar phase + + diff --git a/themes/night-city/assets/pulse-send-wedge.svg b/themes/night-city/assets/pulse-send-wedge.svg new file mode 100644 index 0000000..df545fc --- /dev/null +++ b/themes/night-city/assets/pulse-send-wedge.svg @@ -0,0 +1,4 @@ + +pulse send wedge + + diff --git a/themes/night-city/assets/rain-tick.svg b/themes/night-city/assets/rain-tick.svg new file mode 100644 index 0000000..aa42429 --- /dev/null +++ b/themes/night-city/assets/rain-tick.svg @@ -0,0 +1,4 @@ + +rain tick + + diff --git a/themes/night-city/assets/workspace-panel-safe.jpg b/themes/night-city/assets/workspace-panel-safe.jpg new file mode 100644 index 0000000..7628793 Binary files /dev/null and b/themes/night-city/assets/workspace-panel-safe.jpg differ diff --git a/themes/night-city/background.jpg b/themes/night-city/background.jpg new file mode 100644 index 0000000..844c4f6 Binary files /dev/null and b/themes/night-city/background.jpg differ diff --git a/themes/night-city/preview.jpg b/themes/night-city/preview.jpg new file mode 100644 index 0000000..fa3d4fa Binary files /dev/null and b/themes/night-city/preview.jpg differ diff --git a/themes/night-city/theme.css b/themes/night-city/theme.css new file mode 100644 index 0000000..bcc9322 --- /dev/null +++ b/themes/night-city/theme.css @@ -0,0 +1,175 @@ +html.awesome-codex-theme[data-awesome-codex-theme="night-city"] { + --batch-theme-signature: "night-city"; + --batch-radius: 3px; + --batch-radius-lg: 10px 3px 10px 3px; + --batch-edge: polygon(0 0, 94% 0, 100% 18%, 100% 100%, 6% 100%, 0 82%); + --batch-border-width: 1px; + --batch-display-font: Avenir Next, Helvetica Neue, sans-serif; + --batch-code-font: SFMono-Regular, Menlo, Consolas, monospace; + --batch-mark: var(--act-asset-lunar-phase); + background-position: center !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="night-city"][data-awesome-codex-artifact-panel-state="open"] { + background-image: linear-gradient(115deg, var(--act-scrim), color-mix(in srgb, var(--act-scrim) 54%, transparent)), var(--act-asset-workspace-panel-safe) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="night-city"] :is(h1, h2, h3, [data-awesome-codex-sidebar-brand="true"], #awesome-codex-theme-chrome) { + font-family: var(--batch-display-font) !important; + letter-spacing: 0.11em; + text-transform: uppercase; +} + +html.awesome-codex-theme[data-awesome-codex-theme="night-city"] :is(pre, code, kbd, samp, .monaco-editor, .terminal) { + font-family: var(--batch-code-font) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="night-city"] .app-shell-left-panel { + border-inline-end: var(--batch-border-width) solid var(--act-border-strong) !important; + box-shadow: 12px 12px 0 color-mix(in srgb, var(--act-accent) 12%, transparent) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="night-city"] [data-awesome-codex-sidebar-brand="true"] { + background-image: var(--act-asset-lunar-phase) !important; + background-position: right 10px center !important; + background-repeat: no-repeat !important; + background-size: 24px 24px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="night-city"] :is([data-awesome-codex-sidebar-nav], [data-awesome-codex-domain-header], [data-awesome-codex-project-row], [data-awesome-codex-task-row]) { + border-radius: var(--batch-radius) !important; + transition: background-color 140ms ease, color 140ms ease, transform 140ms ease; +} + +html.awesome-codex-theme[data-awesome-codex-theme="night-city"] :is([data-awesome-codex-sidebar-nav], [data-awesome-codex-project-row], [data-awesome-codex-task-row]):is(:hover, [aria-current="page"], [data-state="active"]) { + background: linear-gradient(90deg, var(--act-active), var(--act-hover)) !important; + box-shadow: inset 3px 0 0 var(--act-accent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="night-city"] [data-awesome-codex-domain-header]::after { + content: ""; + display: inline-block; + width: 18px; + height: 18px; + margin-inline-start: auto; + background: var(--act-asset-fiber-trace) center / contain no-repeat; + opacity: 0.82; +} + +html.awesome-codex-theme[data-awesome-codex-theme="night-city"] [data-awesome-codex-home="true"] { + position: relative; + isolation: isolate; +} + +html.awesome-codex-theme[data-awesome-codex-theme="night-city"] [data-awesome-codex-home-prompt="true"] { + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); + background: color-mix(in srgb, var(--act-surface-elevated) 88%, transparent) !important; + box-shadow: 12px 12px 0 color-mix(in srgb, var(--act-accent) 12%, transparent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="night-city"] :is(.composer-root, .composer-surface-chrome) { + border-width: var(--batch-border-width) !important; + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); +} + +html.awesome-codex-theme[data-awesome-codex-theme="night-city"] [data-awesome-codex-composer-control] { + min-width: 32px; + min-height: 32px; + border-radius: var(--batch-radius) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="night-city"] [data-awesome-codex-composer-control="attachment"] { + background-image: var(--act-asset-cut-corner-reticle) !important; + background-position: center !important; + background-repeat: no-repeat !important; + background-size: 15px 15px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="night-city"] [data-awesome-codex-composer-control="primary"] { + color: var(--act-background) !important; + background-color: var(--act-accent) !important; + background-image: var(--act-asset-pulse-send-wedge) !important; + background-position: center !important; + background-repeat: no-repeat !important; + background-size: 19px 19px !important; + box-shadow: 12px 12px 0 color-mix(in srgb, var(--act-accent) 12%, transparent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="night-city"] [data-awesome-codex-artifact-panel="true"] { + background-color: color-mix(in srgb, var(--act-surface-elevated) 94%, transparent) !important; + background-image: linear-gradient(180deg, color-mix(in srgb, var(--act-surface-overlay) 92%, transparent), var(--act-surface-elevated)), var(--act-asset-workspace-panel-safe) !important; + background-position: center !important; + background-size: cover !important; + border-inline-start: var(--batch-border-width) solid var(--act-border-strong) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="night-city"] [data-awesome-codex-artifact-header="true"] { + border-block-end: var(--batch-border-width) solid var(--act-border) !important; + background-image: var(--act-asset-rain-tick) !important; + background-position: right 14px center !important; + background-repeat: no-repeat !important; + background-size: 44px 10px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="night-city"] :is([role="dialog"], [role="menu"], [role="listbox"], [role="tooltip"], [data-awesome-codex-plugin-card], [data-awesome-codex-collection-row]) { + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); +} + +html.awesome-codex-theme[data-awesome-codex-theme="night-city"] :is([data-awesome-codex-plugin-card], [data-awesome-codex-collection-row]):hover { + border-color: var(--act-border-strong) !important; + background-color: var(--act-hover) !important; + transform: translateY(-1px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="night-city"] :is(input, textarea, select, [contenteditable="true"]):focus-visible { + outline: 2px solid var(--act-focus) !important; + outline-offset: 2px !important; + box-shadow: 0 0 0 4px color-mix(in srgb, var(--act-focus) 18%, transparent) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="night-city"] :is(.monaco-editor, .terminal, .xterm-viewport, pre, code) { + border-radius: var(--batch-radius) !important; + border-color: var(--act-border-subtle) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="night-city"] :is([data-awesome-codex-activity-item], [data-awesome-codex-completed-summary])::before { + content: ""; + display: inline-block; + width: 16px; + height: 16px; + margin-inline-end: 7px; + background: var(--act-asset-fiber-trace) center / contain no-repeat; + vertical-align: -3px; +} + +html.awesome-codex-theme[data-awesome-codex-theme="night-city"] :is(button, [role="button"]):active { + transform: translateY(1px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="night-city"] *::-webkit-scrollbar-thumb { + border-radius: var(--batch-radius) !important; +} + +@media (max-width: 1120px) { + html.awesome-codex-theme[data-awesome-codex-theme="night-city"] :is([data-awesome-codex-project-row], [data-awesome-codex-task-row], [data-awesome-codex-collection-row]) { + min-width: 0; + } + html.awesome-codex-theme[data-awesome-codex-theme="night-city"] :is([data-awesome-codex-project-row], [data-awesome-codex-task-row], [data-awesome-codex-collection-row]) :is(span, p) { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} + +@media (prefers-reduced-motion: reduce) { + html.awesome-codex-theme[data-awesome-codex-theme="night-city"] *, + html.awesome-codex-theme[data-awesome-codex-theme="night-city"] *::before, + html.awesome-codex-theme[data-awesome-codex-theme="night-city"] *::after { + transition-duration: 0.01ms !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + } +} diff --git a/themes/night-city/theme.json b/themes/night-city/theme.json new file mode 100644 index 0000000..e4857e6 --- /dev/null +++ b/themes/night-city/theme.json @@ -0,0 +1,93 @@ +{ + "schemaVersion": 2, + "slug": "night-city", + "version": "0.1.1", + "name": "Night City", + "description": "A moonlit high-rise interior overlooking a rain-blue megacity, with a pale figure, glass reflections, and sparse magenta signal lights.", + "author": { + "name": "Awesome Codex Themes" + }, + "license": { + "code": "MIT", + "artwork": "PROJECT-ASSET" + }, + "categories": [ + "dark", + "full-workspace", + "character-inspired" + ], + "tags": [ + "night-city", + "tinted-glass", + "cut-corner-panes", + "macos" + ], + "compatibility": { + "platforms": [ + "macos" + ], + "status": "experimental", + "appVersions": [ + "26.707.*", + "26.715.*" + ] + }, + "mode": "dark", + "palette": { + "background": "#050E1A", + "scrim": "#050E1AE8", + "surface": "#091827DE", + "surfaceElevated": "#102238F2", + "surfaceOverlay": "#102238F7", + "input": "#0B1C2DF2", + "text": "#EAF8FF", + "textSecondary": "#BAD5E2", + "textMuted": "#7798A8", + "textDisabled": "#4B6572", + "icon": "#EAF8FF", + "iconSecondary": "#BAD5E2", + "iconMuted": "#7798A8", + "border": "#20DFFF3D", + "borderSubtle": "#20DFFF24", + "borderStrong": "#20DFFF78", + "accent": "#20DFFF", + "accentHover": "#65ECFF", + "selection": "#20DFFF38", + "focus": "#F05DC7", + "link": "#48E4F5", + "hover": "#20DFFF1A", + "active": "#F05DC72B", + "code": "#06111DFA", + "terminal": "#030A12FA", + "diffAdded": "#6DDC9B40", + "diffRemoved": "#F05D7240", + "success": "#6DDC9B", + "warning": "#F0B451", + "danger": "#F05D72", + "scrollbar": "#20DFFF3D", + "scrollbarHover": "#20DFFF75", + "composer": "#081827F2" + }, + "experience": { + "brand": "NIGHT CITY", + "eyebrow": "LUNAR LINK", + "headline": "RUN THE CITY AFTER DARK", + "tagline": "Trace signals. Cut latency.", + "status": "NEURAL LINK // CLEAN", + "signature": "NC-10", + "chrome": true + }, + "files": { + "css": "theme.css", + "artwork": "background.jpg", + "preview": "preview.jpg", + "assets": { + "workspace-panel-safe": "assets/workspace-panel-safe.jpg", + "lunar-phase": "assets/lunar-phase.svg", + "fiber-trace": "assets/fiber-trace.svg", + "cut-corner-reticle": "assets/cut-corner-reticle.svg", + "rain-tick": "assets/rain-tick.svg", + "pulse-send-wedge": "assets/pulse-send-wedge.svg" + } + } +} diff --git a/themes/obsidian-bloom/ASSET_LICENSE.md b/themes/obsidian-bloom/ASSET_LICENSE.md deleted file mode 100644 index 8d080c0..0000000 --- a/themes/obsidian-bloom/ASSET_LICENSE.md +++ /dev/null @@ -1,3 +0,0 @@ -# Obsidian Bloom asset license - -`background.svg` and `preview.svg` were created specifically for Awesome Codex Themes and are released under CC0 1.0. The theme CSS is covered by the repository MIT license. diff --git a/themes/obsidian-bloom/background.svg b/themes/obsidian-bloom/background.svg deleted file mode 100644 index 0806178..0000000 --- a/themes/obsidian-bloom/background.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/themes/obsidian-bloom/preview.svg b/themes/obsidian-bloom/preview.svg deleted file mode 100644 index 60341a6..0000000 --- a/themes/obsidian-bloom/preview.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - OBSIDIAN BLOOM - WORKTREE / FOCUSMake the interface feel alive. - APPLY THEME - Ask Codex to build, explain, or repair... - diff --git a/themes/obsidian-bloom/theme.css b/themes/obsidian-bloom/theme.css deleted file mode 100644 index df89e6b..0000000 --- a/themes/obsidian-bloom/theme.css +++ /dev/null @@ -1,24 +0,0 @@ -html.awesome-codex-theme body { - color: var(--act-text); - background-color: var(--act-background); - background-image: linear-gradient(100deg, #100F0EF2 12%, #100F0EC4 56%, #100F0EEB), var(--act-artwork); - background-position: center; - background-size: cover; - background-attachment: fixed; -} - -html.awesome-codex-theme :is(aside, header, nav, [role="dialog"]) { - background-color: color-mix(in srgb, var(--act-surface) 88%, transparent); - border-color: color-mix(in srgb, var(--act-accent) 18%, transparent); - backdrop-filter: blur(24px) saturate(112%); -} - -html.awesome-codex-theme :is(textarea, input, pre, code) { - border-color: color-mix(in srgb, var(--act-accent) 16%, transparent); - caret-color: var(--act-accent); -} - -html.awesome-codex-theme :is(button, [role="button"]):focus-visible { - outline: 2px solid var(--act-accent); - outline-offset: 2px; -} diff --git a/themes/obsidian-bloom/theme.json b/themes/obsidian-bloom/theme.json deleted file mode 100644 index 15ed818..0000000 --- a/themes/obsidian-bloom/theme.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "schemaVersion": 2, - "slug": "obsidian-bloom", - "version": "1.1.0", - "name": "Obsidian Bloom", - "description": "A charcoal garden with ember-orange petals and softly layered glass surfaces.", - "author": { "name": "Awesome Codex Themes" }, - "license": { "code": "MIT", "artwork": "CC0-1.0" }, - "categories": ["dark", "organic"], - "tags": ["charcoal", "orange", "botanical", "glass", "full-workspace"], - "compatibility": { - "platforms": ["macos"], - "status": "experimental", - "appVersions": ["26.707.*"] - }, - "mode": "dark", - "palette": { - "background": "#100F0E", - "scrim": "#100F0ED9", - "surface": "#201D1ACC", - "surfaceElevated": "#29241FEE", - "surfaceOverlay": "#171411F2", - "input": "#24201CE6", - "text": "#F5EDE3", - "textSecondary": "#D8CFC4", - "textMuted": "#A79D91", - "textDisabled": "#756D64", - "icon": "#F5EDE3", - "iconSecondary": "#C6BCAF", - "iconMuted": "#8F867C", - "border": "#FFB18A38", - "borderSubtle": "#FFB18A1F", - "borderStrong": "#FFB18A70", - "accent": "#FF7849", - "accentHover": "#FF936C", - "selection": "#FF78494D", - "focus": "#FFB18A", - "link": "#FF9B75", - "hover": "#FFB18A1F", - "active": "#FF784938", - "code": "#0C0B0AF2", - "terminal": "#080706F2", - "diffAdded": "#2DAA6A42", - "diffRemoved": "#FF5F5242", - "success": "#58D68D", - "warning": "#F6C85F", - "danger": "#FF6B64", - "scrollbar": "#FFB18A45", - "scrollbarHover": "#FFB18A78", - "composer": "#191613F2" - }, - "files": { "css": "theme.css", "artwork": "background.svg", "preview": "preview.svg" } -} diff --git a/themes/overworld-realms/ASSET_LICENSE.md b/themes/overworld-realms/ASSET_LICENSE.md new file mode 100644 index 0000000..e9c2ef1 --- /dev/null +++ b/themes/overworld-realms/ASSET_LICENSE.md @@ -0,0 +1,11 @@ +# Overworld Realms assets + +Asset identifier: `PROJECT-ASSET` + +The project maintainer supplied and approved the artwork and generated UI assets included in this theme package. + +- Background source: `08-minecraft-realms.png` +- Marketing source: `08-minecraft-standard-codex-imagegen-v1.png` +- Runtime files: `background.jpg`, `preview.jpg`, and the local files under `assets/` + +Code and CSS remain covered by the repository MIT license. diff --git a/themes/overworld-realms/README.md b/themes/overworld-realms/README.md new file mode 100644 index 0000000..5c4344f --- /dev/null +++ b/themes/overworld-realms/README.md @@ -0,0 +1,11 @@ +# Overworld Realms + +Schema 2 installable package for Codex Desktop on macOS. + +- Version: 0.1.1 +- Compatibility: experimental for 26.707.* and 26.715.* +- Runtime artwork: background.jpg +- Right-panel-safe artwork: assets/workspace-panel-safe.jpg +- Original component assets: voxel-cube, ore-diamond, chunk-bracket, torch-meter, portal-send-tile +- Live verification: passed on Codex Desktop 26.715.21425; compatibility remains experimental +- Asset record: maintainer-supplied `PROJECT-ASSET`; see `ASSET_LICENSE.md` diff --git a/themes/overworld-realms/assets/chunk-bracket.svg b/themes/overworld-realms/assets/chunk-bracket.svg new file mode 100644 index 0000000..a31b016 --- /dev/null +++ b/themes/overworld-realms/assets/chunk-bracket.svg @@ -0,0 +1,4 @@ + +chunk bracket + + diff --git a/themes/overworld-realms/assets/ore-diamond.svg b/themes/overworld-realms/assets/ore-diamond.svg new file mode 100644 index 0000000..09b974a --- /dev/null +++ b/themes/overworld-realms/assets/ore-diamond.svg @@ -0,0 +1,4 @@ + +ore diamond + + diff --git a/themes/overworld-realms/assets/portal-send-tile.svg b/themes/overworld-realms/assets/portal-send-tile.svg new file mode 100644 index 0000000..257d395 --- /dev/null +++ b/themes/overworld-realms/assets/portal-send-tile.svg @@ -0,0 +1,4 @@ + +portal send tile + + diff --git a/themes/overworld-realms/assets/torch-meter.svg b/themes/overworld-realms/assets/torch-meter.svg new file mode 100644 index 0000000..96fa758 --- /dev/null +++ b/themes/overworld-realms/assets/torch-meter.svg @@ -0,0 +1,4 @@ + +torch meter + + diff --git a/themes/overworld-realms/assets/voxel-cube.svg b/themes/overworld-realms/assets/voxel-cube.svg new file mode 100644 index 0000000..a187107 --- /dev/null +++ b/themes/overworld-realms/assets/voxel-cube.svg @@ -0,0 +1,4 @@ + +voxel cube + + diff --git a/themes/overworld-realms/assets/workspace-panel-safe.jpg b/themes/overworld-realms/assets/workspace-panel-safe.jpg new file mode 100644 index 0000000..94846ab Binary files /dev/null and b/themes/overworld-realms/assets/workspace-panel-safe.jpg differ diff --git a/themes/overworld-realms/background.jpg b/themes/overworld-realms/background.jpg new file mode 100644 index 0000000..9a751db Binary files /dev/null and b/themes/overworld-realms/background.jpg differ diff --git a/themes/overworld-realms/preview.jpg b/themes/overworld-realms/preview.jpg new file mode 100644 index 0000000..697c4d1 Binary files /dev/null and b/themes/overworld-realms/preview.jpg differ diff --git a/themes/overworld-realms/theme.css b/themes/overworld-realms/theme.css new file mode 100644 index 0000000..42e60c2 --- /dev/null +++ b/themes/overworld-realms/theme.css @@ -0,0 +1,175 @@ +html.awesome-codex-theme[data-awesome-codex-theme="overworld-realms"] { + --batch-theme-signature: "overworld-realms"; + --batch-radius: 0; + --batch-radius-lg: 0; + --batch-edge: none; + --batch-border-width: 2px; + --batch-display-font: Avenir Next, Arial Rounded MT Bold, Helvetica Neue, sans-serif; + --batch-code-font: SFMono-Regular, Menlo, Consolas, monospace; + --batch-mark: var(--act-asset-voxel-cube); + background-position: center !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="overworld-realms"][data-awesome-codex-artifact-panel-state="open"] { + background-image: linear-gradient(115deg, var(--act-scrim), color-mix(in srgb, var(--act-scrim) 54%, transparent)), var(--act-asset-workspace-panel-safe) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="overworld-realms"] :is(h1, h2, h3, [data-awesome-codex-sidebar-brand="true"], #awesome-codex-theme-chrome) { + font-family: var(--batch-display-font) !important; + letter-spacing: 0.04em; + text-transform: uppercase; +} + +html.awesome-codex-theme[data-awesome-codex-theme="overworld-realms"] :is(pre, code, kbd, samp, .monaco-editor, .terminal) { + font-family: var(--batch-code-font) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="overworld-realms"] .app-shell-left-panel { + border-inline-end: var(--batch-border-width) solid var(--act-border-strong) !important; + box-shadow: 4px 4px 0 color-mix(in srgb, #000000 35%, transparent) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="overworld-realms"] [data-awesome-codex-sidebar-brand="true"] { + background-image: var(--act-asset-voxel-cube) !important; + background-position: right 10px center !important; + background-repeat: no-repeat !important; + background-size: 24px 24px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="overworld-realms"] :is([data-awesome-codex-sidebar-nav], [data-awesome-codex-domain-header], [data-awesome-codex-project-row], [data-awesome-codex-task-row]) { + border-radius: var(--batch-radius) !important; + transition: background-color 140ms ease, color 140ms ease, transform 140ms ease; +} + +html.awesome-codex-theme[data-awesome-codex-theme="overworld-realms"] :is([data-awesome-codex-sidebar-nav], [data-awesome-codex-project-row], [data-awesome-codex-task-row]):is(:hover, [aria-current="page"], [data-state="active"]) { + background: linear-gradient(90deg, var(--act-active), var(--act-hover)) !important; + box-shadow: inset 3px 0 0 var(--act-accent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="overworld-realms"] [data-awesome-codex-domain-header]::after { + content: ""; + display: inline-block; + width: 18px; + height: 18px; + margin-inline-start: auto; + background: var(--act-asset-ore-diamond) center / contain no-repeat; + opacity: 0.82; +} + +html.awesome-codex-theme[data-awesome-codex-theme="overworld-realms"] [data-awesome-codex-home="true"] { + position: relative; + isolation: isolate; +} + +html.awesome-codex-theme[data-awesome-codex-theme="overworld-realms"] [data-awesome-codex-home-prompt="true"] { + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); + background: color-mix(in srgb, var(--act-surface-elevated) 88%, transparent) !important; + box-shadow: 4px 4px 0 color-mix(in srgb, #000000 35%, transparent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="overworld-realms"] :is(.composer-root, .composer-surface-chrome) { + border-width: var(--batch-border-width) !important; + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); +} + +html.awesome-codex-theme[data-awesome-codex-theme="overworld-realms"] [data-awesome-codex-composer-control] { + min-width: 32px; + min-height: 32px; + border-radius: var(--batch-radius) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="overworld-realms"] [data-awesome-codex-composer-control="attachment"] { + background-image: var(--act-asset-chunk-bracket) !important; + background-position: center !important; + background-repeat: no-repeat !important; + background-size: 15px 15px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="overworld-realms"] [data-awesome-codex-composer-control="primary"] { + color: var(--act-background) !important; + background-color: var(--act-accent) !important; + background-image: var(--act-asset-portal-send-tile) !important; + background-position: center !important; + background-repeat: no-repeat !important; + background-size: 19px 19px !important; + box-shadow: 4px 4px 0 color-mix(in srgb, #000000 35%, transparent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="overworld-realms"] [data-awesome-codex-artifact-panel="true"] { + background-color: color-mix(in srgb, var(--act-surface-elevated) 94%, transparent) !important; + background-image: linear-gradient(180deg, color-mix(in srgb, var(--act-surface-overlay) 92%, transparent), var(--act-surface-elevated)), var(--act-asset-workspace-panel-safe) !important; + background-position: center !important; + background-size: cover !important; + border-inline-start: var(--batch-border-width) solid var(--act-border-strong) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="overworld-realms"] [data-awesome-codex-artifact-header="true"] { + border-block-end: var(--batch-border-width) solid var(--act-border) !important; + background-image: var(--act-asset-torch-meter) !important; + background-position: right 14px center !important; + background-repeat: no-repeat !important; + background-size: 44px 10px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="overworld-realms"] :is([role="dialog"], [role="menu"], [role="listbox"], [role="tooltip"], [data-awesome-codex-plugin-card], [data-awesome-codex-collection-row]) { + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); +} + +html.awesome-codex-theme[data-awesome-codex-theme="overworld-realms"] :is([data-awesome-codex-plugin-card], [data-awesome-codex-collection-row]):hover { + border-color: var(--act-border-strong) !important; + background-color: var(--act-hover) !important; + transform: translateY(-1px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="overworld-realms"] :is(input, textarea, select, [contenteditable="true"]):focus-visible { + outline: 2px solid var(--act-focus) !important; + outline-offset: 2px !important; + box-shadow: 0 0 0 4px color-mix(in srgb, var(--act-focus) 18%, transparent) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="overworld-realms"] :is(.monaco-editor, .terminal, .xterm-viewport, pre, code) { + border-radius: var(--batch-radius) !important; + border-color: var(--act-border-subtle) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="overworld-realms"] :is([data-awesome-codex-activity-item], [data-awesome-codex-completed-summary])::before { + content: ""; + display: inline-block; + width: 16px; + height: 16px; + margin-inline-end: 7px; + background: var(--act-asset-ore-diamond) center / contain no-repeat; + vertical-align: -3px; +} + +html.awesome-codex-theme[data-awesome-codex-theme="overworld-realms"] :is(button, [role="button"]):active { + transform: translateY(1px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="overworld-realms"] *::-webkit-scrollbar-thumb { + border-radius: var(--batch-radius) !important; +} + +@media (max-width: 1120px) { + html.awesome-codex-theme[data-awesome-codex-theme="overworld-realms"] :is([data-awesome-codex-project-row], [data-awesome-codex-task-row], [data-awesome-codex-collection-row]) { + min-width: 0; + } + html.awesome-codex-theme[data-awesome-codex-theme="overworld-realms"] :is([data-awesome-codex-project-row], [data-awesome-codex-task-row], [data-awesome-codex-collection-row]) :is(span, p) { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} + +@media (prefers-reduced-motion: reduce) { + html.awesome-codex-theme[data-awesome-codex-theme="overworld-realms"] *, + html.awesome-codex-theme[data-awesome-codex-theme="overworld-realms"] *::before, + html.awesome-codex-theme[data-awesome-codex-theme="overworld-realms"] *::after { + transition-duration: 0.01ms !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + } +} diff --git a/themes/overworld-realms/theme.json b/themes/overworld-realms/theme.json new file mode 100644 index 0000000..7304fc2 --- /dev/null +++ b/themes/overworld-realms/theme.json @@ -0,0 +1,93 @@ +{ + "schemaVersion": 2, + "slug": "overworld-realms", + "version": "0.1.1", + "name": "Overworld Realms", + "description": "A cross-section of bright overworld village, deep mineral cave, lava seam, and violet portal rendered as an isometric voxel system.", + "author": { + "name": "Awesome Codex Themes" + }, + "license": { + "code": "MIT", + "artwork": "PROJECT-ASSET" + }, + "categories": [ + "dark", + "full-workspace", + "character-inspired" + ], + "tags": [ + "overworld-realms", + "deepslate", + "8px-voxel-grid", + "macos" + ], + "compatibility": { + "platforms": [ + "macos" + ], + "status": "experimental", + "appVersions": [ + "26.707.*", + "26.715.*" + ] + }, + "mode": "dark", + "palette": { + "background": "#0B1117", + "scrim": "#0B1117E8", + "surface": "#111A20DE", + "surfaceElevated": "#19242AF2", + "surfaceOverlay": "#19242AF7", + "input": "#131E24F2", + "text": "#E9F4F1", + "textSecondary": "#B9CBC5", + "textMuted": "#78908A", + "textDisabled": "#4E625E", + "icon": "#E9F4F1", + "iconSecondary": "#B9CBC5", + "iconMuted": "#78908A", + "border": "#27D7DA3D", + "borderSubtle": "#27D7DA24", + "borderStrong": "#27D7DA78", + "accent": "#27D7DA", + "accentHover": "#62F1EC", + "selection": "#27D7DA38", + "focus": "#B461E9", + "link": "#4DE6E2", + "hover": "#27D7DA1A", + "active": "#B461E92B", + "code": "#0A1116FA", + "terminal": "#060C10FA", + "diffAdded": "#71C85A40", + "diffRemoved": "#E3574F40", + "success": "#71C85A", + "warning": "#E89A36", + "danger": "#E3574F", + "scrollbar": "#27D7DA3D", + "scrollbarHover": "#27D7DA75", + "composer": "#10191EF2" + }, + "experience": { + "brand": "OVERWORLD REALMS", + "eyebrow": "CHUNK 08", + "headline": "BUILD THE NEXT REALM", + "tagline": "Mine context. Craft systems.", + "status": "REALM // LOADED", + "signature": "SEED 08", + "chrome": true + }, + "files": { + "css": "theme.css", + "artwork": "background.jpg", + "preview": "preview.jpg", + "assets": { + "workspace-panel-safe": "assets/workspace-panel-safe.jpg", + "voxel-cube": "assets/voxel-cube.svg", + "ore-diamond": "assets/ore-diamond.svg", + "chunk-bracket": "assets/chunk-bracket.svg", + "torch-meter": "assets/torch-meter.svg", + "portal-send-tile": "assets/portal-send-tile.svg" + } + } +} diff --git a/themes/paper-circuit/ASSET_LICENSE.md b/themes/paper-circuit/ASSET_LICENSE.md deleted file mode 100644 index bd3ee1b..0000000 --- a/themes/paper-circuit/ASSET_LICENSE.md +++ /dev/null @@ -1,3 +0,0 @@ -# Paper Circuit asset license - -`background.svg` and `preview.svg` were created specifically for Awesome Codex Themes and are released under CC0 1.0. The theme CSS is covered by the repository MIT license. diff --git a/themes/paper-circuit/background.svg b/themes/paper-circuit/background.svg deleted file mode 100644 index ee129a2..0000000 --- a/themes/paper-circuit/background.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/themes/paper-circuit/preview.svg b/themes/paper-circuit/preview.svg deleted file mode 100644 index 3f1584d..0000000 --- a/themes/paper-circuit/preview.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - PAPER CIRCUIT - SPECIFICATION / 01Draw the system before wiring it. - OPEN BLUEPRINT - Ask Codex to build, explain, or repair... - diff --git a/themes/paper-circuit/theme.css b/themes/paper-circuit/theme.css deleted file mode 100644 index 1b6fc8f..0000000 --- a/themes/paper-circuit/theme.css +++ /dev/null @@ -1,24 +0,0 @@ -html.awesome-codex-theme body { - color: var(--act-text); - background-color: var(--act-background); - background-image: linear-gradient(105deg, #F3EEDCF5 16%, #F3EEDCCE 62%, #F3EEDCF2), var(--act-artwork); - background-position: center; - background-size: cover; - background-attachment: fixed; -} - -html.awesome-codex-theme :is(aside, header, nav, [role="dialog"]) { - background-color: color-mix(in srgb, var(--act-surface) 92%, transparent); - border-color: color-mix(in srgb, var(--act-accent) 18%, #6F6B5F); - backdrop-filter: blur(18px) saturate(105%); -} - -html.awesome-codex-theme :is(textarea, input, pre, code) { - border-color: color-mix(in srgb, var(--act-accent) 20%, transparent); - caret-color: var(--act-accent); -} - -html.awesome-codex-theme :is(button, [role="button"]):focus-visible { - outline: 2px solid var(--act-accent); - outline-offset: 2px; -} diff --git a/themes/paper-circuit/theme.json b/themes/paper-circuit/theme.json deleted file mode 100644 index 1ac5771..0000000 --- a/themes/paper-circuit/theme.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "schemaVersion": 1, - "slug": "paper-circuit", - "version": "1.0.0", - "name": "Paper Circuit", - "description": "A bright drafting-table theme with graphite type, cobalt traces, and warm paper grain.", - "author": { "name": "Awesome Codex Themes" }, - "license": { "code": "MIT", "artwork": "CC0-1.0" }, - "categories": ["light", "editorial"], - "tags": ["paper", "cobalt", "diagram", "low-glare"], - "compatibility": { - "platforms": ["macos"], - "status": "experimental", - "appVersions": ["26.707.*"] - }, - "mode": "light", - "palette": { - "background": "#F3EEDC", - "surface": "#FFFDF5DD", - "text": "#242621", - "accent": "#2455D6" - }, - "files": { "css": "theme.css", "artwork": "background.svg", "preview": "preview.svg" } -} diff --git a/themes/saiyan-ukiyoe/ASSET_LICENSE.md b/themes/saiyan-ukiyoe/ASSET_LICENSE.md new file mode 100644 index 0000000..6691e0d --- /dev/null +++ b/themes/saiyan-ukiyoe/ASSET_LICENSE.md @@ -0,0 +1,11 @@ +# Saiyan Ukiyo-e assets + +Asset identifier: `PROJECT-ASSET` + +The project maintainer supplied and approved the artwork and generated UI assets included in this theme package. + +- Background source: `05-son-goku-ukiyoe.png` +- Marketing source: `05-goku-standard-codex-imagegen-v1.png` +- Runtime files: `background.jpg`, `preview.jpg`, and the local files under `assets/` + +Code and CSS remain covered by the repository MIT license. diff --git a/themes/saiyan-ukiyoe/README.md b/themes/saiyan-ukiyoe/README.md new file mode 100644 index 0000000..e5e55a5 --- /dev/null +++ b/themes/saiyan-ukiyoe/README.md @@ -0,0 +1,11 @@ +# Saiyan Ukiyo-e + +Schema 2 installable package for Codex Desktop on macOS. + +- Version: 0.1.1 +- Compatibility: experimental for 26.707.* and 26.715.* +- Runtime artwork: background.jpg +- Right-panel-safe artwork: assets/workspace-panel-safe.jpg +- Original component assets: brush-sun, ki-slash, square-training-seal, cloud-curl, wave-send-crest +- Live verification: passed on Codex Desktop 26.715.21425; compatibility remains experimental +- Asset record: maintainer-supplied `PROJECT-ASSET`; see `ASSET_LICENSE.md` diff --git a/themes/saiyan-ukiyoe/assets/brush-sun.svg b/themes/saiyan-ukiyoe/assets/brush-sun.svg new file mode 100644 index 0000000..af5e2de --- /dev/null +++ b/themes/saiyan-ukiyoe/assets/brush-sun.svg @@ -0,0 +1,4 @@ + +brush sun + + diff --git a/themes/saiyan-ukiyoe/assets/cloud-curl.svg b/themes/saiyan-ukiyoe/assets/cloud-curl.svg new file mode 100644 index 0000000..49e941f --- /dev/null +++ b/themes/saiyan-ukiyoe/assets/cloud-curl.svg @@ -0,0 +1,4 @@ + +cloud curl + + diff --git a/themes/saiyan-ukiyoe/assets/ki-slash.svg b/themes/saiyan-ukiyoe/assets/ki-slash.svg new file mode 100644 index 0000000..40c79ca --- /dev/null +++ b/themes/saiyan-ukiyoe/assets/ki-slash.svg @@ -0,0 +1,4 @@ + +ki slash + + diff --git a/themes/saiyan-ukiyoe/assets/square-training-seal.svg b/themes/saiyan-ukiyoe/assets/square-training-seal.svg new file mode 100644 index 0000000..2a1bfff --- /dev/null +++ b/themes/saiyan-ukiyoe/assets/square-training-seal.svg @@ -0,0 +1,4 @@ + +square training seal + + diff --git a/themes/saiyan-ukiyoe/assets/wave-send-crest.svg b/themes/saiyan-ukiyoe/assets/wave-send-crest.svg new file mode 100644 index 0000000..f91af83 --- /dev/null +++ b/themes/saiyan-ukiyoe/assets/wave-send-crest.svg @@ -0,0 +1,4 @@ + +wave send crest + + diff --git a/themes/saiyan-ukiyoe/assets/workspace-panel-safe.jpg b/themes/saiyan-ukiyoe/assets/workspace-panel-safe.jpg new file mode 100644 index 0000000..5f9b98d Binary files /dev/null and b/themes/saiyan-ukiyoe/assets/workspace-panel-safe.jpg differ diff --git a/themes/saiyan-ukiyoe/background.jpg b/themes/saiyan-ukiyoe/background.jpg new file mode 100644 index 0000000..e81dca1 Binary files /dev/null and b/themes/saiyan-ukiyoe/background.jpg differ diff --git a/themes/saiyan-ukiyoe/preview.jpg b/themes/saiyan-ukiyoe/preview.jpg new file mode 100644 index 0000000..aeab3fd Binary files /dev/null and b/themes/saiyan-ukiyoe/preview.jpg differ diff --git a/themes/saiyan-ukiyoe/theme.css b/themes/saiyan-ukiyoe/theme.css new file mode 100644 index 0000000..2358bcf --- /dev/null +++ b/themes/saiyan-ukiyoe/theme.css @@ -0,0 +1,175 @@ +html.awesome-codex-theme[data-awesome-codex-theme="saiyan-ukiyoe"] { + --batch-theme-signature: "saiyan-ukiyoe"; + --batch-radius: 1px; + --batch-radius-lg: 5px 2px 7px 3px; + --batch-edge: polygon(1% 0, 100% 2%, 99% 98%, 0 100%); + --batch-border-width: 1px; + --batch-display-font: Hiragino Mincho ProN, YuMincho, Songti SC, Georgia, serif; + --batch-code-font: SFMono-Regular, Menlo, Consolas, monospace; + --batch-mark: var(--act-asset-brush-sun); + background-position: center !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="saiyan-ukiyoe"][data-awesome-codex-artifact-panel-state="open"] { + background-image: linear-gradient(115deg, var(--act-scrim), color-mix(in srgb, var(--act-scrim) 54%, transparent)), var(--act-asset-workspace-panel-safe) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="saiyan-ukiyoe"] :is(h1, h2, h3, [data-awesome-codex-sidebar-brand="true"], #awesome-codex-theme-chrome) { + font-family: var(--batch-display-font) !important; + letter-spacing: 0.1em; + text-transform: uppercase; +} + +html.awesome-codex-theme[data-awesome-codex-theme="saiyan-ukiyoe"] :is(pre, code, kbd, samp, .monaco-editor, .terminal) { + font-family: var(--batch-code-font) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="saiyan-ukiyoe"] .app-shell-left-panel { + border-inline-end: var(--batch-border-width) solid var(--act-border-strong) !important; + box-shadow: 6px 8px 0 color-mix(in srgb, var(--act-focus) 10%, transparent) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="saiyan-ukiyoe"] [data-awesome-codex-sidebar-brand="true"] { + background-image: var(--act-asset-brush-sun) !important; + background-position: right 10px center !important; + background-repeat: no-repeat !important; + background-size: 24px 24px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="saiyan-ukiyoe"] :is([data-awesome-codex-sidebar-nav], [data-awesome-codex-domain-header], [data-awesome-codex-project-row], [data-awesome-codex-task-row]) { + border-radius: var(--batch-radius) !important; + transition: background-color 140ms ease, color 140ms ease, transform 140ms ease; +} + +html.awesome-codex-theme[data-awesome-codex-theme="saiyan-ukiyoe"] :is([data-awesome-codex-sidebar-nav], [data-awesome-codex-project-row], [data-awesome-codex-task-row]):is(:hover, [aria-current="page"], [data-state="active"]) { + background: linear-gradient(90deg, var(--act-active), var(--act-hover)) !important; + box-shadow: inset 3px 0 0 var(--act-accent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="saiyan-ukiyoe"] [data-awesome-codex-domain-header]::after { + content: ""; + display: inline-block; + width: 18px; + height: 18px; + margin-inline-start: auto; + background: var(--act-asset-ki-slash) center / contain no-repeat; + opacity: 0.82; +} + +html.awesome-codex-theme[data-awesome-codex-theme="saiyan-ukiyoe"] [data-awesome-codex-home="true"] { + position: relative; + isolation: isolate; +} + +html.awesome-codex-theme[data-awesome-codex-theme="saiyan-ukiyoe"] [data-awesome-codex-home-prompt="true"] { + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); + background: color-mix(in srgb, var(--act-surface-elevated) 88%, transparent) !important; + box-shadow: 6px 8px 0 color-mix(in srgb, var(--act-focus) 10%, transparent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="saiyan-ukiyoe"] :is(.composer-root, .composer-surface-chrome) { + border-width: var(--batch-border-width) !important; + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); +} + +html.awesome-codex-theme[data-awesome-codex-theme="saiyan-ukiyoe"] [data-awesome-codex-composer-control] { + min-width: 32px; + min-height: 32px; + border-radius: var(--batch-radius) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="saiyan-ukiyoe"] [data-awesome-codex-composer-control="attachment"] { + background-image: var(--act-asset-square-training-seal) !important; + background-position: center !important; + background-repeat: no-repeat !important; + background-size: 15px 15px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="saiyan-ukiyoe"] [data-awesome-codex-composer-control="primary"] { + color: var(--act-background) !important; + background-color: var(--act-accent) !important; + background-image: var(--act-asset-wave-send-crest) !important; + background-position: center !important; + background-repeat: no-repeat !important; + background-size: 19px 19px !important; + box-shadow: 6px 8px 0 color-mix(in srgb, var(--act-focus) 10%, transparent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="saiyan-ukiyoe"] [data-awesome-codex-artifact-panel="true"] { + background-color: color-mix(in srgb, var(--act-surface-elevated) 94%, transparent) !important; + background-image: linear-gradient(180deg, color-mix(in srgb, var(--act-surface-overlay) 92%, transparent), var(--act-surface-elevated)), var(--act-asset-workspace-panel-safe) !important; + background-position: center !important; + background-size: cover !important; + border-inline-start: var(--batch-border-width) solid var(--act-border-strong) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="saiyan-ukiyoe"] [data-awesome-codex-artifact-header="true"] { + border-block-end: var(--batch-border-width) solid var(--act-border) !important; + background-image: var(--act-asset-cloud-curl) !important; + background-position: right 14px center !important; + background-repeat: no-repeat !important; + background-size: 44px 10px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="saiyan-ukiyoe"] :is([role="dialog"], [role="menu"], [role="listbox"], [role="tooltip"], [data-awesome-codex-plugin-card], [data-awesome-codex-collection-row]) { + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); +} + +html.awesome-codex-theme[data-awesome-codex-theme="saiyan-ukiyoe"] :is([data-awesome-codex-plugin-card], [data-awesome-codex-collection-row]):hover { + border-color: var(--act-border-strong) !important; + background-color: var(--act-hover) !important; + transform: translateY(-1px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="saiyan-ukiyoe"] :is(input, textarea, select, [contenteditable="true"]):focus-visible { + outline: 2px solid var(--act-focus) !important; + outline-offset: 2px !important; + box-shadow: 0 0 0 4px color-mix(in srgb, var(--act-focus) 18%, transparent) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="saiyan-ukiyoe"] :is(.monaco-editor, .terminal, .xterm-viewport, pre, code) { + border-radius: var(--batch-radius) !important; + border-color: var(--act-border-subtle) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="saiyan-ukiyoe"] :is([data-awesome-codex-activity-item], [data-awesome-codex-completed-summary])::before { + content: ""; + display: inline-block; + width: 16px; + height: 16px; + margin-inline-end: 7px; + background: var(--act-asset-ki-slash) center / contain no-repeat; + vertical-align: -3px; +} + +html.awesome-codex-theme[data-awesome-codex-theme="saiyan-ukiyoe"] :is(button, [role="button"]):active { + transform: translateY(1px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="saiyan-ukiyoe"] *::-webkit-scrollbar-thumb { + border-radius: var(--batch-radius) !important; +} + +@media (max-width: 1120px) { + html.awesome-codex-theme[data-awesome-codex-theme="saiyan-ukiyoe"] :is([data-awesome-codex-project-row], [data-awesome-codex-task-row], [data-awesome-codex-collection-row]) { + min-width: 0; + } + html.awesome-codex-theme[data-awesome-codex-theme="saiyan-ukiyoe"] :is([data-awesome-codex-project-row], [data-awesome-codex-task-row], [data-awesome-codex-collection-row]) :is(span, p) { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} + +@media (prefers-reduced-motion: reduce) { + html.awesome-codex-theme[data-awesome-codex-theme="saiyan-ukiyoe"] *, + html.awesome-codex-theme[data-awesome-codex-theme="saiyan-ukiyoe"] *::before, + html.awesome-codex-theme[data-awesome-codex-theme="saiyan-ukiyoe"] *::after { + transition-duration: 0.01ms !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + } +} diff --git a/themes/saiyan-ukiyoe/theme.json b/themes/saiyan-ukiyoe/theme.json new file mode 100644 index 0000000..f093c11 --- /dev/null +++ b/themes/saiyan-ukiyoe/theme.json @@ -0,0 +1,93 @@ +{ + "schemaVersion": 2, + "slug": "saiyan-ukiyoe", + "version": "0.1.1", + "name": "Saiyan Ukiyo-e", + "description": "A parchment-and-indigo battle chronicle using ukiyo-e brush rhythm, orange ki, wave energy, and carved seal logic.", + "author": { + "name": "Awesome Codex Themes" + }, + "license": { + "code": "MIT", + "artwork": "PROJECT-ASSET" + }, + "categories": [ + "light", + "full-workspace", + "character-inspired" + ], + "tags": [ + "saiyan-ukiyoe", + "mulberry-paper", + "brush-rectangles", + "macos" + ], + "compatibility": { + "platforms": [ + "macos" + ], + "status": "experimental", + "appVersions": [ + "26.707.*", + "26.715.*" + ] + }, + "mode": "light", + "palette": { + "background": "#EFE2C6", + "scrim": "#EFE2C6E8", + "surface": "#E7D5B2DE", + "surfaceElevated": "#FAEFD8F2", + "surfaceOverlay": "#FAEFD8F7", + "input": "#F3E4C5F2", + "text": "#12293A", + "textSecondary": "#314A59", + "textMuted": "#687A7E", + "textDisabled": "#9DA69E", + "icon": "#12293A", + "iconSecondary": "#314A59", + "iconMuted": "#687A7E", + "border": "#DF68183D", + "borderSubtle": "#DF681824", + "borderStrong": "#DF681878", + "accent": "#DF6818", + "accentHover": "#F2862F", + "selection": "#DF681838", + "focus": "#1E5C86", + "link": "#174D70", + "hover": "#DF68181A", + "active": "#1E5C862B", + "code": "#102330FA", + "terminal": "#091720FA", + "diffAdded": "#3F845E40", + "diffRemoved": "#B93F2D40", + "success": "#3F845E", + "warning": "#B77A19", + "danger": "#B93F2D", + "scrollbar": "#DF68183D", + "scrollbarHover": "#DF681875", + "composer": "#F1E1C0F2" + }, + "experience": { + "brand": "SAIYAN UKIYO-E", + "eyebrow": "KI IMPRESSION", + "headline": "BUILD BEYOND LIMITS", + "tagline": "Refine the form. Raise the power.", + "status": "KI // RISING", + "signature": "UKIYO-E 05", + "chrome": true + }, + "files": { + "css": "theme.css", + "artwork": "background.jpg", + "preview": "preview.jpg", + "assets": { + "workspace-panel-safe": "assets/workspace-panel-safe.jpg", + "brush-sun": "assets/brush-sun.svg", + "ki-slash": "assets/ki-slash.svg", + "square-training-seal": "assets/square-training-seal.svg", + "cloud-curl": "assets/cloud-curl.svg", + "wave-send-crest": "assets/wave-send-crest.svg" + } + } +} diff --git a/themes/satoru-gojo/ASSET_LICENSE.md b/themes/satoru-gojo/ASSET_LICENSE.md new file mode 100644 index 0000000..cc7e1c6 --- /dev/null +++ b/themes/satoru-gojo/ASSET_LICENSE.md @@ -0,0 +1,16 @@ +# Satoru Gojo assets + +Asset identifier: `PROJECT-ASSET` + +The project maintainer supplied and approved the following files for public distribution as part of this theme package: + +- `background.jpg` +- `preview.png` +- `assets/workspace-panel-safe.jpg` +- `assets/six-eyes-emblem.png` +- `assets/send-infinity.png` +- `assets/panel-reticle.png` +- `assets/blindfold-stripe.png` +- `assets/six-dot-sequence.png` + +The identifier applies to the artwork files above. Code and CSS remain covered by the repository's MIT license. diff --git a/themes/satoru-gojo/assets/blindfold-stripe.png b/themes/satoru-gojo/assets/blindfold-stripe.png new file mode 100644 index 0000000..885b227 Binary files /dev/null and b/themes/satoru-gojo/assets/blindfold-stripe.png differ diff --git a/themes/satoru-gojo/assets/panel-reticle.png b/themes/satoru-gojo/assets/panel-reticle.png new file mode 100644 index 0000000..5303f9a Binary files /dev/null and b/themes/satoru-gojo/assets/panel-reticle.png differ diff --git a/themes/satoru-gojo/assets/send-infinity.png b/themes/satoru-gojo/assets/send-infinity.png new file mode 100644 index 0000000..5594898 Binary files /dev/null and b/themes/satoru-gojo/assets/send-infinity.png differ diff --git a/themes/satoru-gojo/assets/six-dot-sequence.png b/themes/satoru-gojo/assets/six-dot-sequence.png new file mode 100644 index 0000000..32f6884 Binary files /dev/null and b/themes/satoru-gojo/assets/six-dot-sequence.png differ diff --git a/themes/satoru-gojo/assets/six-eyes-emblem.png b/themes/satoru-gojo/assets/six-eyes-emblem.png new file mode 100644 index 0000000..f3a9856 Binary files /dev/null and b/themes/satoru-gojo/assets/six-eyes-emblem.png differ diff --git a/themes/satoru-gojo/assets/workspace-panel-safe.jpg b/themes/satoru-gojo/assets/workspace-panel-safe.jpg new file mode 100644 index 0000000..ea993c3 Binary files /dev/null and b/themes/satoru-gojo/assets/workspace-panel-safe.jpg differ diff --git a/themes/limitless-six-eyes/background.jpg b/themes/satoru-gojo/background.jpg similarity index 100% rename from themes/limitless-six-eyes/background.jpg rename to themes/satoru-gojo/background.jpg diff --git a/themes/limitless-six-eyes/preview.png b/themes/satoru-gojo/preview.png similarity index 100% rename from themes/limitless-six-eyes/preview.png rename to themes/satoru-gojo/preview.png diff --git a/themes/satoru-gojo/theme.css b/themes/satoru-gojo/theme.css new file mode 100644 index 0000000..eec08c8 --- /dev/null +++ b/themes/satoru-gojo/theme.css @@ -0,0 +1,1835 @@ +/* + * Satoru Gojo + * Full-workspace theme live-verified on Codex 26.707 / 26.715 and attempted elsewhere as best effort. + */ + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] { + --limitless-ink: #171b31; + --limitless-violet: #6754d9; + --limitless-sky: #4c75e8; + --limitless-cyan: #74c7ff; + --limitless-violet-soft: #ded9fa; + --limitless-paper: #f8f9fd; + --limitless-code-ink: #e8eeff; + --limitless-workspace-art-opacity: 0.48; + --limitless-radius-control: 8px; + --limitless-radius-card: 12px; + --limitless-radius-panel: 16px; + --limitless-radius-composer: 18px; + --limitless-radius-hero: 22px; + --limitless-shadow-panel: 0 14px 36px #263c6712; + --limitless-shadow-floating: 0 20px 56px #22375f1d; + background-color: var(--act-background) !important; + background-image: + radial-gradient(circle at 78% 4%, #7bc9ff24, transparent 34%), + radial-gradient(circle at 48% 94%, #ffffffeb, transparent 42%), + linear-gradient(135deg, #fbfcff, #eaf5ff) !important; + background-position: center !important; + background-size: cover !important; + background-repeat: no-repeat !important; +} + +html.electron-dark.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] { + color-scheme: light !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] body { + font-family: "Avenir Next", "Helvetica Neue", sans-serif; + letter-spacing: -0.006em; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] #root { + position: relative; + isolation: isolate; + overflow: hidden; + background: + radial-gradient(circle at 72% 12%, #9bdcff29, transparent 33%), + linear-gradient(135deg, #fcfdff, #edf6ff 58%, #e4f1ff) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] #root > * { + position: relative; + z-index: 1; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="workspace"] #root::before, +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="workspace"] #root::after, +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="plugins"] #root::before, +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="plugins"] #root::after, +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="scheduled"] #root::before, +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="scheduled"] #root::after, +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="sites"] #root::before, +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="sites"] #root::after, +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="pull-requests"] #root::before, +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="pull-requests"] #root::after { + content: ""; + position: fixed; + z-index: 0; + inset: 46px 0 0 275px; + pointer-events: none; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="workspace"] #root::before, +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="plugins"] #root::before, +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="scheduled"] #root::before, +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="sites"] #root::before, +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="pull-requests"] #root::before { + background: var(--act-artwork) right 34% center / cover no-repeat; + opacity: var(--limitless-workspace-art-opacity); + filter: saturate(0.96) contrast(1.01) brightness(1.02); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="workspace"]:has([data-awesome-codex-artifact-panel="true"]) #root::before { + background-image: var(--act-asset-workspace-panel-safe) !important; + background-position: center !important; + background-size: cover !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="workspace"] #root::after { + background: linear-gradient( + 90deg, + #f8fbfff7 0%, + #f8fbfff0 38%, + #f1f7ffc2 58%, + #e9f3ff75 76%, + #dbeaff29 100% + ); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="plugins"] { + --limitless-workspace-art-opacity: 0.38; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"]:is( + [data-awesome-codex-surface="scheduled"], + [data-awesome-codex-surface="sites"], + [data-awesome-codex-surface="pull-requests"] +) { + --limitless-workspace-art-opacity: 0.25; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="plugins"] #root::after { + background: linear-gradient( + 90deg, + #f8fbfffa 0%, + #f8fbfff3 43%, + #f3f8ffdb 63%, + #eaf3ff9c 79%, + #dfeeff4d 100% + ); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"]:is( + [data-awesome-codex-surface="scheduled"], + [data-awesome-codex-surface="sites"], + [data-awesome-codex-surface="pull-requests"] +) #root::after { + background: + linear-gradient(90deg, #f9fcfffc 0%, #f7fbfff6 48%, #f1f7ffe8 68%, #e9f3ffb8 84%, #deecff70 100%), + linear-gradient(180deg, #ffffff28, #dceaff17); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] :is( + .app-shell-main, + .main-surface, + .thread-root, + .thread-scroll-container, + .app-shell-main-content-frame, + .app-shell-main-content-viewport +) { + background-color: transparent !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] .app-shell-left-panel { + position: relative; + isolation: isolate; + overflow: hidden; + color: var(--act-text) !important; + background: + linear-gradient(180deg, #fbfdfff5, #edf6fff0), + var(--act-surface) !important; + border-right: 1px solid #6d91c52e !important; + box-shadow: + 12px 0 36px #355b850f, + inset -1px 0 #ffffffcf !important; + -webkit-backdrop-filter: blur(28px) saturate(118%); + backdrop-filter: blur(28px) saturate(118%); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] .app-shell-left-panel::before { + content: ""; + position: absolute; + z-index: 0; + right: 14px; + bottom: 64px; + width: 92px; + height: 92px; + pointer-events: none; + background-image: var(--act-asset-six-eyes-emblem); + background-position: center; + background-size: contain; + background-repeat: no-repeat; + opacity: 0.14; + filter: saturate(0.82) contrast(0.94); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] .app-shell-left-panel::after { + content: "SIX EYES // 05"; + position: absolute; + z-index: 0; + top: 148px; + right: 5px; + height: auto; + pointer-events: none; + color: #526fcf12; + font-family: "SFMono-Regular", Consolas, monospace; + font-size: 28px; + font-weight: 760; + letter-spacing: 0.18em; + line-height: 1; + text-transform: uppercase; + writing-mode: vertical-rl; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] .app-shell-left-panel > * { + position: relative; + z-index: 1; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] .app-shell-left-panel :is(button, a) { + border-color: transparent !important; + border-radius: var(--limitless-radius-control) !important; + box-shadow: none !important; + transition: + color 140ms ease, + background-color 140ms ease, + transform 140ms ease !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] .app-shell-left-panel :is(button, a):hover { + color: var(--act-text) !important; + background: #73a6e819 !important; + border-color: transparent !important; + transform: translateX(1px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-sidebar-brand="true"] { + position: relative; + min-height: 50px; + padding: 7px 10px 16px 44px !important; + justify-content: flex-start !important; + color: #25395f !important; + font-family: "Avenir Next Condensed", "Avenir Next", sans-serif !important; + font-size: 18px !important; + font-weight: 760 !important; + letter-spacing: -0.02em !important; + background-image: + var(--act-asset-six-eyes-emblem), + linear-gradient(90deg, #f9fcfff2, #edf5ff9e 76%, transparent) !important; + background-position: + left 10px center, + center !important; + background-size: + 27px 27px, + auto !important; + background-repeat: no-repeat !important; + border: 0 !important; + border-radius: 0 0 9px 0 !important; + box-shadow: inset 0 -1px #6c8fca24 !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-sidebar-brand="true"]::after { + content: "LIMITLESS / EDITORIAL 05"; + position: absolute; + left: 44px; + bottom: 5px; + color: #61749b; + font-family: "SFMono-Regular", Consolas, monospace; + font-size: 5.5px; + font-weight: 720; + letter-spacing: 0.13em; + line-height: 1; + text-transform: uppercase; + opacity: 0.8; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-sidebar-nav] { + position: relative; + padding-right: 32px !important; + background-image: none !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-sidebar-nav]::after { + content: ""; + position: absolute; + top: 50%; + right: 9px; + width: 15px; + height: 15px; + pointer-events: none; + background-image: var(--act-asset-panel-reticle); + background-position: center; + background-size: contain; + background-repeat: no-repeat; + opacity: 0.18; + transform: translateY(-50%); + transition: + opacity 140ms ease, + transform 180ms ease; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-sidebar-nav]:is(:hover, [aria-current="page"])::after { + opacity: 0.72; + transform: translateY(-50%) rotate(18deg); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] .app-shell-left-panel :is( + [aria-current="page"], + [class~="bg-token-list-hover-background"] +) { + color: #24447c !important; + background-image: linear-gradient(90deg, #dbeaffdc, #eef6ffad 76%, transparent) !important; + background-position: center !important; + background-size: auto !important; + background-repeat: no-repeat !important; + border-color: transparent !important; + box-shadow: + inset 3px 0 #5279ef, + 0 8px 22px #3f6fa910 !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-domain-header="true"] { + position: relative; + gap: 8px !important; + min-height: 26px; + margin-top: 7px; + padding-right: 48px !important; + background-image: var(--act-asset-six-dot-sequence) !important; + background-position: right 5px center !important; + background-size: 34px auto !important; + background-repeat: no-repeat !important; + border: 0 !important; + border-radius: 0 !important; + box-shadow: inset 0 -1px #6d8dbf20 !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-domain-header="true"] span { + flex: 0 0 auto !important; + min-width: max-content !important; + max-width: none !important; + overflow: visible !important; + color: #657b9e !important; + font-family: "SFMono-Regular", Consolas, monospace !important; + font-size: 8px !important; + font-weight: 700 !important; + letter-spacing: 0.12em !important; + text-overflow: clip !important; + text-transform: uppercase; + white-space: nowrap !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-domain-header="true"] > svg { + flex: 0 0 11px !important; + width: 11px !important; + height: 11px !important; + margin-left: 2px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-domain-header="true"] span::after { + content: " / DOMAIN INDEX"; + color: #8295b3; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-project-row="true"] { + position: relative; + padding-right: 52px !important; + color: #314b70 !important; + background: transparent !important; + border: 0 !important; + border-radius: var(--limitless-radius-control) !important; + box-shadow: none !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-project-row="true"]:hover { + color: #254479 !important; + background: linear-gradient(90deg, #dcecff99, transparent) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-domain-id]::after { + content: attr(data-awesome-codex-domain-id); + position: absolute; + top: 50%; + right: 10px; + color: #6f85aa; + font-family: "SFMono-Regular", Consolas, monospace; + font-size: 6px; + font-weight: 720; + letter-spacing: 0.14em; + line-height: 1; + transform: translateY(-50%); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-domain-id]:is( + [aria-current="page"], + [class~="bg-token-list-hover-background"] +) { + background-image: + var(--act-asset-blindfold-stripe), + linear-gradient(90deg, #dbeaffdc, #eef6ffad 76%, transparent) !important; + background-position: + right 34px bottom 4px, + center !important; + background-size: + 50px 5px, + auto !important; + background-repeat: no-repeat !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-task-row="true"] { + position: relative; + padding-right: 32px !important; + color: #3d5678 !important; + background: transparent !important; + border: 0 !important; + border-radius: var(--limitless-radius-control) !important; + box-shadow: none !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-task-row="true"]:hover { + color: #294878 !important; + background: linear-gradient(90deg, #dfeeff8f, transparent 84%) !important; + box-shadow: inset 2px 0 #79a8ee66 !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-task-row="true"]::after { + content: ""; + position: absolute; + top: 50%; + right: 25px; + width: 13px; + height: 13px; + pointer-events: none; + background-image: var(--act-asset-panel-reticle); + background-position: center; + background-size: contain; + background-repeat: no-repeat; + opacity: 0.2; + transform: translateY(-50%); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-task-row="true"]:has(.animate-spin)::after { + display: none; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-task-row="true"] .animate-spin { + isolation: isolate; + border-radius: 50%; + background: #f7fbffde; + box-shadow: 0 0 0 3px #edf5ffb8; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-task-row="true"] :is(button, [role="button"]) { + border-color: transparent !important; + box-shadow: none !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] :is( + [data-awesome-codex-account-trigger="true"], + [data-awesome-codex-help-trigger="true"] +) { + border-color: transparent !important; + box-shadow: none !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-account-trigger="true"]:hover { + background: linear-gradient(90deg, #dcecff9e, #eef7ff4a) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-account-identity="true"] { + position: relative; + min-height: 46px; + padding-bottom: 13px !important; + background-image: + var(--act-asset-six-dot-sequence), + linear-gradient(90deg, #f8fcfff0, #eaf4ff88 78%, transparent) !important; + background-position: + right 8px center, + center !important; + background-size: + 42px auto, + auto !important; + background-repeat: no-repeat !important; + border: 0 !important; + border-radius: 0 9px 0 0 !important; + box-shadow: inset 0 1px #6c8fca24 !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-account-identity="true"]::after { + content: "LIMITLESS ACTIVE"; + position: absolute; + left: 38px; + bottom: 5px; + color: #65799e; + font-family: "SFMono-Regular", Consolas, monospace; + font-size: 5.5px; + font-weight: 720; + letter-spacing: 0.13em; + line-height: 1; + text-transform: uppercase; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-help-trigger="true"] { + color: #58739b !important; + background-color: transparent !important; + background-image: var(--act-asset-panel-reticle) !important; + background-position: center !important; + background-size: 25px 25px !important; + background-repeat: no-repeat !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-help-trigger="true"]:hover { + color: #315fa7 !important; + background: #dcecff9c !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] :is( + .app-header-tint, + .app-shell-main-content-top-fade +) { + background: linear-gradient(180deg, #fbfdfff0, #f3f9ffd9 76%, transparent) !important; + border-color: #6e91c527 !important; + -webkit-backdrop-filter: blur(20px) saturate(114%); + backdrop-filter: blur(20px) saturate(114%); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-header-control="true"] { + border-radius: var(--limitless-radius-control) !important; + border-color: transparent !important; + background: transparent !important; + box-shadow: none !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-header-control="true"]:hover, +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-header-control="true"][data-state="open"] { + color: #2f548b !important; + background: #dcecff96 !important; + border-color: transparent !important; + box-shadow: none !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="workspace"] .thread-scroll-container { + background: transparent !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="workspace"] .app-shell-main-content-frame { + border-color: #668bc31c !important; + box-shadow: inset 0 1px #ffffffb8 !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [class*="bg-token-foreground/5"]:not([data-awesome-codex-header-control="true"]) { + background: + linear-gradient(145deg, #ffffffe0, #edf5ffc7), + var(--act-surface-elevated) !important; + border: 1px solid #728ac92b !important; + border-radius: var(--limitless-radius-card) !important; + box-shadow: + 0 9px 24px #3c638c12, + inset 0 1px #ffffff !important; + -webkit-backdrop-filter: blur(16px) saturate(114%); + backdrop-filter: blur(16px) saturate(114%); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] :is( + [data-message-author-role], + [role="listitem"], + [data-browser-comment-editor-surface] +) { + border-color: #6b90c52b !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [class*="rounded-lg"][class*="bg-token-dropdown-background"] { + background: + linear-gradient(145deg, #fbfdffe8, #eaf5ffd8), + var(--act-surface-elevated) !important; + border-color: #668ec52e !important; + border-radius: var(--limitless-radius-card) !important; + box-shadow: 0 10px 28px #345c8614 !important; + -webkit-backdrop-filter: blur(20px) saturate(112%); + backdrop-filter: blur(20px) saturate(112%); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] :is( + [role="dialog"], + [role="menu"], + [role="listbox"], + [role="tooltip"], + [data-radix-menu-content], + .dialog-layout +) { + color: var(--act-text) !important; + background: + linear-gradient(145deg, #fcfefffa, #eaf5fff7), + var(--act-surface-overlay) !important; + border: 1px solid #648bc83b !important; + border-radius: var(--limitless-radius-panel) !important; + box-shadow: + var(--limitless-shadow-floating), + inset 0 1px #ffffff !important; + -webkit-backdrop-filter: blur(24px) saturate(116%); + backdrop-filter: blur(24px) saturate(116%); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-artifact-panel="true"] { + position: relative; + isolation: isolate; + overflow: hidden; + color: var(--act-text) !important; + background-image: + var(--act-asset-panel-reticle), + linear-gradient(160deg, #fffefcf7 0%, #f2f6ffe9 58%, #e7efffd7 100%) !important; + background-position: + right -46px bottom -48px, + center !important; + background-size: + 190px 190px, + auto !important; + background-repeat: no-repeat !important; + border: 0 !important; + border-radius: var(--limitless-radius-panel) !important; + box-shadow: + 0 18px 48px #31577d18, + inset 3px 0 #6684df, + inset 0 1px #ffffffcf !important; + -webkit-backdrop-filter: blur(24px) saturate(118%); + backdrop-filter: blur(24px) saturate(118%); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-artifact-panel="true"] > div > section + section { + border-top: 1px solid #7296c72b !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-artifact-header="true"] { + color: var(--act-text-secondary) !important; + background: #f8fbffb8 !important; + border: 0 !important; + border-bottom: 1px solid #7395c72e !important; + border-radius: 0 !important; + box-shadow: none !important; + -webkit-backdrop-filter: blur(14px) saturate(110%); + backdrop-filter: blur(14px) saturate(110%); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-artifact-header="true"] :is(button, [role="button"]) { + border: 0 !important; + border-radius: 8px !important; + box-shadow: none !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-panel-more="true"] { + color: #6780a5 !important; + background: transparent !important; + border: 0 !important; + border-radius: 7px !important; + box-shadow: none !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-panel-more="true"]:hover { + color: #315da4 !important; + background: #dcecff87 !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-completed-summary="true"] { + min-height: 28px; + padding: 4px 96px 4px 9px !important; + color: #3b5680 !important; + background-image: + var(--act-asset-six-dot-sequence), + linear-gradient(90deg, #eaf4ffcf, #f5faff6b) !important; + background-position: + right 10px center, + center !important; + background-size: + 76px auto, + auto !important; + background-repeat: no-repeat !important; + border: 0 !important; + border-radius: 7px !important; + box-shadow: inset 2px 0 #6285dd96 !important; + -webkit-backdrop-filter: blur(12px); + backdrop-filter: blur(12px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-completed-summary="true"]:hover { + color: #294a7d !important; + background-image: + var(--act-asset-six-dot-sequence), + linear-gradient(90deg, #e4f1ffdc, #f5faff73) !important; + border-color: transparent !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-activity-item="true"] { + color: var(--act-text-secondary) !important; + padding: 3px 76px 3px 7px !important; + background-image: var(--act-asset-six-dot-sequence) !important; + background-position: right 8px center !important; + background-size: 58px auto !important; + background-repeat: no-repeat !important; + border: 0 !important; + border-radius: 7px !important; + box-shadow: inset 2px 0 #7798c52e !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-activity-item="true"]:hover { + color: #35567f !important; + background-image: + var(--act-asset-six-dot-sequence), + linear-gradient(90deg, #e8f3ff8c, transparent) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [class*="bg-token-input-background/70"][class*="rounded-3xl"] { + color: var(--act-text-secondary) !important; + background: #f7fbffcf !important; + border-color: transparent !important; + border-radius: var(--limitless-radius-control) !important; + box-shadow: inset 2px 0 #6d90d871 !important; + -webkit-backdrop-filter: blur(14px); + backdrop-filter: blur(14px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] :is( + [role="menuitem"], + [role="option"], + [data-radix-menu-item] +) { + border-radius: 8px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] :is( + [role="menuitem"], + [role="option"], + [data-radix-menu-item] +):hover { + color: var(--act-text) !important; + background: #6f9fe820 !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] :is( + input, + textarea, + select +) { + color: var(--act-text) !important; + caret-color: var(--act-accent) !important; + background: + linear-gradient(180deg, #ffffffed, #edf6ffe8), + var(--act-input) !important; + border: 1px solid #718ac927 !important; + border-radius: var(--limitless-radius-control) !important; + box-shadow: + inset 0 1px 2px #31577f0b, + inset 2px 0 #6754d942 !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] :is(input, textarea)::placeholder { + color: var(--act-text-muted) !important; + opacity: 0.78 !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] :is( + button, + [role="button"], + a +) { + transition: + color 140ms ease, + background-color 140ms ease, + box-shadow 140ms ease, + transform 140ms ease !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] :is( + button, + [role="button"], + a, + input, + textarea, + select, + [contenteditable="true"] +):focus-visible { + outline: 2px solid var(--act-focus) !important; + outline-offset: 2px !important; + box-shadow: 0 0 0 4px #5e9df024 !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="plugins"] [data-awesome-codex-plugin-search="true"] { + color: var(--act-text-secondary) !important; + background: #f9fcffcf !important; + border: 0 !important; + border-radius: 10px !important; + box-shadow: + inset 2px 0 #6754d978, + inset 0 -1px #7091c62e, + 0 8px 22px #3159850f !important; + -webkit-backdrop-filter: blur(16px) saturate(112%); + backdrop-filter: blur(16px) saturate(112%); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="plugins"] [data-awesome-codex-plugin-search="true"]:focus-within { + background: #fcfdffe8 !important; + box-shadow: + inset 3px 0 var(--act-focus), + inset 0 -1px #6284d35c, + 0 10px 26px #31598517 !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="plugins"] [data-awesome-codex-plugin-search="true"] input { + background: transparent !important; + border: 0 !important; + border-radius: 0 !important; + outline: 0 !important; + box-shadow: none !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="plugins"] [data-awesome-codex-plugin-tab="true"] { + color: #657a9c !important; + background: transparent !important; + border: 0 !important; + border-radius: 6px !important; + box-shadow: none !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="plugins"] [data-awesome-codex-plugin-tab="true"][class*="bg-token-foreground/5"] { + color: #2b4e86 !important; + background: linear-gradient(90deg, #dceaffb8, #eef6ff69 78%, transparent) !important; + box-shadow: inset 2px 0 #5e7fe0 !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="plugins"] [data-awesome-codex-plugin-tab="true"]:hover { + color: #315a97 !important; + background: #dfeeff73 !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="plugins"] [data-awesome-codex-plugin-section-header="true"] { + background-image: var(--act-asset-six-dot-sequence) !important; + background-position: right 6px center !important; + background-size: 52px auto !important; + background-repeat: no-repeat !important; + border: 0 !important; + border-bottom: 1px solid #7596c729 !important; + border-radius: 0 !important; + box-shadow: none !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="plugins"] [data-awesome-codex-plugin-installed="true"] { + background: transparent !important; + border: 0 !important; + border-radius: 8px !important; + box-shadow: none !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="plugins"] [data-awesome-codex-plugin-installed="true"]:hover { + background: #dcecff7d !important; + box-shadow: 0 0 0 4px #6d9ee815 !important; + transform: translateY(-1px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="plugins"] [data-awesome-codex-plugin-icon="true"] { + background: transparent !important; + border: 0 !important; + border-radius: 8px !important; + box-shadow: none !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="plugins"] .grid:has(> [data-awesome-codex-plugin-card="true"]) { + gap-block: 8px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="plugins"] [data-awesome-codex-plugin-card="true"] { + color: var(--act-text) !important; + background: linear-gradient(90deg, #f9fcff73, #f4f9ff45 82%, transparent) !important; + border: 0 !important; + border-radius: 10px !important; + box-shadow: none !important; + -webkit-backdrop-filter: blur(8px); + backdrop-filter: blur(8px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="plugins"] [data-awesome-codex-plugin-card="true"]:hover { + color: #223f70 !important; + background: linear-gradient(90deg, #e8f3ffcf, #f5f9ff8c 84%, transparent) !important; + border: 0 !important; + box-shadow: + inset 2px 0 #5e7fe0, + 0 10px 24px #31598510 !important; + transform: translateX(2px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="plugins"] [data-awesome-codex-plugin-card="true"]:focus-visible { + background: linear-gradient(90deg, #e8f3ffdb, #f5f9ff9c 84%, transparent) !important; + border: 0 !important; + outline: 2px solid var(--act-focus) !important; + outline-offset: 2px !important; + box-shadow: inset 2px 0 #5e7fe0 !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="plugins"] [data-awesome-codex-plugin-action] { + border: 0 !important; + box-shadow: none !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="plugins"] [data-awesome-codex-plugin-action="menu"] { + color: #6b82a5 !important; + background: transparent !important; + border-radius: 7px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="plugins"] [data-awesome-codex-plugin-action="menu"]:hover { + color: #315d9f !important; + background: #dcecff8a !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="plugins"] [data-awesome-codex-plugin-action="install"] { + color: #4165ae !important; + background: transparent !important; + border-radius: 6px !important; + font-weight: 650 !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="plugins"] [data-awesome-codex-plugin-action="install"]:hover { + color: #ffffff !important; + background: linear-gradient(135deg, #6daeff, #597ce3 64%, #6754d9) !important; + box-shadow: 0 7px 18px #4d74ce2e !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-collection-search="true"] { + color: var(--act-text-secondary) !important; + background: linear-gradient(90deg, #fbfdffde, #f3f8ffc7 88%, transparent) !important; + border: 0 !important; + border-radius: 9px !important; + box-shadow: + inset 2px 0 #6754d982, + inset 0 -1px #7794c42b, + 0 10px 28px #3159850d !important; + -webkit-backdrop-filter: blur(16px) saturate(112%); + backdrop-filter: blur(16px) saturate(112%); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-collection-search="true"]:focus-within { + background: linear-gradient(90deg, #ffffffef, #f3f8ffda 88%, transparent) !important; + box-shadow: + inset 3px 0 var(--act-focus), + inset 0 -1px #6284d34f, + 0 12px 30px #31598514 !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-collection-search="true"] input { + background: transparent !important; + border: 0 !important; + border-radius: 0 !important; + outline: 0 !important; + box-shadow: none !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-collection-tab="true"] { + color: #697d9d !important; + background: transparent !important; + border: 0 !important; + border-radius: 6px !important; + box-shadow: none !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-collection-tab="true"][class*="bg-token-foreground/5"] { + color: #294d86 !important; + background: linear-gradient(90deg, #dceaffb5, #eef6ff62 78%, transparent) !important; + box-shadow: inset 2px 0 #5e7fe0 !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-collection-tab="true"]:hover { + color: #315a97 !important; + background: #dfeeff70 !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-collection-section-header="true"] { + background-image: var(--act-asset-six-dot-sequence) !important; + background-position: right 8px center !important; + background-size: 52px auto !important; + background-repeat: no-repeat !important; + border: 0 !important; + border-bottom: 1px solid #7896c526 !important; + border-radius: 0 !important; + box-shadow: none !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="scheduled"] [data-awesome-codex-collection-row="true"] { + color: var(--act-text-secondary) !important; + background: linear-gradient(90deg, #f9fcff42, transparent 88%) !important; + border: 0 !important; + border-radius: 9px !important; + box-shadow: none !important; + opacity: 0.82 !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="scheduled"] [data-awesome-codex-collection-row="true"]:hover, +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="scheduled"] [data-awesome-codex-collection-row="true"]:focus-within { + color: #294b7f !important; + background: linear-gradient(90deg, #e4f1ffbd, #f5f9ff68 84%, transparent) !important; + box-shadow: + inset 2px 0 #5e7fe0, + 0 10px 24px #3159850d !important; + opacity: 1 !important; + transform: translateX(2px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="sites"] [data-awesome-codex-collection-empty="true"] { + position: relative; + width: min(560px, calc(100% - 40px)) !important; + min-height: 220px; + padding: 34px 40px !important; + color: var(--act-text-secondary) !important; + background-image: + var(--act-asset-panel-reticle), + linear-gradient(90deg, #fbfdffdb, #f1f7ffa6 78%, transparent) !important; + background-position: + right -36px center, + center !important; + background-size: + 160px 160px, + auto !important; + background-repeat: no-repeat !important; + border: 0 !important; + border-radius: 0 16px 16px 0 !important; + box-shadow: + inset 2px 0 #5d7fdf, + 0 18px 46px #3159850d !important; + -webkit-backdrop-filter: blur(14px) saturate(110%); + backdrop-filter: blur(14px) saturate(110%); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-collection-action="primary"] { + color: #ffffff !important; + background: linear-gradient(135deg, #70b8ff, #547be4 62%, #6754d9) !important; + border: 0 !important; + border-radius: 8px !important; + box-shadow: + 0 10px 24px #3d72cb2f, + inset 0 1px #ffffff5c !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-collection-action="primary"]:hover { + color: #ffffff !important; + background: linear-gradient(135deg, #82c4ff, #668af0 62%, #7564e4) !important; + transform: translateY(-1px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="pull-requests"] [data-awesome-codex-collection-status="true"] { + width: min(540px, calc(100% - 40px)); + min-height: 168px !important; + margin-inline: auto; + color: #607695 !important; + background-image: + var(--act-asset-six-dot-sequence), + linear-gradient(90deg, #fbfdffcf, #f1f7ff91 82%, transparent) !important; + background-position: + right 24px center, + center !important; + background-size: + 64px auto, + auto !important; + background-repeat: no-repeat !important; + border: 0 !important; + border-radius: 0 14px 14px 0 !important; + box-shadow: + inset 2px 0 #5d7fdf, + 0 16px 42px #3159850b !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="pull-requests"] [data-awesome-codex-collection-empty="true"] { + width: min(540px, calc(100% - 40px)) !important; + min-height: 176px; + padding: 32px 40px !important; + color: #607695 !important; + background-image: + var(--act-asset-panel-reticle), + linear-gradient(90deg, #fbfdffcc, #f1f7ff8f 82%, transparent) !important; + background-position: + right -34px center, + center !important; + background-size: + 148px 148px, + auto !important; + background-repeat: no-repeat !important; + border: 0 !important; + border-radius: 0 14px 14px 0 !important; + box-shadow: + inset 2px 0 #5d7fdf, + 0 16px 42px #3159850b !important; + opacity: 1 !important; + -webkit-backdrop-filter: blur(14px) saturate(110%); + backdrop-filter: blur(14px) saturate(110%); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] :is( + pre, + code, + kbd, + samp, + .vscode-markdown pre, + .vscode-markdown code +) { + color: var(--limitless-code-ink) !important; + background-color: var(--act-code) !important; + border-color: #7399e036 !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] pre { + background-image: var(--act-asset-blindfold-stripe) !important; + background-position: right 12px top 10px !important; + background-size: 168px 8px !important; + background-repeat: no-repeat !important; + border-radius: 14px !important; + box-shadow: + 0 14px 38px #101b351f, + inset 0 1px #ffffff0a !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] :is( + .monaco-editor, + .monaco-editor-background, + .margin, + .terminal, + .xterm, + .xterm-viewport +) { + color: var(--limitless-code-ink) !important; + background-color: var(--act-terminal) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] :is( + .monaco-editor, + .terminal, + .xterm +) { + border-color: #7298df38 !important; + border-radius: 14px !important; + box-shadow: + 0 16px 40px #101b3529, + inset 0 1px #ffffff0a !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] .composer-root { + color: var(--act-text) !important; + background: transparent !important; + border: 0 !important; + border-radius: 0 !important; + box-shadow: none !important; + -webkit-backdrop-filter: none !important; + backdrop-filter: none !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] .composer-surface-chrome { + position: relative; + overflow: hidden; + padding-top: 26px !important; + color: var(--act-text) !important; + background-image: + var(--act-asset-panel-reticle), + linear-gradient(155deg, #ffffffe8, #eef4ffd9 72%, #e7edffd1) !important; + background-position: + right -28px top -32px, + center !important; + background-size: + 118px 118px, + auto !important; + background-repeat: no-repeat !important; + border: 1px solid #647dc52b !important; + border-radius: var(--limitless-radius-composer) !important; + box-shadow: + 0 16px 42px #244f8018, + 0 0 0 1px #ffffffa6 inset !important; + -webkit-backdrop-filter: blur(22px) saturate(116%); + backdrop-filter: blur(22px) saturate(116%); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] .composer-surface-chrome::before { + content: "DOMAIN CONTROL DECK // SIX EYES"; + position: absolute; + z-index: 2; + top: 9px; + left: 16px; + color: #586c99; + font-family: "SFMono-Regular", Consolas, monospace; + font-size: 7px; + font-weight: 720; + letter-spacing: 0.16em; + line-height: 1; + pointer-events: none; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] .composer-surface-chrome::after { + content: ""; + position: absolute; + z-index: 2; + top: 10px; + right: 54px; + width: 74px; + height: 8px; + pointer-events: none; + background-image: var(--act-asset-blindfold-stripe); + background-position: center; + background-size: contain; + background-repeat: no-repeat; + opacity: 0.72; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"][data-awesome-codex-surface="workspace"] .composer-surface-chrome { + background-image: + var(--act-asset-panel-reticle), + linear-gradient(155deg, #ffffffe8, #eef4ffd8 70%, #e8eeffd0) !important; + border-color: #5f79c536 !important; + box-shadow: + 0 18px 48px #244f801d, + 0 0 0 1px #ffffffb8 inset !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] .composer-surface-chrome:has(.ProseMirror:focus) { + border-color: #4e76ed91 !important; + box-shadow: + 0 0 0 3px #5d9ff022, + 0 20px 52px #244f8024, + 0 0 0 1px #ffffffc2 inset !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] :is( + .composer-root, + .composer-surface-chrome +) .ProseMirror { + color: var(--act-text) !important; + caret-color: var(--act-accent) !important; + background: transparent !important; + border: 0 !important; + border-radius: 0 !important; + outline: 0 !important; + box-shadow: none !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-composer-control] { + border-color: transparent !important; + box-shadow: none !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] :is( + [data-awesome-codex-composer-control="attachment"], + [data-awesome-codex-composer-control="dictation"] +) { + color: #61799d !important; + background: transparent !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] :is( + [data-awesome-codex-composer-control="attachment"], + [data-awesome-codex-composer-control="dictation"] +):hover { + color: #365f9f !important; + background: #dcecff9c !important; + border-color: transparent !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-composer-control="access"] { + position: relative; + padding-left: 102px !important; + color: #ad741b !important; + background: transparent !important; + border-color: transparent !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-composer-control="access"]::before { + content: "LIMITLESS ACCESS"; + position: absolute; + left: 6px; + top: 50%; + padding: 0 9px 0 0; + color: #5869a6; + font-family: "SFMono-Regular", Consolas, monospace; + font-size: 6px; + font-weight: 760; + letter-spacing: 0.12em; + line-height: 1; + background: transparent; + border: 0 !important; + border-right: 1px solid #6d7fbd61 !important; + border-radius: 0; + transform: translateY(-50%); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-composer-control="access"]:hover { + color: #89580e !important; + background: #fff0c96e !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-composer-control="model"] { + color: #3d567a !important; + background: #e7f2ff91 !important; + border-color: transparent !important; + border-radius: 8px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-composer-control="model"]:hover, +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-composer-control="model"][data-state="open"] { + color: #284e88 !important; + background: #d7eaffb8 !important; + border-color: transparent !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] :is( + button[data-testid="composer-send-button"], + .composer-send-button, + [data-awesome-codex-composer-control="primary"] +) { + color: #ffffff !important; + background-image: + var(--act-asset-send-infinity), + linear-gradient(145deg, #68a9ff, #4c75e8 60%, #6754d9) !important; + background-position: + center, + center !important; + background-size: + 23px 23px, + auto !important; + background-repeat: no-repeat !important; + border: 0 !important; + box-shadow: + 0 8px 22px #3e70d347, + 0 0 18px #69cbff3d, + inset 0 1px #ffffff4a !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] :is( + button[data-testid="composer-send-button"], + .composer-send-button, + [data-awesome-codex-composer-control="primary"] +) > svg { + opacity: 0 !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] :is( + button[data-testid="composer-send-button"], + .composer-send-button, + [data-awesome-codex-composer-control="primary"] +):not(:disabled):hover { + background-image: + var(--act-asset-send-infinity), + linear-gradient(145deg, #7bc1ff, #658af1 60%, #7564e4) !important; + transform: translateY(-2px) scale(1.02); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] ::selection { + color: var(--act-text) !important; + background: var(--act-selection) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] * { + scrollbar-color: var(--act-scrollbar) transparent; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] *::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] *::-webkit-scrollbar-track { + background: transparent; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] *::-webkit-scrollbar-thumb { + background: var(--act-scrollbar) !important; + border: 2px solid transparent; + border-radius: 999px; + background-clip: padding-box !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] *::-webkit-scrollbar-thumb:hover { + background: var(--act-scrollbar-hover) !important; + background-clip: padding-box !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-home="true"] { + --thread-content-max-width: min(1120px, calc(100cqw - 48px)) !important; + position: relative; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-home="true"] > div:first-child > div:first-child > div:first-child { + position: relative; + min-height: 304px !important; + overflow: hidden; + background: + linear-gradient(90deg, #fbfdfffc 0%, #f7fbfff5 36%, #edf6ffcc 54%, #e8f2ff55 72%, transparent 86%), + var(--act-artwork) !important; + background-position: center, right -24px top !important; + background-size: auto, auto 205% !important; + background-repeat: no-repeat !important; + border: 0 !important; + border-radius: var(--limitless-radius-hero) !important; + box-shadow: + 0 22px 56px #31598518, + inset 0 0 0 1px #ffffffd4 !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-home="true"] > div:first-child > div:first-child > div:first-child::after { + content: none; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-home="true"] [data-testid="home-icon"] { + opacity: 0 !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-home="true"] [data-feature="game-source"], +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-home-prompt="true"] { + position: absolute !important; + z-index: 2; + bottom: 24px; + left: 30px; + width: min(58%, 680px); + margin: 0 !important; + min-height: 100px; + padding: 31px 72px 16px 18px !important; + align-items: center !important; + justify-content: flex-start !important; + color: var(--act-text) !important; + text-align: left !important; + font-size: clamp(24px, 1.75vw, 29px) !important; + font-weight: 600 !important; + letter-spacing: 0.012em !important; + line-height: 1.18 !important; + font-family: + "Songti SC", + STSong, + "Noto Serif CJK SC", + "Source Han Serif SC", + serif !important; + font-kerning: normal; + text-rendering: optimizeLegibility; + background: linear-gradient(90deg, #fbfdffe6, #f1f7ffad 78%, transparent) !important; + border-left: 3px solid #5a7fe0 !important; + border-radius: 0 8px 8px 0 !important; + -webkit-backdrop-filter: blur(12px); + backdrop-filter: blur(12px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-home-prompt="true"]::before { + content: "ACTIVE PROJECT // 01"; + position: absolute; + top: 8px; + left: 18px; + color: #5474c3; + font-family: "SFMono-Regular", Consolas, monospace; + font-size: 8px; + font-weight: 740; + letter-spacing: 0.16em; + line-height: 1; + text-transform: uppercase; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-home-prompt="true"]::after { + content: ""; + position: absolute; + top: 7px; + right: 12px; + width: 52px; + height: 10px; + pointer-events: none; + background-image: var(--act-asset-six-dot-sequence); + background-position: center; + background-size: contain; + background-repeat: no-repeat; + opacity: 0.76; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-home-prompt="true"] :is(h1, h2, p) { + max-width: 100% !important; + margin-inline: 0 !important; + text-align: left !important; + text-wrap: balance; + -webkit-font-smoothing: antialiased; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-home-prompt="true"] :is(h1, h2) { + font-size: clamp(24px, 1.95vw, 31px) !important; + font-weight: 600 !important; + letter-spacing: 0.012em !important; + line-height: 1.18 !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-domain-marker="true"] { + display: inline-flex !important; + align-items: center !important; + min-height: 1.2em; + margin-inline: 0.16em !important; + padding: 0.06em 0.32em 0.06em 0.58em !important; + color: #3159ad !important; + font-family: + "Hiragino Sans GB", + "PingFang SC", + "Noto Sans CJK SC", + sans-serif !important; + font-weight: 600 !important; + letter-spacing: -0.018em !important; + line-height: 1 !important; + vertical-align: 0; + background-image: + var(--act-asset-six-eyes-emblem), + linear-gradient(180deg, #eff7ffde, #dfeeffb8) !important; + background-position: + left 0.09em center, + center !important; + background-size: + 0.36em 0.36em, + auto !important; + background-repeat: no-repeat !important; + border: 0 !important; + border-bottom: 1px solid #6684df82 !important; + border-radius: 5px !important; + box-shadow: inset 0 -1px #ffffffc2 !important; + text-decoration: none !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-domain-marker="true"]:hover { + color: #254a9b !important; + background-color: #e5f1ff !important; + box-shadow: + inset 0 -2px #526fe0, + 0 7px 16px #365e9b18 !important; + transform: translateY(-1px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-home="true"] [class~="group/home-suggestions"] { + overflow: visible !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-home="true"] [class~="group/home-suggestions"] button { + min-height: 84px !important; + color: var(--act-text) !important; + background: linear-gradient(90deg, #fbfdffba, #eef6ff7a 82%, transparent) !important; + border: 0 !important; + border-radius: 10px !important; + box-shadow: inset 2px 0 #7692cc30 !important; + -webkit-backdrop-filter: blur(10px) saturate(108%); + backdrop-filter: blur(10px) saturate(108%); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-home="true"] [class~="group/home-suggestions"] button:hover { + color: #294b7f !important; + background: linear-gradient(90deg, #e5f1ffcf, #f5f9ff83 84%, transparent) !important; + border: 0 !important; + box-shadow: + inset 2px 0 #5e7fe0, + 0 12px 28px #315d8b12 !important; + transform: translateX(2px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-home="true"] [class~="group/project-selector"] > button { + color: var(--act-text) !important; + background: #fafdffeb !important; + border-color: transparent !important; + border-radius: var(--limitless-radius-control) !important; + box-shadow: + 0 7px 20px #315a8214, + inset 2px 0 #6688dd78 !important; +} + +#awesome-codex-theme-chrome.act-experience { + position: fixed; + z-index: 2; + inset: 0; + overflow: hidden; + pointer-events: none; + color: var(--act-text); + font-family: "Avenir Next", "Helvetica Neue", sans-serif; +} + +#awesome-codex-theme-chrome.act-experience::before { + content: "LIMITLESS // SIX EYES"; + position: absolute; + top: 18px; + left: 50%; + transform: translateX(-50%); + color: #4f6592; + font-family: "SFMono-Regular", Consolas, monospace; + font-size: 7px; + font-weight: 740; + letter-spacing: 0.18em; + text-transform: uppercase; + opacity: 0.9; +} + +#awesome-codex-theme-chrome.act-experience::after { + content: ""; + position: absolute; + top: 10px; + left: calc(50% - 110px); + width: 24px; + height: 24px; + background-image: var(--act-asset-six-eyes-emblem); + background-position: center; + background-size: contain; + background-repeat: no-repeat; + opacity: 0.82; +} + +#awesome-codex-theme-chrome .act-experience__identity { + position: absolute; + top: 116px; + left: clamp(356px, 25vw, 438px); + width: min(308px, 25vw); + padding: 12px 16px; + opacity: 0; + transform: translateY(8px); + transition: + opacity 260ms ease, + transform 340ms cubic-bezier(0.16, 1, 0.3, 1); +} + +html.awesome-codex-theme-experience[data-awesome-codex-surface="home"] #awesome-codex-theme-chrome .act-experience__identity { + opacity: 1; + transform: translateY(0); +} + +#awesome-codex-theme-chrome .act-experience__eyebrow, +#awesome-codex-theme-chrome .act-experience__tagline, +#awesome-codex-theme-chrome .act-experience__signature, +#awesome-codex-theme-chrome .act-experience__status-copy { + display: block; + font-family: "SFMono-Regular", Consolas, monospace; + text-transform: uppercase; + letter-spacing: 0.16em; +} + +#awesome-codex-theme-chrome .act-experience__eyebrow { + margin-bottom: 6px; + color: var(--act-accent); + font-size: 9px; + font-weight: 720; +} + +#awesome-codex-theme-chrome .act-experience__brand { + display: block; + color: #4164b8; + font-family: "Avenir Next Condensed", "Avenir Next", sans-serif; + font-size: clamp(40px, 4vw, 56px); + font-weight: 800; + letter-spacing: -0.07em; + line-height: 0.82; + text-transform: uppercase; + text-shadow: 0 1px #ffffff; +} + +#awesome-codex-theme-chrome .act-experience__headline { + display: block; + margin-top: 10px; + color: var(--act-text); + font-family: "Avenir Next Condensed", "Avenir Next", sans-serif; + font-size: clamp(12px, 1.1vw, 16px); + font-weight: 700; + letter-spacing: 0.14em; + line-height: 1; + text-transform: uppercase; +} + +#awesome-codex-theme-chrome .act-experience__tagline { + display: none; +} + +#awesome-codex-theme-chrome .act-experience__signature { + display: none; +} + +#awesome-codex-theme-chrome .act-experience__status { + position: absolute; + top: 11px; + right: 118px; + display: flex; + align-items: center; + gap: 8px; + height: 24px; + min-width: 182px; + padding: 0 54px 0 14px; + color: #4e6085; + background-image: + var(--act-asset-six-dot-sequence), + linear-gradient(90deg, #fbfdffd9, #edf6ff8f 72%, transparent) !important; + background-position: + right 10px center, + center !important; + background-size: + 38px auto, + auto !important; + background-repeat: no-repeat !important; + border: 0; + border-radius: 0; + box-shadow: + inset 3px 0 #5d7fdf, + inset 0 -1px #6d88b92b; + opacity: 0; + transform: translateY(-4px); + transition: + opacity 180ms ease, + transform 180ms ease; + -webkit-backdrop-filter: blur(12px); + backdrop-filter: blur(12px); +} + +html.awesome-codex-theme-experience[data-awesome-codex-surface="home"] #awesome-codex-theme-chrome .act-experience__status { + opacity: 1; + transform: translateY(0); +} + +html.awesome-codex-theme-experience[data-awesome-codex-surface="workspace"] #awesome-codex-theme-chrome .act-experience__status { + opacity: 1; + transform: translateY(0); +} + +html.awesome-codex-theme-experience[data-awesome-codex-surface="plugins"] #awesome-codex-theme-chrome .act-experience__status { + opacity: 1; + transform: translateY(0); +} + +html.awesome-codex-theme-experience:is( + [data-awesome-codex-surface="scheduled"], + [data-awesome-codex-surface="sites"], + [data-awesome-codex-surface="pull-requests"] +) #awesome-codex-theme-chrome .act-experience__status { + opacity: 1; + transform: translateY(0); +} + +#awesome-codex-theme-chrome .act-experience__status-dot { + width: 5px; + height: 5px; + border-radius: 50%; + background: var(--act-success); + box-shadow: 0 0 0 3px #1e9a6512; + animation: limitless-status 2.8s ease-in-out infinite; +} + +#awesome-codex-theme-chrome .act-experience__status-copy { + font-size: 6.5px; + font-weight: 720; + letter-spacing: 0.13em; + line-height: 1; +} + +#awesome-codex-theme-chrome .act-experience__orbit { + position: absolute; + display: block; + width: 210px; + height: 210px; + background-image: var(--act-asset-panel-reticle); + background-position: center; + background-size: contain; + background-repeat: no-repeat; + opacity: 0; +} + +html.awesome-codex-theme-experience[data-awesome-codex-surface="home"] #awesome-codex-theme-chrome .act-experience__orbit { + top: 96px; + right: 7%; + opacity: 0.48; + animation: limitless-orbit 28s linear infinite; +} + +html.awesome-codex-theme-experience[data-awesome-codex-surface="workspace"] #awesome-codex-theme-chrome .act-experience__orbit { + top: 74px; + right: 4%; + width: 190px; + height: 190px; + opacity: 0.18; + animation: limitless-orbit 42s linear infinite; +} + +html.awesome-codex-theme-experience[data-awesome-codex-surface="plugins"] #awesome-codex-theme-chrome .act-experience__orbit { + top: 82px; + right: 4%; + width: 176px; + height: 176px; + opacity: 0.14; + animation: limitless-orbit 46s linear infinite; +} + +#awesome-codex-theme-chrome .act-experience__orbit::before, +#awesome-codex-theme-chrome .act-experience__orbit::after { + content: none; +} + +#awesome-codex-theme-chrome .act-experience__particles { + display: none; +} + +html.awesome-codex-theme-experience[data-awesome-codex-surface="home"] #awesome-codex-theme-chrome .act-experience__particles { + opacity: 0.42; +} + +html.awesome-codex-theme-experience[data-awesome-codex-surface="workspace"] #awesome-codex-theme-chrome .act-experience__particles { + opacity: 0.2; +} + +#awesome-codex-theme-chrome .act-experience__particle { + display: none; +} + +@keyframes limitless-status { + 50% { + opacity: 0.55; + transform: scale(0.86); + } +} + +@keyframes limitless-orbit { + to { + transform: rotate(360deg); + } +} + +@media (max-width: 1100px) { + #awesome-codex-theme-chrome .act-experience__identity { + left: 318px; + width: 280px; + } + + #awesome-codex-theme-chrome .act-experience__brand { + font-size: 46px; + } + + #awesome-codex-theme-chrome .act-experience__orbit { + display: none; + } +} + +@media (max-width: 820px) { + #awesome-codex-theme-chrome .act-experience__identity, + #awesome-codex-theme-chrome .act-experience__status { + display: none; + } + + html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] [data-awesome-codex-home="true"] > div:first-child > div:first-child > div:first-child { + min-height: 218px !important; + background: + linear-gradient(90deg, #fbfdfffa, #f4f9ffe8 52%, transparent 88%), + var(--act-artwork) !important; + background-position: center, right -12px top !important; + background-size: auto, auto 190% !important; + background-repeat: no-repeat !important; + } +} + +@media (prefers-reduced-motion: reduce) { + html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] *, + html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] *::before, + html.awesome-codex-theme[data-awesome-codex-theme="satoru-gojo"] *::after, + #awesome-codex-theme-chrome *, + #awesome-codex-theme-chrome *::before, + #awesome-codex-theme-chrome *::after { + scroll-behavior: auto !important; + transition-duration: 0.01ms !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + } +} diff --git a/themes/satoru-gojo/theme.json b/themes/satoru-gojo/theme.json new file mode 100644 index 0000000..ccdd300 --- /dev/null +++ b/themes/satoru-gojo/theme.json @@ -0,0 +1,99 @@ +{ + "schemaVersion": 2, + "slug": "satoru-gojo", + "version": "1.2.0", + "name": "Satoru Gojo", + "nativeName": "五条 悟", + "nativeLocale": "ja-JP", + "description": "A porcelain-white editorial workspace shaped by Limitless, Six Eyes, iris-violet telemetry, and a midnight code cockpit.", + "author": { + "name": "Awesome Codex Themes" + }, + "license": { + "code": "MIT", + "artwork": "PROJECT-ASSET" + }, + "categories": [ + "light", + "editorial" + ], + "tags": [ + "featured", + "full-workspace", + "composer", + "editorial", + "lavender", + "glass", + "anime", + "character" + ], + "compatibility": { + "platforms": [ + "macos" + ], + "status": "experimental", + "strategy": "best-effort-all", + "verifiedAppVersions": [ + "26.707.*", + "26.715.*" + ] + }, + "mode": "light", + "palette": { + "background": "#EAF5FF", + "scrim": "#EAF5FFE8", + "surface": "#F8F9FDDE", + "surfaceElevated": "#FFFFFFF2", + "surfaceOverlay": "#F8F9FDF7", + "input": "#F4F8FFF2", + "text": "#171B31", + "textSecondary": "#3E4A69", + "textMuted": "#74829F", + "textDisabled": "#ADB7C9", + "icon": "#202A4A", + "iconSecondary": "#61718E", + "iconMuted": "#92A0B7", + "border": "#718BC63D", + "borderSubtle": "#7891C624", + "borderStrong": "#5D73B078", + "accent": "#4C75E8", + "accentHover": "#658AF1", + "selection": "#4C75E838", + "focus": "#6754D9", + "link": "#3B62C9", + "hover": "#4C75E81A", + "active": "#6754D92B", + "code": "#111426FA", + "terminal": "#0A0D1BFA", + "diffAdded": "#59BFA440", + "diffRemoved": "#DB627640", + "success": "#59BFA4", + "warning": "#B77712", + "danger": "#DB6276", + "scrollbar": "#6B82B83D", + "scrollbarHover": "#566FAE75", + "composer": "#F8F9FDF2" + }, + "experience": { + "brand": "LIMITLESS", + "eyebrow": "SIX EYES", + "headline": "CODEX THEME SYSTEM", + "tagline": "Editorial workspace protocol.", + "status": "LIMITLESS // ACTIVE", + "signature": "SATORU GOJO // 五条 悟", + "chrome": true + }, + "files": { + "css": "theme.css", + "artwork": "background.jpg", + "preview": "preview.png", + "assets": { + "six-eyes-emblem": "assets/six-eyes-emblem.png", + "send-infinity": "assets/send-infinity.png", + "panel-reticle": "assets/panel-reticle.png", + "blindfold-stripe": "assets/blindfold-stripe.png", + "six-dot-sequence": "assets/six-dot-sequence.png", + "workspace-panel-safe": "assets/workspace-panel-safe.jpg" + } + } +} diff --git a/themes/slingshot-lab/ASSET_LICENSE.md b/themes/slingshot-lab/ASSET_LICENSE.md new file mode 100644 index 0000000..f94d22e --- /dev/null +++ b/themes/slingshot-lab/ASSET_LICENSE.md @@ -0,0 +1,11 @@ +# Slingshot Lab assets + +Asset identifier: `PROJECT-ASSET` + +The project maintainer supplied and approved the artwork and generated UI assets included in this theme package. + +- Background source: `04-angry-birds.png` +- Marketing source: `04-angry-birds-standard-codex-imagegen-v1.png` +- Runtime files: `background.jpg`, `preview.jpg`, and the local files under `assets/` + +Code and CSS remain covered by the repository MIT license. diff --git a/themes/slingshot-lab/README.md b/themes/slingshot-lab/README.md new file mode 100644 index 0000000..dfd4b7d --- /dev/null +++ b/themes/slingshot-lab/README.md @@ -0,0 +1,11 @@ +# Slingshot Lab + +Schema 2 installable package for Codex Desktop on macOS. + +- Version: 0.1.1 +- Compatibility: experimental for 26.707.* and 26.715.* +- Runtime artwork: background.jpg +- Right-panel-safe artwork: assets/workspace-panel-safe.jpg +- Original component assets: forked-slingshot, dotted-arc, impact-star, feather-vane, red-launch-arrow +- Live verification: passed on Codex Desktop 26.715.21425; compatibility remains experimental +- Asset record: maintainer-supplied `PROJECT-ASSET`; see `ASSET_LICENSE.md` diff --git a/themes/slingshot-lab/assets/dotted-arc.svg b/themes/slingshot-lab/assets/dotted-arc.svg new file mode 100644 index 0000000..3874122 --- /dev/null +++ b/themes/slingshot-lab/assets/dotted-arc.svg @@ -0,0 +1,4 @@ + +dotted arc + + diff --git a/themes/slingshot-lab/assets/feather-vane.svg b/themes/slingshot-lab/assets/feather-vane.svg new file mode 100644 index 0000000..d91a0b9 --- /dev/null +++ b/themes/slingshot-lab/assets/feather-vane.svg @@ -0,0 +1,4 @@ + +feather vane + + diff --git a/themes/slingshot-lab/assets/forked-slingshot.svg b/themes/slingshot-lab/assets/forked-slingshot.svg new file mode 100644 index 0000000..e98e582 --- /dev/null +++ b/themes/slingshot-lab/assets/forked-slingshot.svg @@ -0,0 +1,4 @@ + +forked slingshot + + diff --git a/themes/slingshot-lab/assets/impact-star.svg b/themes/slingshot-lab/assets/impact-star.svg new file mode 100644 index 0000000..fa8a843 --- /dev/null +++ b/themes/slingshot-lab/assets/impact-star.svg @@ -0,0 +1,4 @@ + +impact star + + diff --git a/themes/slingshot-lab/assets/red-launch-arrow.svg b/themes/slingshot-lab/assets/red-launch-arrow.svg new file mode 100644 index 0000000..d2b46c6 --- /dev/null +++ b/themes/slingshot-lab/assets/red-launch-arrow.svg @@ -0,0 +1,4 @@ + +red launch arrow + + diff --git a/themes/slingshot-lab/assets/workspace-panel-safe.jpg b/themes/slingshot-lab/assets/workspace-panel-safe.jpg new file mode 100644 index 0000000..54a952a Binary files /dev/null and b/themes/slingshot-lab/assets/workspace-panel-safe.jpg differ diff --git a/themes/slingshot-lab/background.jpg b/themes/slingshot-lab/background.jpg new file mode 100644 index 0000000..63df8b9 Binary files /dev/null and b/themes/slingshot-lab/background.jpg differ diff --git a/themes/slingshot-lab/preview.jpg b/themes/slingshot-lab/preview.jpg new file mode 100644 index 0000000..c28ec64 Binary files /dev/null and b/themes/slingshot-lab/preview.jpg differ diff --git a/themes/slingshot-lab/theme.css b/themes/slingshot-lab/theme.css new file mode 100644 index 0000000..5444feb --- /dev/null +++ b/themes/slingshot-lab/theme.css @@ -0,0 +1,175 @@ +html.awesome-codex-theme[data-awesome-codex-theme="slingshot-lab"] { + --batch-theme-signature: "slingshot-lab"; + --batch-radius: 8px; + --batch-radius-lg: 14px; + --batch-edge: polygon(2% 0, 98% 0, 100% 12%, 100% 88%, 98% 100%, 2% 100%, 0 88%, 0 12%); + --batch-border-width: 2px; + --batch-display-font: Avenir Next Rounded, Trebuchet MS, Helvetica Neue, sans-serif; + --batch-code-font: SFMono-Regular, Menlo, Consolas, monospace; + --batch-mark: var(--act-asset-forked-slingshot); + background-position: center !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="slingshot-lab"][data-awesome-codex-artifact-panel-state="open"] { + background-image: linear-gradient(115deg, var(--act-scrim), color-mix(in srgb, var(--act-scrim) 54%, transparent)), var(--act-asset-workspace-panel-safe) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="slingshot-lab"] :is(h1, h2, h3, [data-awesome-codex-sidebar-brand="true"], #awesome-codex-theme-chrome) { + font-family: var(--batch-display-font) !important; + letter-spacing: 0.04em; + text-transform: uppercase; +} + +html.awesome-codex-theme[data-awesome-codex-theme="slingshot-lab"] :is(pre, code, kbd, samp, .monaco-editor, .terminal) { + font-family: var(--batch-code-font) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="slingshot-lab"] .app-shell-left-panel { + border-inline-end: var(--batch-border-width) solid var(--act-border-strong) !important; + box-shadow: 0 6px 0 color-mix(in srgb, var(--act-text) 15%, transparent) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="slingshot-lab"] [data-awesome-codex-sidebar-brand="true"] { + background-image: var(--act-asset-forked-slingshot) !important; + background-position: right 10px center !important; + background-repeat: no-repeat !important; + background-size: 24px 24px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="slingshot-lab"] :is([data-awesome-codex-sidebar-nav], [data-awesome-codex-domain-header], [data-awesome-codex-project-row], [data-awesome-codex-task-row]) { + border-radius: var(--batch-radius) !important; + transition: background-color 140ms ease, color 140ms ease, transform 140ms ease; +} + +html.awesome-codex-theme[data-awesome-codex-theme="slingshot-lab"] :is([data-awesome-codex-sidebar-nav], [data-awesome-codex-project-row], [data-awesome-codex-task-row]):is(:hover, [aria-current="page"], [data-state="active"]) { + background: linear-gradient(90deg, var(--act-active), var(--act-hover)) !important; + box-shadow: inset 3px 0 0 var(--act-accent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="slingshot-lab"] [data-awesome-codex-domain-header]::after { + content: ""; + display: inline-block; + width: 18px; + height: 18px; + margin-inline-start: auto; + background: var(--act-asset-dotted-arc) center / contain no-repeat; + opacity: 0.82; +} + +html.awesome-codex-theme[data-awesome-codex-theme="slingshot-lab"] [data-awesome-codex-home="true"] { + position: relative; + isolation: isolate; +} + +html.awesome-codex-theme[data-awesome-codex-theme="slingshot-lab"] [data-awesome-codex-home-prompt="true"] { + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); + background: color-mix(in srgb, var(--act-surface-elevated) 88%, transparent) !important; + box-shadow: 0 6px 0 color-mix(in srgb, var(--act-text) 15%, transparent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="slingshot-lab"] :is(.composer-root, .composer-surface-chrome) { + border-width: var(--batch-border-width) !important; + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); +} + +html.awesome-codex-theme[data-awesome-codex-theme="slingshot-lab"] [data-awesome-codex-composer-control] { + min-width: 32px; + min-height: 32px; + border-radius: var(--batch-radius) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="slingshot-lab"] [data-awesome-codex-composer-control="attachment"] { + background-image: var(--act-asset-impact-star) !important; + background-position: center !important; + background-repeat: no-repeat !important; + background-size: 15px 15px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="slingshot-lab"] [data-awesome-codex-composer-control="primary"] { + color: var(--act-background) !important; + background-color: var(--act-accent) !important; + background-image: var(--act-asset-red-launch-arrow) !important; + background-position: center !important; + background-repeat: no-repeat !important; + background-size: 19px 19px !important; + box-shadow: 0 6px 0 color-mix(in srgb, var(--act-text) 15%, transparent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="slingshot-lab"] [data-awesome-codex-artifact-panel="true"] { + background-color: color-mix(in srgb, var(--act-surface-elevated) 94%, transparent) !important; + background-image: linear-gradient(180deg, color-mix(in srgb, var(--act-surface-overlay) 92%, transparent), var(--act-surface-elevated)), var(--act-asset-workspace-panel-safe) !important; + background-position: center !important; + background-size: cover !important; + border-inline-start: var(--batch-border-width) solid var(--act-border-strong) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="slingshot-lab"] [data-awesome-codex-artifact-header="true"] { + border-block-end: var(--batch-border-width) solid var(--act-border) !important; + background-image: var(--act-asset-feather-vane) !important; + background-position: right 14px center !important; + background-repeat: no-repeat !important; + background-size: 44px 10px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="slingshot-lab"] :is([role="dialog"], [role="menu"], [role="listbox"], [role="tooltip"], [data-awesome-codex-plugin-card], [data-awesome-codex-collection-row]) { + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); +} + +html.awesome-codex-theme[data-awesome-codex-theme="slingshot-lab"] :is([data-awesome-codex-plugin-card], [data-awesome-codex-collection-row]):hover { + border-color: var(--act-border-strong) !important; + background-color: var(--act-hover) !important; + transform: translateY(-1px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="slingshot-lab"] :is(input, textarea, select, [contenteditable="true"]):focus-visible { + outline: 2px solid var(--act-focus) !important; + outline-offset: 2px !important; + box-shadow: 0 0 0 4px color-mix(in srgb, var(--act-focus) 18%, transparent) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="slingshot-lab"] :is(.monaco-editor, .terminal, .xterm-viewport, pre, code) { + border-radius: var(--batch-radius) !important; + border-color: var(--act-border-subtle) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="slingshot-lab"] :is([data-awesome-codex-activity-item], [data-awesome-codex-completed-summary])::before { + content: ""; + display: inline-block; + width: 16px; + height: 16px; + margin-inline-end: 7px; + background: var(--act-asset-dotted-arc) center / contain no-repeat; + vertical-align: -3px; +} + +html.awesome-codex-theme[data-awesome-codex-theme="slingshot-lab"] :is(button, [role="button"]):active { + transform: translateY(1px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="slingshot-lab"] *::-webkit-scrollbar-thumb { + border-radius: var(--batch-radius) !important; +} + +@media (max-width: 1120px) { + html.awesome-codex-theme[data-awesome-codex-theme="slingshot-lab"] :is([data-awesome-codex-project-row], [data-awesome-codex-task-row], [data-awesome-codex-collection-row]) { + min-width: 0; + } + html.awesome-codex-theme[data-awesome-codex-theme="slingshot-lab"] :is([data-awesome-codex-project-row], [data-awesome-codex-task-row], [data-awesome-codex-collection-row]) :is(span, p) { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} + +@media (prefers-reduced-motion: reduce) { + html.awesome-codex-theme[data-awesome-codex-theme="slingshot-lab"] *, + html.awesome-codex-theme[data-awesome-codex-theme="slingshot-lab"] *::before, + html.awesome-codex-theme[data-awesome-codex-theme="slingshot-lab"] *::after { + transition-duration: 0.01ms !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + } +} diff --git a/themes/slingshot-lab/theme.json b/themes/slingshot-lab/theme.json new file mode 100644 index 0000000..2c1325c --- /dev/null +++ b/themes/slingshot-lab/theme.json @@ -0,0 +1,93 @@ +{ + "schemaVersion": 2, + "slug": "slingshot-lab", + "version": "0.1.1", + "name": "Slingshot Lab", + "description": "A bright island construction lab where timber, stone, glass blocks, tension bands, and projectile arcs form the system language.", + "author": { + "name": "Awesome Codex Themes" + }, + "license": { + "code": "MIT", + "artwork": "PROJECT-ASSET" + }, + "categories": [ + "light", + "full-workspace", + "character-inspired" + ], + "tags": [ + "slingshot-lab", + "sunlit-wood", + "square-timber-joints", + "macos" + ], + "compatibility": { + "platforms": [ + "macos" + ], + "status": "experimental", + "appVersions": [ + "26.707.*", + "26.715.*" + ] + }, + "mode": "light", + "palette": { + "background": "#E6F6FB", + "scrim": "#E6F6FBE8", + "surface": "#F0E0B8DE", + "surfaceElevated": "#FFF4D5F2", + "surfaceOverlay": "#FFF4D5F7", + "input": "#F8EBCBF2", + "text": "#2B2116", + "textSecondary": "#594631", + "textMuted": "#806B52", + "textDisabled": "#B2A187", + "icon": "#2B2116", + "iconSecondary": "#594631", + "iconMuted": "#806B52", + "border": "#D9382B3D", + "borderSubtle": "#D9382B24", + "borderStrong": "#D9382B78", + "accent": "#D9382B", + "accentHover": "#F05442", + "selection": "#D9382B38", + "focus": "#148DAD", + "link": "#0E7895", + "hover": "#D9382B1A", + "active": "#148DAD2B", + "code": "#17232AFA", + "terminal": "#0D171DFA", + "diffAdded": "#4E9B3B40", + "diffRemoved": "#C92F2F40", + "success": "#4E9B3B", + "warning": "#D88A1D", + "danger": "#C92F2F", + "scrollbar": "#D9382B3D", + "scrollbarHover": "#D9382B75", + "composer": "#F6E7C5F2" + }, + "experience": { + "brand": "SLINGSHOT LAB", + "eyebrow": "TRAJECTORY", + "headline": "AIM. BUILD. RELEASE.", + "tagline": "Turn intent into impact.", + "status": "FLIGHTPATH // CLEAR", + "signature": "LAB 04", + "chrome": true + }, + "files": { + "css": "theme.css", + "artwork": "background.jpg", + "preview": "preview.jpg", + "assets": { + "workspace-panel-safe": "assets/workspace-panel-safe.jpg", + "forked-slingshot": "assets/forked-slingshot.svg", + "dotted-arc": "assets/dotted-arc.svg", + "impact-star": "assets/impact-star.svg", + "feather-vane": "assets/feather-vane.svg", + "red-launch-arrow": "assets/red-launch-arrow.svg" + } + } +} diff --git a/themes/solar-archive/ASSET_LICENSE.md b/themes/solar-archive/ASSET_LICENSE.md deleted file mode 100644 index 981d811..0000000 --- a/themes/solar-archive/ASSET_LICENSE.md +++ /dev/null @@ -1,3 +0,0 @@ -# Solar Archive asset license - -`background.svg` and `preview.svg` were created specifically for Awesome Codex Themes and are released under CC0 1.0. The theme CSS is covered by the repository MIT license. diff --git a/themes/solar-archive/background.svg b/themes/solar-archive/background.svg deleted file mode 100644 index 46cce95..0000000 --- a/themes/solar-archive/background.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/themes/solar-archive/preview.svg b/themes/solar-archive/preview.svg deleted file mode 100644 index 9202d89..0000000 --- a/themes/solar-archive/preview.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - SOLAR ARCHIVE - ARCHIVE NOTE / 184Recover the shape of an old idea. - INDEX RECORD - Ask Codex to build, explain, or repair... - diff --git a/themes/solar-archive/theme.css b/themes/solar-archive/theme.css deleted file mode 100644 index 08fd3aa..0000000 --- a/themes/solar-archive/theme.css +++ /dev/null @@ -1,24 +0,0 @@ -html.awesome-codex-theme body { - color: var(--act-text); - background-color: var(--act-background); - background-image: linear-gradient(95deg, #15130EF5 12%, #15130EBF 60%, #15130EE8), var(--act-artwork); - background-position: center; - background-size: cover; - background-attachment: fixed; -} - -html.awesome-codex-theme :is(aside, header, nav, [role="dialog"]) { - background-color: color-mix(in srgb, var(--act-surface) 90%, transparent); - border-color: color-mix(in srgb, var(--act-accent) 20%, transparent); - backdrop-filter: blur(20px) sepia(8%); -} - -html.awesome-codex-theme :is(textarea, input, pre, code) { - border-color: color-mix(in srgb, var(--act-accent) 18%, transparent); - caret-color: var(--act-accent); -} - -html.awesome-codex-theme :is(button, [role="button"]):focus-visible { - outline: 2px solid var(--act-accent); - outline-offset: 2px; -} diff --git a/themes/solar-archive/theme.json b/themes/solar-archive/theme.json deleted file mode 100644 index 6d29542..0000000 --- a/themes/solar-archive/theme.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "schemaVersion": 1, - "slug": "solar-archive", - "version": "1.0.0", - "name": "Solar Archive", - "description": "A midnight reading room illuminated by amber index marks and orbiting archival forms.", - "author": { "name": "Awesome Codex Themes" }, - "license": { "code": "MIT", "artwork": "CC0-1.0" }, - "categories": ["dark", "editorial"], - "tags": ["amber", "archive", "warm", "reading"], - "compatibility": { - "platforms": ["macos"], - "status": "experimental", - "appVersions": ["26.707.*"] - }, - "mode": "dark", - "palette": { - "background": "#15130E", - "surface": "#292419D9", - "text": "#FFF4D6", - "accent": "#F5B942" - }, - "files": { "css": "theme.css", "artwork": "background.svg", "preview": "preview.svg" } -} diff --git a/themes/symbiote-sumi-e/ASSET_LICENSE.md b/themes/symbiote-sumi-e/ASSET_LICENSE.md new file mode 100644 index 0000000..e27fae2 --- /dev/null +++ b/themes/symbiote-sumi-e/ASSET_LICENSE.md @@ -0,0 +1,11 @@ +# Symbiote Sumi-e assets + +Asset identifier: `PROJECT-ASSET` + +The project maintainer supplied and approved the artwork and generated UI assets included in this theme package. + +- Background source: `03-venom-sumi-e.png` +- Marketing source: `03-venom-standard-codex-imagegen-v1.png` +- Runtime files: `background.jpg`, `preview.jpg`, and the local files under `assets/` + +Code and CSS remain covered by the repository MIT license. diff --git a/themes/symbiote-sumi-e/README.md b/themes/symbiote-sumi-e/README.md new file mode 100644 index 0000000..6eae399 --- /dev/null +++ b/themes/symbiote-sumi-e/README.md @@ -0,0 +1,11 @@ +# Symbiote Sumi-e + +Schema 2 installable package for Codex Desktop on macOS. + +- Version: 0.1.1 +- Compatibility: experimental for 26.707.* and 26.715.* +- Runtime artwork: background.jpg +- Right-panel-safe artwork: assets/workspace-panel-safe.jpg +- Original component assets: ink-drop-mon, brush-slash, repair-seam, negative-space-fang-notch, seal-send-mark +- Live verification: passed on Codex Desktop 26.715.21425; compatibility remains experimental +- Asset record: maintainer-supplied `PROJECT-ASSET`; see `ASSET_LICENSE.md` diff --git a/themes/symbiote-sumi-e/assets/brush-slash.svg b/themes/symbiote-sumi-e/assets/brush-slash.svg new file mode 100644 index 0000000..6b0fd84 --- /dev/null +++ b/themes/symbiote-sumi-e/assets/brush-slash.svg @@ -0,0 +1,4 @@ + +brush slash + + diff --git a/themes/symbiote-sumi-e/assets/ink-drop-mon.svg b/themes/symbiote-sumi-e/assets/ink-drop-mon.svg new file mode 100644 index 0000000..b783d03 --- /dev/null +++ b/themes/symbiote-sumi-e/assets/ink-drop-mon.svg @@ -0,0 +1,4 @@ + +ink-drop mon + + diff --git a/themes/symbiote-sumi-e/assets/negative-space-fang-notch.svg b/themes/symbiote-sumi-e/assets/negative-space-fang-notch.svg new file mode 100644 index 0000000..a843ba5 --- /dev/null +++ b/themes/symbiote-sumi-e/assets/negative-space-fang-notch.svg @@ -0,0 +1,4 @@ + +negative-space fang notch + + diff --git a/themes/symbiote-sumi-e/assets/repair-seam.svg b/themes/symbiote-sumi-e/assets/repair-seam.svg new file mode 100644 index 0000000..6a2218b --- /dev/null +++ b/themes/symbiote-sumi-e/assets/repair-seam.svg @@ -0,0 +1,4 @@ + +repair seam + + diff --git a/themes/symbiote-sumi-e/assets/seal-send-mark.svg b/themes/symbiote-sumi-e/assets/seal-send-mark.svg new file mode 100644 index 0000000..b3fcf05 --- /dev/null +++ b/themes/symbiote-sumi-e/assets/seal-send-mark.svg @@ -0,0 +1,4 @@ + +seal send mark + + diff --git a/themes/symbiote-sumi-e/assets/workspace-panel-safe.jpg b/themes/symbiote-sumi-e/assets/workspace-panel-safe.jpg new file mode 100644 index 0000000..eaa7879 Binary files /dev/null and b/themes/symbiote-sumi-e/assets/workspace-panel-safe.jpg differ diff --git a/themes/symbiote-sumi-e/background.jpg b/themes/symbiote-sumi-e/background.jpg new file mode 100644 index 0000000..614df46 Binary files /dev/null and b/themes/symbiote-sumi-e/background.jpg differ diff --git a/themes/symbiote-sumi-e/preview.jpg b/themes/symbiote-sumi-e/preview.jpg new file mode 100644 index 0000000..ccc7674 Binary files /dev/null and b/themes/symbiote-sumi-e/preview.jpg differ diff --git a/themes/symbiote-sumi-e/theme.css b/themes/symbiote-sumi-e/theme.css new file mode 100644 index 0000000..30afe7c --- /dev/null +++ b/themes/symbiote-sumi-e/theme.css @@ -0,0 +1,175 @@ +html.awesome-codex-theme[data-awesome-codex-theme="symbiote-sumi-e"] { + --batch-theme-signature: "symbiote-sumi-e"; + --batch-radius: 2px 12px 3px 9px; + --batch-radius-lg: 4px 22px 5px 16px; + --batch-edge: polygon(0 3%, 98% 0, 100% 92%, 96% 100%, 2% 97%); + --batch-border-width: 1px; + --batch-display-font: YuMincho, Hiragino Mincho ProN, Songti SC, Georgia, serif; + --batch-code-font: SFMono-Regular, Menlo, Consolas, monospace; + --batch-mark: var(--act-asset-ink-drop-mon); + background-position: center !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="symbiote-sumi-e"][data-awesome-codex-artifact-panel-state="open"] { + background-image: linear-gradient(115deg, var(--act-scrim), color-mix(in srgb, var(--act-scrim) 54%, transparent)), var(--act-asset-workspace-panel-safe) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="symbiote-sumi-e"] :is(h1, h2, h3, [data-awesome-codex-sidebar-brand="true"], #awesome-codex-theme-chrome) { + font-family: var(--batch-display-font) !important; + letter-spacing: 0.02em; + text-transform: none; +} + +html.awesome-codex-theme[data-awesome-codex-theme="symbiote-sumi-e"] :is(pre, code, kbd, samp, .monaco-editor, .terminal) { + font-family: var(--batch-code-font) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="symbiote-sumi-e"] .app-shell-left-panel { + border-inline-end: var(--batch-border-width) solid var(--act-border-strong) !important; + box-shadow: 0 10px 30px color-mix(in srgb, var(--act-text) 10%, transparent) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="symbiote-sumi-e"] [data-awesome-codex-sidebar-brand="true"] { + background-image: var(--act-asset-ink-drop-mon) !important; + background-position: right 10px center !important; + background-repeat: no-repeat !important; + background-size: 24px 24px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="symbiote-sumi-e"] :is([data-awesome-codex-sidebar-nav], [data-awesome-codex-domain-header], [data-awesome-codex-project-row], [data-awesome-codex-task-row]) { + border-radius: var(--batch-radius) !important; + transition: background-color 140ms ease, color 140ms ease, transform 140ms ease; +} + +html.awesome-codex-theme[data-awesome-codex-theme="symbiote-sumi-e"] :is([data-awesome-codex-sidebar-nav], [data-awesome-codex-project-row], [data-awesome-codex-task-row]):is(:hover, [aria-current="page"], [data-state="active"]) { + background: linear-gradient(90deg, var(--act-active), var(--act-hover)) !important; + box-shadow: inset 3px 0 0 var(--act-accent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="symbiote-sumi-e"] [data-awesome-codex-domain-header]::after { + content: ""; + display: inline-block; + width: 18px; + height: 18px; + margin-inline-start: auto; + background: var(--act-asset-brush-slash) center / contain no-repeat; + opacity: 0.82; +} + +html.awesome-codex-theme[data-awesome-codex-theme="symbiote-sumi-e"] [data-awesome-codex-home="true"] { + position: relative; + isolation: isolate; +} + +html.awesome-codex-theme[data-awesome-codex-theme="symbiote-sumi-e"] [data-awesome-codex-home-prompt="true"] { + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); + background: color-mix(in srgb, var(--act-surface-elevated) 88%, transparent) !important; + box-shadow: 0 10px 30px color-mix(in srgb, var(--act-text) 10%, transparent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="symbiote-sumi-e"] :is(.composer-root, .composer-surface-chrome) { + border-width: var(--batch-border-width) !important; + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); +} + +html.awesome-codex-theme[data-awesome-codex-theme="symbiote-sumi-e"] [data-awesome-codex-composer-control] { + min-width: 32px; + min-height: 32px; + border-radius: var(--batch-radius) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="symbiote-sumi-e"] [data-awesome-codex-composer-control="attachment"] { + background-image: var(--act-asset-repair-seam) !important; + background-position: center !important; + background-repeat: no-repeat !important; + background-size: 15px 15px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="symbiote-sumi-e"] [data-awesome-codex-composer-control="primary"] { + color: var(--act-background) !important; + background-color: var(--act-accent) !important; + background-image: var(--act-asset-seal-send-mark) !important; + background-position: center !important; + background-repeat: no-repeat !important; + background-size: 19px 19px !important; + box-shadow: 0 10px 30px color-mix(in srgb, var(--act-text) 10%, transparent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="symbiote-sumi-e"] [data-awesome-codex-artifact-panel="true"] { + background-color: color-mix(in srgb, var(--act-surface-elevated) 94%, transparent) !important; + background-image: linear-gradient(180deg, color-mix(in srgb, var(--act-surface-overlay) 92%, transparent), var(--act-surface-elevated)), var(--act-asset-workspace-panel-safe) !important; + background-position: center !important; + background-size: cover !important; + border-inline-start: var(--batch-border-width) solid var(--act-border-strong) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="symbiote-sumi-e"] [data-awesome-codex-artifact-header="true"] { + border-block-end: var(--batch-border-width) solid var(--act-border) !important; + background-image: var(--act-asset-negative-space-fang-notch) !important; + background-position: right 14px center !important; + background-repeat: no-repeat !important; + background-size: 44px 10px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="symbiote-sumi-e"] :is([role="dialog"], [role="menu"], [role="listbox"], [role="tooltip"], [data-awesome-codex-plugin-card], [data-awesome-codex-collection-row]) { + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); +} + +html.awesome-codex-theme[data-awesome-codex-theme="symbiote-sumi-e"] :is([data-awesome-codex-plugin-card], [data-awesome-codex-collection-row]):hover { + border-color: var(--act-border-strong) !important; + background-color: var(--act-hover) !important; + transform: translateY(-1px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="symbiote-sumi-e"] :is(input, textarea, select, [contenteditable="true"]):focus-visible { + outline: 2px solid var(--act-focus) !important; + outline-offset: 2px !important; + box-shadow: 0 0 0 4px color-mix(in srgb, var(--act-focus) 18%, transparent) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="symbiote-sumi-e"] :is(.monaco-editor, .terminal, .xterm-viewport, pre, code) { + border-radius: var(--batch-radius) !important; + border-color: var(--act-border-subtle) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="symbiote-sumi-e"] :is([data-awesome-codex-activity-item], [data-awesome-codex-completed-summary])::before { + content: ""; + display: inline-block; + width: 16px; + height: 16px; + margin-inline-end: 7px; + background: var(--act-asset-brush-slash) center / contain no-repeat; + vertical-align: -3px; +} + +html.awesome-codex-theme[data-awesome-codex-theme="symbiote-sumi-e"] :is(button, [role="button"]):active { + transform: translateY(1px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="symbiote-sumi-e"] *::-webkit-scrollbar-thumb { + border-radius: var(--batch-radius) !important; +} + +@media (max-width: 1120px) { + html.awesome-codex-theme[data-awesome-codex-theme="symbiote-sumi-e"] :is([data-awesome-codex-project-row], [data-awesome-codex-task-row], [data-awesome-codex-collection-row]) { + min-width: 0; + } + html.awesome-codex-theme[data-awesome-codex-theme="symbiote-sumi-e"] :is([data-awesome-codex-project-row], [data-awesome-codex-task-row], [data-awesome-codex-collection-row]) :is(span, p) { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} + +@media (prefers-reduced-motion: reduce) { + html.awesome-codex-theme[data-awesome-codex-theme="symbiote-sumi-e"] *, + html.awesome-codex-theme[data-awesome-codex-theme="symbiote-sumi-e"] *::before, + html.awesome-codex-theme[data-awesome-codex-theme="symbiote-sumi-e"] *::after { + transition-duration: 0.01ms !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + } +} diff --git a/themes/symbiote-sumi-e/theme.json b/themes/symbiote-sumi-e/theme.json new file mode 100644 index 0000000..625c79d --- /dev/null +++ b/themes/symbiote-sumi-e/theme.json @@ -0,0 +1,93 @@ +{ + "schemaVersion": 2, + "slug": "symbiote-sumi-e", + "version": "0.1.1", + "name": "Symbiote Sumi-e", + "description": "A monochrome symbiote emerging from rice-paper negative space, ink blooms, brush drag, and sparse gold repair seams.", + "author": { + "name": "Awesome Codex Themes" + }, + "license": { + "code": "MIT", + "artwork": "PROJECT-ASSET" + }, + "categories": [ + "light", + "full-workspace", + "character-inspired" + ], + "tags": [ + "symbiote-sumi-e", + "uncoated-washi", + "brush-cut-edges", + "macos" + ], + "compatibility": { + "platforms": [ + "macos" + ], + "status": "experimental", + "appVersions": [ + "26.707.*", + "26.715.*" + ] + }, + "mode": "light", + "palette": { + "background": "#F1EEE5", + "scrim": "#F1EEE5E8", + "surface": "#E9E4D9DE", + "surfaceElevated": "#FBF8F0F2", + "surfaceOverlay": "#FBF8F0F7", + "input": "#F6F1E7F2", + "text": "#171512", + "textSecondary": "#413B32", + "textMuted": "#756C5E", + "textDisabled": "#A79E90", + "icon": "#171512", + "iconSecondary": "#413B32", + "iconMuted": "#756C5E", + "border": "#A77C2D3D", + "borderSubtle": "#A77C2D24", + "borderStrong": "#A77C2D78", + "accent": "#A77C2D", + "accentHover": "#C69B48", + "selection": "#A77C2D38", + "focus": "#6E4E18", + "link": "#76551F", + "hover": "#A77C2D1A", + "active": "#6E4E182B", + "code": "#181714FA", + "terminal": "#0D0D0BFA", + "diffAdded": "#3D7B5C40", + "diffRemoved": "#A63D3240", + "success": "#3D7B5C", + "warning": "#A96A1F", + "danger": "#A63D32", + "scrollbar": "#A77C2D3D", + "scrollbarHover": "#A77C2D75", + "composer": "#F5F0E6F2" + }, + "experience": { + "brand": "SYMBIOTE SUMI-E", + "eyebrow": "INKBOND", + "headline": "LET THE INK ADAPT", + "tagline": "One surface. Two instincts.", + "status": "BOND // STABLE", + "signature": "SUMI PROTOCOL 03", + "chrome": true + }, + "files": { + "css": "theme.css", + "artwork": "background.jpg", + "preview": "preview.jpg", + "assets": { + "workspace-panel-safe": "assets/workspace-panel-safe.jpg", + "ink-drop-mon": "assets/ink-drop-mon.svg", + "brush-slash": "assets/brush-slash.svg", + "repair-seam": "assets/repair-seam.svg", + "negative-space-fang-notch": "assets/negative-space-fang-notch.svg", + "seal-send-mark": "assets/seal-send-mark.svg" + } + } +} diff --git a/themes/zaun-workshop/ASSET_LICENSE.md b/themes/zaun-workshop/ASSET_LICENSE.md new file mode 100644 index 0000000..19445f1 --- /dev/null +++ b/themes/zaun-workshop/ASSET_LICENSE.md @@ -0,0 +1,11 @@ +# Zaun Workshop assets + +Asset identifier: `PROJECT-ASSET` + +The project maintainer supplied and approved the artwork and generated UI assets included in this theme package. + +- Background source: `02-jinx-arcane.png` +- Marketing source: `02-jinx-standard-codex-imagegen-v2.png` +- Runtime files: `background.jpg`, `preview.jpg`, and the local files under `assets/` + +Code and CSS remain covered by the repository MIT license. diff --git a/themes/zaun-workshop/README.md b/themes/zaun-workshop/README.md new file mode 100644 index 0000000..c1ad74c --- /dev/null +++ b/themes/zaun-workshop/README.md @@ -0,0 +1,11 @@ +# Zaun Workshop + +Schema 2 installable package for Codex Desktop on macOS. + +- Version: 0.1.1 +- Compatibility: experimental for 26.707.* and 26.715.* +- Runtime artwork: background.jpg +- Right-panel-safe artwork: assets/workspace-panel-safe.jpg +- Original component assets: fractured-hex-mark, rivet-chevron, ampoule-bolt, spray-cross, ballistic-send-arrow +- Live verification: passed on Codex Desktop 26.715.21425; compatibility remains experimental +- Asset record: maintainer-supplied `PROJECT-ASSET`; see `ASSET_LICENSE.md` diff --git a/themes/zaun-workshop/assets/ampoule-bolt.svg b/themes/zaun-workshop/assets/ampoule-bolt.svg new file mode 100644 index 0000000..5b16a8a --- /dev/null +++ b/themes/zaun-workshop/assets/ampoule-bolt.svg @@ -0,0 +1,4 @@ + +ampoule bolt + + diff --git a/themes/zaun-workshop/assets/ballistic-send-arrow.svg b/themes/zaun-workshop/assets/ballistic-send-arrow.svg new file mode 100644 index 0000000..e9a9d68 --- /dev/null +++ b/themes/zaun-workshop/assets/ballistic-send-arrow.svg @@ -0,0 +1,4 @@ + +ballistic send arrow + + diff --git a/themes/zaun-workshop/assets/fractured-hex-mark.svg b/themes/zaun-workshop/assets/fractured-hex-mark.svg new file mode 100644 index 0000000..d24bfa0 --- /dev/null +++ b/themes/zaun-workshop/assets/fractured-hex-mark.svg @@ -0,0 +1,4 @@ + +fractured-hex mark + + diff --git a/themes/zaun-workshop/assets/rivet-chevron.svg b/themes/zaun-workshop/assets/rivet-chevron.svg new file mode 100644 index 0000000..3c41dc1 --- /dev/null +++ b/themes/zaun-workshop/assets/rivet-chevron.svg @@ -0,0 +1,4 @@ + +rivet chevron + + diff --git a/themes/zaun-workshop/assets/spray-cross.svg b/themes/zaun-workshop/assets/spray-cross.svg new file mode 100644 index 0000000..839c2b7 --- /dev/null +++ b/themes/zaun-workshop/assets/spray-cross.svg @@ -0,0 +1,4 @@ + +spray cross + + diff --git a/themes/zaun-workshop/assets/workspace-panel-safe.jpg b/themes/zaun-workshop/assets/workspace-panel-safe.jpg new file mode 100644 index 0000000..73ab793 Binary files /dev/null and b/themes/zaun-workshop/assets/workspace-panel-safe.jpg differ diff --git a/themes/zaun-workshop/background.jpg b/themes/zaun-workshop/background.jpg new file mode 100644 index 0000000..3aa7abb Binary files /dev/null and b/themes/zaun-workshop/background.jpg differ diff --git a/themes/zaun-workshop/preview.jpg b/themes/zaun-workshop/preview.jpg new file mode 100644 index 0000000..27b6104 Binary files /dev/null and b/themes/zaun-workshop/preview.jpg differ diff --git a/themes/zaun-workshop/theme.css b/themes/zaun-workshop/theme.css new file mode 100644 index 0000000..f3ff9eb --- /dev/null +++ b/themes/zaun-workshop/theme.css @@ -0,0 +1,175 @@ +html.awesome-codex-theme[data-awesome-codex-theme="zaun-workshop"] { + --batch-theme-signature: "zaun-workshop"; + --batch-radius: 4px; + --batch-radius-lg: 8px; + --batch-edge: polygon(0 0, 96% 0, 100% 24%, 100% 100%, 4% 100%, 0 76%); + --batch-border-width: 2px; + --batch-display-font: Avenir Next Condensed, DIN Condensed, Helvetica Neue, sans-serif; + --batch-code-font: SFMono-Regular, Menlo, Consolas, monospace; + --batch-mark: var(--act-asset-fractured-hex-mark); + background-position: center !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="zaun-workshop"][data-awesome-codex-artifact-panel-state="open"] { + background-image: linear-gradient(115deg, var(--act-scrim), color-mix(in srgb, var(--act-scrim) 54%, transparent)), var(--act-asset-workspace-panel-safe) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="zaun-workshop"] :is(h1, h2, h3, [data-awesome-codex-sidebar-brand="true"], #awesome-codex-theme-chrome) { + font-family: var(--batch-display-font) !important; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +html.awesome-codex-theme[data-awesome-codex-theme="zaun-workshop"] :is(pre, code, kbd, samp, .monaco-editor, .terminal) { + font-family: var(--batch-code-font) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="zaun-workshop"] .app-shell-left-panel { + border-inline-end: var(--batch-border-width) solid var(--act-border-strong) !important; + box-shadow: 10px 10px 0 color-mix(in srgb, var(--act-focus) 18%, transparent) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="zaun-workshop"] [data-awesome-codex-sidebar-brand="true"] { + background-image: var(--act-asset-fractured-hex-mark) !important; + background-position: right 10px center !important; + background-repeat: no-repeat !important; + background-size: 24px 24px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="zaun-workshop"] :is([data-awesome-codex-sidebar-nav], [data-awesome-codex-domain-header], [data-awesome-codex-project-row], [data-awesome-codex-task-row]) { + border-radius: var(--batch-radius) !important; + transition: background-color 140ms ease, color 140ms ease, transform 140ms ease; +} + +html.awesome-codex-theme[data-awesome-codex-theme="zaun-workshop"] :is([data-awesome-codex-sidebar-nav], [data-awesome-codex-project-row], [data-awesome-codex-task-row]):is(:hover, [aria-current="page"], [data-state="active"]) { + background: linear-gradient(90deg, var(--act-active), var(--act-hover)) !important; + box-shadow: inset 3px 0 0 var(--act-accent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="zaun-workshop"] [data-awesome-codex-domain-header]::after { + content: ""; + display: inline-block; + width: 18px; + height: 18px; + margin-inline-start: auto; + background: var(--act-asset-rivet-chevron) center / contain no-repeat; + opacity: 0.82; +} + +html.awesome-codex-theme[data-awesome-codex-theme="zaun-workshop"] [data-awesome-codex-home="true"] { + position: relative; + isolation: isolate; +} + +html.awesome-codex-theme[data-awesome-codex-theme="zaun-workshop"] [data-awesome-codex-home-prompt="true"] { + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); + background: color-mix(in srgb, var(--act-surface-elevated) 88%, transparent) !important; + box-shadow: 10px 10px 0 color-mix(in srgb, var(--act-focus) 18%, transparent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="zaun-workshop"] :is(.composer-root, .composer-surface-chrome) { + border-width: var(--batch-border-width) !important; + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); +} + +html.awesome-codex-theme[data-awesome-codex-theme="zaun-workshop"] [data-awesome-codex-composer-control] { + min-width: 32px; + min-height: 32px; + border-radius: var(--batch-radius) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="zaun-workshop"] [data-awesome-codex-composer-control="attachment"] { + background-image: var(--act-asset-ampoule-bolt) !important; + background-position: center !important; + background-repeat: no-repeat !important; + background-size: 15px 15px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="zaun-workshop"] [data-awesome-codex-composer-control="primary"] { + color: var(--act-background) !important; + background-color: var(--act-accent) !important; + background-image: var(--act-asset-ballistic-send-arrow) !important; + background-position: center !important; + background-repeat: no-repeat !important; + background-size: 19px 19px !important; + box-shadow: 10px 10px 0 color-mix(in srgb, var(--act-focus) 18%, transparent); +} + +html.awesome-codex-theme[data-awesome-codex-theme="zaun-workshop"] [data-awesome-codex-artifact-panel="true"] { + background-color: color-mix(in srgb, var(--act-surface-elevated) 94%, transparent) !important; + background-image: linear-gradient(180deg, color-mix(in srgb, var(--act-surface-overlay) 92%, transparent), var(--act-surface-elevated)), var(--act-asset-workspace-panel-safe) !important; + background-position: center !important; + background-size: cover !important; + border-inline-start: var(--batch-border-width) solid var(--act-border-strong) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="zaun-workshop"] [data-awesome-codex-artifact-header="true"] { + border-block-end: var(--batch-border-width) solid var(--act-border) !important; + background-image: var(--act-asset-spray-cross) !important; + background-position: right 14px center !important; + background-repeat: no-repeat !important; + background-size: 44px 10px !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="zaun-workshop"] :is([role="dialog"], [role="menu"], [role="listbox"], [role="tooltip"], [data-awesome-codex-plugin-card], [data-awesome-codex-collection-row]) { + border-radius: var(--batch-radius-lg) !important; + clip-path: var(--batch-edge); +} + +html.awesome-codex-theme[data-awesome-codex-theme="zaun-workshop"] :is([data-awesome-codex-plugin-card], [data-awesome-codex-collection-row]):hover { + border-color: var(--act-border-strong) !important; + background-color: var(--act-hover) !important; + transform: translateY(-1px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="zaun-workshop"] :is(input, textarea, select, [contenteditable="true"]):focus-visible { + outline: 2px solid var(--act-focus) !important; + outline-offset: 2px !important; + box-shadow: 0 0 0 4px color-mix(in srgb, var(--act-focus) 18%, transparent) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="zaun-workshop"] :is(.monaco-editor, .terminal, .xterm-viewport, pre, code) { + border-radius: var(--batch-radius) !important; + border-color: var(--act-border-subtle) !important; +} + +html.awesome-codex-theme[data-awesome-codex-theme="zaun-workshop"] :is([data-awesome-codex-activity-item], [data-awesome-codex-completed-summary])::before { + content: ""; + display: inline-block; + width: 16px; + height: 16px; + margin-inline-end: 7px; + background: var(--act-asset-rivet-chevron) center / contain no-repeat; + vertical-align: -3px; +} + +html.awesome-codex-theme[data-awesome-codex-theme="zaun-workshop"] :is(button, [role="button"]):active { + transform: translateY(1px); +} + +html.awesome-codex-theme[data-awesome-codex-theme="zaun-workshop"] *::-webkit-scrollbar-thumb { + border-radius: var(--batch-radius) !important; +} + +@media (max-width: 1120px) { + html.awesome-codex-theme[data-awesome-codex-theme="zaun-workshop"] :is([data-awesome-codex-project-row], [data-awesome-codex-task-row], [data-awesome-codex-collection-row]) { + min-width: 0; + } + html.awesome-codex-theme[data-awesome-codex-theme="zaun-workshop"] :is([data-awesome-codex-project-row], [data-awesome-codex-task-row], [data-awesome-codex-collection-row]) :is(span, p) { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} + +@media (prefers-reduced-motion: reduce) { + html.awesome-codex-theme[data-awesome-codex-theme="zaun-workshop"] *, + html.awesome-codex-theme[data-awesome-codex-theme="zaun-workshop"] *::before, + html.awesome-codex-theme[data-awesome-codex-theme="zaun-workshop"] *::after { + transition-duration: 0.01ms !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + } +} diff --git a/themes/zaun-workshop/theme.json b/themes/zaun-workshop/theme.json new file mode 100644 index 0000000..6716334 --- /dev/null +++ b/themes/zaun-workshop/theme.json @@ -0,0 +1,93 @@ +{ + "schemaVersion": 2, + "slug": "zaun-workshop", + "version": "0.1.1", + "name": "Zaun Workshop", + "description": "A volatile undercity workshop framed by fractured stained glass, bridge ironwork, chemical smoke, and kinetic diagonal motion.", + "author": { + "name": "Awesome Codex Themes" + }, + "license": { + "code": "MIT", + "artwork": "PROJECT-ASSET" + }, + "categories": [ + "dark", + "full-workspace", + "character-inspired" + ], + "tags": [ + "zaun-workshop", + "smoked-glass", + "fractured-polygons", + "macos" + ], + "compatibility": { + "platforms": [ + "macos" + ], + "status": "experimental", + "appVersions": [ + "26.707.*", + "26.715.*" + ] + }, + "mode": "dark", + "palette": { + "background": "#080817", + "scrim": "#080817E8", + "surface": "#101125DE", + "surfaceElevated": "#17182FF2", + "surfaceOverlay": "#17182FF7", + "input": "#111329F2", + "text": "#F5F4FF", + "textSecondary": "#C7C6D9", + "textMuted": "#8E8AA8", + "textDisabled": "#5D596F", + "icon": "#F5F4FF", + "iconSecondary": "#C7C6D9", + "iconMuted": "#8E8AA8", + "border": "#00DCEB3D", + "borderSubtle": "#00DCEB24", + "borderStrong": "#00DCEB78", + "accent": "#00DCEB", + "accentHover": "#52F4FF", + "selection": "#00DCEB38", + "focus": "#B864FF", + "link": "#45E7F3", + "hover": "#00DCEB1A", + "active": "#B864FF2B", + "code": "#080913FA", + "terminal": "#05060DFA", + "diffAdded": "#73E35F40", + "diffRemoved": "#FF416740", + "success": "#73E35F", + "warning": "#FFB83E", + "danger": "#FF4167", + "scrollbar": "#00DCEB3D", + "scrollbarHover": "#00DCEB75", + "composer": "#0E1022F2" + }, + "experience": { + "brand": "ZAUN WORKSHOP", + "eyebrow": "SHIMMER CIRCUIT", + "headline": "MAKE CHAOS USEFUL", + "tagline": "Build loud. Ship precise.", + "status": "REACTOR // ONLINE", + "signature": "WORKSHOP 02", + "chrome": true + }, + "files": { + "css": "theme.css", + "artwork": "background.jpg", + "preview": "preview.jpg", + "assets": { + "workspace-panel-safe": "assets/workspace-panel-safe.jpg", + "fractured-hex-mark": "assets/fractured-hex-mark.svg", + "rivet-chevron": "assets/rivet-chevron.svg", + "ampoule-bolt": "assets/ampoule-bolt.svg", + "spray-cross": "assets/spray-cross.svg", + "ballistic-send-arrow": "assets/ballistic-send-arrow.svg" + } + } +}