diff --git a/site/favicon.svg b/site/favicon.svg index 2cfc32f..6fff6d3 100644 --- a/site/favicon.svg +++ b/site/favicon.svg @@ -1,6 +1,8 @@ - - - - > - ↑ + + + + + + + diff --git a/site/index.html b/site/index.html index 2e002dc..8e6b4ff 100644 --- a/site/index.html +++ b/site/index.html @@ -3,200 +3,391 @@ - cloudsql-autoscaler — right-size your Cloud SQL instances automatically + CloudSQL Autoscaler — right-size Cloud SQL automatically + + +
-
- -

Right-size your Cloud SQL instances automatically.

-

- A tool for Google Cloud SQL that analyzes real utilization and - recommends — or applies — the right machine type. It looks at sustained - load instead of momentary spikes, respects edition - downtime constraints, and runs as a - one-shot CLI or a continuous daemon. -

- -
-
- +
+
+
+ Google Cloud SQL · autoscaling +

+ Right-size Cloud SQL from + real utilization. +

+

+ CloudSQL Autoscaler reads three days of CPU and memory metrics, + finds the sustained load with P95 percentiles, and recommends — or + safely applies — the machine type your database actually needs. +

+ +

+ + Dry-run by default — go install and analyze in one + command. +

-
# Install
-$ go install github.com/fraser-isbester/cloudsql-autoscaler/cmd/cloudsql-autoscaler@latest
-
-# Analyze all instances (dry-run by default)
-$ cloudsql-autoscaler --project my-gcp-project
-
-# Apply scaling recommendations
-$ cloudsql-autoscaler --project my-gcp-project --dry-run=false
-
-
- View on GitHub - Download binaries + +
+
+ CPU utilization + last 3 days · P95 +
+ +
+ + db-n2-standard-8 + → + db-n2-standard-4 + + + + ~42% / month saved + +
+
-
+ - -
+ +
-

What is it?

-

- cloudsql-autoscaler watches your Cloud SQL - instances and figures out when they're over- or under-provisioned. It - pulls three days of CPU and memory data from Cloud Monitoring, uses - P95 percentiles to separate real sustained - load from temporary spikes, and recommends machine-type changes within - safe constraints. -

-

- Scaling on Cloud SQL isn't free — some changes require a restart. The - autoscaler understands the difference between - Enterprise Plus near-zero-downtime - windows and Enterprise edition - downtime requirements, so it can apply changes safely. Run it once from - your laptop, on a schedule, or as an always-on daemon in your cluster. -

+
+ How it works +

Measure, decide, resize — on a loop.

+

+ Every cycle turns raw Cloud Monitoring data into a single, safe + decision per instance. +

+
+
+
+ 1 +

Collect

+

+ Pulls 3 days of CPU and memory time series from Cloud Monitoring + for every instance in the project. +

+
+
+ 2 +

Analyze

+

+ Computes P95 percentiles to separate sustained pressure from + short-lived spikes, then checks it against your scaling profile. +

+
+
+ 3 +

Right-size

+

+ Recommends the next machine type up or down — and applies it when + you opt in, honoring each edition's downtime rules. +

+
+
-
+
-

Features

-
-
-

01Smart P95 analysis

+
+ Features +

Built to touch production databases carefully.

+
+
+
+ + + +

Smart P95 analysis

- Uses 3 days of CPU/memory metrics and P95 percentiles to identify - sustained load and avoid scaling on temporary spikes. + Three days of metrics and P95 percentiles identify real sustained + load, so a nightly batch spike never triggers a resize.

-
-

02Downtime aware

+
+ + + +

Downtime aware

- Understands Enterprise Plus - zero-downtime windows versus - Enterprise edition downtime - requirements before applying changes. + Distinguishes Enterprise Plus near-zero-downtime windows + from Enterprise edition restarts + before it changes anything.

-
-

03CLI · Docker · Kubernetes

+
+ + + +

CLI · Docker · Kubernetes

- Run it as a one-shot CLI, a Docker container, or a Kubernetes - deployment with Workload Identity. + Run it once from your laptop, on a schedule in a container, or as + an always-on daemon with GKE Workload Identity.

-
-

04Scaling profiles

+
+ + + +

Scaling profiles

- Choose conservative, default, or - aggressive profiles to tune how eagerly it resizes. + Pick conservative, default, or + aggressive to tune how eagerly it resizes to your + risk appetite.

-
-

05Prometheus & health

+
+ + + +

Observable

