diff --git a/docs/icons.svg b/docs/icons.svg index 23c4fd4..300fe63 100644 --- a/docs/icons.svg +++ b/docs/icons.svg @@ -4,10 +4,7 @@ - - - @@ -39,4 +36,5 @@ + diff --git a/docs/index.html b/docs/index.html index 0553b89..23287df 100644 --- a/docs/index.html +++ b/docs/index.html @@ -6,7 +6,7 @@ BarSQL - A fast, local-first SQL workbench - + @@ -47,34 +47,36 @@ "offers": { "@type": "Offer", "price": "0", "priceCurrency": "EUR" } } - +
@@ -82,549 +84,500 @@
-
- Rust - GPUI - Windows · macOS · Linux - GPL-3.0 -
-

A fast, local-first SQL workbench

-

- No accounts, no telemetry, nothing between you and your data. One native, GPU-rendered desktop app - for six database engines: -

- -
- +

Fast, local-first SQL workbench

+

A native, GPU-rendered app with no accounts and no telemetry.

+
+

+ Free and open source · Windows, macOS and Linux + Other platforms +

+ +
+
+ +
-

Free and open source · Portable · Updates itself

-
-
- -
-
- -
-
- Portable - Fast startup - Local-first - Six engines - Developer-focused - Auto-updating
-
+
-

SQL tools are usually overkill. BarSQL isn't.

-

- Most SQL clients are slow and heavy, cloud-connected by default, tied to subscriptions and packed with - features you never use. BarSQL does one thing: a fast, local place to work with your databases. -

-
-
- -

Native and GPU-rendered

+
+

SQL tools are usually overkill. BarSQL isn't.

+
+
+
+ 01 +

Native and GPU-rendered

Drawn on the GPU with GPUI, the UI framework behind the Zed editor: one Rust codebase from the database drivers to the window.

-
- -

Yours, entirely

+
+ 02 +

Yours, entirely

Everything runs and stays on your machine. No account, no sign-in, no telemetry: your connections and queries never leave your computer.

-
- -

Portable by design

+
+ 03 +

Portable by design

Connections, history, saved queries and settings live in one BarSQL-data folder next to the app. Copy both to a USB stick and carry on elsewhere.

-
- -

Tested for real

+
+ 04 +

Tested for real

Whole-window UI tests drive the app with real keystrokes and clicks, and end-to-end suites run against real PostgreSQL, MySQL, MariaDB, SQL Server, ClickHouse and libSQL servers.

-
+
-

Six engines. One app.

-

- The same editor, grid, schema tree and plan viewer on every engine, speaking each one's own dialect. - Read-only connections are refused by BarSQL and, wherever the engine allows, by the server as well. -

-
-
-

PostgreSQL

Server
-
    -
  • SSL: disable, require, verify-full
  • -
  • Notices and warnings as they arrive, in the Messages tab
  • -
  • Activity, blocking and long-running queries in one click
  • -
  • DDL composed from the catalog
  • -
  • Powered by tokio-postgres
  • -
+
+ Tour +

A closer look

+

The work you do every day, without the detours.

+
+
+
+
+ SQL editor + +
+
+

Write SQL at full speed

+

Suggestions know your schema and your engine: tables, columns, aliases and CTEs, every built-in function with its arguments, and JOIN snippets where a join fits.

+
    +
  • Run the statement at the caret, a selection, or the whole script
  • +
  • Multiple cursors, line commands and go to definition, as in VS Code
  • +
  • :name parameters ask for their values, and remember them
  • +
  • A result tab per output, and notices, warnings and PRINT output in a Messages tab
  • +
  • Pinned transactions per tab: BEGIN, then commit or roll back from the toolbar
  • +
  • An error is underlined where it happened, and a mistyped name gets a one-click fix
  • +
+ More about the SQL editor +
-
-

MySQL / MariaDB

Server
-
    -
  • TLS transport
  • -
  • Warnings and notes in the Messages tab
  • -
  • The process list and open InnoDB transactions
  • -
  • The server's own SHOW CREATE DDL
  • -
  • Powered by mysql_async
  • -
+
+
+ Plan viewer + +
+
+

See why a query is slow

+

Explain shows the planner's estimate; Explain analyze runs the statement and reports what really happened. Every engine's plan lands in the same tree, with costs and timings wherever the engine reports them.

+
    +
  • A heat bar on each node's own share of time, cost or rows
  • +
  • Row estimates 10x off are flagged: that's where a missing index hides
  • +
  • Every detail the engine reported, plus its raw output
  • +
  • Analyzing a write always rolls it back, so nothing lands
  • +
+ More about the plan viewer +
-
-

SQL Server

Server
-
    -
  • TLS, including TDS 8.0 strict, and named instances
  • -
  • GO batches, with PRINT output in the Messages tab
  • -
  • Estimated and actual plans from Showplan XML
  • -
  • Requests, sessions and blocking at a glance
  • -
  • SQL Server 2017 or newer, powered by tiberius
  • -
+
+
+ Table data + +
+
+

Edit rows, not UPDATE statements

+

Browse any table, filter it with a WHERE condition that autocompletes, and change it in place. A query's results edit the same way, when they come from one table with its primary key.

+
    +
  • Edits and deletes are staged until you apply them, with undo and redo
  • +
  • Paste blocks of cells, set NULL, insert rows
  • +
  • Checkboxes for booleans, and a picker for enums
  • +
  • Jump through foreign keys to the row a cell points at
  • +
  • The focused row as JSON beside the grid, filterable by key
  • +
+ More about editing data +
-
-

ClickHouse

HTTP
-
    -
  • HTTP or HTTPS, with a session per tab, so SET and USE hold
  • -
  • Read-only connections run with readonly=2 on the server too
  • -
  • Plans with the indexes each step reads
  • -
  • Array, Map and Tuple values read as JSON
  • -
  • A table's parts, mutations and merges
  • -
+
+
+ Results + +
+
+

See the shape of a result

+

The Chart button plots any result as bars or lines, beside the grid it came from. Pick the labels and up to five values, summed, counted or averaged where labels repeat.

+
    +
  • Sensible picks to start from, kept when you switch back to the grid
  • +
  • The count, sum, average, min and max of a selection in the status bar
  • +
  • Numbers lined up on the decimal point, and each column's type and keys in its header
  • +
  • Many bars scroll sideways rather than squeeze together
  • +
+ More about the results grid +
-
-

Turso / libSQL

HTTP
-
    -
  • Turso Cloud, or your own sqld, with an auth token
  • -
  • Rows stream in as the server sends them
  • -
  • An expired stream reopens by itself, and says what was lost
  • -
  • libSQL's vector functions in completion and hover
  • -
  • SSH tunnel to a self-hosted sqld
  • -
+
+
+ Schema + +
+
+

Your schema, down to the triggers

+

Schemas, tables and columns, then indexes, constraints, triggers and functions, fetched only when you open them.

+
    +
  • An ER diagram of a schema, with a table's keys lit as you point at it
  • +
  • Compare two schemas and get the script that syncs them
  • +
  • Copy any object's DDL, or open it in a tab ready to run
  • +
  • Rename, truncate and drop, with the exact SQL shown first
  • +
  • Back a table up to a .sql file that restores it exactly
  • +
  • System views for what the server is doing, or how a table is doing
  • +
+ More about the schema explorer +
-
-

SQLite

File
-
    -
  • Open a file, or drop it on the window
  • -
  • Opens straight from Finder or Explorer
  • -
  • Extensions such as cr-sqlite, loaded as it opens
  • -
  • Local libSQL and Turso files open here too
  • -
  • Bundled SQLite via rusqlite
  • -
+
+
+ Structure editor + +
+
+

Change a table without writing ALTER

+

Edit a table's columns, indexes, foreign keys and checks in a Structure tab, or design a new table there. The SQL it will run sits right below, and follows every edit.

+
    +
  • Added rows in green, changed fields in yellow, dropped ones struck through
  • +
  • One transaction on PostgreSQL and SQL Server, and a single ALTER TABLE on MySQL
  • +
  • SQLite tables rebuilt in a transaction when ALTER can't do the job
  • +
  • If the server refuses, its message shows and your edits stay as they are
  • +
+ More about the structure editor +
-
- -

What each engine gets

-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
FeaturePostgreSQLMySQL / MariaDBSQL ServerClickHouseTurso / libSQLSQLite
Read-only modeYesYesYesYesYesYes
SSH tunnelYesYesYesYes1Yes, self-hosted sqld onlyNot applicable
Transactions pinned to a tabYesYesYesNo2No, though a script's own BEGIN … COMMIT worksYes
Edit rows and query resultsYesYesYesNoYesYes
Structure editorYesYesYes3Yes, columns onlyYesYes
Function completion and hoverYesYesYesYesYesYes
Messages tabYesYesYesNoNoNo
System viewsYesYesYesYesNoNo
Query plan treeYesYesYesYesYesYes
Measured plans (explain analyze)YesYesYesNoNoNo
Copy DDL and table backupsYesYesYesYesYesYes
AI assistants (MCP)YesYes4Yes, with a read-only loginYes4Yes, with a read-only tokenYes
-
-

- 1 To a self-hosted sqld; Turso Cloud is reached directly. - 2 Turso holds no transaction between runs, but a script's own BEGIN … COMMIT works. - 3 Columns only. - 4 The server doesn't refuse writes by itself here, so give the AI's connection a read-only login or token. - ClickHouse has no transactions and no primary keys to edit rows by, so its tables are browse-only. -

-
-
- -
-
-

A closer look

-

The work you do every day, without the detours.

- -
-
- SQL editor -

Write SQL at full speed

-

Suggestions know your schema and your engine: tables, columns, aliases and CTEs, every built-in function with its arguments, and JOIN snippets where a join fits.

-
    -
  • Run the statement at the caret, a selection, or the whole script
  • -
  • Multiple cursors, line commands and go to definition, as in VS Code
  • -
  • :name parameters ask for their values, and remember them
  • -
  • A result tab per output, and notices, warnings and PRINT output in a Messages tab
  • -
  • Pinned transactions per tab: BEGIN, then commit or roll back from the toolbar
  • -
  • An error is underlined where it happened, and a mistyped name gets a one-click fix
  • -
