commit c08a3e482beecb1c879b822f790173068ab42a4d Author: ilia Date: Wed May 20 21:05:29 2026 -0400 Launch AutoBank site with Serial Console theme and scroll hourglass logo. - AutoBank branding, phosphor-green terminal aesthetic - Five normalized logo frames that advance on scroll - Logo build scripts and design exploration docs under docs/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dc12da9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +*.log +node_modules/ + +# Local preview / editor +.cursor/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..4e2a973 --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +# AutoBank + +Static landing page for **AutoBank** — automation consulting by Ilia Dobkin (scripts, n8n/Zapier/Make, CI/CD, webhooks, AI integrations). + +**Live site:** [auto.levkin.ca](https://auto.levkin.ca) + +## Stack + +- `index.html` — single page +- `styles.css` — Serial Console theme (phosphor green on black) +- `assets/logos/` — hourglass favicon + scroll-driven header logo (`*-64.png`) + +No build step for deploy. Plain HTML/CSS. + +## Local preview + +```bash +python3 -m http.server 8080 +# http://localhost:8080 +``` + +## Logo tooling (optional) + +Requires Node 18+. + +```bash +npm install +npm run logos:normalize # source PNGs → assets/logos/*-64.png & *-128.png +``` + +Source art: `assets/logos/source/`. Design pickers (not deployed): `docs/brand-names.html`, `docs/style-directions.html`. + +## Deploy + +Publish the repo root as static files (nginx `root`, Gitea pages, etc.). + +```bash +git push origin master +``` + +## Links + +| What | URL | +|------|-----| +| Book a call | [cal.levkin.ca/ilia/consult](https://cal.levkin.ca/ilia/consult) | +| Email | [ilia@levkine.ca](mailto:ilia@levkine.ca) | +| LinkedIn | [linkedin.com/in/ilia-dobkin-8263343](https://www.linkedin.com/in/ilia-dobkin-8263343/) | +| Git | [git.levkin.ca](https://git.levkin.ca) | + +## Edit + +| What | Where | +|------|--------| +| Copy / sections | `index.html` | +| Theme colors | `styles.css` (`:root`) | +| Logo frames | `assets/logos/source/` then `npm run logos:normalize` | +| Booking URL | `cal.levkin.ca` in `index.html` | diff --git a/assets/logos/00-full-128.png b/assets/logos/00-full-128.png new file mode 100644 index 0000000..2269028 Binary files /dev/null and b/assets/logos/00-full-128.png differ diff --git a/assets/logos/00-full-64.png b/assets/logos/00-full-64.png new file mode 100644 index 0000000..0c5e977 Binary files /dev/null and b/assets/logos/00-full-64.png differ diff --git a/assets/logos/01-25pct-128.png b/assets/logos/01-25pct-128.png new file mode 100644 index 0000000..1ccdb08 Binary files /dev/null and b/assets/logos/01-25pct-128.png differ diff --git a/assets/logos/01-25pct-64.png b/assets/logos/01-25pct-64.png new file mode 100644 index 0000000..4c25302 Binary files /dev/null and b/assets/logos/01-25pct-64.png differ diff --git a/assets/logos/02-15pct-128.png b/assets/logos/02-15pct-128.png new file mode 100644 index 0000000..bdbe4da Binary files /dev/null and b/assets/logos/02-15pct-128.png differ diff --git a/assets/logos/02-15pct-64.png b/assets/logos/02-15pct-64.png new file mode 100644 index 0000000..7855ba6 Binary files /dev/null and b/assets/logos/02-15pct-64.png differ diff --git a/assets/logos/03-3grains-128.png b/assets/logos/03-3grains-128.png new file mode 100644 index 0000000..4c191bc Binary files /dev/null and b/assets/logos/03-3grains-128.png differ diff --git a/assets/logos/03-3grains-64.png b/assets/logos/03-3grains-64.png new file mode 100644 index 0000000..1637b1e Binary files /dev/null and b/assets/logos/03-3grains-64.png differ diff --git a/assets/logos/04-1grain-128.png b/assets/logos/04-1grain-128.png new file mode 100644 index 0000000..6c92a94 Binary files /dev/null and b/assets/logos/04-1grain-128.png differ diff --git a/assets/logos/04-1grain-64.png b/assets/logos/04-1grain-64.png new file mode 100644 index 0000000..3357a61 Binary files /dev/null and b/assets/logos/04-1grain-64.png differ diff --git a/assets/logos/README.md b/assets/logos/README.md new file mode 100644 index 0000000..e8bdc13 --- /dev/null +++ b/assets/logos/README.md @@ -0,0 +1,27 @@ +# Hourglass logo frames + +Scroll order (top → bottom of page): sand depletes as time “runs out.” + +| File | Step | +|------|------| +| `00-full-64.png` | Hero — fullest | +| `01-25pct-64.png` | ~25% scroll | +| `02-15pct-64.png` | ~50% | +| `03-3grains-64.png` | ~75% | +| `04-1grain-64.png` | Footer — one grain | + +`*-128.png` are retina variants. Source art lives in `source/`. + +## Regenerate web sizes + +```bash +npm install +npm run logos:normalize +``` + +After editing the full frame draft: + +```bash +node scripts/match-full-brightness.mjs path/to/draft.png +npm run logos:normalize +``` diff --git a/assets/logos/source/README.md b/assets/logos/source/README.md new file mode 100644 index 0000000..6089692 --- /dev/null +++ b/assets/logos/source/README.md @@ -0,0 +1,11 @@ +# Logo source art (not served on the site) + +| File | Role | +|------|------| +| `hourglass-mint-full.png` | Frame 0 — matched to 25% brightness | +| `hourglass-mint-25pct.png` | Frame 1 — style reference | +| `hourglass-mint-15pct.png` | Frame 2 | +| `hourglass-mint-3grains.png` | Frame 3 | +| `hourglass-mint-1grain.png` | Frame 4 | + +Run `npm run logos:normalize` from repo root to rebuild `../00-*-64.png` etc. diff --git a/assets/logos/source/hourglass-mint-15pct.png b/assets/logos/source/hourglass-mint-15pct.png new file mode 100644 index 0000000..9810ec1 Binary files /dev/null and b/assets/logos/source/hourglass-mint-15pct.png differ diff --git a/assets/logos/source/hourglass-mint-1grain.png b/assets/logos/source/hourglass-mint-1grain.png new file mode 100644 index 0000000..5328092 Binary files /dev/null and b/assets/logos/source/hourglass-mint-1grain.png differ diff --git a/assets/logos/source/hourglass-mint-25pct.png b/assets/logos/source/hourglass-mint-25pct.png new file mode 100644 index 0000000..6ed9ba3 Binary files /dev/null and b/assets/logos/source/hourglass-mint-25pct.png differ diff --git a/assets/logos/source/hourglass-mint-3grains.png b/assets/logos/source/hourglass-mint-3grains.png new file mode 100644 index 0000000..ff79bc8 Binary files /dev/null and b/assets/logos/source/hourglass-mint-3grains.png differ diff --git a/assets/logos/source/hourglass-mint-full.png b/assets/logos/source/hourglass-mint-full.png new file mode 100644 index 0000000..887ac6b Binary files /dev/null and b/assets/logos/source/hourglass-mint-full.png differ diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..a71ba97 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,8 @@ +# Design exploration (local only) + +| File | Purpose | +|------|---------| +| [brand-names.html](brand-names.html) | Brand name brainstorm picker | +| [style-directions.html](style-directions.html) | Visual style brainstorm picker | + +Open in a browser; not deployed to production. diff --git a/docs/brand-names.html b/docs/brand-names.html new file mode 100644 index 0000000..44eae36 --- /dev/null +++ b/docs/brand-names.html @@ -0,0 +1,807 @@ + + + + + + Brand name & logo options — 40 picks + + + + + + +
+
+