- Built-in Prometheus metrics plus /health, - /ready, and /status endpoints for - daemon mode. + Prometheus metrics plus /health, /ready, + and /status endpoints make daemon mode easy to + monitor.

-
-

06Dry-run by default

+
+ + + +

Safe by default

- Recommendations are shown as table or - json; nothing changes until you pass - --dry-run=false. + Every run is a dry run until you pass --dry-run=false. + Preview recommendations as a table or JSON first.

- -
+ +
-

Deployment

-

- Ship it however you run infrastructure. Pull the container image and - run it as a daemon, or deploy the bundled Kubernetes manifests. -

+
+ Deploy +

Ship it however you run infrastructure.

+
+
+
+
+ + CLI +
+
# install
+go install github.com/fraser-isbester/\
+  cloudsql-autoscaler/cmd/cloudsql-autoscaler@latest
+
+# analyze (dry-run)
+cloudsql-autoscaler --project my-project
+
+# apply
+cloudsql-autoscaler --project my-project \
+  --dry-run=false
+
-
-

Docker

-
# Pull and run as a daemon
+            
+
+ + Docker +
+
# run as a daemon
 docker run -d \
   -e GOOGLE_APPLICATION_CREDENTIALS=/creds.json \
-  -v /path/to/creds.json:/creds.json:ro \
-  ghcr.io/fraser-isbester/cloudsql-autoscaler:latest \
-  --daemon --project my-gcp-project
-
+ -v /path/creds.json:/creds.json:ro \ + ghcr.io/fraser-isbester/\ + cloudsql-autoscaler:latest \ + --daemon --project my-project
+
-
-

Kubernetes

-
# Clone and deploy
-git clone https://github.com/fraser-isbester/cloudsql-autoscaler.git
+            
+
+ + Kubernetes +
+
# clone and deploy
+git clone https://github.com/fraser-isbester/\
+  cloudsql-autoscaler.git
 cd cloudsql-autoscaler
 make deploy-k8s
 
-# Check status
-make k8s-status
-make k8s-logs
-

- Requires a GKE cluster with Workload Identity enabled and a service - account with Cloud SQL Admin permissions. +# check status +make k8s-status

+
+ Needs GKE Workload Identity + a service account with Cloud SQL + Admin. +
+
+
+ +
+ GET /health + GET /ready + GET /status + GET /metrics +
+
+
+ + +
+
+
+

Stop guessing at machine types.

+

+ Point it at a project and see what your Cloud SQL fleet should + actually be running — no changes until you say so.

