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. +

+ +
    +
  • Universal build for Apple Silicon and Intel
  • +
  • Developer ID signed and notarized
  • +
  • Distributed directly through GitHub Releases
  • +
+
+ + +
+ +
+
+

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. +

+
+
+ + + +
+
+

Release details

+

Install the current public build in a minute.

+
+
    +
  1. Download the latest notarized DMG.
  2. +
  3. Open it and drag Float into Applications.
  4. +
  5. Launch Float, press ⌘O, and open your first image.
  6. +
+ +
+
+
+ + 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