Brand brainstorm

+

40 name & logo directions

+

+ Round 4 is all hourglass logos — time, minutes, payback, sand running down while + automation runs. Click a card to pick. +

+

+ Why iAutomate feels off: The “i” prefix reads as Apple cosplay circa 2008. “Automate” + is generic — half the industry uses it. These alternatives aim for memorable, credible, and easy to say on + a call. +

+
+ +
+ Your pick: none yet + + +
+ +
+ +
+

For comparison — current name

+
+ +
+
iAutomate
+

Generic + dated Apple vibe. Logo reads as “org chart” not “automation.”

+
+
+
+
+ + + + diff --git a/docs/style-directions.html b/docs/style-directions.html new file mode 100644 index 0000000..af29806 --- /dev/null +++ b/docs/style-directions.html @@ -0,0 +1,2371 @@ + + + + + + Visual style directions + + + + + + +
+
+

Visual direction

+

40 style ideas for the site

+

+ You liked Brutalist Mono and Swiss Minimal — Round 4 (#31–40) goes + further into hacker code, SSH, and server aesthetics. Click to pick. +

+
+ +
+ Your pick: none yet + + +
+ +
+
+ + + + diff --git a/index.html b/index.html new file mode 100644 index 0000000..58a09af --- /dev/null +++ b/index.html @@ -0,0 +1,566 @@ + + + + + + AutoBank — Automate the boring parts of your business + + + + + + + + + + + + + + + +
+ +
+ + +
+
+ + Currently taking on new automation projects +
+ +

+ Automate the boring parts
+ of your business. + +

+ +

+ I build production-ready automation that runs while you sleep — custom scripts, no-code workflows, + CI/CD pipelines, webhooks, AI integrations. Less manual work. Faster turnaround. Fewer mistakes. +

+ + + + + +
+
+ + +
+
+

Tools I work with

+
    +
  • n8n
  • +
  • Zapier
  • +
  • Make
  • +
  • GitHub Actions
  • +
  • OpenAI
  • +
  • Claude
  • +
  • Webhooks
  • +
  • Shortcuts
  • +
  • Python · Node · Bash
  • +
+
+
+ + +
+
+
+

What I build

+

From a small script that saves an hour a day,
to full pipelines that run your operations.

+
+ +
+
+
+ +
+

Custom scripts & CLI tools

+

Python, Node, and Bash automations that take a repetitive task and reduce it to one command — or zero.

+
+ +
+
+ +
+

n8n, Zapier & Make workflows

+

No-code workflows that connect the tools you already use — Gmail, Slack, Notion, HubSpot, Stripe, Sheets, and 500+ more.

+
+ +
+
+ +
+

CI/CD pipelines

+

GitHub Actions, Jenkins, Azure DevOps. Build, test, and deployment pipelines that turn manual hours into automated minutes.

+
+ +
+
+ +
+

Webhooks & triggers

+

Event-driven systems that react in real time — form submissions, payments, support tickets, file uploads, anything with an API.

+
+ +
+
+ +
+

AI & LLM integrations

+

Drop GPT, Claude, or local models into real workflows — summarize email, triage tickets, extract data, draft replies, generate reports.

+
+ +
+
+ +
+

Scheduled jobs & reports

+

Daily digests, weekly KPIs, end-of-month reports — running on schedule, landing where your team already lives.

+
+ +
+
+ +
+

macOS & iOS Shortcuts

+

Personal productivity automations — one tap to capture, route, summarize, or trigger workflows from your phone or Mac.

+
+ +
+
+ +
+

Custom integrations

+

When off-the-shelf connectors don't cut it — custom APIs, OAuth flows, and bridges between systems that weren't meant to talk.

+
+
+ +

+ Don't see your use case? Most automation problems look unique until you describe them out loud. + Tell me what you're stuck on. (opens in new tab) +

+
+
+ + +
+
+
+
10×
+
faster workflows than manual
+
+
+
24/7
+
runs without you watching it
+
+
+
0
+
copy-paste errors at 2 a.m.
+
+
+
+ + +
+
+
+

How it works

+

Four steps. Production-ready at the end of them.

+
+ +
    +
  1. +
    01
    +
    +

    Discover

    +

    Free 15-min call. You describe the painful part of your workflow. I tell you whether automation makes sense — honestly.

    +
    +
  2. +
  3. +
    02
    +
    +

    Design

    +

    A short proposal: what gets automated, which tools, how long it'll take, what it costs. No surprises.

    +
    +
  4. +
  5. +
    03
    +
    +

    Ship

    +

    I build it, test it, document it, and walk you through it. Production-ready means it survives bad data, retries, and a Monday morning.

    +
    +
  6. +
  7. +
    04
    +
    +

    Maintain

    +

    Optional ongoing support — monitoring, tweaks, and new automations as your business grows.

    +
    +
  8. +
+
+
+ + +
+
+
+

Examples

+

Things teams hire me to automate.

+
+
+
+ Sales +

New lead arrives → enriched in Clearbit/Apollo → scored → routed to the right rep on Slack → logged in CRM. All in seconds.

+
+
+ Support +

Incoming tickets summarized by an LLM, tagged by topic, severity-scored, and drafted with a suggested reply for an agent to send.

+
+
+ Ops +

End-of-day reports pulled from Stripe, Shopify, and Sheets — assembled into a single Slack digest at 6 p.m. sharp.

+
+
+ Marketing +

Blog post published → auto-generate social copy → schedule across LinkedIn, X, and Bluesky → log performance back into Notion.

+
+
+ Engineering +

Pull request opened → tests run → preview deployed → screenshots posted as a comment → Slack notified. Faster reviews, fewer regressions.

+
+
+ Personal +

One Shortcut on your phone: captures voice → transcribes → routes to the right project in your Notion or Things inbox.

+
+
+
+
+ + +
+
+
+
+

Why me

+

15+ years building automation that has to work.

+

+ I come from a software engineering background — JavaScript, .NET, CI/CD pipelines, and large enterprise + products where automation isn't a nice-to-have, it's how releases ship at all. That same engineering + discipline is what I bring to your business automation. +

+
    +
  • Production-ready by default. Error handling, retries, alerts, logs — not a happy-path demo.
  • +
  • Documented. You shouldn't need me to understand what's running.
  • +
  • Tested. Before it touches your live data.
  • +
  • Pragmatic. A 20-line script often beats a 200-node workflow. I'll tell you which one you need.
  • +
+
+ +
+
+
+ + +
+
+
+

FAQ

+

Common questions.

+
+
+
+ How much does a typical automation cost? +

Most projects land between a quick fixed-fee script and a multi-week build. I quote per project after the discovery call — no hourly billing surprises.

+
+
+ Do I need to use a specific tool? +

No. I pick the right tool for the job — sometimes it's n8n, sometimes Zapier, sometimes a custom script. I'll explain the tradeoffs in plain language.

+
+
+ What if I'm not sure automation is worth it? +

That's exactly what the free 15-min call is for. If automating a process won't pay back the cost of building it, I'll tell you.

+
+
+ Do you work with companies outside Canada? +

Yes — fully remote, comfortable across North American and European time zones.

+
+
+ Can you maintain what you build? +

Optional. I can hand it off with documentation, or stay on for monitoring and ongoing improvements.

+
+
+
+
+ + +
+ +
+

Let's talk

+

Book a free 15-minute consultation.

+

+ Tell me about the part of your business that drains the most time. I'll tell you whether it's + worth automating — and if it is, exactly how I'd do it. +

+ +

No commitment. No sales pitch. Just a conversation.

+
+
+
+ + + + + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..3ab5e9b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,641 @@ +{ + "name": "autobank-site", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "autobank-site", + "version": "1.0.0", + "license": "UNLICENSED", + "devDependencies": { + "sharp": "^0.34.2" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..29ff8f1 --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "autobank-site", + "version": "1.0.0", + "private": true, + "description": "AutoBank — static landing page for automation consulting", + "scripts": { + "logos:normalize": "node scripts/normalize-logos.mjs", + "logos:match-full": "node scripts/match-full-brightness.mjs assets/logos/source/hourglass-mint-full-v2.png" + }, + "devDependencies": { + "sharp": "^0.34.2" + }, + "repository": { + "type": "git", + "url": "git@gitea.levkin.ca:ilia/auto.git" + }, + "license": "UNLICENSED" +} diff --git a/scripts/match-full-brightness.mjs b/scripts/match-full-brightness.mjs new file mode 100644 index 0000000..0d4f796 --- /dev/null +++ b/scripts/match-full-brightness.mjs @@ -0,0 +1,40 @@ +import sharp from "sharp"; +import { existsSync } from "fs"; +import { join } from "path"; + +const REF = "assets/logos/source/hourglass-mint-25pct.png"; +const FULL = process.argv[2]; +const OUT = "assets/logos/source/hourglass-mint-full.png"; + +if (!FULL || !existsSync(FULL)) { + console.error("Usage: node scripts/match-full-brightness.mjs "); + process.exit(1); +} + +/** Mean luminance of non-black pixels (0–255). */ +async function meanLuma(path) { + const { data } = await sharp(path).ensureAlpha().raw().toBuffer({ resolveWithObject: true }); + let sum = 0; + let n = 0; + for (let i = 0; i < data.length; i += 4) { + const r = data[i]; + const g = data[i + 1]; + const b = data[i + 2]; + const l = 0.2126 * r + 0.7152 * g + 0.0722 * b; + if (l > 12) { + sum += l; + n++; + } + } + return n ? sum / n : 0; +} + +const refLuma = await meanLuma(REF); +let fullLuma = await meanLuma(FULL); +const brightness = Math.min(1, Math.max(0.72, (refLuma / fullLuma) * 0.96)); + +await sharp(FULL).modulate({ brightness, saturation: 0.92 }).png().toFile(OUT); + +fullLuma = await meanLuma(OUT); +console.log(`ref ${refLuma.toFixed(1)} → out ${fullLuma.toFixed(1)} (brightness ${brightness.toFixed(3)})`); +console.log(`wrote ${OUT} — run npm run logos:normalize`); diff --git a/scripts/normalize-logos.mjs b/scripts/normalize-logos.mjs new file mode 100644 index 0000000..83e4b56 --- /dev/null +++ b/scripts/normalize-logos.mjs @@ -0,0 +1,50 @@ +import sharp from "sharp"; +import { existsSync } from "fs"; +import { join } from "path"; + +const SRC = "assets/logos/source"; +const OUT = "assets/logos"; +const SIZE = 128; +const ICON_W = 56; +const ICON_H = 96; + +const sources = [ + ["hourglass-mint-full.png", "00-full"], + ["hourglass-mint-25pct.png", "01-25pct"], + ["hourglass-mint-15pct.png", "02-15pct"], + ["hourglass-mint-3grains.png", "03-3grains"], + ["hourglass-mint-1grain.png", "04-1grain"], +]; + +async function normalize(input, name) { + const pipeline = sharp(input).ensureAlpha().trim({ threshold: 12 }); + const trimmed = await pipeline.toBuffer(); + const meta = await sharp(trimmed).metadata(); + + const icon = await sharp(trimmed) + .resize(ICON_W, ICON_H, { fit: "contain", background: { r: 0, g: 0, b: 0, alpha: 0 } }) + .extend({ + top: Math.floor((SIZE - ICON_H) / 2), + bottom: Math.ceil((SIZE - ICON_H) / 2), + left: Math.floor((SIZE - ICON_W) / 2), + right: Math.ceil((SIZE - ICON_W) / 2), + background: { r: 0, g: 0, b: 0, alpha: 1 }, + }) + .flatten({ background: { r: 0, g: 0, b: 0 } }) + .png() + .toBuffer(); + + await sharp(icon).resize(128, 128).toFile(join(OUT, `${name}-128.png`)); + await sharp(icon).resize(64, 64).toFile(join(OUT, `${name}-64.png`)); + + console.log(`${name}: ${meta.width}x${meta.height} → ${ICON_W}x${ICON_H} → 64/128px`); +} + +for (const [file, name] of sources) { + const path = join(SRC, file); + if (!existsSync(path)) { + console.warn("skip missing", path); + continue; + } + await normalize(path, name); +} diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..1e34d3b --- /dev/null +++ b/styles.css @@ -0,0 +1,1023 @@ +/* ========================================================= + AutoBank — Serial Console theme + Bare-metal green phosphor, minimal chrome, IPMI/serial aesthetic + Type: JetBrains Mono (display) + Inter (body) + ========================================================= */ + +:root { + --bg: #0c0c0c; + --bg-2: #000000; + --bg-3: #111111; + --surface: #0a0a0a; + --surface-2: #141414; + --line: #1a331a; + --line-strong: #2a4a2a; + --ink: #c8e6c8; + --ink-soft: #9fdf9f; + --ink-muted: #3db83d; + --accent: #33ff33; + --accent-2: #228822; + --accent-3: #66ff66; + --warn: #ffb86b; + --danger: #ff6b8a; + --shadow-lg: none; + + --font-display: "JetBrains Mono", ui-monospace, monospace; + --font-body: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; + --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace; + + --maxw: 1180px; + --radius: 6px; + --radius-lg: 10px; +} + +* { + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; + background: var(--bg); +} + +body { + margin: 0; + font-family: var(--font-body); + font-size: 16px; + line-height: 1.6; + color: var(--ink); + background: var(--bg); + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + overflow-x: hidden; + position: relative; +} + +body::before { + content: ""; + position: fixed; + inset: 0; + pointer-events: none; + z-index: 9999; + background: radial-gradient(ellipse at center, transparent 55%, rgba(0, 0, 0, 0.55) 100%); + opacity: 1; +} + +img, +svg { + display: block; + max-width: 100%; +} + +a { + color: var(--accent); + text-decoration: none; + transition: color 0.2s ease, opacity 0.2s ease, text-decoration-color 0.2s ease; +} +a:hover { + color: #66ff66; +} + +main a:not(.btn):hover, +main a:not(.btn):focus-visible, +.site-footer a:hover, +.site-footer a:focus-visible { + text-decoration: underline; + text-underline-offset: 3px; +} + +:focus { + outline: none; +} + +:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 3px; +} + +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.primary-nav a:hover, +.hero-title, +.kicker, +.brand-name, +.section-head h2 { + color: var(--accent); +} + +.container { + width: 100%; + max-width: var(--maxw); + margin: 0 auto; + padding: 0 clamp(20px, 4vw, 40px); + position: relative; + z-index: 1; +} + +.skip-link { + position: absolute; + left: -9999px; +} +.skip-link:focus-visible { + left: 12px; + top: 12px; + background: var(--accent); + color: var(--bg); + padding: 8px 14px; + border-radius: 8px; + z-index: 100; + font-weight: 600; + outline: 2px solid var(--ink); + outline-offset: 2px; +} + +/* ---------- Header ---------- */ +.site-header { + position: sticky; + top: 0; + z-index: 50; + background: rgba(12, 12, 12, 0.9); + backdrop-filter: saturate(140%) blur(12px); + border-bottom: 1px solid transparent; + transition: border-color 0.25s ease, background 0.25s ease; +} +.site-header.is-scrolled { + border-bottom-color: var(--line); + background: rgba(12, 12, 12, 0.98); +} +.header-inner { + display: flex; + align-items: center; + justify-content: space-between; + height: 70px; + gap: 24px; +} +.brand { + display: flex; + align-items: center; + gap: 12px; + color: var(--accent); +} +.brand:hover { + color: var(--accent); +} +.brand-mark-wrap { + width: 40px; + height: 40px; + flex-shrink: 0; + display: grid; + place-items: center; + background: var(--bg); + overflow: hidden; + border-radius: 0; +} + +.brand-mark { + width: 40px; + height: 40px; + display: block; + object-fit: contain; + object-position: center; +} + +.brand-name { + font-family: var(--font-display); + font-weight: 600; + font-size: 16px; + letter-spacing: 0.04em; + color: var(--accent); + text-transform: uppercase; +} +.brand-accent { + color: var(--accent); +} +.header-nav { + display: flex; + align-items: center; + gap: 28px; +} + +.nav-toggle { + display: none; + align-items: center; + justify-content: center; + width: 44px; + height: 44px; + padding: 0; + border: 1px solid var(--line); + border-radius: var(--radius); + background: transparent; + color: var(--accent); + cursor: pointer; + flex-shrink: 0; +} + +.nav-toggle-icon { + display: block; + width: 18px; + height: 2px; + background: currentColor; + position: relative; + transition: background 0.2s ease; +} + +.nav-toggle-icon::before, +.nav-toggle-icon::after { + content: ""; + position: absolute; + left: 0; + width: 18px; + height: 2px; + background: currentColor; + transition: transform 0.2s ease, top 0.2s ease; +} + +.nav-toggle-icon::before { + top: -6px; +} + +.nav-toggle-icon::after { + top: 6px; +} + +.nav-toggle[aria-expanded="true"] .nav-toggle-icon { + background: transparent; +} + +.nav-toggle[aria-expanded="true"] .nav-toggle-icon::before { + top: 0; + transform: rotate(45deg); +} + +.nav-toggle[aria-expanded="true"] .nav-toggle-icon::after { + top: 0; + transform: rotate(-45deg); +} + +.primary-nav { + display: flex; + align-items: center; + gap: 28px; +} + +.primary-nav a { + font-size: 14px; + color: var(--ink-soft); + font-weight: 500; +} + +.primary-nav a:hover { + color: var(--ink); +} + +.nav-cta { + padding: 9px 18px; + border-radius: 0; + border: 1px solid var(--accent); + background: transparent; + color: var(--accent) !important; + font-weight: 600; +} + +.nav-cta:hover { + background: var(--accent); + color: var(--bg) !important; +} + +@media (max-width: 720px) { + .header-nav { + position: relative; + gap: 10px; + } + + .nav-toggle { + display: flex; + } + + .primary-nav { + display: none; + position: absolute; + top: calc(100% + 8px); + right: 0; + flex-direction: column; + align-items: stretch; + gap: 4px; + min-width: 200px; + padding: 10px; + background: rgba(12, 12, 12, 0.98); + border: 1px solid var(--line); + border-radius: var(--radius); + box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45); + } + + .primary-nav.is-open { + display: flex; + } + + .primary-nav a { + padding: 10px 12px; + border-radius: var(--radius); + } + + .primary-nav a:hover, + .primary-nav a:focus-visible { + background: var(--surface-2); + } + + .nav-cta { + text-align: center; + margin-top: 4px; + } +} + +/* ---------- Hero ---------- */ +.hero { + position: relative; + padding: clamp(80px, 12vw, 140px) 0 clamp(60px, 9vw, 100px); + overflow: hidden; + isolation: isolate; +} +.hero-bg { + position: absolute; + inset: 0; + z-index: 0; + pointer-events: none; +} +.grid-bg { + position: absolute; + inset: 0; + background-image: + linear-gradient(rgba(51, 255, 51, 0.04) 1px, transparent 1px), + linear-gradient(90deg, rgba(51, 255, 51, 0.04) 1px, transparent 1px); + background-size: 56px 56px; + background-position: center top; + mask-image: radial-gradient(ellipse 80% 70% at 50% 30%, black 30%, transparent 75%); +} +.glow { + position: absolute; + border-radius: 50%; + filter: blur(80px); + opacity: 0.55; +} +.glow-1, +.glow-2, +.glow-3 { + display: none; +} + +.hero-inner { + position: relative; + z-index: 1; +} + +.hero-eyebrow { + display: inline-flex; + align-items: center; + gap: 10px; + padding: 7px 14px; + background: transparent; + border: 1px solid var(--line-strong); + border-radius: 999px; + font-size: 13px; + color: var(--accent); + font-weight: 500; + font-family: var(--font-mono); + margin-bottom: 28px; +} +.pulse { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--accent); + box-shadow: 0 0 0 0 rgba(51, 255, 51, 0.5); + animation: pulse 2s infinite; +} +@keyframes pulse { + 0% { + box-shadow: 0 0 0 0 rgba(51, 255, 51, 0.5); + } + 70% { + box-shadow: 0 0 0 8px rgba(51, 255, 51, 0); + } + 100% { + box-shadow: 0 0 0 0 rgba(51, 255, 51, 0); + } +} + +.hero-title { + font-family: var(--font-mono); + font-weight: 600; + text-transform: uppercase; + font-size: clamp(32px, 6vw, 68px); + line-height: 1.05; + letter-spacing: -0.02em; + margin: 0 0 28px; + color: var(--accent); + max-width: 20ch; +} +.cursor { + display: inline-block; + width: 0.6ch; + height: 0.9em; + background: var(--accent); + vertical-align: -0.05em; + margin-left: 0.1em; + animation: blink 1.1s infinite step-end; + transform: translateY(0.05em); +} +@keyframes blink { + 0%, 50% { opacity: 1; } + 51%, 100% { opacity: 0; } +} + +.hero-lede { + font-size: clamp(17px, 1.6vw, 20px); + line-height: 1.6; + color: var(--ink-soft); + max-width: 58ch; + margin: 0 0 36px; +} + +.hero-cta-row { + display: flex; + flex-wrap: wrap; + gap: 12px; + margin-bottom: 56px; +} + +/* ---------- Buttons ---------- */ +.btn { + display: inline-flex; + align-items: center; + gap: 10px; + padding: 13px 22px; + border-radius: 10px; + font-weight: 600; + font-size: 15px; + line-height: 1; + transition: transform 0.15s ease, background 0.2s ease, color 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease; + border: 1px solid transparent; + cursor: pointer; + font-family: var(--font-body); +} +.btn-large { + padding: 16px 26px; + font-size: 16px; +} +.btn-primary { + background: transparent; + color: var(--accent) !important; + border: 1px solid var(--accent); + border-radius: 0; + box-shadow: none; +} +.btn-primary:hover { + background: var(--accent); + color: var(--bg) !important; + transform: none; +} +.btn-ghost { + background: transparent; + color: var(--ink-soft) !important; + border: 1px solid var(--line); + border-radius: 0; +} +.btn-ghost:hover { + background: transparent; + border-color: var(--accent); + color: var(--accent) !important; +} + +/* ---------- Terminal ---------- */ +.terminal { + background: #000; + border: none; + border-radius: 0; + overflow: hidden; + box-shadow: none; + max-width: 760px; + margin-top: 8px; +} +.terminal-bar { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 14px; + background: #0c1124; + border-bottom: 1px solid var(--line); +} +.terminal-bar .dot { + width: 11px; + height: 11px; + border-radius: 50%; +} +.dot.red { background: #ff6b6b; } +.dot.yellow { background: #ffcc66; } +.dot.green { background: #66e088; } +.terminal-title { + margin-left: 10px; + font-family: var(--font-mono); + font-size: 12px; + color: var(--ink-muted); +} +.terminal-body { + margin: 0; + padding: 22px 22px 26px; + font-family: var(--font-mono); + font-size: clamp(12.5px, 1.25vw, 14px); + line-height: 1.85; + color: var(--ink-muted); + white-space: pre-wrap; + word-break: break-word; +} +.terminal-body .prompt { + color: var(--accent); + margin-right: 4px; +} +.terminal-body .cmd { + color: var(--accent); +} +.terminal-body .muted { + color: var(--ink-muted); +} +.terminal-body .hl { + color: var(--accent-2); +} +.terminal-body .ok { + color: var(--accent); +} +.blink { + animation: blink 1.1s infinite step-end; +} + +/* ---------- Strip ---------- */ +.strip { + padding: 40px 0; + border-top: 1px solid var(--line); + border-bottom: 1px solid var(--line); + background: var(--bg-2); +} +.strip-label { + margin: 0 0 14px; + font-family: var(--font-mono); + font-size: 11px; + letter-spacing: 0.16em; + text-transform: uppercase; + color: var(--ink-muted); + text-align: center; +} +.strip-list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: clamp(18px, 3vw, 40px); +} +.strip-list li { + font-family: var(--font-mono); + font-size: 14px; + color: var(--ink-soft); + letter-spacing: 0.01em; +} + +/* ---------- Sections ---------- */ +.section { + padding: clamp(72px, 10vw, 120px) 0; + position: relative; +} +.section-alt { + background: var(--bg-2); + border-top: 1px solid var(--line); + border-bottom: 1px solid var(--line); +} +.section-head { + max-width: 800px; + margin: 0 0 56px; +} +.kicker { + font-family: var(--font-mono); + font-size: 12px; + letter-spacing: 0.16em; + text-transform: uppercase; + color: var(--accent); + margin: 0 0 14px; + font-weight: 500; +} +.kicker-light { + color: var(--accent); +} +h2 { + font-family: var(--font-display); + font-weight: 600; + font-size: clamp(28px, 3.8vw, 46px); + line-height: 1.1; + letter-spacing: -0.025em; + margin: 0; + color: var(--ink); +} + +/* ---------- Service grid ---------- */ +.service-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 18px; +} +.service-card { + background: var(--surface); + border: 1px solid var(--line); + border-radius: var(--radius); + padding: 26px 24px; + transition: border-color 0.2s ease, transform 0.2s ease, background 0.2s ease; + position: relative; +} +.service-card:hover { + border-color: var(--accent); + transform: translateY(-2px); + background: var(--surface-2); +} +.service-icon { + width: 38px; + height: 38px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 9px; + background: rgba(51, 255, 51, 0.05); + color: var(--accent); + margin-bottom: 16px; +} +.service-icon svg { + width: 22px; + height: 22px; +} +.service-card h3 { + font-family: var(--font-display); + font-weight: 600; + font-size: 18px; + margin: 0 0 8px; + color: var(--ink); + letter-spacing: -0.005em; +} +.service-card p { + margin: 0; + font-size: 14.5px; + line-height: 1.55; + color: var(--ink-soft); +} +.services-footnote { + margin: 36px 0 0; + text-align: center; + color: var(--ink-muted); + font-size: 15px; +} + +/* ---------- Outcomes band ---------- */ +.outcomes { + padding: clamp(40px, 7vw, 80px) 0; + background: + linear-gradient(180deg, var(--bg-2), var(--bg)); + border-top: 1px solid var(--line); + border-bottom: 1px solid var(--line); +} +.outcomes-inner { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 32px; + text-align: center; +} +@media (max-width: 720px) { + .outcomes-inner { + grid-template-columns: 1fr; + } +} +.outcome-num { + font-family: var(--font-display); + font-weight: 600; + font-size: clamp(40px, 5.2vw, 64px); + color: var(--accent); + letter-spacing: -0.03em; + line-height: 1; +} +.outcome-label { + margin-top: 10px; + font-size: 14px; + color: var(--ink-soft); + letter-spacing: 0.01em; +} + +/* ---------- Process ---------- */ +.process-list { + list-style: none; + margin: 0; + padding: 0; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); + gap: 18px; + counter-reset: step; +} +.process-step { + background: var(--surface); + border: 1px solid var(--line); + border-radius: var(--radius); + padding: 26px 24px; + display: flex; + flex-direction: column; + gap: 14px; + position: relative; + overflow: hidden; +} +.process-step::before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 2px; + background: linear-gradient(90deg, var(--accent), transparent); + opacity: 0.6; +} +.step-num { + font-family: var(--font-mono); + font-size: 12px; + color: var(--accent); + letter-spacing: 0.08em; +} +.process-step h3 { + font-family: var(--font-display); + font-weight: 600; + font-size: 19px; + margin: 0 0 8px; + color: var(--ink); + letter-spacing: -0.005em; +} +.process-step p { + margin: 0; + color: var(--ink-soft); + font-size: 14.5px; + line-height: 1.55; +} + +/* ---------- Use cases ---------- */ +.usecase-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 18px; +} +.usecase { + background: var(--surface); + border: 1px solid var(--line); + border-radius: var(--radius); + padding: 24px; + transition: border-color 0.2s ease; +} +.usecase:hover { + border-color: var(--line-strong); +} +.usecase-tag { + display: inline-block; + font-family: var(--font-mono); + font-size: 11px; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--accent-3); + background: rgba(167, 139, 250, 0.12); + border: 1px solid rgba(167, 139, 250, 0.25); + padding: 4px 10px; + border-radius: 999px; + margin-bottom: 14px; +} +.usecase p { + margin: 0; + color: var(--ink-soft); + font-size: 14.5px; + line-height: 1.6; +} + +/* ---------- Why ---------- */ +.why-grid { + display: grid; + grid-template-columns: 1.4fr 1fr; + gap: clamp(28px, 5vw, 64px); + align-items: start; +} +@media (max-width: 880px) { + .why-grid { + grid-template-columns: 1fr; + } +} +.why-lede { + font-size: 17px; + color: var(--ink-soft); + line-height: 1.65; + margin: 18px 0 28px; + max-width: 60ch; +} +.why-list { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: 14px; +} +.why-list li { + padding-left: 26px; + position: relative; + color: var(--ink-soft); + font-size: 15.5px; + line-height: 1.55; +} +.why-list li strong { + color: var(--ink); + font-weight: 600; +} +.why-list li::before { + content: "→"; + position: absolute; + left: 0; + top: 0; + color: var(--accent); + font-weight: 700; +} +.why-card { + background: #0a0a0a; + border: 1px solid var(--line); + border-radius: var(--radius-lg); + padding: 30px; +} +.why-card-eyebrow { + font-family: var(--font-mono); + font-size: 11px; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--accent); + margin: 0 0 12px; +} +.why-card-stat { + font-family: var(--font-display); + font-weight: 600; + font-size: clamp(30px, 4vw, 44px); + color: var(--ink); + margin: 0 0 16px; + letter-spacing: -0.02em; + line-height: 1.05; +} +.why-card-desc { + margin: 0; + color: var(--ink-soft); + font-size: 14.5px; + line-height: 1.6; +} + +/* ---------- FAQ ---------- */ +.faq { + max-width: 760px; + display: flex; + flex-direction: column; + gap: 10px; +} +.faq details { + background: var(--surface); + border: 1px solid var(--line); + border-radius: var(--radius); + padding: 18px 22px; + transition: border-color 0.2s ease; +} +.faq details[open] { + border-color: var(--line-strong); +} +.faq summary { + list-style: none; + cursor: pointer; + font-family: var(--font-display); + font-weight: 600; + font-size: 17px; + color: var(--ink); + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; +} +.faq summary::-webkit-details-marker { + display: none; +} +.faq summary::after { + content: "+"; + color: var(--accent); + font-family: var(--font-mono); + font-weight: 400; + font-size: 22px; + transition: transform 0.2s ease; +} +.faq details[open] summary::after { + transform: rotate(45deg); +} +.faq p { + margin: 12px 0 0; + color: var(--ink-soft); + font-size: 15px; + line-height: 1.6; +} + +/* ---------- CTA / Book ---------- */ +.cta-section { + position: relative; + padding: clamp(80px, 10vw, 130px) 0; + text-align: center; + isolation: isolate; + overflow: hidden; + border-top: 1px solid var(--line); +} +.cta-bg { + position: absolute; + inset: 0; + z-index: 0; + pointer-events: none; +} +.cta-inner { + max-width: 720px; + text-align: center; +} +.cta-section h2 { + color: var(--ink); +} +.cta-lede { + font-size: 17px; + color: var(--ink-soft); + max-width: 56ch; + margin: 18px auto 36px; + line-height: 1.6; +} +.cta-row { + display: inline-flex; + flex-wrap: wrap; + gap: 12px; + justify-content: center; +} +.cta-foot { + margin: 28px 0 0; + font-size: 13px; + color: var(--ink-muted); + font-family: var(--font-mono); + letter-spacing: 0.02em; +} + +/* ---------- Footer ---------- */ +.site-footer { + padding: 36px 0; + background: var(--bg-2); + border-top: 1px solid var(--line); +} +.footer-inner { + display: flex; + justify-content: space-between; + flex-wrap: wrap; + gap: 8px; + font-size: 13px; + color: var(--ink-muted); +} +.footer-inner p { + margin: 0; +} +.footer-meta a { + color: var(--ink-soft); +} +.footer-meta a:hover { + color: var(--accent); +} + +/* ---------- Reveal ---------- */ +@media (prefers-reduced-motion: no-preference) { + .reveal { + opacity: 0; + transform: translateY(14px); + transition: opacity 0.6s ease, transform 0.6s ease; + } + .reveal.is-in { + opacity: 1; + transform: none; + } +} + +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; + } + .pulse, + .cursor, + .blink { + animation: none; + } +}