+
diff --git a/site/styles.css b/site/styles.css index c0c441a..3b24d13 100644 --- a/site/styles.css +++ b/site/styles.css @@ -1,331 +1,609 @@ -/* cloudsql-autoscaler branding site — terminal aesthetic - Google Cloud blue/green palette */ +/* CloudSQL Autoscaler — brand site + A light, editorial product design. Deliberately NOT a terminal theme: + real sans-serif type, generous whitespace, soft cards, one confident + emerald accent against slate ink. Dark mode via prefers-color-scheme. */ :root { - --blue: #4285f4; /* primary accent / links / logo */ - --green: #34a853; /* secondary accent / scaling up / success */ - --amber: #fbbc04; /* warnings / downtime callouts */ - --red: #ea4335; - --blue-bright: #669df6; + --ink: #0d1526; /* headings / primary text */ + --body: #414b5e; /* body copy */ + --muted: #6b7688; /* secondary copy */ + --bg: #ffffff; + --bg-soft: #f5f7fa; /* alternating section wash */ + --surface: #ffffff; /* cards */ + --border: #e4e8ee; + --border-strong: #d3d9e2; - --bg: #0a0a0a; - --panel: #141414; - --panel-2: #1c1c1c; - --header-bg: #303030; - --border: #2a2a2a; - --fg: #d0d0d0; - --fg-bright: #ffffff; - --dim: #585858; + --accent: #059669; /* emerald — primary action / brand */ + --accent-strong: #047857; + --accent-soft: #ecfdf5; /* tinted fills */ + --accent-ink: #064e3b; + --amber: #b45309; /* downtime / caution copy */ + --violet: #6d28d9; /* secondary data accent */ - --mono: ui-monospace, "JetBrains Mono", "SF Mono", "Menlo", "Cascadia Code", - "Consolas", monospace; - --maxw: 1040px; + --shadow-sm: 0 1px 2px rgba(13, 21, 38, 0.06), + 0 1px 3px rgba(13, 21, 38, 0.05); + --shadow-md: 0 6px 16px rgba(13, 21, 38, 0.08), + 0 2px 6px rgba(13, 21, 38, 0.05); + --shadow-lg: 0 24px 60px rgba(13, 21, 38, 0.14); + + --sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + Helvetica, Arial, "Apple Color Emoji", sans-serif; + --mono: "SF Mono", ui-monospace, "JetBrains Mono", "Cascadia Code", Menlo, + Consolas, monospace; + --maxw: 1120px; + --radius: 16px; + --radius-sm: 10px; } * { box-sizing: border-box; } - html { scroll-behavior: smooth; + -webkit-text-size-adjust: 100%; } - body { margin: 0; background: var(--bg); - color: var(--fg); - font-family: var(--mono); - font-size: 15px; + color: var(--body); + font-family: var(--sans); + font-size: 17px; line-height: 1.65; + letter-spacing: -0.01em; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; } - +h1, +h2, +h3 { + color: var(--ink); + line-height: 1.12; + letter-spacing: -0.025em; + margin: 0; +} +p { + margin: 0 0 1rem; +} a { - color: var(--blue); + color: var(--accent-strong); text-decoration: none; } a:hover { text-decoration: underline; } - -/* subtle terminal grid backdrop */ -body::before { - content: ""; - position: fixed; - inset: 0; - pointer-events: none; - z-index: 0; - background: radial-gradient( - 1200px 600px at 50% -10%, - rgba(66, 133, 244, 0.07), - transparent 70% - ); +.wrap { + max-width: var(--maxw); + margin: 0 auto; + padding: 0 1.5rem; +} +.eyebrow { + display: inline-block; + font-size: 0.8rem; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--accent-strong); + margin-bottom: 0.75rem; +} +.lede { + font-size: 1.15rem; + color: var(--muted); + max-width: 60ch; } /* ---------- Nav ---------- */ .nav { position: sticky; top: 0; - z-index: 10; + z-index: 20; + background: rgba(255, 255, 255, 0.8); + backdrop-filter: saturate(180%) blur(12px); + border-bottom: 1px solid var(--border); +} +.nav-inner { + max-width: var(--maxw); + margin: 0 auto; + padding: 0.85rem 1.5rem; display: flex; align-items: center; justify-content: space-between; gap: 1rem; - padding: 0.75rem 1.25rem; - background: rgba(10, 10, 10, 0.85); - backdrop-filter: blur(8px); - border-bottom: 1px solid var(--border); } -.nav-brand { +.brand { + display: inline-flex; + align-items: center; + gap: 0.6rem; font-weight: 700; - letter-spacing: 0.08em; - color: var(--blue); - text-transform: lowercase; + font-size: 1.05rem; + color: var(--ink); + letter-spacing: -0.02em; +} +.brand:hover { + text-decoration: none; } -.nav-brand::before { - content: "> "; - color: var(--green); +.brand img { + width: 30px; + height: 30px; + border-radius: 8px; + display: block; } .nav-links { display: flex; - gap: 1.25rem; - flex-wrap: wrap; + align-items: center; + gap: 1.5rem; } .nav-links a { - color: var(--fg); + color: var(--body); + font-weight: 500; + font-size: 0.95rem; } -.nav-links a:hover, -.nav-links a.active { - color: var(--blue); +.nav-links a:hover { + color: var(--ink); text-decoration: none; } +.nav-links .btn { + margin-left: 0.25rem; +} -/* ---------- Layout ---------- */ -main { - position: relative; - z-index: 1; +/* ---------- Buttons ---------- */ +.btn { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.7rem 1.25rem; + border-radius: 999px; + font-weight: 600; + font-size: 0.95rem; + border: 1px solid var(--border-strong); + color: var(--ink); + background: var(--surface); + cursor: pointer; + transition: transform 0.12s ease, box-shadow 0.12s ease, + border-color 0.12s ease, background 0.12s ease; } -.wrap { - max-width: var(--maxw); - margin: 0 auto; - padding: 0 1.25rem; +.btn:hover { + text-decoration: none; + transform: translateY(-1px); + box-shadow: var(--shadow-md); + border-color: var(--border-strong); } -section { - padding: 3.5rem 0; - border-bottom: 1px solid var(--border); +.btn svg { + width: 18px; + height: 18px; } -h1, -h2, -h3 { - color: var(--fg-bright); - line-height: 1.25; +.btn-primary { + background: var(--accent); + border-color: var(--accent); + color: #fff; } -h2 { - font-size: 1.6rem; - margin-top: 0; +.btn-primary:hover { + background: var(--accent-strong); + border-color: var(--accent-strong); + box-shadow: 0 10px 24px rgba(5, 150, 105, 0.28); } -h2::before { - content: "# "; - color: var(--green); +.btn-lg { + padding: 0.85rem 1.5rem; + font-size: 1rem; } -.muted { - color: var(--dim); + +/* ---------- Hero ---------- */ +.hero { + position: relative; + overflow: hidden; + border-bottom: 1px solid var(--border); + background: radial-gradient( + 1100px 520px at 82% -8%, + var(--accent-soft), + transparent 60% + ), + radial-gradient(900px 500px at 0% 0%, #f3f0ff, transparent 55%); } -.accent { - color: var(--blue); +.hero-grid { + max-width: var(--maxw); + margin: 0 auto; + padding: 5rem 1.5rem 5.5rem; + display: grid; + grid-template-columns: 1.05fr 0.95fr; + gap: 3.5rem; + align-items: center; } -.accent-green { - color: var(--green); +.hero h1 { + font-size: clamp(2.2rem, 4.6vw, 3.5rem); + margin-bottom: 1.1rem; } -.accent-amber { - color: var(--amber); +.hero h1 .hl { + color: var(--accent-strong); } - -/* ---------- Hero ---------- */ -.hero { - text-align: center; - padding: 4rem 1.25rem 3.5rem; +.hero .lede { + margin-bottom: 1.75rem; } -.hero .logo { - color: var(--blue); - font-size: clamp(0.4rem, 1.5vw, 0.8rem); - line-height: 1.1; - margin: 0 auto 1.5rem; - display: inline-block; - text-align: left; - text-shadow: 0 0 18px rgba(66, 133, 244, 0.35); - white-space: pre; - overflow-x: auto; - max-width: 100%; +.hero-cta { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + align-items: center; } -.hero .tagline { - font-size: clamp(1.1rem, 2.6vw, 1.5rem); - color: var(--fg-bright); - margin: 0.5rem auto 0.75rem; - max-width: 44ch; +.hero-note { + margin-top: 1.25rem; + font-size: 0.9rem; + color: var(--muted); + display: flex; + align-items: center; + gap: 0.5rem; } -.hero .sub { - color: var(--dim); - max-width: 62ch; - margin: 0 auto 1.75rem; +.hero-note code { + font-family: var(--mono); + background: var(--bg-soft); + border: 1px solid var(--border); + border-radius: 6px; + padding: 0.15em 0.45em; + font-size: 0.85em; + color: var(--ink); } -/* ---------- Buttons ---------- */ -.btn-row { +/* ---------- Hero illustration (data-viz card) ---------- */ +.viz { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius); + box-shadow: var(--shadow-lg); + padding: 1.25rem 1.35rem 1.4rem; +} +.viz-head { display: flex; - gap: 0.75rem; - justify-content: center; + align-items: baseline; + justify-content: space-between; + margin-bottom: 0.35rem; +} +.viz-title { + font-weight: 700; + color: var(--ink); + font-size: 0.95rem; +} +.viz-sub { + font-size: 0.8rem; + color: var(--muted); +} +.viz svg { + width: 100%; + height: auto; + display: block; +} +.viz-rec { + margin-top: 1rem; + padding-top: 1rem; + border-top: 1px dashed var(--border-strong); + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; flex-wrap: wrap; - margin-top: 1.5rem; } -.btn { - display: inline-block; - padding: 0.6rem 1.2rem; - border: 1px solid var(--border); - border-radius: 4px; - color: var(--fg); - background: var(--panel); +.viz-move { + display: flex; + align-items: center; + gap: 0.55rem; + font-family: var(--mono); + font-size: 0.9rem; + color: var(--ink); font-weight: 600; - transition: all 0.15s ease; } -.btn:hover { - text-decoration: none; - border-color: var(--blue); - color: var(--blue); - box-shadow: 0 0 0 1px var(--blue); +.viz-move .from { + color: var(--muted); + text-decoration: line-through; + text-decoration-color: var(--border-strong); } -.btn-primary { - background: var(--blue); - color: #001024; - border-color: var(--blue); +.viz-move .arrow { + color: var(--accent); } -.btn-primary:hover { - background: var(--blue-bright); - color: #001024; - box-shadow: 0 0 22px rgba(66, 133, 244, 0.45); +.pill { + display: inline-flex; + align-items: center; + gap: 0.35rem; + padding: 0.3rem 0.7rem; + border-radius: 999px; + font-size: 0.82rem; + font-weight: 700; + background: var(--accent-soft); + color: var(--accent-ink); + border: 1px solid #bfe9d3; } -/* ---------- Code ---------- */ -code { +/* ---------- Sections ---------- */ +section { + padding: 5rem 0; +} +.section-soft { + background: var(--bg-soft); + border-top: 1px solid var(--border); + border-bottom: 1px solid var(--border); +} +.section-head { + max-width: 62ch; + margin-bottom: 2.5rem; +} +.section-head h2 { + font-size: clamp(1.7rem, 3vw, 2.3rem); + margin-bottom: 0.75rem; +} + +/* ---------- Steps ---------- */ +.steps { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1.25rem; + counter-reset: step; +} +.step { + position: relative; + padding: 1.5rem 1.4rem; +} +.step .num { font-family: var(--mono); - background: var(--panel-2); - border: 1px solid var(--border); - border-radius: 3px; - padding: 0.1em 0.4em; - color: var(--blue); - font-size: 0.9em; + font-size: 0.85rem; + font-weight: 700; + color: var(--accent-strong); + background: var(--accent-soft); + border: 1px solid #bfe9d3; + width: 2rem; + height: 2rem; + border-radius: 8px; + display: inline-flex; + align-items: center; + justify-content: center; + margin-bottom: 0.9rem; +} +.step h3 { + font-size: 1.1rem; + margin-bottom: 0.4rem; +} +.step p { + color: var(--muted); + font-size: 0.98rem; + margin: 0; +} + +/* ---------- Feature cards ---------- */ +.features { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1.25rem; } -pre { - background: var(--panel); +.feature { + background: var(--surface); border: 1px solid var(--border); - border-radius: 6px; - padding: 1rem 1.15rem; - overflow-x: auto; - position: relative; + border-radius: var(--radius-sm); + padding: 1.6rem 1.5rem; + box-shadow: var(--shadow-sm); + transition: transform 0.14s ease, box-shadow 0.14s ease; +} +.feature:hover { + transform: translateY(-3px); + box-shadow: var(--shadow-md); +} +.feature .ico { + width: 42px; + height: 42px; + border-radius: 11px; + display: inline-flex; + align-items: center; + justify-content: center; + background: var(--accent-soft); + color: var(--accent-strong); + margin-bottom: 1rem; +} +.feature .ico svg { + width: 22px; + height: 22px; +} +.feature h3 { + font-size: 1.12rem; + margin-bottom: 0.45rem; } -pre code { - background: none; - border: none; - padding: 0; - color: var(--fg); +.feature p { + margin: 0; + color: var(--muted); + font-size: 0.98rem; } -.terminal { +.feature p .cite-amber { + color: var(--amber); + font-weight: 600; +} + +/* ---------- Deploy ---------- */ +.deploy { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1.25rem; +} +.code-card { + background: var(--surface); border: 1px solid var(--border); - border-radius: 8px; + border-radius: var(--radius-sm); overflow: hidden; - background: var(--panel); - max-width: 720px; - margin: 0 auto; - text-align: left; + box-shadow: var(--shadow-sm); + display: flex; + flex-direction: column; } -.terminal-bar { - background: var(--header-bg); - padding: 0.5rem 0.75rem; +.code-card .code-head { + padding: 0.85rem 1.15rem; + border-bottom: 1px solid var(--border); + font-weight: 700; + color: var(--ink); + font-size: 0.95rem; display: flex; - gap: 0.4rem; align-items: center; + gap: 0.5rem; } -.dot { - width: 11px; - height: 11px; - border-radius: 50%; - display: inline-block; -} -.dot.r { - background: var(--red); +.code-card .code-head svg { + width: 18px; + height: 18px; + color: var(--accent-strong); } -.dot.y { - background: var(--amber); +.code-card pre { + margin: 0; + padding: 1.15rem; + overflow-x: auto; + background: #0d1526; + flex: 1; } -.dot.g { - background: var(--green); +.code-card code { + font-family: var(--mono); + font-size: 0.82rem; + line-height: 1.7; + color: #d6def0; } -.terminal pre { - border: none; - border-radius: 0; - margin: 0; +.code-card .cmt { + color: #7c88a3; } -.terminal .prompt { - color: var(--green); +.code-card .flag { + color: #6ee7b7; } -.terminal .comment { - color: var(--dim); +.code-card .foot { + padding: 0.85rem 1.15rem; + font-size: 0.85rem; + color: var(--muted); + border-top: 1px solid var(--border); } -/* ---------- Feature grid ---------- */ -.grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); - gap: 1rem; - margin-top: 1.75rem; +/* ---------- Endpoints strip ---------- */ +.endpoints { + display: flex; + flex-wrap: wrap; + gap: 0.6rem; + margin-top: 1.5rem; } -.card { - background: var(--panel); +.endpoints span { + font-family: var(--mono); + font-size: 0.85rem; + padding: 0.4rem 0.8rem; + border-radius: 999px; + background: var(--surface); border: 1px solid var(--border); - border-radius: 6px; - padding: 1.25rem; - transition: border-color 0.15s ease; + color: var(--ink); } -.card:hover { - border-color: var(--blue-bright); + +/* ---------- CTA band ---------- */ +.cta { + background: linear-gradient(135deg, #0d1526, #12203a 60%, #0f2e28); + color: #e7ecf5; + border-radius: var(--radius); + padding: 3.25rem 2rem; + text-align: center; } -.card h3 { - margin: 0 0 0.5rem; - font-size: 1.05rem; - color: var(--blue); +.cta h2 { + color: #fff; + font-size: clamp(1.6rem, 3vw, 2.15rem); + margin-bottom: 0.75rem; } -.card h3 .tag { - color: var(--green); - margin-right: 0.4rem; +.cta p { + color: #aeb9cd; + max-width: 52ch; + margin: 0 auto 1.75rem; } -.card p { - margin: 0; - color: var(--fg); +.cta .btn { + background: rgba(255, 255, 255, 0.06); + border-color: rgba(255, 255, 255, 0.18); + color: #fff; } - -/* ---------- Deployment ---------- */ -.deploy-block { - margin-top: 1.5rem; +.cta .btn:hover { + background: rgba(255, 255, 255, 0.12); } -.deploy-block h3 { - color: var(--green); - font-size: 1.05rem; - margin-bottom: 0.5rem; +.cta .btn-primary { + background: var(--accent); + border-color: var(--accent); +} +.cta .btn-primary:hover { + background: var(--accent-strong); } /* ---------- Footer ---------- */ .footer { - position: relative; - z-index: 1; - text-align: center; - padding: 2.5rem 1.25rem; - color: var(--dim); - font-size: 0.85rem; + border-top: 1px solid var(--border); + padding: 2.5rem 0; +} +.footer .wrap { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + flex-wrap: wrap; + color: var(--muted); + font-size: 0.9rem; } .footer a { - color: var(--dim); + color: var(--muted); } .footer a:hover { - color: var(--blue); + color: var(--ink); +} + +/* ---------- Responsive ---------- */ +@media (max-width: 900px) { + .hero-grid { + grid-template-columns: 1fr; + gap: 2.5rem; + padding-top: 3.5rem; + padding-bottom: 4rem; + } + .steps, + .features, + .deploy { + grid-template-columns: 1fr; + } +} +@media (max-width: 620px) { + body { + font-size: 16px; + } + .nav-links { + gap: 1rem; + } + .nav-links .hide-sm { + display: none; + } + section { + padding: 3.5rem 0; + } +} + +/* ---------- Dark mode ---------- */ +@media (prefers-color-scheme: dark) { + :root { + --ink: #f2f5fb; + --body: #c2cbda; + --muted: #8b96a9; + --bg: #0b1120; + --bg-soft: #0f1728; + --surface: #121b2e; + --border: #223049; + --border-strong: #2c3d5a; + --accent: #10b981; + --accent-strong: #34d399; + --accent-soft: rgba(16, 185, 129, 0.14); + --accent-ink: #6ee7b7; + --amber: #fbbf24; + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4); + --shadow-md: 0 8px 22px rgba(0, 0, 0, 0.5); + --shadow-lg: 0 24px 60px rgba(0, 0, 0, 0.6); + } + .nav { + background: rgba(11, 17, 32, 0.8); + } + .hero { + background: radial-gradient( + 1100px 520px at 82% -8%, + rgba(16, 185, 129, 0.12), + transparent 60% + ), + radial-gradient(900px 500px at 0% 0%, rgba(109, 40, 217, 0.14), transparent 55%); + } + .step .num, + .pill { + border-color: rgba(16, 185, 129, 0.35); + } + .btn-primary, + .cta .btn-primary { + color: #04140d; + } }