diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml
new file mode 100644
index 0000000..1c4ac77
--- /dev/null
+++ b/.github/workflows/pages.yml
@@ -0,0 +1,37 @@
+name: Pages
+
+on:
+ push:
+ branches: [master]
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+
+concurrency:
+ group: pages
+ cancel-in-progress: true
+
+jobs:
+ deploy:
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Configure GitHub Pages
+ uses: actions/configure-pages@v5
+
+ - name: Upload site artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: site
+
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
diff --git a/.github/workflows/release-bundles.yml b/.github/workflows/release-bundles.yml
index 8a1c4d6..4609ff5 100644
--- a/.github/workflows/release-bundles.yml
+++ b/.github/workflows/release-bundles.yml
@@ -10,19 +10,37 @@ permissions:
contents: write
jobs:
- build:
- name: Build ${{ matrix.os }}
- strategy:
- fail-fast: false
- matrix:
- os: [macos-latest, windows-latest]
- runs-on: ${{ matrix.os }}
+ build-macos-public:
+ name: Build signed macOS release
+ runs-on: macos-latest
+ env:
+ APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
+ APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
+ APPLE_ID: ${{ secrets.APPLE_ID }}
+ APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
+ APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
steps:
+ - name: Check required secrets
+ run: |
+ set -euo pipefail
+ missing=0
+ for name in APPLE_CERTIFICATE APPLE_CERTIFICATE_PASSWORD APPLE_ID APPLE_PASSWORD APPLE_TEAM_ID; do
+ if [ -z "${!name:-}" ]; then
+ echo "Missing required secret: $name" >&2
+ missing=1
+ fi
+ done
+ if [ "$missing" -ne 0 ]; then
+ exit 1
+ fi
+
- name: Checkout
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
+ with:
+ targets: aarch64-apple-darwin,x86_64-apple-darwin
- name: Setup Node
uses: actions/setup-node@v4
@@ -36,49 +54,122 @@ jobs:
- name: Install Tauri CLI
run: cargo install tauri-cli --locked --force
- - name: Build Tauri bundles
- run: cargo tauri build --ci
+ - name: Import Apple Developer ID certificate
+ uses: apple-actions/import-codesign-certs@v3
+ with:
+ p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }}
+ p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
+
+ - name: Resolve signing identity
+ id: signing_identity
+ run: |
+ set -euo pipefail
+ identity=$(security find-identity -v -p codesigning | grep "Developer ID Application" | head -n 1 | sed -E 's/.*"([^"]+)".*/\1/')
+ if [ -z "$identity" ]; then
+ echo "Developer ID Application identity not found in keychain" >&2
+ security find-identity -v -p codesigning || true
+ exit 1
+ fi
+ echo "identity=$identity" >> "$GITHUB_OUTPUT"
+
+ - name: Build universal app and DMG
+ env:
+ APPLE_SIGNING_IDENTITY: ${{ steps.signing_identity.outputs.identity }}
+ run: cargo tauri build --ci --bundles app,dmg --target universal-apple-darwin
- - name: Package macOS artifacts
- if: matrix.os == 'macos-latest'
+ - name: Staple and validate notarized artifacts
run: |
- app_path="src-tauri/target/release/bundle/macos/Always On Top.app"
+ set -euo pipefail
+ app_path="src-tauri/target/universal-apple-darwin/release/bundle/macos/Float.app"
+ dmg_path=$(find src-tauri/target/universal-apple-darwin/release/bundle/dmg -maxdepth 1 -name '*.dmg' | head -n 1)
if [ ! -d "$app_path" ]; then
echo "App not found at $app_path" >&2
exit 1
fi
- ditto -c -k --sequesterRsrc --keepParent "$app_path" "Always-On-Top-macos.zip"
+ if [ -z "$dmg_path" ]; then
+ echo "DMG output not found" >&2
+ exit 1
+ fi
+ xcrun stapler staple "$app_path"
+ xcrun stapler validate "$app_path"
+ xcrun stapler staple "$dmg_path"
+ xcrun stapler validate "$dmg_path"
+ spctl -a -vvv "$app_path"
+ spctl -a -vvv -t open "$dmg_path"
+
+ - name: Create stable public artifacts
+ run: |
+ set -euo pipefail
+ dmg_path=$(find src-tauri/target/universal-apple-darwin/release/bundle/dmg -maxdepth 1 -name '*.dmg' | head -n 1)
+ cp "$dmg_path" Float-macos-universal.dmg
+ shasum -a 256 Float-macos-universal.dmg > Float-macos-universal.sha256
+
+ - name: Upload macOS release artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: float-macos-public
+ path: |
+ Float-macos-universal.dmg
+ Float-macos-universal.sha256
+ if-no-files-found: error
+ retention-days: 14
- - name: Package Windows artifacts
- if: matrix.os == 'windows-latest'
+ build-windows-preview:
+ name: Build Windows preview artifact
+ runs-on: windows-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Install Rust
+ uses: dtolnay/rust-toolchain@stable
+
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: "20"
+ cache: "npm"
+
+ - name: Install JS dependencies
+ run: npm ci
+
+ - name: Install Tauri CLI
+ run: cargo install tauri-cli --locked --force
+
+ - name: Build Windows NSIS installer
+ run: cargo tauri build --ci --bundles nsis
+
+ - name: Collect Windows preview artifact
shell: pwsh
run: |
$installer = Get-ChildItem "src-tauri/target/release/bundle/nsis/*.exe" | Select-Object -First 1
if (-not $installer) { Write-Error "Installer not found"; exit 1 }
- Copy-Item $installer.FullName "Always-On-Top-windows.exe" -Force
+ Copy-Item $installer.FullName "Float-windows-preview.exe" -Force
- - name: Upload artifact
+ - name: Upload Windows preview artifact
uses: actions/upload-artifact@v4
with:
- name: always-on-top-${{ matrix.os }}
- path: |
- Always-On-Top-macos.zip
- Always-On-Top-windows.exe
+ name: float-windows-preview
+ path: Float-windows-preview.exe
if-no-files-found: error
+ retention-days: 14
release:
- name: Publish release artifacts
- needs: build
+ name: Publish public release assets
+ needs: build-macos-public
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
steps:
- - name: Download artifacts
+ - name: Download macOS release artifact
uses: actions/download-artifact@v4
with:
- path: dist-artifacts
+ name: float-macos-public
+ path: release-artifacts
- name: Publish to GitHub Releases
uses: softprops/action-gh-release@v2
with:
- files: dist-artifacts/**/*
+ files: |
+ release-artifacts/Float-macos-universal.dmg
+ release-artifacts/Float-macos-universal.sha256
generate_release_notes: true
diff --git a/.github/workflows/release-plz.yml b/.github/workflows/release-plz.yml
index ec041b1..81abae0 100644
--- a/.github/workflows/release-plz.yml
+++ b/.github/workflows/release-plz.yml
@@ -2,7 +2,7 @@ name: release-plz
on:
push:
- branches: [main]
+ branches: [master]
workflow_dispatch:
inputs:
dry_run:
diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml
index 8182739..6497ffe 100644
--- a/.github/workflows/ui-tests.yml
+++ b/.github/workflows/ui-tests.yml
@@ -1,8 +1,8 @@
-name: UI Tests (Playwright + tauri-driver)
+name: UI Tests
on:
push:
- branches: [ main ]
+ branches: [ master ]
pull_request:
jobs:
@@ -16,19 +16,11 @@ jobs:
with:
node-version: '22'
- - name: Set up Rust
- uses: dtolnay/rust-toolchain@stable
-
- name: Install JS deps
run: npm install --no-fund
- name: Install Playwright browsers
run: npx playwright install --with-deps
- - name: Install tauri-driver
- run: cargo install tauri-driver --locked
-
- - name: Run Playwright tests
- env:
- PATH: "$HOME/.cargo/bin:${PATH}"
+ - name: Run Playwright smoke tests
run: npm run test:ui
diff --git a/README.md b/README.md
index 8637bf7..2e5c68d 100644
--- a/README.md
+++ b/README.md
@@ -24,9 +24,8 @@ Relevant specs: `specs/always-on-top/`, `specs/file-selection/`, `specs/fit-wind
- Linux: system dependencies per Tauri docs; only dev run covered here.
- Optional: `just` for common tasks (install via `cargo install just`).
-## Quick Start (Tauri shell)
+## Local Development
```sh
-# Clone and enter repo
just tauri-dev # Runs Tauri in dev mode
```
- The window launches always-on-top; use File → Open… to pick an image.
@@ -36,11 +35,11 @@ just tauri-dev # Runs Tauri in dev mode
- Install the Tauri WebDriver once via `cargo install tauri-driver --locked` so the `tauri-driver` binary is on your `PATH` (or export `TAURI_DRIVER_PATH` pointing to it).
- Execute `npm run test:ui` to run the Playwright spec in `tests/`.
-### Build Bundles (release artifacts)
+## Internal Packaging
```sh
just tauri-build # macOS .app + Windows NSIS installer
```
-Artifacts:
+Outputs:
- macOS app bundle: `src-tauri/target/release/bundle/macos/Float.app`
- Windows NSIS installer: `src-tauri/target/release/bundle/nsis/Float_*.exe`
@@ -49,7 +48,7 @@ To open the built macOS app locally:
just tauri-open
```
-### Cross-build Windows executable (macOS host)
+### Windows cross-build from macOS
```sh
just tauri-build-windows
```
@@ -62,17 +61,40 @@ just build-run # cargo run
just bundle-run # cargo bundle --release (macOS .app)
```
-## Downloads
-- GitHub Actions (workflow: `release-bundles`) builds macOS and Windows artifacts and uploads them to the Releases page when a tag (`v*`) is pushed or the workflow is dispatched manually.
-- If no release is published yet, build locally using the commands above.
-- Linux packages are not produced; run locally on Linux if needed.
+## Public Release
-## Release Pipeline (manual + CI)
-1) Ensure `cargo fmt`, `cargo clippy --all-targets -- -D warnings`, and `cargo check` pass.
-2) Release automation: `.github/workflows/release-plz.yml` (runs on `main` pushes or manual dispatch) uses `release-plz` to update `CHANGELOG.md`, bump versions, tag with `v*`, and create the GitHub Release (no crates.io publish).
-3) Bundles on tags: `.github/workflows/release-bundles.yml` builds on `v*` tags (or manual dispatch), uploads build artifacts as workflow artifacts, and publishes/updates the GitHub Release with the macOS zip + Windows installer.
-4) Local build sanity (optional): `just tauri-build`; collect artifacts from `src-tauri/target/release/bundle/macos/Float.app` and `src-tauri/target/release/bundle/nsis/Float_*.exe`.
-5) Draft release notes summarizing changes and link relevant OpenSpec change IDs (release-plz populates the changelog automatically).
+The public distribution channel is:
+
+- GitHub Pages for the landing page
+- GitHub Releases for the notarized macOS download
+
+Public macOS assets are published with stable names:
+
+- `Float-macos-universal.dmg`
+- `Float-macos-universal.sha256`
+
+The landing page lives in `site/` and links to:
+
+- `https://github.com/Zacaria/float/releases/latest/download/Float-macos-universal.dmg`
+- `https://github.com/Zacaria/float/releases/latest/download/Float-macos-universal.sha256`
+
+### CI release flow
+
+1. `release-plz` updates `CHANGELOG.md`, versions, tags, and the GitHub Release from `.github/workflows/release-plz.yml`.
+2. `.github/workflows/release-bundles.yml` builds a universal macOS bundle on `v*` tags, signs it with a `Developer ID Application` certificate, staples and validates the notarized app and DMG, generates `Float-macos-universal.sha256`, and publishes the macOS assets to the GitHub Release.
+3. `.github/workflows/pages.yml` deploys the static landing page from `site/` to GitHub Pages on `master`.
+
+### CI secret contract
+
+The macOS public release workflow requires these repository secrets:
+
+- `APPLE_CERTIFICATE`
+- `APPLE_CERTIFICATE_PASSWORD`
+- `APPLE_ID`
+- `APPLE_PASSWORD`
+- `APPLE_TEAM_ID`
+
+See [`docs/releasing.md`](docs/releasing.md) for the exact contract, fallback commands, and the release checklist.
## Troubleshooting
- **Tauri missing deps**: install platform prereqs (Xcode CLT on macOS; MSVC + WebView2 on Windows).
@@ -82,3 +104,5 @@ just bundle-run # cargo bundle --release (macOS .app)
## Contributing
- Specs live under `openspec/specs/`; proposed changes go in `openspec/changes/`.
- Prefer `just tauri-dev` for local runs; keep changes small and update specs when behavior changes.
+- Commit subjects must use Conventional Commits such as `feat: ...`, `fix(menu): ...`, or `chore!: ...`.
+- Run `just install-git-hooks` once to enable the local `commit-msg` guard, or validate a range manually with `just check-commits origin/main..HEAD`.
diff --git a/docs/releasing.md b/docs/releasing.md
new file mode 100644
index 0000000..3b42f3c
--- /dev/null
+++ b/docs/releasing.md
@@ -0,0 +1,98 @@
+# Releasing Float
+
+Float ships publicly as a notarized macOS download hosted on GitHub Releases and promoted through GitHub Pages. The public deliverables are:
+
+- `Float-macos-universal.dmg`
+- `Float-macos-universal.sha256`
+
+Windows can continue to build as an internal preview artifact, but it is not part of the public release surface yet.
+
+## CI secret contract
+
+The `release-bundles` workflow expects these repository secrets:
+
+- `APPLE_CERTIFICATE`: base64-encoded `.p12` for a `Developer ID Application` certificate.
+- `APPLE_CERTIFICATE_PASSWORD`: export password for the `.p12`.
+- `APPLE_ID`: Apple account email used for notarization.
+- `APPLE_PASSWORD`: app-specific password for the Apple account.
+- `APPLE_TEAM_ID`: Apple Developer team identifier.
+
+The workflow imports the certificate, resolves the `Developer ID Application` signing identity, builds a universal Tauri bundle, staples the app and DMG, validates notarization, and publishes only the stable macOS assets to GitHub Releases.
+
+## GitHub Pages landing page
+
+The marketing/download site lives under `site/` and deploys with `.github/workflows/pages.yml`.
+
+- Public download CTA: `https://github.com/Zacaria/float/releases/latest/download/Float-macos-universal.dmg`
+- Checksum CTA: `https://github.com/Zacaria/float/releases/latest/download/Float-macos-universal.sha256`
+
+When updating the landing page, keep it macOS-only until the Windows public release path has signing, trust, and support language of its own.
+
+## Manual fallback
+
+If the GitHub workflow is unavailable, use a local macOS machine with the Developer ID certificate installed in Keychain.
+
+1. Confirm the signing identity:
+
+```sh
+security find-identity -v -p codesigning
+```
+
+2. Build a universal macOS bundle:
+
+```sh
+rustup target add aarch64-apple-darwin x86_64-apple-darwin
+cargo tauri build --bundles app,dmg --target universal-apple-darwin
+```
+
+3. Locate the outputs:
+
+```sh
+APP_PATH="src-tauri/target/universal-apple-darwin/release/bundle/macos/Float.app"
+DMG_PATH="$(find src-tauri/target/universal-apple-darwin/release/bundle/dmg -maxdepth 1 -name '*.dmg' | head -n 1)"
+```
+
+4. Submit the DMG for notarization if it was not handled during build:
+
+```sh
+xcrun notarytool submit "$DMG_PATH" \
+ --apple-id "$APPLE_ID" \
+ --password "$APPLE_PASSWORD" \
+ --team-id "$APPLE_TEAM_ID" \
+ --wait
+```
+
+5. Staple and validate:
+
+```sh
+xcrun stapler staple "$APP_PATH"
+xcrun stapler validate "$APP_PATH"
+xcrun stapler staple "$DMG_PATH"
+xcrun stapler validate "$DMG_PATH"
+spctl -a -vvv "$APP_PATH"
+spctl -a -vvv -t open "$DMG_PATH"
+```
+
+6. Rename to the stable public asset names and create the checksum:
+
+```sh
+cp "$DMG_PATH" Float-macos-universal.dmg
+shasum -a 256 Float-macos-universal.dmg > Float-macos-universal.sha256
+```
+
+7. Upload both files to the tagged GitHub Release.
+
+## Release checklist
+
+Before creating the tag:
+
+- Run `cargo check --manifest-path src-tauri/Cargo.toml`
+- Run `cargo clippy --manifest-path src-tauri/Cargo.toml --all-targets -- -D warnings`
+- Confirm the Pages site still describes the current release and links to the stable asset names
+
+After the tag build finishes:
+
+- Verify the release contains `Float-macos-universal.dmg` and `Float-macos-universal.sha256`
+- Download the DMG from the release, compare the checksum, and test the install from a clean macOS machine
+- Confirm first launch, file open, fit-to-image, aspect lock, slideshow, multi-file navigation, and persistence all work
+- Confirm the GitHub Pages download button resolves to the latest release asset
diff --git a/site/assets/combos_cheatsheet.png b/site/assets/combos_cheatsheet.png
new file mode 100644
index 0000000..6afe1ce
Binary files /dev/null and b/site/assets/combos_cheatsheet.png differ
diff --git a/site/assets/figma_shortcuts_cheatsheet.png b/site/assets/figma_shortcuts_cheatsheet.png
new file mode 100644
index 0000000..3518065
Binary files /dev/null and b/site/assets/figma_shortcuts_cheatsheet.png differ
diff --git a/site/assets/float-icon.png b/site/assets/float-icon.png
new file mode 100644
index 0000000..3ae0eb9
Binary files /dev/null and b/site/assets/float-icon.png differ
diff --git a/site/assets/lol_wave_management.png b/site/assets/lol_wave_management.png
new file mode 100644
index 0000000..d8c6896
Binary files /dev/null and b/site/assets/lol_wave_management.png differ
diff --git a/site/index.html b/site/index.html
new file mode 100644
index 0000000..ad04d10
--- /dev/null
+++ b/site/index.html
@@ -0,0 +1,154 @@
+
+
+
+
+
+ Float for macOS
+
+
+
+
+
+
+
+
+
+
+
+
Always on top, without the ceremony
+
Keep the image you need hovering over the work that matters.
+
+ Float is a compact macOS viewer for cheat sheets, mockups, diagrams, and reference
+ images. Open a file, fit it instantly, and keep it pinned above your editor, browser,
+ or design tools.
+
+
+
+
+
+
+
+
+
+
+
Built for real reference work
+
One job, done fast.
+
+
+
+ Float opens local images with native file pickers, keeps the window above the rest of
+ your desktop, and stays out of the way when you just need a visual anchor nearby.
+
+
+
+
+
+
+ Reference-first
+ Pin shortcut sheets, diagrams, or screenshots over active work.
+
+ Keep a single image visible while coding, designing, editing, or reviewing. Float
+ removes everything that slows down that loop.
+
+
+
+ Native controls
+ Open files quickly, fit the frame, and move through a selected image set.
+
+ Multi-file navigation, slideshow mode, aspect lock, and window persistence are built
+ into the current release without adding app chrome everywhere.
+
+
+
+ Trusted download
+ Ship a direct macOS build with the checks users expect.
+
+ Public downloads come from GitHub Releases with a stable asset name, checksum, and a
+ notarized DMG so the install path feels legitimate instead of improvised.
+
+
+
+
+
+
+
In use
+
Reference imagery stays legible without turning into another workspace.
+
+ The app is intentionally sparse: no dashboard, no sync model, no browser tabs. Just a
+ lightweight viewer for keeping visual context alive on the desktop.
+
+
+
+
+
+
+
+
Release details
+
Install the current public build in a minute.
+
+
+ Download the latest notarized DMG.
+ Open it and drag Float into Applications.
+ Launch Float, press ⌘O , and open your first image.
+
+
+
+
+
+
+
diff --git a/site/styles.css b/site/styles.css
new file mode 100644
index 0000000..42683d9
--- /dev/null
+++ b/site/styles.css
@@ -0,0 +1,459 @@
+:root {
+ color-scheme: dark;
+ --bg: #0e1015;
+ --bg-soft: #151922;
+ --line: rgba(255, 255, 255, 0.12);
+ --text: #f2eee8;
+ --muted: rgba(242, 238, 232, 0.72);
+ --accent: #ff8b5d;
+ --accent-soft: rgba(255, 139, 93, 0.16);
+ --shadow: 0 30px 80px rgba(0, 0, 0, 0.38);
+ --hero-serif: "Iowan Old Style", "Palatino Linotype", "Book Antiqua", Palatino, Georgia, serif;
+ --ui-sans: "Avenir Next Condensed", "Franklin Gothic Medium", "Arial Narrow", sans-serif;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+html {
+ scroll-behavior: smooth;
+}
+
+body {
+ margin: 0;
+ min-height: 100vh;
+ background:
+ radial-gradient(circle at top right, rgba(255, 139, 93, 0.2), transparent 24rem),
+ radial-gradient(circle at left center, rgba(101, 168, 255, 0.14), transparent 26rem),
+ linear-gradient(180deg, #171a22 0%, var(--bg) 44%, #090b0f 100%);
+ color: var(--text);
+ font-family: var(--ui-sans);
+}
+
+img {
+ display: block;
+ max-width: 100%;
+}
+
+a {
+ color: inherit;
+ text-decoration: none;
+}
+
+.page-shell {
+ width: min(1200px, calc(100% - 2rem));
+ margin: 0 auto;
+ padding-bottom: 4rem;
+}
+
+.site-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 1.5rem;
+ padding: 1.25rem 0;
+ position: sticky;
+ top: 0;
+ z-index: 20;
+ backdrop-filter: blur(18px);
+ background: linear-gradient(180deg, rgba(14, 16, 21, 0.92), rgba(14, 16, 21, 0.48));
+}
+
+.brand-lockup {
+ display: flex;
+ align-items: center;
+ gap: 0.85rem;
+}
+
+.brand-mark {
+ width: 2.75rem;
+ height: 2.75rem;
+ border-radius: 0.8rem;
+ box-shadow: 0 14px 30px rgba(0, 0, 0, 0.32);
+}
+
+.brand-name {
+ font-size: 1.4rem;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+}
+
+.eyebrow,
+.hero-kicker {
+ margin: 0 0 0.45rem;
+ color: var(--accent);
+ text-transform: uppercase;
+ letter-spacing: 0.16em;
+ font-size: 0.78rem;
+}
+
+.site-nav {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1rem;
+ color: var(--muted);
+ font-size: 0.95rem;
+}
+
+.site-nav a:hover,
+.release-links a:hover {
+ color: var(--text);
+}
+
+.hero {
+ display: grid;
+ grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
+ gap: 2.5rem;
+ align-items: center;
+ min-height: calc(100svh - 5.5rem);
+ padding: 2.5rem 0 2rem;
+}
+
+.hero-copy,
+.hero-visual,
+.support-strip,
+.feature-runway article,
+.gallery-copy,
+.gallery-card,
+.release-panel {
+ animation: rise 0.8s ease-out both;
+}
+
+.hero-copy {
+ animation-delay: 0.08s;
+}
+
+.hero-visual {
+ animation-delay: 0.18s;
+}
+
+.hero h1,
+.support-strip h2,
+.gallery-copy h2,
+.release-panel h2 {
+ margin: 0;
+ font-family: var(--hero-serif);
+ line-height: 0.97;
+ letter-spacing: -0.03em;
+}
+
+.hero h1 {
+ font-size: clamp(3.2rem, 8vw, 6.5rem);
+ max-width: 10ch;
+}
+
+.hero-body,
+.support-copy p,
+.feature-runway p,
+.gallery-copy p,
+.release-panel {
+ color: var(--muted);
+ font-size: 1.02rem;
+ line-height: 1.6;
+}
+
+.hero-body {
+ max-width: 34rem;
+ margin: 1.2rem 0 0;
+}
+
+.hero-actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.9rem;
+ margin: 1.6rem 0;
+}
+
+.button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 3.1rem;
+ padding: 0 1.25rem;
+ border-radius: 999px;
+ border: 1px solid var(--line);
+ transition:
+ transform 180ms ease,
+ background-color 180ms ease,
+ border-color 180ms ease;
+}
+
+.button:hover {
+ transform: translateY(-2px);
+}
+
+.button-primary {
+ background: var(--accent);
+ color: #1c120e;
+ border-color: transparent;
+}
+
+.button-secondary {
+ background: rgba(255, 255, 255, 0.04);
+}
+
+.hero-meta {
+ list-style: none;
+ padding: 0;
+ margin: 1.5rem 0 0;
+ display: grid;
+ gap: 0.55rem;
+ color: var(--muted);
+}
+
+.hero-meta li::before {
+ content: "•";
+ color: var(--accent);
+ margin-right: 0.55rem;
+}
+
+.hero-visual {
+ position: relative;
+ min-height: 32rem;
+}
+
+.visual-plane {
+ position: absolute;
+ inset: 8% 10% 12% 16%;
+ border-radius: 2rem;
+ background:
+ linear-gradient(145deg, rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0.03)),
+ linear-gradient(160deg, rgba(255, 139, 93, 0.2), rgba(101, 168, 255, 0.1));
+ border: 1px solid rgba(255, 255, 255, 0.12);
+ filter: blur(0.2px);
+}
+
+.shot {
+ position: absolute;
+ overflow: hidden;
+ border-radius: 1.4rem;
+ border: 1px solid rgba(255, 255, 255, 0.12);
+ box-shadow: var(--shadow);
+ background: #0d1117;
+}
+
+.shot img,
+.gallery-card img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.shot-large {
+ --drift-rotate-start: -3deg;
+ --drift-rotate-mid: -1.5deg;
+ inset: 0 8% 14% 6%;
+ transform: rotate(-3deg);
+ animation: drift 12s ease-in-out infinite;
+}
+
+.shot-small {
+ width: 38%;
+ height: 34%;
+}
+
+.shot-left {
+ --drift-rotate-start: -6deg;
+ --drift-rotate-mid: -4deg;
+ left: 2%;
+ bottom: 3%;
+ transform: rotate(-6deg);
+ animation: drift 14s ease-in-out infinite reverse;
+}
+
+.shot-right {
+ --drift-rotate-start: 6deg;
+ --drift-rotate-mid: 4deg;
+ right: 0;
+ top: 5%;
+ transform: rotate(6deg);
+ animation: drift 16s ease-in-out infinite;
+}
+
+.support-strip,
+.gallery,
+.release-panel {
+ border-top: 1px solid var(--line);
+}
+
+.support-strip {
+ display: grid;
+ grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
+ gap: 2rem;
+ padding: 2rem 0;
+}
+
+.support-strip h2,
+.gallery-copy h2,
+.release-panel h2 {
+ font-size: clamp(2rem, 4vw, 3.6rem);
+}
+
+.feature-runway {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 1rem;
+ padding: 1rem 0 3rem;
+}
+
+.feature-runway article {
+ padding: 1.4rem 0.3rem 1rem 0;
+ border-top: 1px solid var(--line);
+}
+
+.feature-runway h3 {
+ margin: 0;
+ font-size: 1.5rem;
+ line-height: 1.05;
+ font-weight: 500;
+}
+
+.feature-runway p {
+ margin: 0.9rem 0 0;
+}
+
+.gallery {
+ display: grid;
+ grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr);
+ gap: 2rem;
+ align-items: start;
+ padding: 2.5rem 0 3rem;
+}
+
+.gallery-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 1rem;
+}
+
+.gallery-card {
+ min-height: 15rem;
+ border-radius: 1.5rem;
+ overflow: hidden;
+ box-shadow: var(--shadow);
+ transform: translateY(0);
+ transition: transform 220ms ease;
+}
+
+.gallery-card:first-child {
+ grid-column: span 2;
+ min-height: 21rem;
+}
+
+.gallery-card:hover {
+ transform: translateY(-4px);
+}
+
+.release-panel {
+ padding: 2.5rem 0 0;
+}
+
+.install-steps {
+ margin: 1.25rem 0 1rem;
+ padding-left: 1.3rem;
+ display: grid;
+ gap: 0.75rem;
+ color: var(--muted);
+}
+
+.install-steps span {
+ color: var(--text);
+}
+
+.release-links {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1.2rem;
+ margin-top: 1.1rem;
+ color: var(--muted);
+}
+
+@keyframes rise {
+ from {
+ opacity: 0;
+ transform: translateY(18px);
+ }
+
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+@keyframes drift {
+ 0%,
+ 100% {
+ transform: translateY(0) rotate(var(--drift-rotate-start, -3deg));
+ }
+
+ 50% {
+ transform: translateY(-10px) rotate(var(--drift-rotate-mid, -1.5deg));
+ }
+}
+
+@media (max-width: 980px) {
+ .site-header,
+ .hero,
+ .support-strip,
+ .gallery,
+ .feature-runway {
+ grid-template-columns: 1fr;
+ }
+
+ .site-header {
+ position: static;
+ padding-bottom: 0.5rem;
+ }
+
+ .hero {
+ min-height: auto;
+ padding-top: 1.5rem;
+ }
+
+ .hero-visual {
+ order: -1;
+ min-height: 24rem;
+ }
+
+ .feature-runway {
+ gap: 0.2rem;
+ }
+}
+
+@media (max-width: 640px) {
+ .page-shell {
+ width: min(100% - 1.25rem, 1200px);
+ }
+
+ .hero h1 {
+ max-width: 11ch;
+ }
+
+ .hero-actions,
+ .release-links,
+ .site-nav {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+
+ .button {
+ width: 100%;
+ }
+
+ .gallery-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .gallery-card:first-child {
+ grid-column: auto;
+ }
+
+ .shot-small {
+ display: none;
+ }
+
+ .shot-large {
+ inset: 0;
+ }
+}
diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png
new file mode 100644
index 0000000..0f20569
Binary files /dev/null and b/src-tauri/icons/128x128.png differ
diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png
new file mode 100644
index 0000000..d100457
Binary files /dev/null and b/src-tauri/icons/128x128@2x.png differ
diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png
new file mode 100644
index 0000000..99a3b01
Binary files /dev/null and b/src-tauri/icons/32x32.png differ
diff --git a/src-tauri/icons/Square107x107Logo.png b/src-tauri/icons/Square107x107Logo.png
new file mode 100644
index 0000000..b19b85b
Binary files /dev/null and b/src-tauri/icons/Square107x107Logo.png differ
diff --git a/src-tauri/icons/Square142x142Logo.png b/src-tauri/icons/Square142x142Logo.png
new file mode 100644
index 0000000..79e4ee9
Binary files /dev/null and b/src-tauri/icons/Square142x142Logo.png differ
diff --git a/src-tauri/icons/Square150x150Logo.png b/src-tauri/icons/Square150x150Logo.png
new file mode 100644
index 0000000..4455f22
Binary files /dev/null and b/src-tauri/icons/Square150x150Logo.png differ
diff --git a/src-tauri/icons/Square284x284Logo.png b/src-tauri/icons/Square284x284Logo.png
new file mode 100644
index 0000000..1d0ebbd
Binary files /dev/null and b/src-tauri/icons/Square284x284Logo.png differ
diff --git a/src-tauri/icons/Square30x30Logo.png b/src-tauri/icons/Square30x30Logo.png
new file mode 100644
index 0000000..14f7456
Binary files /dev/null and b/src-tauri/icons/Square30x30Logo.png differ
diff --git a/src-tauri/icons/Square310x310Logo.png b/src-tauri/icons/Square310x310Logo.png
new file mode 100644
index 0000000..ac049ca
Binary files /dev/null and b/src-tauri/icons/Square310x310Logo.png differ
diff --git a/src-tauri/icons/Square44x44Logo.png b/src-tauri/icons/Square44x44Logo.png
new file mode 100644
index 0000000..085a471
Binary files /dev/null and b/src-tauri/icons/Square44x44Logo.png differ
diff --git a/src-tauri/icons/Square71x71Logo.png b/src-tauri/icons/Square71x71Logo.png
new file mode 100644
index 0000000..62493aa
Binary files /dev/null and b/src-tauri/icons/Square71x71Logo.png differ
diff --git a/src-tauri/icons/Square89x89Logo.png b/src-tauri/icons/Square89x89Logo.png
new file mode 100644
index 0000000..db1d4c8
Binary files /dev/null and b/src-tauri/icons/Square89x89Logo.png differ
diff --git a/src-tauri/icons/StoreLogo.png b/src-tauri/icons/StoreLogo.png
new file mode 100644
index 0000000..79388cc
Binary files /dev/null and b/src-tauri/icons/StoreLogo.png differ
diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..824bd52
Binary files /dev/null and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png differ
diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..57027eb
Binary files /dev/null and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..824bd52
Binary files /dev/null and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..1435bb2
Binary files /dev/null and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png differ
diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..6fd72cd
Binary files /dev/null and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..1435bb2
Binary files /dev/null and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..c497fcf
Binary files /dev/null and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..f1216dd
Binary files /dev/null and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..c497fcf
Binary files /dev/null and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..a3b78c2
Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..2a10b96
Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..a3b78c2
Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4456d4d
Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..82e54e3
Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..4456d4d
Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns
index 3c50905..7b38882 100644
Binary files a/src-tauri/icons/icon.icns and b/src-tauri/icons/icon.icns differ
diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico
index bd91d13..30f57c2 100644
Binary files a/src-tauri/icons/icon.ico and b/src-tauri/icons/icon.ico differ
diff --git a/src-tauri/icons/icon.png b/src-tauri/icons/icon.png
index da6c642..abbe8e2 100644
Binary files a/src-tauri/icons/icon.png and b/src-tauri/icons/icon.png differ
diff --git a/src-tauri/icons/icon_base_1024.png b/src-tauri/icons/icon_base_1024.png
index 81d5b9b..3ae0eb9 100644
Binary files a/src-tauri/icons/icon_base_1024.png and b/src-tauri/icons/icon_base_1024.png differ
diff --git a/src-tauri/icons/ios/AppIcon-20x20@1x.png b/src-tauri/icons/ios/AppIcon-20x20@1x.png
new file mode 100644
index 0000000..a2891d7
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@1x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-20x20@2x-1.png b/src-tauri/icons/ios/AppIcon-20x20@2x-1.png
new file mode 100644
index 0000000..3eadfae
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@2x-1.png differ
diff --git a/src-tauri/icons/ios/AppIcon-20x20@2x.png b/src-tauri/icons/ios/AppIcon-20x20@2x.png
new file mode 100644
index 0000000..3eadfae
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@2x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-20x20@3x.png b/src-tauri/icons/ios/AppIcon-20x20@3x.png
new file mode 100644
index 0000000..71a1804
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@3x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-29x29@1x.png b/src-tauri/icons/ios/AppIcon-29x29@1x.png
new file mode 100644
index 0000000..da4936f
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@1x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-29x29@2x-1.png b/src-tauri/icons/ios/AppIcon-29x29@2x-1.png
new file mode 100644
index 0000000..a3ef8da
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@2x-1.png differ
diff --git a/src-tauri/icons/ios/AppIcon-29x29@2x.png b/src-tauri/icons/ios/AppIcon-29x29@2x.png
new file mode 100644
index 0000000..a3ef8da
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@2x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-29x29@3x.png b/src-tauri/icons/ios/AppIcon-29x29@3x.png
new file mode 100644
index 0000000..38eecb9
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@3x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-40x40@1x.png b/src-tauri/icons/ios/AppIcon-40x40@1x.png
new file mode 100644
index 0000000..3eadfae
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@1x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-40x40@2x-1.png b/src-tauri/icons/ios/AppIcon-40x40@2x-1.png
new file mode 100644
index 0000000..9d0963b
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@2x-1.png differ
diff --git a/src-tauri/icons/ios/AppIcon-40x40@2x.png b/src-tauri/icons/ios/AppIcon-40x40@2x.png
new file mode 100644
index 0000000..9d0963b
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@2x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-40x40@3x.png b/src-tauri/icons/ios/AppIcon-40x40@3x.png
new file mode 100644
index 0000000..5f175e3
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@3x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-512@2x.png b/src-tauri/icons/ios/AppIcon-512@2x.png
new file mode 100644
index 0000000..f773679
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-512@2x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-60x60@2x.png b/src-tauri/icons/ios/AppIcon-60x60@2x.png
new file mode 100644
index 0000000..5f175e3
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-60x60@2x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-60x60@3x.png b/src-tauri/icons/ios/AppIcon-60x60@3x.png
new file mode 100644
index 0000000..d10d9bc
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-60x60@3x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-76x76@1x.png b/src-tauri/icons/ios/AppIcon-76x76@1x.png
new file mode 100644
index 0000000..68cbc7f
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-76x76@1x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-76x76@2x.png b/src-tauri/icons/ios/AppIcon-76x76@2x.png
new file mode 100644
index 0000000..5ecc167
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-76x76@2x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png b/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png
new file mode 100644
index 0000000..9189e51
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png differ
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index a6117f8..b8b787f 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -33,12 +33,19 @@
},
"bundle": {
"active": true,
+ "category": "Utility",
"targets": [
"app",
"dmg",
"nsis"
],
- "icon": [],
+ "icon": [
+ "icons/32x32.png",
+ "icons/128x128.png",
+ "icons/128x128@2x.png",
+ "icons/icon.icns",
+ "icons/icon.ico"
+ ],
"windows": {
"nsis": {
"displayLanguageSelector": false