- More about the SQL editor -
- -
- -
-
- Plan viewer -

See why a query is slow

-

Explain shows the planner's estimate; Explain analyze runs the statement and reports what really happened. Every engine's plan lands in the same tree, with costs and timings wherever the engine reports them.

-
    -
  • A heat bar on each node's own share of time, cost or rows
  • -
  • Row estimates 10x off are flagged: that's where a missing index hides
  • -
  • Every detail the engine reported, plus its raw output
  • -
  • Analyzing a write always rolls it back, so nothing lands
  • -
- More about the plan viewer -
- -
- -
-
- Table data -

Edit rows, not UPDATE statements

-

Browse any table, filter it with a WHERE condition that autocompletes, and change it in place. A query's results edit the same way, when they come from one table with its primary key.

-
    -
  • Edits and deletes are staged until you apply them, with undo and redo
  • -
  • Paste blocks of cells, set NULL, insert rows
  • -
  • Checkboxes for booleans, and a picker for enums
  • -
  • Jump through foreign keys to the row a cell points at
  • -
  • The focused row as JSON beside the grid, filterable by key
  • -
- More about editing data -
- -
- -
-
- Results -

See the shape of a result

-

The Chart button plots any result as bars or lines, beside the grid it came from. Pick the labels and up to five values, summed, counted or averaged where labels repeat.

-
    -
  • Sensible picks to start from, kept when you switch back to the grid
  • -
  • The count, sum, average, min and max of a selection in the status bar
  • -
  • Numbers lined up on the decimal point, and each column's type and keys in its header
  • -
  • Many bars scroll sideways rather than squeeze together
  • -
- More about the results grid -
- -
- -
-
- Schema -

Your schema, down to the triggers

-

Schemas, tables and columns, then indexes, constraints, triggers and functions, fetched only when you open them.

-
    -
  • An ER diagram of a schema, with a table's keys lit as you point at it
  • -
  • Compare two schemas and get the script that syncs them
  • -
  • Copy any object's DDL, or open it in a tab ready to run
  • -
  • Rename, truncate and drop, with the exact SQL shown first
  • -
  • Back a table up to a .sql file that restores it exactly
  • -
  • System views for what the server is doing, or how a table is doing
  • -
- More about the schema explorer -
- -
- -
-
- Structure editor -

Change a table without writing ALTER

-

Edit a table's columns, indexes, foreign keys and checks in a Structure tab, or design a new table there. The SQL it will run sits right below, and follows every edit.

-
    -
  • Added rows in green, changed fields in yellow, dropped ones struck through
  • -
  • One transaction on PostgreSQL and SQL Server, and a single ALTER TABLE on MySQL
  • -
  • SQLite tables rebuilt in a transaction when ALTER can't do the job
  • -
  • If the server refuses, its message shows and your edits stay as they are
  • -
- More about the structure editor -
- -
- -
-
- Import and export -

Data in, data out

-

Load a CSV into a new or existing table, or run a .sql script, and export any result in the format you need.

-
    -
  • Delimiter detected, columns mapped, types inferred for a new table
  • -
  • Bad rows skipped and reported, with live progress and Stop
  • -
  • Export as Text, CSV, JSON, Markdown, or SQL INSERTs in your engine's own syntax
  • -
  • CSV and SQL exports import back cleanly, NULLs included
  • -
- More about import and export -
- -
- -
-
- AI assistants -

Your databases, in your AI assistant

-

BarSQL is an MCP server too. Claude, Cursor, VS Code and other AI apps start BarSQL --mcp in the background, with no window, and work with the connections you share.

-
    -
  • Opt in per connection: the rest stay invisible to the AI
  • -
  • Queries pass BarSQL's read-only check, and PostgreSQL, MySQL, SQLite and ClickHouse sessions refuse writes too
  • -
  • Changes only where you allow them, with a preview that rolls back
  • -
  • No password or token reaches the AI, and every call is logged
  • -
  • Set up with one paste: tick Allow AI assistants, then Copy MCP setup
  • -
- More about AI assistants -
-
list_connections  shared connections
+        
+
+ Import and export + +
+
+

Data in, data out

+

Load a CSV into a new or existing table, or run a .sql script, and export any result in the format you need.

+
    +
  • Delimiter detected, columns mapped, types inferred for a new table
  • +
  • Bad rows skipped and reported, with live progress and Stop
  • +
  • Export as Text, CSV, JSON, Markdown, or SQL INSERTs in your engine's own syntax
  • +
  • CSV and SQL exports import back cleanly, NULLs included
  • +
+ More about import and export +
+
+
+
+ AI assistants +
list_connections  shared connections
 list_tables       schemas and tables
 describe_table    columns and keys
 show_ddl          CREATE statements
 run_query         read-only SQL
 explain_query     query plans
 run_change        changes you allow
+
+
+

Your databases, in your AI assistant

+

BarSQL is an MCP server too. Claude, Cursor, VS Code and other AI apps start BarSQL --mcp in the background, with no window, and work with the connections you share.

+
    +
  • Opt in per connection: the rest stay invisible to the AI
  • +
  • Queries pass BarSQL's read-only check, and PostgreSQL, MySQL, SQLite and ClickHouse sessions refuse writes too
  • +
  • Changes only where you allow them, with a preview that rolls back
  • +
  • No password or token reaches the AI, and every call is logged
  • +
  • Set up with one paste: tick Allow AI assistants, then Copy MCP setup
  • +
+ More about AI assistants +
+
-
+
-

Everything you need. Nothing you don't.

-
-

Smart autocomplete

Fuzzy and context-aware: schema.table.column dot completion, quoted identifiers, aliases, CTEs and JOIN snippets.

-

Function help

Every built-in function of your engine, plus the server's own, completes with its arguments. Hover one for its signatures and what it does.

-

Multiple cursors

Add a cursor at the next occurrence or at every one, and edit them all at once. Line commands, go to definition and go to line work as in VS Code.

-

Messages tab

PostgreSQL notices, MySQL warnings and SQL Server PRINT output, grouped under the statement that raised them, with levels and codes.

-

Multi-statement scripts

Statements run in order on one connection, so temp tables, SET and scripted transactions hold. SQL Server scripts run by GO batch.

-

Pinned transactions

BEGIN, COMMIT and ROLLBACK as SQL or from the toolbar. Queries run inside the tab's open transaction until it ends.

-

Streaming results

Rows render as the driver yields them, in a virtualized grid that scrolls smoothly through thousands of rows.

-

Quick charts

Plot a result as bars or lines: pick the labels and up to five value columns, summed, counted or averaged.

-

Query plan viewer

Estimated or measured plans as one tree on every engine, with heat bars and flagged estimates. Type EXPLAIN yourself and it opens there too.

-

System views

Activity, blocking, sessions and running queries, or a table's statistics, index usage, parts and merges, each in a tab you can tweak.

-

Schema explorer

Tables, views, columns, indexes, constraints, triggers and functions, with type and key icons, instant search and a row count that needs no tab.

-

ER diagrams and compare

Every table and key of a schema in a diagram you can pan, zoom and search, and a compare with another schema that writes the sync script.

-

Structure editor

Edit columns, indexes, foreign keys and checks, or design a new table. The exact SQL shows as you go, in a script your engine can apply safely.

-

Schema changes

Rename, truncate and drop from the tree. Each dialog shows the exact SQL for your engine, with Cascade on PostgreSQL.

-

Table backups

Structure and rows to a .sql file. The server writes every value as a literal, so dates, JSON and binary survive the round trip.

-

Edit data in the grid

In a table, or a query's results from one: staged edits, deletes and new rows with undo, paste and foreign-key jumps, checkboxes for booleans and pickers for enums.

-

Cell and JSON viewers

A cell editor for large values (JSON, XML, HTML or text), and a JSON view of the row. PostgreSQL arrays and ClickHouse maps and tuples read as JSON too.

-

CSV and SQL import

Column mapping, inferred types, configurable NULL text and header rows, or a .sql script run statement by statement.

-

One-click export

Text, CSV, JSON, Markdown or SQL INSERT written for your engine, for all or the selected rows, to the clipboard or a file.

-

Saved queries and history

Save and reuse queries with unsaved-change tracking, plus per-connection history with status and duration, filterable to failed runs.

-

Safety nets

Ask before changes on production connections, a check before an UPDATE or DELETE without WHERE, and read-only mode the server enforces too, where it can.

-

SSH tunnel

Reach a database only its bastion can see, with a key, a password or ssh-agent, and host keys checked against known_hosts.

-

Connection manager

Folders, tab colors, read-only mode and a database list fetched from the server, plus SQL Server named instances and Turso tokens.

-

SQLite extensions

Load extensions such as cr-sqlite into a connection each time it opens. Their functions complete and show on hover, and Test says why a file didn't load.

-

Keyboard-first

Quick Search jumps to tabs, tables, saved queries and connections, and the command palette runs any command, with its shortcut. Shortcuts are remappable.

-

Instant and polished

Dark and light themes that can follow the system, English, Deutsch, Български and Español, no animations in the way, and your window restored where you left it.

-

Updates itself

A new release shows up with its notes. One click downloads it, checks its SHA-256 and restarts into it.

+
+ Engines +

Six engines. One app.

+

The same editor, grid, schema tree and plan viewer on every engine, speaking each one's own dialect. Read-only connections are refused by BarSQL and, wherever the engine allows, by the server as well.

+
+
+
+

PostgreSQL

Server
+
    +
  • SSL: disable, require, verify-full
  • +
  • Notices and warnings as they arrive, in the Messages tab
  • +
  • Activity, blocking and long-running queries in one click
  • +
  • DDL composed from the catalog
  • +
  • Powered by tokio-postgres
  • +
+
+
+

MySQL / MariaDB

Server
+
    +
  • TLS transport
  • +
  • Warnings and notes in the Messages tab
  • +
  • The process list and open InnoDB transactions
  • +
  • The server's own SHOW CREATE DDL
  • +
  • Powered by mysql_async
  • +
+
+
+

SQL Server

