diff --git a/docs/graalvm.md b/docs/graalvm.md index 2adf582d2..e8c4c5317 100644 --- a/docs/graalvm.md +++ b/docs/graalvm.md @@ -97,15 +97,9 @@ metadata directory, small enough to read in a minute: - [Spring Boot example metadata](https://github.com/storm-orm/storm-example-kotlin-spring-boot-4-graalvm/blob/main/src/main/kotlin/st/orm/demo/imdb/ApplicationRuntimeHints.kt) - [Ktor example metadata](https://github.com/storm-orm/storm-example-kotlin-ktor-graalvm/tree/main/src/main/resources/META-INF/native-image) -## Measured +## What you get -Both examples, on an Apple M-series MacBook against PostgreSQL 17, verified by their Playwright -interface suites running against the native binaries: - -| | Startup | Dataset import (1.5M rows) | Binary | -|---|---|---|---| -| Spring Boot 4 | ~0.25 s | 87 s | 122 MB | -| Ktor 3 (CIO) | ~0.13 s | 83 s | 97 MB | - -The import runs faster natively than on the JVM (117 s): a one-shot batch job never gets the JIT -warmup it would need to catch up. +Both examples compile to a self-contained native binary that starts in a fraction of a second, +with no JVM and no warmup, and both are verified by their Playwright interface suites running +against the native binaries. A batch import can even finish faster natively than on the JVM: a +one-shot job never runs long enough to earn back the JIT warmup it would need to catch up. diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts index 2ed76a3b2..3131c5acb 100644 --- a/website/docusaurus.config.ts +++ b/website/docusaurus.config.ts @@ -13,6 +13,12 @@ const config: Config = { tagline: 'A modern, high-performance ORM for Kotlin 2.0+ and Java 21+', favicon: 'img/storm-dark.png', + // Load the marketing type pair (Inter + JetBrains Mono) on the docs pages too, + // so the Docusaurus-chrome theme matches the custom pages (see custom.css). + stylesheets: [ + 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;700&display=swap', + ], + url: 'https://orm.st', baseUrl: '/', @@ -151,7 +157,7 @@ const config: Config = { // TODO(campaign): replace with a purpose-built 1200x630 social card. image: 'img/storm.png', navbar: { - title: 'Storm', + title: 'ST/ORM', logo: { alt: 'Storm Logo', src: 'img/storm-dark.png', @@ -231,9 +237,9 @@ const config: Config = { additionalLanguages: ['java', 'kotlin', 'groovy'], }, colorMode: { - defaultMode: 'light', - disableSwitch: false, - respectPrefersColorScheme: true, + defaultMode: 'dark', + disableSwitch: true, + respectPrefersColorScheme: false, }, } satisfies Preset.ThemeConfig, }; diff --git a/website/src/components/blog/blogTheme.js b/website/src/components/blog/blogTheme.js index 66c3f05a0..4b4cf0ad9 100644 --- a/website/src/components/blog/blogTheme.js +++ b/website/src/components/blog/blogTheme.js @@ -15,7 +15,7 @@ export function BlogPage({title, description, slug, dateISO, body}) { <> - {`${title} · Storm Blog`} + {`${title} · ST/ORM Blog`} diff --git a/website/src/components/examples/ExampleReadmePage.js b/website/src/components/examples/ExampleReadmePage.js index 6831a36c8..e3c120843 100644 --- a/website/src/components/examples/ExampleReadmePage.js +++ b/website/src/components/examples/ExampleReadmePage.js @@ -1,6 +1,6 @@ -import React from 'react'; +import React, {useEffect} from 'react'; import Head from '@docusaurus/Head'; -import {TUT_CSS, navHtml, FOOT_HTML} from '../tutorial/tutorialTheme'; +import {TUT_CSS, navHtml, FOOT_HTML, clonebar, wireSqlToggles} from '../tutorial/tutorialTheme'; // Route component for the /examples/ pages added by the // example-readmes plugin. Receives the example (title, description, chips, @@ -45,7 +45,9 @@ export default function ExampleReadmePage({example}) { const {slug, repo, title, description, chips, html} = example; const url = `https://orm.st/examples/${slug}/`; const githubUrl = `https://github.com/storm-orm/${repo}`; - const pageTitle = `${title} · Storm Example Projects`; + const pageTitle = `${title} · ST/ORM Example Projects`; + + useEffect(() => wireSqlToggles(), []); const body = ` ${navHtml('examples')} @@ -56,7 +58,7 @@ ${navHtml('examples')}

${description}

${chips.map((chip) => `${chip}`).join('')}
-
$git clone ${githubUrl}.git
+ ${clonebar(`git clone ${githubUrl}.git`)} View on GitHub →
diff --git a/website/src/components/tutorial/tutorialTheme.js b/website/src/components/tutorial/tutorialTheme.js index 79352f804..0b8d19518 100644 --- a/website/src/components/tutorial/tutorialTheme.js +++ b/website/src/components/tutorial/tutorialTheme.js @@ -130,6 +130,15 @@ export function editor({file, tag, code, sql, copy, variants}) { `; } +// A one-line "$ " shell bar with a Copy button, matching the editor +// copy control. `command` is copied verbatim (without the leading $). Wired by +// wireSqlToggles(), so pages that render a clonebar() must call it in useEffect. +export function clonebar(command) { + const data = esc(command).replace(/"/g, '"'); + return `
$${esc(command)}` + + `
`; +} + // A compact two-pane "at a glance" comparison for one-liner contrasts. Only // use where both snippets are genuinely short; full examples stay sequential. export function glance({left, right}) { @@ -140,8 +149,43 @@ export function glance({left, right}) { `; } -// Wires up every "Show SQL" and "Copy" button rendered by editor(). Returns a -// cleanup function for the calling useEffect. +// Wires one .copybtn: copies its data-copy to the clipboard and flips the label +// to "Copied" for a moment. Returns a cleanup function. +function attachCopy(copyBtn) { + const txt = copyBtn.querySelector('.copybtntext'); + let timer = null; + const onCopy = () => { + const text = copyBtn.dataset.copy; + // The Clipboard API needs a secure context; fall back to a throwaway + // textarea so copy also works on plain-http previews (e.g. LAN). + if (navigator.clipboard && window.isSecureContext) { + navigator.clipboard.writeText(text); + } else { + const ta = document.createElement('textarea'); + ta.value = text; + document.body.appendChild(ta); + ta.select(); + document.execCommand('copy'); + ta.remove(); + } + copyBtn.classList.add('on'); + if (txt) txt.textContent = 'Copied'; + clearTimeout(timer); + timer = setTimeout(() => { + copyBtn.classList.remove('on'); + if (txt) txt.textContent = 'Copy'; + }, 1600); + }; + copyBtn.addEventListener('click', onCopy); + return () => { + copyBtn.removeEventListener('click', onCopy); + clearTimeout(timer); + }; +} + +// Wires up every "Show SQL" and "Copy" button rendered by editor(), plus the +// Copy button on any clonebar(). Returns a cleanup function for the calling +// useEffect. export function wireSqlToggles() { const cleanups = []; document.querySelectorAll('.storm-tut .editor').forEach((ed) => { @@ -157,37 +201,7 @@ export function wireSqlToggles() { cleanups.push(() => btn.removeEventListener('click', onClick)); } const copyBtn = ed.querySelector('.copybtn'); - if (copyBtn) { - const txt = copyBtn.querySelector('.copybtntext'); - let timer = null; - const onCopy = () => { - const text = copyBtn.dataset.copy; - // The Clipboard API needs a secure context; fall back to a throwaway - // textarea so copy also works on plain-http previews (e.g. LAN). - if (navigator.clipboard && window.isSecureContext) { - navigator.clipboard.writeText(text); - } else { - const ta = document.createElement('textarea'); - ta.value = text; - document.body.appendChild(ta); - ta.select(); - document.execCommand('copy'); - ta.remove(); - } - copyBtn.classList.add('on'); - if (txt) txt.textContent = 'Copied'; - clearTimeout(timer); - timer = setTimeout(() => { - copyBtn.classList.remove('on'); - if (txt) txt.textContent = 'Copy'; - }, 1600); - }; - copyBtn.addEventListener('click', onCopy); - cleanups.push(() => { - copyBtn.removeEventListener('click', onCopy); - clearTimeout(timer); - }); - } + if (copyBtn) cleanups.push(attachCopy(copyBtn)); const sel = ed.querySelector('.varsel'); if (sel) { const panes = ed.querySelectorAll('.varpane'); @@ -202,6 +216,9 @@ export function wireSqlToggles() { cleanups.push(() => sel.removeEventListener('change', onChange)); } }); + document.querySelectorAll('.storm-tut .clonebar .copybtn').forEach((copyBtn) => { + cleanups.push(attachCopy(copyBtn)); + }); return () => cleanups.forEach((fn) => fn()); } @@ -215,7 +232,7 @@ export function TutorialPage({title, description, slug, body}) { <> - {`${title} · Storm Tutorials`} + {`${title} · ST/ORM Tutorials`} @@ -342,6 +359,8 @@ export const TUT_CSS = ` .storm-tut .sqlbtn:hover,.storm-tut .copybtn:hover{background:rgba(129,140,248,.12);border-color:rgba(129,140,248,.5)} .storm-tut .sqlbtn.on,.storm-tut .copybtn.on{background:rgba(129,140,248,.16);color:#aab2ff} .storm-tut .sqlbtn .ico,.storm-tut .copybtn .ico{width:13px;height:13px;opacity:.9} + .storm-tut .clonebar .clonecmd{overflow-x:auto} + .storm-tut .clonebar .copybtn{margin-left:auto;flex:none;gap:0;padding:5px 8px} /* variant selector (same chrome as the buttons; native select behind a custom chevron because appearance:none drops the platform arrow) */ .storm-tut .varwrap{position:relative;margin-left:14px;display:inline-flex;align-items:center} diff --git a/website/src/css/custom.css b/website/src/css/custom.css index cd8f603fe..8d35dd550 100644 --- a/website/src/css/custom.css +++ b/website/src/css/custom.css @@ -1,22 +1,79 @@ +/* Docs (Infima) theme aligned with the marketing site's dark palette. The + custom landing/tutorial/blog pages carry their own dark shell; these variables + bring the Docusaurus-chrome pages (/docs) onto the same tokens: background + #070709, panels #0f0f14, text #eaeaf0, indigo accent #818cf8, borders #20202a, + and the Inter / JetBrains Mono type pair. Default mode is dark (see + docusaurus.config.ts colorMode); the light theme keeps a readable purple. */ + :root { - --ifm-color-primary: #3949ab; - --ifm-color-primary-dark: #303f9f; - --ifm-color-primary-darker: #283593; - --ifm-color-primary-darkest: #1a237e; - --ifm-color-primary-light: #5c6bc0; - --ifm-color-primary-lighter: #7986cb; - --ifm-color-primary-lightest: #9fa8da; - --ifm-code-font-size: 95%; + /* Type pair shared with the marketing pages. */ + --ifm-font-family-base: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; + --ifm-font-family-monospace: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace; + --ifm-code-font-size: 92%; + + /* Light theme uses a darker indigo so buttons/links keep contrast on white. */ + --ifm-color-primary: #4f46e5; + --ifm-color-primary-dark: #4740ce; + --ifm-color-primary-darker: #433cc2; + --ifm-color-primary-darkest: #3731a0; + --ifm-color-primary-light: #6a62e9; + --ifm-color-primary-lighter: #7972eb; + --ifm-color-primary-lightest: #9d98f0; } [data-theme='dark'] { - --ifm-color-primary: #7986cb; - --ifm-color-primary-dark: #5c6bc0; - --ifm-color-primary-darker: #3949ab; - --ifm-color-primary-darkest: #303f9f; - --ifm-color-primary-light: #9fa8da; - --ifm-color-primary-lighter: #c5cae9; - --ifm-color-primary-lightest: #e8eaf6; + /* Accent: the marketing --accent (#818cf8), the lighter shade on dark. */ + --ifm-color-primary: #818cf8; + --ifm-color-primary-dark: #6470f6; + --ifm-color-primary-darker: #5563f5; + --ifm-color-primary-darkest: #2b3cf2; + --ifm-color-primary-light: #9ea8fa; + --ifm-color-primary-lighter: #adb5fb; + --ifm-color-primary-lightest: #d8dcfe; + + /* Surfaces and text mapped from the site tokens. */ + --ifm-background-color: #070709; + --ifm-background-surface-color: #0f0f14; + --ifm-navbar-background-color: #0b0b0f; + --ifm-footer-background-color: #0b0b0f; + --ifm-font-color-base: #eaeaf0; + --ifm-heading-color: #eaeaf0; + --ifm-color-content: #b7b8c2; + --ifm-color-content-secondary: #8a8a96; + + /* Borders / rules → --border #20202a, softened via the emphasis ramp. */ + --ifm-toc-border-color: #20202a; + --ifm-table-border-color: #20202a; + --ifm-hr-border-color: #20202a; + --ifm-color-emphasis-100: #0f0f14; + --ifm-color-emphasis-200: #17171f; + --ifm-color-emphasis-300: #20202a; + + /* Links, sidebar menu, breadcrumbs → accent. */ + --ifm-link-color: #a5b4fc; + --ifm-menu-color: #b7b8c2; + --ifm-menu-color-active: #a5b4fc; + --ifm-menu-color-background-active: rgba(129, 140, 248, 0.08); + --ifm-menu-color-background-hover: rgba(129, 140, 248, 0.06); + --ifm-breadcrumb-color-active: #a5b4fc; + --ifm-breadcrumb-item-background-active: rgba(129, 140, 248, 0.1); + + /* Code blocks → panel background (dracula tokens still read fine on it). */ + --ifm-code-background: #0f0f14; + --prism-background-color: #0b0b0f; + --docusaurus-highlighted-code-line-bg: rgba(129, 140, 248, 0.14); +} + +/* Navbar: subtle bottom rule like the marketing nav. */ +[data-theme='dark'] .navbar { + border-bottom: 1px solid #17171f; + box-shadow: none; +} + +/* Inline code chips: match the marketing --panel/--border-soft look. */ +[data-theme='dark'] code { + background: #0f0f14; + border: 1px solid #17171f; } /* Cleaner tables in docs and blog. Infima draws a full cell grid (vertical + diff --git a/website/src/pages/benchmarks.js b/website/src/pages/benchmarks.js index d4eaa5c74..f569c0855 100644 --- a/website/src/pages/benchmarks.js +++ b/website/src/pages/benchmarks.js @@ -1,11 +1,11 @@ import React, {useEffect} from 'react'; import Head from '@docusaurus/Head'; import { - TUT_CSS, navHtml, FOOT_HTML, wireSqlToggles, editor, + TUT_CSS, navHtml, FOOT_HTML, wireSqlToggles, editor, clonebar, K, T, S, C, F, N, A, P, QK, QQ, } from '../components/tutorial/tutorialTheme'; -const TITLE = 'Benchmarks · Storm ORM vs Hibernate, jOOQ, Exposed and Jimmer'; +const TITLE = 'Benchmarks · ST/ORM vs Hibernate, jOOQ, Exposed and Jimmer'; const DESC = 'Reproducible JMH benchmarks of Storm against JDBC, Hibernate, jOOQ, Exposed and Jimmer on PostgreSQL 17, with the entity and query code behind every number.'; // Results from the reproducible suite: one tuned PostgreSQL 17 container over TCP, JMH, 2 forks, @@ -486,7 +486,7 @@ ${charts}

Versions: Storm 1.13.0, Hibernate 7.4.5, jOOQ 3.21.6, Exposed 1.3.1, Jimmer 0.11.0, PostgreSQL 17, JDK 21.

The repository contains the full methodology, the statement-log auditing tools used to verify round-trip counts, and every implementation in full; scripts/run.sh reproduces the numbers.

-
$git clone https://github.com/storm-orm/storm-benchmarks.git
+ ${clonebar('git clone https://github.com/storm-orm/storm-benchmarks.git')} View on GitHub →
diff --git a/website/src/pages/blog/index.js b/website/src/pages/blog/index.js index e7efdb2bd..5003138c4 100644 --- a/website/src/pages/blog/index.js +++ b/website/src/pages/blog/index.js @@ -7,7 +7,7 @@ import {TUT_CSS, navHtml, FOOT_HTML} from '../../components/tutorial/tutorialThe // matches the tutorials and examples. Each article lives next to this file as // its own custom page; keep this list newest-first and in sync with them. -const TITLE = 'Storm Blog · Design decisions and deep dives'; +const TITLE = 'ST/ORM Blog · Design decisions and deep dives'; const DESC = 'The design decisions behind ST/ORM, deep dives into how it works, and the ' + 'thinking that shapes the API. Plain values, explicit SQL, and no hidden ' + diff --git a/website/src/pages/comparison.js b/website/src/pages/comparison.js index fae1c1fd9..58ce65b2f 100644 --- a/website/src/pages/comparison.js +++ b/website/src/pages/comparison.js @@ -12,7 +12,7 @@ import {TUT_CSS, navHtml, FOOT_HTML, wireSqlToggles} from '../components/tutoria // only differences that are real and that the rival does not already cover // are called out. -const TITLE = 'Comparison · Storm vs JPA, jOOQ, Exposed, Jimmer'; +const TITLE = 'Comparison · ST/ORM vs JPA, jOOQ, Exposed, Jimmer'; const DESC = 'How Storm compares to Hibernate/JPA, Spring Data, jOOQ, Exposed, Ktorm, ' + 'MyBatis, and Jimmer. A decision matrix plus a short, fair take on each, with ' + diff --git a/website/src/pages/examples/index.js b/website/src/pages/examples/index.js index d2389f75d..3a86cf416 100644 --- a/website/src/pages/examples/index.js +++ b/website/src/pages/examples/index.js @@ -8,7 +8,7 @@ import {TUT_CSS, navHtml, FOOT_HTML} from '../../components/tutorial/tutorialThe // and Java. Each card links to its detail page (README rendered inline by // the example-readmes plugin, with the clone command and GitHub link). -const TITLE = 'Storm Example Projects · Complete applications built with Storm'; +const TITLE = 'ST/ORM Example Projects · Complete applications built with Storm'; const DESC = 'Complete example applications built with Storm ORM: a movie browser on ' + 'the public IMDB dataset, implemented on Spring Boot 4 and Ktor, in Kotlin ' + diff --git a/website/src/pages/index.js b/website/src/pages/index.js index f93f26450..718f67c33 100644 --- a/website/src/pages/index.js +++ b/website/src/pages/index.js @@ -774,7 +774,7 @@ export default function Home() { <> - Storm · The type-safe Kotlin ORM + ST/ORM · The type-safe Kotlin ORM - {`${TITLE} · Storm Tutorials`} + {`${TITLE} · ST/ORM Tutorials`}