Server
+
    +
  • TLS, including TDS 8.0 strict, and named instances
  • +
  • GO batches, with PRINT output in the Messages tab
  • +
  • Estimated and actual plans from Showplan XML
  • +
  • Requests, sessions and blocking at a glance
  • +
  • SQL Server 2017 or newer, powered by tiberius
  • +
+
+
+

ClickHouse

HTTP
+
    +
  • HTTP or HTTPS, with a session per tab, so SET and USE hold
  • +
  • Read-only connections run with readonly=2 on the server too
  • +
  • Plans with the indexes each step reads
  • +
  • Array, Map and Tuple values read as JSON
  • +
  • A table's parts, mutations and merges
  • +
+
+
+

Turso / libSQL

HTTP
+
    +
  • Turso Cloud, or your own sqld, with an auth token
  • +
  • Rows stream in as the server sends them
  • +
  • An expired stream reopens by itself, and says what was lost
  • +
  • libSQL's vector functions in completion and hover
  • +
  • SSH tunnel to a self-hosted sqld
  • +
+
+
+

SQLite

File
+
    +
  • Open a file, or drop it on the window
  • +
  • Opens straight from Finder or Explorer
  • +
  • Extensions such as cr-sqlite, loaded as it opens
  • +
  • Local libSQL and Turso files open here too
  • +
  • Bundled SQLite via rusqlite
  • +
+
+ +

What each engine gets

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
FeaturePostgreSQLMySQL / MariaDBSQL ServerClickHouseTurso / libSQLSQLite
Read-only modeYesYesYesYesYesYes
SSH tunnelYesYesYesYes1Yes, self-hosted sqld onlyNot applicable
Transactions pinned to a tabYesYesYesNo2No, though a script's own BEGIN … COMMIT worksYes
Edit rows and query resultsYesYesYesNoYesYes
Structure editorYesYesYes3Yes, columns onlyYesYes
Function completion and hoverYesYesYesYesYesYes
Messages tabYesYesYesNoNoNo
System viewsYesYesYesYesNoNo
Query plan treeYesYesYesYesYesYes
Measured plans (explain analyze)YesYesYesNoNoNo
Copy DDL and table backupsYesYesYesYesYesYes
AI assistants (MCP)YesYes4Yes, with a read-only loginYes4Yes, with a read-only tokenYes
+
+

+ 1 To a self-hosted sqld; Turso Cloud is reached directly. + 2 Turso holds no transaction between runs, but a script's own BEGIN … COMMIT works. + 3 Columns only. + 4 The server doesn't refuse writes by itself here, so give the AI's connection a read-only login or token. + ClickHouse has no transactions and no primary keys to edit rows by, so its tables are browse-only. +

-
+
-

Screenshots

-

Click a screenshot to see it full size, then use the arrow keys to browse.

-
-
+
-

Keyboard-first

-

Most can be remapped in the app's shortcuts editor. The editor's own keys work as in VS Code.

-
-
Quick Search
CtrlP
-
Command palette
CtrlShiftP
-
Run selection, or the statement at the caret
CtrlEnter
-
Run all statements
CtrlShiftEnter
-
Explain the query plan
CtrlShiftE
-
Explain analyze
CtrlShiftA
-
Go to definition
F12
-
Go to line
CtrlG
-
Add a cursor at the next occurrence
CtrlD
-
Save the query, or apply staged edits
CtrlS
-
New tab / close tab
CtrlT / CtrlW
-
Reopen a closed tab
CtrlShiftT
-
Toggle sidebar / JSON viewer
CtrlB / CtrlJ
-
Set a cell to NULL
CtrlBackspace
-
Zoom in / out
Ctrl= / Ctrl-
-
Rename a saved query
F2
-
+
+ Gallery +

Screenshots

+

Click a screenshot to see it full size, then use the arrow keys to browse.

+
+
-
+
-

Download

-

No account needed: download it and run it.

-
- - - -
-

- Every build updates itself from inside the app. All of them, with their checksums, are on the - latest release. - Prefer to build it? cargo run -p barsql --release, as the - contributing guide explains. -

+
+ Shortcuts +

Keyboard-first

+

Most can be remapped in the app's shortcuts editor. The editor's own keys work as in VS Code.

+
+
+
Quick Search
CtrlP
+
Command palette
CtrlShiftP
+
Run selection, or the statement at the caret
CtrlEnter
+
Run all statements
CtrlShiftEnter
+
Explain the query plan
CtrlShiftE
+
Explain analyze
CtrlShiftA
+
Go to definition
F12
+
Go to line
CtrlG
+
Add a cursor at the next occurrence
CtrlD
+
Save the query, or apply staged edits
CtrlS
+
New tab / close tab
CtrlT / CtrlW
+
Reopen a closed tab
CtrlShiftT
+
Toggle sidebar / JSON viewer
CtrlB / CtrlJ
+
Set a cell to NULL
CtrlBackspace
+
Zoom in / out
Ctrl= / Ctrl-
+
Rename a saved query
F2
+
-
+
+ Your data

Portable by design

- Everything lives in one BarSQL-data folder next to the app (beside the .app on macOS): - connections, the editor session, history, saved queries and settings. Move both to a USB stick, a network - drive or another computer and pick up where you left off. -

+ Everything lives in one BarSQL-data folder next to the app (beside the .app on macOS): + connections, the editor session, history, saved queries and settings. Move both to a USB stick, a network + drive or another computer and pick up where you left off. +

- When the app sits somewhere read-only, the folder goes to your user data directory instead, and - BARSQL_DATA_DIR puts it anywhere you like. -

+ When the app sits somewhere read-only, the folder goes to your user data directory instead, and + BARSQL_DATA_DIR puts it anywhere you like. +

-
BarSQL(.exe)
+      
+
BarSQL(.exe)
 BarSQL-data/
   connections.json
   editor_session.json
   query_history.json
   saved_queries.json
   settings.json
+
-

Built with

-

One Rust codebase: a background Tokio runtime talks to the databases, so the UI thread only draws. ClickHouse and Turso go through BarSQL's own small HTTP client, which also runs over SSH.

+
+ Open source +

Built with

+

One Rust codebase: a background Tokio runtime talks to the databases, so the UI thread only draws. ClickHouse and Turso go through BarSQL's own small HTTP client, which also runs over SSH.

+
RustLanguage GPUIUI framework @@ -642,17 +595,72 @@

Built with

-
+
- -

Ready to try BarSQL?

-

Free, open source and yours to keep, on Windows, macOS and Linux.

-
- - - Download now - - Star on GitHub +
+ +

Download BarSQL

+

Free, open source and yours to keep. No account needed: download it and run it.

+
+
+ + + +
+

+ Every build updates itself from inside the app. All of them, with their checksums, are on the + latest release. + Prefer to build it? cargo run -p barsql --release, as the + contributing guide explains. +

+
@@ -660,17 +668,33 @@

Ready to try BarSQL?

diff --git a/docs/logos.svg b/docs/logos.svg index bbea534..a94a751 100644 --- a/docs/logos.svg +++ b/docs/logos.svg @@ -6,4 +6,9 @@ + + + + + diff --git a/docs/script.js b/docs/script.js index 53f2ee9..b32dceb 100644 --- a/docs/script.js +++ b/docs/script.js @@ -1,149 +1,154 @@ // A saved theme wins. Otherwise follow the system setting until the visitor picks one. -(function () { - var root = document.documentElement; - var KEY = "barsql-theme"; +function setUpTheme() { + const root = document.documentElement; + const KEY = "barsql-theme"; - function chosen() { + const chosen = () => { try { - var theme = localStorage.getItem(KEY); + const theme = localStorage.getItem(KEY); return theme === "light" || theme === "dark" ? theme : null; - } catch (e) { + } catch { return null; } - } + }; - document.getElementById("theme-toggle").addEventListener("click", function () { - var next = root.dataset.theme === "dark" ? "light" : "dark"; + document.getElementById("theme-toggle").addEventListener("click", () => { + const next = root.dataset.theme === "dark" ? "light" : "dark"; root.dataset.theme = next; try { localStorage.setItem(KEY, next); - } catch (e) {} + } catch {} }); - if (window.matchMedia) { - window.matchMedia("(prefers-color-scheme: light)").addEventListener("change", function (event) { - if (!chosen()) root.dataset.theme = event.matches ? "light" : "dark"; - }); - } -})(); - -// Label the download button, highlight the download card and pick the modifier key for the visitor's OS. -// Phones and tablets keep the generic button. -(function () { - var ua = navigator.userAgent; - var platform = (navigator.userAgentData && navigator.userAgentData.platform) || navigator.platform || ""; - var mobile = /Android|iPhone|iPad|iPod/i.test(ua) || (/Mac/i.test(platform) && navigator.maxTouchPoints > 1); - var os = null; - if (!mobile) { - if (/Mac/i.test(platform) || /Macintosh/.test(ua)) os = "macos"; - else if (/Win/i.test(platform) || /Windows/.test(ua)) os = "windows"; - else if (/Linux|X11/i.test(platform + " " + ua)) os = "linux"; - } + matchMedia("(prefers-color-scheme: light)").addEventListener("change", (event) => { + if (!chosen()) root.dataset.theme = event.matches ? "light" : "dark"; + }); +} + +function detectOS() { + const ua = navigator.userAgent; + const platform = navigator.userAgentData?.platform || navigator.platform || ""; + const mobile = /Android|iPhone|iPad|iPod/i.test(ua) || (/Mac/i.test(platform) && navigator.maxTouchPoints > 1); + if (mobile) return null; + if (/Mac/i.test(platform) || /Macintosh/.test(ua)) return "macos"; + if (/Win/i.test(platform) || /Windows/.test(ua)) return "windows"; + if (/Linux|X11/i.test(`${platform} ${ua}`)) return "linux"; + return null; +} + +// Point the hero button at the visitor's own build, highlight its card and pick the modifier key. +// Phones and tablets keep the generic button, which leads to every download. +function pointDownloadAtVisitor() { + const os = detectOS(); if (!os) return; - var names = { windows: "Windows", macos: "macOS", linux: "Linux" }; - document.getElementById("hero-download-label").textContent = "Download for " + names[os]; - var card = document.getElementById("dl-" + os); - if (card) card.classList.add("dl-detected"); + const names = { windows: "Windows", macos: "macOS", linux: "Linux" }; + const card = document.getElementById(`dl-${os}`); + const main = card.querySelector(".dl-main"); + const hero = document.getElementById("hero-download"); + const icon = hero.querySelector("svg"); + hero.href = main.href; + icon.setAttribute("class", "logo"); + icon.querySelector("use").setAttribute("href", `logos.svg#l-${os === "macos" ? "apple" : os}`); + document.getElementById("hero-download-label").textContent = `Download for ${names[os]}`; + document.getElementById("hero-download-meta").textContent = main.dataset.meta; + card.classList.add("dl-detected"); if (os === "macos") { - document.querySelectorAll("kbd[data-mod]").forEach(function (key) { + for (const key of document.querySelectorAll("kbd[data-mod]")) { key.textContent = "⌘"; key.title = "Command"; - }); + } } -})(); - -// Lightbox with the full-size WebP screenshots. Arrow keys, swipes and the buttons move between them. -(function () { - var box = document.getElementById("lightbox"); - var img = box.querySelector("img"); - var caption = box.querySelector(".lightbox-caption"); - var count = box.querySelector(".lightbox-count"); - var buttons = { - close: box.querySelector(".lightbox-close"), - prev: box.querySelector(".lightbox-prev"), - next: box.querySelector(".lightbox-next"), - }; - var focusable = [buttons.close, buttons.prev, buttons.next]; - var items = Array.prototype.map.call(document.querySelectorAll(".gallery .shot"), function (shot) { - return { id: shot.dataset.shot, caption: shot.dataset.caption, alt: shot.querySelector("img").alt }; - }); - var index = 0; - var opener = null; - var touchX = null; +} - function source(item) { - return "screenshots/" + item.id + ".webp"; - } +// Lightbox with every screenshot on the page, full size and in order. Arrow keys, swipes and the buttons move between them. +function setUpLightbox() { + const box = document.getElementById("lightbox"); + const img = box.querySelector("img"); + const caption = box.querySelector(".lightbox-caption"); + const count = box.querySelector(".lightbox-count"); + const closeButton = box.querySelector(".lightbox-close"); + const prevButton = box.querySelector(".lightbox-prev"); + const nextButton = box.querySelector(".lightbox-next"); + const focusable = [closeButton, prevButton, nextButton]; + const shots = [...document.querySelectorAll(".shot[data-shot]")]; + + const seen = new Set(); + const items = shots + .filter((shot) => !seen.has(shot.dataset.shot) && seen.add(shot.dataset.shot)) + .map((shot) => ({ id: shot.dataset.shot, caption: shot.dataset.caption, alt: shot.querySelector("img").alt })) + .sort((a, b) => a.id - b.id); + + let index = 0; + let opener = null; + let touchX = null; - function show(at) { + const source = (item) => `screenshots/${item.id}.webp`; + + const show = (at) => { index = (at + items.length) % items.length; - var item = items[index]; + const item = items[index]; img.src = source(item); img.alt = item.alt; caption.textContent = item.caption; - count.textContent = index + 1 + " / " + items.length; + count.textContent = `${index + 1} / ${items.length}`; new Image().src = source(items[(index + 1) % items.length]); - } + }; - function open(id, trigger) { - var at = 0; - for (var i = 0; i < items.length; i++) { - if (items[i].id === id) at = i; - } + const open = (id, trigger) => { opener = trigger; - show(at); + show(Math.max(0, items.findIndex((item) => item.id === id))); box.hidden = false; document.body.style.overflow = "hidden"; - buttons.close.focus(); - } + closeButton.focus(); + }; - function close() { + const close = () => { box.hidden = true; document.body.style.overflow = ""; - if (opener) opener.focus(); - } + opener?.focus(); + }; - document.querySelectorAll(".shot").forEach(function (shot) { - shot.addEventListener("click", function () { - open(shot.dataset.shot, shot); - }); - }); + for (const shot of shots) { + shot.addEventListener("click", () => open(shot.dataset.shot, shot)); + } - buttons.close.addEventListener("click", close); - buttons.prev.addEventListener("click", function (event) { + closeButton.addEventListener("click", close); + prevButton.addEventListener("click", (event) => { event.stopPropagation(); show(index - 1); }); - buttons.next.addEventListener("click", function (event) { + nextButton.addEventListener("click", (event) => { event.stopPropagation(); show(index + 1); }); - img.addEventListener("click", function (event) { - event.stopPropagation(); - }); + img.addEventListener("click", (event) => event.stopPropagation()); box.addEventListener("click", close); - document.addEventListener("keydown", function (event) { + document.addEventListener("keydown", (event) => { if (box.hidden) return; if (event.key === "Escape") close(); else if (event.key === "ArrowRight") show(index + 1); else if (event.key === "ArrowLeft") show(index - 1); else if (event.key === "Tab") { - var at = focusable.indexOf(document.activeElement); + const at = focusable.indexOf(document.activeElement); focusable[(at + (event.shiftKey ? -1 : 1) + focusable.length) % focusable.length].focus(); event.preventDefault(); } }); - box.addEventListener("touchstart", function (event) { + box.addEventListener("touchstart", (event) => { touchX = event.touches[0].clientX; }, { passive: true }); - box.addEventListener("touchend", function (event) { + box.addEventListener("touchend", (event) => { if (touchX === null) return; - var dx = event.changedTouches[0].clientX - touchX; + const dx = event.changedTouches[0].clientX - touchX; touchX = null; if (Math.abs(dx) > 50) show(index + (dx < 0 ? 1 : -1)); }); -})(); +} + +setUpTheme(); +pointDownloadAtVisitor(); +setUpLightbox(); diff --git a/docs/styles.css b/docs/styles.css index b95866a..e5145ad 100644 --- a/docs/styles.css +++ b/docs/styles.css @@ -1,62 +1,58 @@ /* Theme colors, dark by default */ :root { - --font-sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, "Helvetica Neue", Arial, sans-serif; + --font-sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI Variable Text", "Segoe UI", Inter, Roboto, "Helvetica Neue", Arial, sans-serif; + --font-display: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI Variable Display", "Segoe UI", Inter, Roboto, "Helvetica Neue", Arial, sans-serif; --font-mono: ui-monospace, "SF Mono", "Cascadia Code", "JetBrains Mono", Menlo, Consolas, monospace; - --radius: 14px; - --radius-sm: 9px; - --container: 1120px; + --radius: 16px; + --radius-sm: 10px; + --container: 1160px; --gutter: 24px; } :root, :root[data-theme="dark"] { color-scheme: dark; - --bg: #0f1117; - --bg-alt: #12151c; - --card: #161b22; - --card-hover: #1c2128; - --border: #30363d; - --border-soft: #262c36; - --text: #e6edf3; - --muted: #8b949e; + --bg: #09090b; + --surface: #111113; + --surface-2: #19191c; + --line: rgba(255, 255, 255, 0.08); + --line-strong: rgba(255, 255, 255, 0.14); + --text: #ededef; + --muted: #a1a1aa; + --faint: #8a8a93; --accent: #3b82f6; - --accent-hover: #2563eb; --accent-text: #60a5fa; - /* White text needs 4.5:1, which --accent's blue doesn't reach */ - --button: #2563eb; - --button-hover: #1d4ed8; --accent-soft: rgba(59, 130, 246, 0.14); - --code-bg: #161b22; - --header-bg: rgba(15, 17, 23, 0.8); - --shadow: 0 30px 90px rgba(0, 0, 0, 0.55), 0 8px 24px rgba(0, 0, 0, 0.35); - --shadow-sm: 0 10px 30px rgba(0, 0, 0, 0.35); - --glow: rgba(59, 130, 246, 0.24); - --glow-2: rgba(40, 127, 247, 0.12); + --primary: #fafafa; + --primary-hover: #d4d4d8; + --on-primary: #09090b; + --header-bg: rgba(17, 17, 19, 0.72); + --glow: rgba(59, 130, 246, 0.3); + --shadow: 0 40px 120px rgba(0, 0, 0, 0.6), 0 12px 32px rgba(0, 0, 0, 0.4); + --highlight: inset 0 1px 0 rgba(255, 255, 255, 0.05); } :root[data-theme="light"] { color-scheme: light; --bg: #ffffff; - --bg-alt: #f6f8fa; - --card: #ffffff; - --card-hover: #f6f8fa; - --border: #d0d7de; - --border-soft: #eef1f4; - --text: #1f2328; - --muted: #656d76; - --accent: #0969da; - --accent-hover: #0550ae; - --accent-text: #0969da; - --button: #0969da; - --button-hover: #0550ae; - --accent-soft: rgba(9, 105, 218, 0.1); - --code-bg: #f6f8fa; - --header-bg: rgba(255, 255, 255, 0.82); - --shadow: 0 30px 80px rgba(31, 35, 40, 0.18), 0 8px 20px rgba(31, 35, 40, 0.08); - --shadow-sm: 0 10px 28px rgba(31, 35, 40, 0.1); - --glow: rgba(9, 105, 218, 0.14); - --glow-2: rgba(9, 105, 218, 0.07); + --surface: #fafafa; + --surface-2: #f1f1f3; + --line: rgba(9, 9, 11, 0.08); + --line-strong: rgba(9, 9, 11, 0.15); + --text: #09090b; + --muted: #52525b; + --faint: #71717a; + --accent: #2563eb; + --accent-text: #1d4ed8; + --accent-soft: rgba(37, 99, 235, 0.08); + --primary: #09090b; + --primary-hover: #3f3f46; + --on-primary: #fafafa; + --header-bg: rgba(255, 255, 255, 0.78); + --glow: rgba(37, 99, 235, 0.16); + --shadow: 0 40px 100px rgba(9, 9, 11, 0.14), 0 10px 28px rgba(9, 9, 11, 0.08); + --highlight: inset 0 1px 0 rgba(255, 255, 255, 0.7); } /* Base */ @@ -65,7 +61,7 @@ *::before, *::after { box-sizing: border-box; } -html { scroll-behavior: smooth; scroll-padding-top: 76px; } +html { scroll-behavior: smooth; scroll-padding-top: 96px; } body { margin: 0; @@ -92,19 +88,21 @@ code, kbd, pre { font-family: var(--font-mono); } code { font-size: 0.86em; - padding: 0.12em 0.4em; - border: 1px solid var(--border-soft); + padding: 0.1em 0.38em; + border: 1px solid var(--line); border-radius: 6px; - background: var(--code-bg); + background: var(--surface-2); white-space: nowrap; } -h1, h2, h3 { margin: 0 0 0.5em; line-height: 1.2; text-wrap: balance; } -h2 { font-size: clamp(1.65rem, 3.6vw, 2.35rem); font-weight: 800; letter-spacing: -0.02em; text-align: center; } -h3 { font-size: 1.05rem; font-weight: 700; } +h1, h2, h3 { margin: 0; font-family: var(--font-display); line-height: 1.15; text-wrap: balance; } +h2 { font-size: clamp(2rem, 4vw, 3rem); font-weight: 650; letter-spacing: -0.035em; line-height: 1.08; } +h3 { font-size: 1.02rem; font-weight: 600; letter-spacing: -0.01em; } +h3 code { font-size: 0.8em; } p, li, figcaption, dd { text-wrap: pretty; } -.accent { color: var(--accent); } +.accent { color: var(--accent-text); } +.nowrap { white-space: nowrap; } .container { width: 100%; max-width: var(--container); margin: 0 auto; padding: 0 var(--gutter); } @@ -119,7 +117,7 @@ p, li, figcaption, dd { text-wrap: pretty; } stroke-linejoin: round; } -/* Engine logos are filled shapes, unlike the stroked icons. */ +/* Logos are filled shapes, unlike the stroked icons. */ .logo { width: 1.15em; height: 1.15em; flex: none; fill: currentColor; } .skip-link { @@ -129,76 +127,107 @@ p, li, figcaption, dd { text-wrap: pretty; } z-index: 100; padding: 8px 14px; border-radius: var(--radius-sm); - background: var(--button); - color: #fff; + background: var(--primary); + color: var(--on-primary); font-weight: 600; } .skip-link:focus { top: 12px; text-decoration: none; } -.section { padding: 96px 0; } -.section-alt { background: var(--bg-alt); border-top: 1px solid var(--border-soft); border-bottom: 1px solid var(--border-soft); } +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} -.section-sub { - max-width: 700px; - margin: 0 auto 48px; - text-align: center; - color: var(--muted); - font-size: 1.07rem; - text-wrap: balance; +.section { padding: 120px 0; border-top: 1px solid var(--line); } + +.section-head { max-width: 720px; margin-bottom: 56px; } +.section-head h2 { margin-top: 14px; } +.section-head p { margin: 18px 0 0; color: var(--muted); font-size: 1.12rem; } +.section-head.center { margin-left: auto; margin-right: auto; text-align: center; } + +.eyebrow { + display: inline-block; + color: var(--accent-text); + font-family: var(--font-mono); + font-size: 0.78rem; + font-weight: 500; + letter-spacing: 0.06em; + text-transform: uppercase; } -/* Header */ +/* Header: a floating bar that stays in reach */ -.site-header { - position: sticky; - top: 0; - z-index: 50; +.site-header { position: sticky; top: 0; z-index: 50; padding: 12px 0; } + +.nav { + display: grid; + grid-template-columns: 1fr auto 1fr; + align-items: center; + gap: 12px; + height: 56px; + padding: 0 10px 0 16px; + border: 1px solid var(--line); + border-radius: 14px; background: var(--header-bg); - border-bottom: 1px solid var(--border-soft); - backdrop-filter: saturate(160%) blur(14px); - -webkit-backdrop-filter: saturate(160%) blur(14px); + box-shadow: var(--highlight); + backdrop-filter: saturate(180%) blur(16px); + -webkit-backdrop-filter: saturate(180%) blur(16px); } -.nav { display: flex; align-items: center; justify-content: space-between; height: 64px; } - .brand { display: inline-flex; align-items: center; gap: 10px; + justify-self: start; color: var(--text); - font-weight: 800; - font-size: 1.15rem; + font-family: var(--font-display); + font-weight: 700; + font-size: 1.08rem; letter-spacing: -0.01em; } .brand:hover { text-decoration: none; } +.brand img { flex: none; max-width: none; } -.nav-links { display: flex; align-items: center; gap: 6px; } +.nav-links { display: flex; align-items: center; gap: 2px; } -.nav-section { - padding: 8px 12px; - border-radius: var(--radius-sm); +.nav-links a { + display: inline-flex; + align-items: center; + gap: 3px; + padding: 7px 12px; + border-radius: 8px; color: var(--muted); + font-size: 0.92rem; font-weight: 500; - font-size: 0.95rem; } -.nav-section:hover { color: var(--text); background: var(--card-hover); text-decoration: none; } +.nav-links a:hover { color: var(--text); background: var(--surface-2); text-decoration: none; } +.nav-links .icon { width: 14px; height: 14px; } + +.nav-actions { display: flex; align-items: center; gap: 6px; justify-self: end; } .nav-icon { display: inline-flex; align-items: center; justify-content: center; - width: 38px; - height: 38px; - margin-left: 4px; - border: 1px solid var(--border); - border-radius: var(--radius-sm); - background: var(--card); + width: 36px; + height: 36px; + border: 1px solid var(--line); + border-radius: 9px; + background: transparent; color: var(--text); cursor: pointer; - transition: border-color 0.15s ease, background 0.15s ease; + transition: background 0.15s ease, border-color 0.15s ease; } -.nav-icon:hover { border-color: var(--accent); text-decoration: none; } -.nav-icon .icon { width: 18px; height: 18px; } +.nav-icon:hover { background: var(--surface-2); border-color: var(--line-strong); text-decoration: none; } +.nav-icon .icon, +.nav-icon .logo { width: 17px; height: 17px; } :root[data-theme="dark"] .icon-moon, :root[data-theme="light"] .icon-sun { display: none; } @@ -209,116 +238,115 @@ p, li, figcaption, dd { text-wrap: pretty; } display: inline-flex; align-items: center; justify-content: center; - gap: 9px; - padding: 12px 24px; + gap: 8px; + height: 42px; + padding: 0 18px; border: 1px solid transparent; - border-radius: 11px; + border-radius: var(--radius-sm); font: inherit; - font-weight: 650; - font-size: 1rem; - line-height: 1.2; + font-size: 0.95rem; + font-weight: 600; + line-height: 1; + white-space: nowrap; cursor: pointer; - transition: background 0.15s ease, border-color 0.15s ease, transform 0.12s ease, box-shadow 0.15s ease; + transition: background 0.15s ease, border-color 0.15s ease; } -.btn:hover { text-decoration: none; transform: translateY(-1px); } -.btn .icon { width: 18px; height: 18px; } +.btn:hover { text-decoration: none; } +.btn .icon, +.btn .logo { width: 17px; height: 17px; } -.btn-primary { background: var(--button); color: #fff; box-shadow: 0 6px 20px var(--glow); } -.btn-primary:hover { background: var(--button-hover); } +.btn-primary { background: var(--primary); color: var(--on-primary); } +.btn-primary:hover { background: var(--primary-hover); } -.btn-secondary { background: var(--card); border-color: var(--border); color: var(--text); } -.btn-secondary:hover { border-color: var(--accent); } +.btn-secondary { background: var(--surface); border-color: var(--line-strong); color: var(--text); } +.btn-secondary:hover { background: var(--surface-2); } -.cta { display: flex; flex-wrap: wrap; justify-content: center; gap: 14px; } +.btn-lg { height: 48px; padding: 0 22px; border-radius: 12px; font-size: 1rem; } +.btn-sm { height: 36px; padding: 0 14px; border-radius: 9px; font-size: 0.9rem; } /* Hero */ -.hero { - position: relative; - padding: 88px 0 0; - text-align: center; - overflow: hidden; - background: - radial-gradient(ellipse 60% 45% at 50% -8%, var(--glow), transparent 70%), - radial-gradient(ellipse 40% 35% at 85% 40%, var(--glow-2), transparent 70%), - radial-gradient(ellipse 40% 35% at 15% 55%, var(--glow-2), transparent 70%); +.hero { position: relative; padding: 72px 0 64px; overflow: hidden; isolation: isolate; } + +/* A faint grid behind the headline, fading out towards the screenshot */ +.hero::before { + content: ""; + position: absolute; + inset: -96px 0 auto; + height: 820px; + z-index: -1; + background-image: linear-gradient(var(--line) 1px, transparent 1px), linear-gradient(90deg, var(--line) 1px, transparent 1px); + background-size: 56px 56px; + -webkit-mask-image: radial-gradient(ellipse 70% 60% at 30% 10%, #000 20%, transparent 72%); + mask-image: radial-gradient(ellipse 70% 60% at 30% 10%, #000 20%, transparent 72%); } -.hero-badges { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; margin-bottom: 28px; } +.hero h1 { font-size: clamp(2.75rem, 6.6vw, 5rem); font-weight: 650; letter-spacing: -0.045em; line-height: 1.02; } +.h1-muted { display: block; color: var(--faint); } -.badge { - padding: 5px 12px; - border: 1px solid var(--border); - border-radius: 999px; - background: var(--card); - color: var(--muted); - font-family: var(--font-mono); - font-size: 0.78rem; - font-weight: 600; -} -.badge-accent { color: var(--accent-text); border-color: var(--accent); background: var(--accent-soft); } +.hero-sub { max-width: 600px; margin: 26px 0 0; color: var(--muted); font-size: clamp(1.08rem, 1.6vw, 1.22rem); } -.hero h1 { - max-width: 820px; - margin: 0 auto 20px; - font-size: clamp(2.3rem, 6.2vw, 4rem); - font-weight: 850; - letter-spacing: -0.035em; - line-height: 1.05; -} +.hero-actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 36px; } -.hero-sub { - max-width: 640px; - margin: 0 auto 34px; - color: var(--muted); - font-size: clamp(1.05rem, 2vw, 1.25rem); - text-wrap: balance; +.hero-meta { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 6px 18px; + margin: 18px 0 0; + color: var(--faint); + font-family: var(--font-mono); + font-size: 0.8rem; } -.hero-sub strong { color: var(--text); font-weight: 650; } +.hero-meta a { display: inline-flex; align-items: center; gap: 2px; color: var(--muted); text-decoration: none; } +.hero-meta a:hover { color: var(--text); } +.hero-meta .icon { width: 14px; height: 14px; } -.hero-note { margin: 16px 0 0; color: var(--muted); font-size: 0.9rem; } +.hero-shot { position: relative; margin-top: 64px; } +.hero-shot::before { + content: ""; + position: absolute; + inset: -10% 6% 25%; + z-index: -1; + background: radial-gradient(ellipse at 50% 45%, var(--glow), transparent 68%); + filter: blur(40px); +} -.hero-sub-tight { margin-bottom: 18px; } +.works { display: flex; flex-wrap: wrap; align-items: center; gap: 16px 32px; margin-top: 40px; } -/* A grid rather than wrapping, so the six always split evenly: three a row, two on phones. */ -.engines { - display: grid; - grid-template-columns: repeat(3, auto); - justify-content: center; - gap: 10px; - margin: 0 auto 34px; - padding: 0; - list-style: none; +.works-label { + flex: none; + color: var(--faint); + font-family: var(--font-mono); + font-size: 0.76rem; + letter-spacing: 0.06em; + text-transform: uppercase; } -.engines li { display: flex; } + +.engines { display: flex; flex-wrap: wrap; gap: 14px 28px; margin: 0; padding: 0; list-style: none; } .engines a { display: inline-flex; - flex: 1; align-items: center; - justify-content: center; - gap: 8px; - padding: 7px 14px; - border: 1px solid var(--border); - border-radius: 999px; - background: var(--card); - color: var(--text); - font-size: 0.92rem; + gap: 9px; + color: var(--muted); + font-size: 0.95rem; font-weight: 600; - transition: border-color 0.15s ease, transform 0.15s ease; + white-space: nowrap; + transition: color 0.15s ease; } -.engines a:hover { border-color: var(--accent); transform: translateY(-1px); text-decoration: none; } -.engines .logo { width: 18px; height: 18px; color: var(--accent-text); } +.engines a:hover { color: var(--text); text-decoration: none; } +.engines .logo { width: 22px; height: 22px; } -.hero-shot { position: relative; margin-top: 60px; padding-bottom: 0; } +/* Frames around the hero screenshot and the data folder */ -.hero-shot::before { - content: ""; - position: absolute; - inset: 8% 6% -10%; - background: radial-gradient(ellipse at center, var(--glow), transparent 65%); - filter: blur(30px); - z-index: 0; +.window { + position: relative; + border: 1px solid var(--line-strong); + border-radius: var(--radius); + background: var(--surface); + box-shadow: var(--shadow), var(--highlight); + overflow: hidden; } /* Screenshot buttons */ @@ -331,129 +359,156 @@ p, li, figcaption, dd { text-wrap: pretty; } border: 0; background: none; cursor: zoom-in; - z-index: 1; } .shot img { width: 100%; aspect-ratio: 16 / 9; - border: 1px solid var(--border); - border-radius: var(--radius); - background: var(--card); - box-shadow: var(--shadow-sm); - transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease; + border: 1px solid var(--line-strong); + border-radius: 12px; + background: var(--surface-2); + transition: border-color 0.2s ease; } .shot:hover img { border-color: var(--accent); } -.shot-hero img { - border-radius: var(--radius) var(--radius) 0 0; - border-bottom: 0; - box-shadow: var(--shadow); -} +.window .shot img { border: 0; border-radius: 0; } -/* Highlights strip */ +/* Why BarSQL */ -.strip { border-top: 1px solid var(--border-soft); border-bottom: 1px solid var(--border-soft); background: var(--bg-alt); } +.statement { max-width: 1000px; font-family: var(--font-display); font-size: clamp(1.55rem, 3.1vw, 2.35rem); font-weight: 600; letter-spacing: -0.025em; line-height: 1.25; } +.statement h2 { margin: 0; font: inherit; letter-spacing: inherit; } -.strip-inner { - display: grid; - grid-template-columns: repeat(6, auto); - justify-content: center; - gap: 14px 40px; - padding-top: 22px; - padding-bottom: 22px; +.pillars { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 32px; margin-top: 64px; } +/* Each point's number leads the hairline above it */ +.pillar-num { display: flex; align-items: center; gap: 12px; margin-bottom: 22px; color: var(--faint); font-family: var(--font-mono); font-size: 0.78rem; } +.pillar-num::after { content: ""; flex: 1; height: 1px; background: var(--line-strong); } +.pillar h3 { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; font-size: 1.05rem; } +.pillar .icon { width: 20px; height: 20px; color: var(--accent-text); } +.pillar p { margin: 0; color: var(--muted); font-size: 0.95rem; } + +/* Tour */ + +.tour { display: grid; grid-template-columns: minmax(0, 1fr); gap: 32px; } + +.tour-card { + display: flex; + flex-direction: column; + border: 1px solid var(--line); + border-radius: var(--radius); + background: var(--surface); + box-shadow: var(--highlight); + overflow: hidden; } -.strip-inner span { display: inline-flex; align-items: center; gap: 9px; color: var(--muted); font-weight: 600; white-space: nowrap; } -.strip-inner .icon { color: var(--accent-text); } +/* Stacked, the screenshot rises from the foot of a tinted panel, under its label */ +.tour-media { + display: flex; + flex-direction: column; + align-items: flex-start; + padding: 24px 28px 0; + border-bottom: 1px solid var(--line); + background: radial-gradient(ellipse 80% 90% at 50% 100%, var(--accent-soft), transparent 70%), var(--surface-2); +} +.tour-media .eyebrow { margin-bottom: 16px; } +.tour-media .shot img { border-bottom: 0; border-radius: 10px 10px 0 0; } +.tour-media-code { padding: 24px 28px 28px; } +.tour-media .code-block { width: 100%; border: 1px solid var(--line-strong); border-radius: 12px; } + +.tour-text { display: flex; flex-direction: column; align-items: flex-start; padding: 28px; } +.tour-text h3 { margin-bottom: 12px; font-size: 1.4rem; font-weight: 650; letter-spacing: -0.02em; } +.tour-text > p { margin: 0 0 20px; color: var(--muted); font-size: 0.97rem; } +.tour-text strong { color: var(--text); } -/* Cards */ +.ticks { margin: 0; padding: 0; list-style: none; } +.ticks li { position: relative; padding-left: 28px; color: var(--text); font-size: 0.95rem; } +.ticks li + li { margin-top: 10px; } +.ticks li::before { + content: ""; + position: absolute; + left: 0; + top: 0.22em; + width: 18px; + height: 18px; + background: var(--accent-text); + -webkit-mask: no-repeat center / 14px url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E"); + mask: no-repeat center / 14px url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E"); +} -.card-grid { display: grid; gap: 20px; } -.card-grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } -.card-grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } +.tour-more { display: inline-flex; align-items: center; gap: 4px; margin-top: 22px; font-weight: 600; } +.tour-more .icon { width: 17px; height: 17px; } -.card { - padding: 24px; - border: 1px solid var(--border); - border-radius: var(--radius); - background: var(--card); - transition: border-color 0.15s ease, transform 0.15s ease, background 0.15s ease; +/* Side by side where there's room, with the screenshot swapping sides from card to card */ +@media (min-width: 1001px) { + .tour-card { display: grid; grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); grid-template-areas: "text media"; } + .tour-card:nth-child(even) { grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr); grid-template-areas: "media text"; } + .tour-text { grid-area: text; justify-content: center; padding: 40px; } + .tour-text h3 { font-size: clamp(1.45rem, 2.4vw, 1.9rem); } + .tour-media { grid-area: media; justify-content: center; padding: 36px; border-bottom: 0; border-left: 1px solid var(--line); } + .tour-card:nth-child(even) .tour-media { border-left: 0; border-right: 1px solid var(--line); } + .tour-media .shot img { border-bottom: 1px solid var(--line-strong); border-radius: 12px; box-shadow: var(--shadow); } } -.card:hover { border-color: var(--accent); transform: translateY(-2px); } -.card h3 { margin: 14px 0 8px; } -.card p { margin: 0; color: var(--muted); font-size: 0.94rem; } + +/* Engines */ + +.engine-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; } + +.card { padding: 24px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); box-shadow: var(--highlight); } .chip { display: inline-flex; align-items: center; justify-content: center; + flex: none; width: 40px; height: 40px; - border-radius: 11px; - background: var(--accent-soft); + border: 1px solid var(--line); + border-radius: 10px; + background: var(--surface-2); color: var(--accent-text); } -.chip .icon { width: 20px; height: 20px; } -.chip .logo { width: 22px; height: 22px; } - -.features .card { padding: 22px; } - -/* In the intro and feature cards the chip and the title share a line, with the text below. */ -.card-grid-4 > .card, -.features > .card { - display: grid; - grid-template-columns: auto minmax(0, 1fr); - align-content: start; - align-items: center; - column-gap: 14px; -} -.card-grid-4 > .card h3, -.features > .card h3 { margin: 0; } -.card-grid-4 > .card p, -.features > .card p { grid-column: 1 / -1; margin-top: 14px; } - -.db-head { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; } -.db-head h3 { margin: 0; font-size: 1.15rem; } - -.db-card ul { margin: 0; padding-left: 20px; color: var(--muted); font-size: 0.94rem; } -.db-card li + li { margin-top: 6px; } +.chip .icon { width: 19px; height: 19px; } +.chip .logo { width: 21px; height: 21px; } -.db-name { display: flex; flex-direction: column; align-items: flex-start; gap: 5px; min-width: 0; } -.db-name h3 { margin: 0; } +.db-card { scroll-margin-top: 96px; transition: border-color 0.2s ease; } +.db-card:target { border-color: var(--accent); } +.db-head { display: flex; align-items: center; gap: 12px; margin-bottom: 18px; } +.db-name { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; min-width: 0; } +.db-name h3 { font-size: 1.1rem; } .db-tag { - padding: 1px 8px; - border: 1px solid var(--border); - border-radius: 999px; - color: var(--muted); + padding: 0 7px; + border: 1px solid var(--line-strong); + border-radius: 6px; + color: var(--faint); font-family: var(--font-mono); - font-size: 0.72rem; - font-weight: 600; + font-size: 0.7rem; + line-height: 1.6; white-space: nowrap; } -/* Engine matrix */ +.db-card ul { margin: 0; padding-left: 18px; color: var(--muted); font-size: 0.93rem; } +.db-card li + li { margin-top: 6px; } +.db-card li::marker { color: var(--faint); } -.matrix-title { margin: 72px 0 20px; text-align: center; font-size: clamp(1.25rem, 2.4vw, 1.5rem); font-weight: 800; letter-spacing: -0.01em; } +.matrix-title { margin: 88px 0 20px; font-size: clamp(1.3rem, 2.4vw, 1.6rem); font-weight: 650; letter-spacing: -0.02em; } .matrix-wrap { /* Positioned, so the cells' screen-reader text scrolls inside it instead of widening the page */ position: relative; overflow-x: auto; - border: 1px solid var(--border); + border: 1px solid var(--line); border-radius: var(--radius); - background: var(--card); + background: var(--surface); } .matrix { width: 100%; min-width: 820px; border-collapse: collapse; font-size: 0.92rem; } -.matrix th, .matrix td { padding: 11px 14px; border-bottom: 1px solid var(--border-soft); text-align: center; white-space: nowrap; } -.matrix thead th { background: var(--bg-alt); color: var(--text); font-size: 0.84rem; font-weight: 700; } -.matrix tbody th { text-align: left; font-weight: 600; } +.matrix th, .matrix td { padding: 12px 14px; border-bottom: 1px solid var(--line); text-align: center; white-space: nowrap; } +.matrix thead th { color: var(--text); font-size: 0.84rem; font-weight: 600; } +.matrix tbody th { text-align: left; font-weight: 500; } .engine-head { display: inline-flex; flex-direction: column; align-items: center; gap: 7px; } .engine-head .logo { width: 20px; height: 20px; color: var(--accent-text); } .matrix tbody tr:last-child > * { border-bottom: 0; } -.matrix tbody tr:hover > * { background: var(--card-hover); } +.matrix tbody tr:hover > * { background: var(--surface-2); } .matrix .yes::before { content: ""; display: inline-block; @@ -464,229 +519,174 @@ p, li, figcaption, dd { text-wrap: pretty; } -webkit-mask: no-repeat center / 18px url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E"); mask: no-repeat center / 18px url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E"); } -.matrix .no { color: var(--muted); } +.matrix .no { color: var(--faint); } .matrix sup { margin-left: 2px; color: var(--accent-text); font-size: 0.7rem; font-weight: 700; } /* The feature column stays in view while the table scrolls sideways */ -.matrix tr > :first-child { position: sticky; left: 0; z-index: 1; background: var(--card); } -.matrix thead tr > :first-child { background: var(--bg-alt); } +.matrix tr > :first-child { position: sticky; left: 0; z-index: 1; background: var(--surface); } -.matrix-notes { max-width: 780px; margin: 16px auto 0; text-align: center; color: var(--muted); font-size: 0.86rem; text-wrap: balance; } +.matrix-notes { max-width: 820px; margin: 16px 0 0; color: var(--faint); font-size: 0.86rem; } .matrix-notes sup { color: var(--accent-text); font-weight: 700; } -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; -} +/* Features */ -/* Spotlights */ +.features { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0 40px; } -.spotlight { +.feature { display: grid; - grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); - gap: 56px; + grid-template-columns: auto minmax(0, 1fr); + align-content: start; align-items: center; + gap: 12px; + padding: 24px 0 28px; + border-top: 1px solid var(--line); } -.spotlight + .spotlight { margin-top: 96px; } -.spotlight-flip { grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr); } -.spotlight-flip .spotlight-text { order: 2; } - -.eyebrow { - display: inline-block; - margin-bottom: 10px; - color: var(--accent-text); - font-size: 0.8rem; - font-weight: 700; - letter-spacing: 0.08em; - text-transform: uppercase; -} - -.spotlight h3 { font-size: clamp(1.4rem, 2.6vw, 1.85rem); font-weight: 800; letter-spacing: -0.02em; margin-bottom: 14px; } -.spotlight h3 code { font-size: 0.8em; } -.spotlight-text > p { margin: 0 0 18px; color: var(--muted); font-size: 1.02rem; } -.spotlight-text strong { color: var(--text); } - -.ticks { margin: 0; padding: 0; list-style: none; } -.ticks li { position: relative; padding-left: 30px; color: var(--text); font-size: 0.96rem; } -.ticks li + li { margin-top: 10px; } -.ticks li::before, -.ticks li::after { - content: ""; - position: absolute; - left: 0; - top: 0.2em; - width: 20px; - height: 20px; -} -.ticks li::before { border-radius: 6px; background: var(--accent-soft); } -.ticks li::after { - background: var(--accent-text); - -webkit-mask: no-repeat center / 13px url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E"); - mask: no-repeat center / 13px url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E"); -} - -.spotlight-more { display: inline-flex; align-items: center; gap: 4px; margin-top: 22px; font-weight: 600; } -.spotlight-more .icon { width: 17px; height: 17px; } - -.spotlight .shot img { box-shadow: var(--shadow); } +.feature .chip { width: 34px; height: 34px; border-radius: 9px; } +.feature .chip .icon { width: 17px; height: 17px; } +.feature h3 { font-size: 1rem; } +.feature p { grid-column: 1 / -1; margin: 0; color: var(--muted); font-size: 0.93rem; } -/* Gallery */ +/* Gallery: twelve screenshots, so the rows come out even at 4, 3 and 2 across */ -.gallery { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 24px; } +.gallery { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 28px 20px; } .gallery figure { margin: 0; } -.gallery .shot:hover img { transform: translateY(-3px); box-shadow: var(--shadow); } -.gallery figcaption { margin-top: 10px; text-align: center; color: var(--muted); font-size: 0.9rem; font-weight: 500; } +.gallery figcaption { margin-top: 12px; color: var(--muted); font-size: 0.88rem; font-weight: 500; } /* Shortcuts */ -.keys { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 0 40px; - max-width: 900px; - margin: 0 auto; -} +.keys { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 48px; margin: 0; } .keys div { display: flex; align-items: center; justify-content: space-between; gap: 16px; - padding: 12px 2px; - border-bottom: 1px solid var(--border-soft); + padding: 13px 0; + border-bottom: 1px solid var(--line); } .keys dt { color: var(--text); font-size: 0.95rem; } .keys dd { display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: center; gap: 5px; margin: 0; white-space: nowrap; } -.keys dd span { color: var(--muted); padding: 0 2px; } +.keys dd span { color: var(--faint); padding: 0 2px; } kbd { min-width: 26px; padding: 3px 8px; - border: 1px solid var(--border); + border: 1px solid var(--line-strong); border-bottom-width: 2px; border-radius: 7px; - background: var(--card); + background: var(--surface-2); color: var(--text); font-size: 0.78rem; - font-weight: 600; + font-weight: 500; text-align: center; } +/* Portable */ + +.portable { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 0.9fr); gap: 64px; align-items: center; } +.portable h2 { margin: 14px 0 22px; } +.portable p { color: var(--muted); } + +.code-block { margin: 0; padding: 24px 26px; background: var(--surface); font-size: 0.9rem; line-height: 1.8; overflow-x: auto; } +.code-block code { padding: 0; border: 0; background: none; font-size: inherit; white-space: pre; } + +/* Stack: one-pixel gaps over a line-colored background draw the grid lines */ + +.stack { + display: grid; + grid-template-columns: repeat(6, minmax(0, 1fr)); + gap: 1px; + border: 1px solid var(--line); + border-radius: var(--radius); + background: var(--line); + overflow: hidden; +} + +.stack a { display: flex; flex-direction: column; gap: 2px; padding: 20px; background: var(--bg); color: var(--text); transition: background 0.15s ease; } +.stack a:hover { background: var(--surface-2); text-decoration: none; } +.stack strong { font-size: 0.98rem; font-weight: 600; } +.stack span { color: var(--faint); font-size: 0.82rem; } + /* Download */ -.dl-card { display: flex; flex-direction: column; gap: 12px; } -.dl-card:hover { transform: none; } -.dl-card.dl-detected { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent), var(--shadow-sm); } +.download-icon { margin: 0 auto 24px; filter: drop-shadow(0 16px 32px var(--glow)); } + +.downloads { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; } + +.dl { + display: flex; + flex-direction: column; + padding: 24px; + border: 1px solid var(--line); + border-radius: var(--radius); + background: var(--surface); + box-shadow: var(--highlight); +} +.dl.dl-detected { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent), 0 24px 60px var(--glow); } -.dl-head { display: flex; align-items: center; gap: 12px; margin-bottom: 4px; } -.dl-head h3 { margin: 0; font-size: 1.2rem; } -.dl-head svg { flex: none; color: var(--accent-text); } +.dl-head { display: flex; align-items: center; gap: 10px; } +.dl-head .logo { width: 22px; height: 22px; } +.dl-head h3 { font-size: 1.15rem; } .dl-detected-label { display: none; margin-left: auto; - padding: 2px 9px; + padding: 2px 8px; border-radius: 999px; background: var(--accent-soft); color: var(--accent-text); - font-size: 0.75rem; - font-weight: 700; + font-size: 0.72rem; + font-weight: 600; + white-space: nowrap; } .dl-detected .dl-detected-label { display: inline-block; } -.dl-card .btn { width: 100%; } +.dl-desc { margin: 12px 0 20px; color: var(--muted); font-size: 0.92rem; } +.dl-main { width: 100%; } -/* Negative margins so the dividers reach the card edges */ -.dl-other { margin: 6px -24px -14px; padding-top: 14px; border-top: 1px solid var(--border-soft); } +/* Every card gives its other formats two rows at the bottom, so the cards line up */ +.dl-more { display: grid; justify-items: start; gap: 8px; margin-top: auto; padding-top: 20px; } +.dl-row { display: flex; flex-wrap: wrap; gap: 8px; } -.dl-other-title { - display: block; - margin-bottom: 6px; - padding: 0 24px; - color: var(--muted); - font-size: 0.76rem; - font-weight: 700; - letter-spacing: 0.07em; +.dl-more-label { + color: var(--faint); + font-family: var(--font-mono); + font-size: 0.72rem; + letter-spacing: 0.06em; text-transform: uppercase; } -.dl-other ul { margin: 0; padding: 0; list-style: none; } -.dl-other li + li { border-top: 1px solid var(--border-soft); } - -.dl-other a { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - padding: 9px 24px; - color: var(--text); - font-size: 0.93rem; -} -.dl-other a:hover { background: var(--card-hover); color: var(--accent-text); text-decoration: none; } -.dl-other a span { color: var(--muted); font-family: var(--font-mono); font-size: 0.78rem; white-space: nowrap; } - -.dl-all { max-width: 760px; margin: 36px auto 0; text-align: center; color: var(--muted); font-size: 0.95rem; text-wrap: balance; } - -/* Portable */ - -.portable { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 0.85fr); gap: 56px; align-items: center; } -.portable h2 { text-align: left; } -.portable p { color: var(--muted); } - -.code-block { - margin: 0; - padding: 26px 28px; - border: 1px solid var(--border); - border-radius: var(--radius); - background: var(--code-bg); - box-shadow: var(--shadow-sm); - font-size: 0.92rem; - line-height: 1.75; - overflow-x: auto; -} -.code-block code { padding: 0; border: 0; background: none; white-space: pre; } - -/* Stack */ - -.stack { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 14px; } - -.stack a { - display: flex; - flex-direction: column; +.dl-more a { + display: inline-flex; align-items: center; - gap: 2px; - padding: 18px 12px; - border: 1px solid var(--border); - border-radius: var(--radius-sm); - background: var(--card); + gap: 6px; + height: 32px; + padding: 0 11px; + border: 1px solid var(--line-strong); + border-radius: 8px; color: var(--text); - text-align: center; - transition: border-color 0.15s ease, transform 0.15s ease; + font-size: 0.84rem; + font-weight: 500; + transition: background 0.15s ease; } -.stack a:hover { border-color: var(--accent); transform: translateY(-2px); text-decoration: none; } -.stack strong { font-size: 0.98rem; } -.stack span { color: var(--muted); font-size: 0.8rem; text-wrap: balance; } +.dl-more a:hover { background: var(--surface-2); text-decoration: none; } +.dl-more a span { color: var(--faint); font-family: var(--font-mono); font-size: 0.76rem; } -/* Final CTA and footer */ +.dl-all { max-width: 760px; margin: 40px auto 0; text-align: center; color: var(--faint); font-size: 0.92rem; text-wrap: balance; } +.dl-star { display: flex; justify-content: center; margin-top: 24px; } -.cta-final { text-align: center; } -/* The icon draws its own rounded corners, so the glow follows its shape instead of clipping it. */ -.cta-icon { margin: 0 auto 22px; filter: drop-shadow(0 12px 22px var(--glow)); } +/* Footer */ -.site-footer { padding: 28px 0; border-top: 1px solid var(--border-soft); background: var(--bg-alt); } -.footer-inner { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 16px; } -.footer-inner .brand { font-size: 1rem; } -.footer-links { display: flex; flex-wrap: wrap; gap: 22px; } -.footer-links a { color: var(--muted); font-size: 0.92rem; } -.footer-links a:hover { color: var(--accent-text); } +.site-footer { padding: 64px 0 48px; border-top: 1px solid var(--line); } +.footer-inner { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 2fr); gap: 40px; } +.footer-brand .brand { font-size: 1rem; } +.footer-brand p { max-width: 300px; margin: 14px 0 0; color: var(--faint); font-size: 0.9rem; } +.footer-cols { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 24px; } +.footer-cols div { display: flex; flex-direction: column; align-items: flex-start; gap: 10px; } +.footer-title { margin-bottom: 4px; color: var(--text); font-size: 0.85rem; font-weight: 600; } +.footer-cols a { color: var(--muted); font-size: 0.9rem; } +.footer-cols a:hover { color: var(--text); text-decoration: none; } /* Lightbox */ @@ -715,8 +715,8 @@ kbd { cursor: default; } -.lightbox figcaption { color: #d0d7de; font-size: 0.95rem; text-align: center; } -.lightbox-count { margin-left: 8px; color: #8b949e; font-variant-numeric: tabular-nums; } +.lightbox figcaption { color: #d4d4d8; font-size: 0.95rem; text-align: center; } +.lightbox-count { margin-left: 8px; color: #8a8a93; font-variant-numeric: tabular-nums; } .lightbox-button { display: inline-flex; @@ -727,70 +727,63 @@ kbd { height: 46px; border: 1px solid rgba(255, 255, 255, 0.16); border-radius: 50%; - background: rgba(22, 27, 34, 0.8); - color: #e6edf3; + background: rgba(24, 24, 27, 0.8); + color: #ededef; cursor: pointer; transition: background 0.15s ease, border-color 0.15s ease; } -.lightbox-button:hover { background: #262c36; border-color: rgba(255, 255, 255, 0.3); } +.lightbox-button:hover { background: #27272a; border-color: rgba(255, 255, 255, 0.3); } .lightbox-button .icon { width: 22px; height: 22px; } .lightbox-close { position: absolute; top: 14px; right: 16px; } /* Responsive */ +/* Below this, Linux's first row of formats no longer fits a third of the width */ @media (max-width: 1100px) { - .strip-inner { grid-template-columns: repeat(3, auto); } + .downloads { grid-template-columns: minmax(0, 1fr); max-width: 480px; margin: 0 auto; } + .pillars { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .gallery { grid-template-columns: repeat(3, minmax(0, 1fr)); } } @media (max-width: 1000px) { - .card-grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } - .card-grid-3, .gallery { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .features, .engine-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .portable { grid-template-columns: minmax(0, 1fr); gap: 32px; } .stack { grid-template-columns: repeat(3, minmax(0, 1fr)); } - .spotlight, .spotlight-flip, .portable { grid-template-columns: minmax(0, 1fr); gap: 32px; } - .spotlight-flip .spotlight-text { order: 0; } - .spotlight + .spotlight { margin-top: 72px; } .keys { grid-template-columns: minmax(0, 1fr); } + .tour-text { max-width: 720px; } } -/* Beside the five sections, Docs only fits from about 790px */ -@media (max-width: 800px) { - .nav-docs { display: none; } -} - -@media (max-width: 720px) { - .nav-section { display: none; } -} - -@media (max-width: 560px) { - .engines { grid-template-columns: repeat(2, auto); gap: 8px; } - .engines a { gap: 7px; padding: 6px 12px; font-size: 0.86rem; } - .engines .logo { width: 16px; height: 16px; } - .strip-inner { grid-template-columns: repeat(2, auto); gap: 12px 26px; font-size: 0.92rem; } - /* The three short badges share a row and the platforms take the next, rather than one badge alone */ - .hero-badges .badge-platforms { order: 1; } +@media (max-width: 920px) { + .nav-links .nav-docs { display: none; } } -@media (max-width: 400px) { - .engines a { gap: 6px; padding: 5px 10px; font-size: 0.8rem; } - .engines .logo { width: 16px; height: 16px; } - .strip-inner { gap: 11px 18px; font-size: 0.86rem; } - .footer-links { display: grid; grid-template-columns: repeat(2, auto); gap: 8px 24px; } -} - -@media (max-width: 340px) { - .engines { grid-template-columns: minmax(0, 220px); } +@media (max-width: 760px) { + .nav { grid-template-columns: 1fr auto; } + .nav-links { display: none; } } @media (max-width: 640px) { :root { --gutter: 16px; } - .section { padding: 64px 0; } - .hero { padding-top: 56px; } - .hero-shot { margin-top: 44px; } - .card-grid-3, .card-grid-4, .gallery { grid-template-columns: minmax(0, 1fr); } + .section { padding: 80px 0; } + .hero { padding: 40px 0 48px; } + .engines { display: grid; grid-template-columns: repeat(2, auto); justify-content: start; } + .hero-actions .btn { flex: 1 1 auto; } + .hero-shot { margin-top: 48px; } + .pillars, .features, .engine-grid { grid-template-columns: minmax(0, 1fr); } + .gallery { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px 12px; } + .pillars { margin-top: 56px; } + .tour { gap: 24px; } + .tour-media { padding: 20px 20px 0; } + .tour-media-code { padding: 20px 12px 12px; } + .tour-media-code .eyebrow { margin-left: 8px; } + .tour-text { padding: 22px 20px; } .stack { grid-template-columns: repeat(2, minmax(0, 1fr)); } .keys div { flex-direction: column; align-items: flex-start; gap: 6px; } .keys dd { justify-content: flex-start; } + .footer-inner { grid-template-columns: minmax(0, 1fr); } .code-block { padding: 20px 18px; } + /* The tool list's longest line is 36 characters, so the font follows the screen width to fit it */ + .tour-media .code-block { padding: 16px 14px; font-size: clamp(0.66rem, 3.3vw, 0.9rem); } .lightbox { padding: 64px 8px 24px; gap: 6px; } .lightbox-prev, .lightbox-next { position: absolute; bottom: 18px; } .lightbox-prev { left: calc(50% - 56px); } @@ -800,8 +793,17 @@ kbd { code { white-space: normal; } } +@media (max-width: 400px) { + .footer-cols { grid-template-columns: repeat(2, minmax(0, 1fr)); } +} + +@media (max-width: 370px) { + .nav-github { display: none; } + .hero h1 { font-size: 2.4rem; } + .engines { display: flex; } +} + @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } *, *::before, *::after { transition: none !important; } - .btn:hover, .card:hover, .stack a:hover, .engines a:hover, .gallery .shot:hover img { transform: none; } }