diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index b1d73d1..dec098d 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -1,5 +1,5 @@ # Build, test and publish the page to GitHub Pages at -# https://abap2ui5.github.io/test-docs/. +# https://abap2ui5.github.io/why/. # # On a pull request it builds and tests only. On main it also deploys. # diff --git a/AGENTS.md b/AGENTS.md index 53f9e41..65f859f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,7 +3,7 @@ abap2UI5 for decision makers: one static page that presents abap2UI5 to IT management and product owners - what it can build, who runs it, where it is the shorter path to a UI5 app on SAP and where it is not, what it costs - -published at https://abap2ui5.github.io/test-docs/ by GitHub Pages. A +published at https://abap2ui5.github.io/why/ by GitHub Pages. A standalone site — its own repository, build and deployment — and nothing in it depends on abap2UI5/docs or abap2UI5/playground at build time. @@ -24,7 +24,7 @@ it depends on abap2UI5/docs or abap2UI5/playground at build time. ```bash npm ci -npm run dev # dev server, http://localhost:4321/test-docs/ +npm run dev # dev server, http://localhost:4321/why/ npm run build # dist/ npm test # the tests, against dist/ - build first npm run check # build + test, what CI runs @@ -49,9 +49,11 @@ npm run check # build + test, what CI runs - **Short, honest, in this order.** A reader gives the page a few minutes: the visible text outside `
` stays under 2,600 words (the test holds it). The comparison names where abap2UI5 is *not* ahead (rows marked `even`, - the "Where something else fits better" box) and the support it has (community, - best effort, no vendor SLA on offer - `SUPPORT.md` in the framework is the - source). Proof ("Who runs it") comes before the comparison, cost before AI. + the "Where something else fits better" box) and the support it has: in the + open first - anyone can open an issue or send a pull request on GitHub at any + time - and more by mail to contact@abap2UI5.org on request; what is not on + offer is an SLA in a contract (`SUPPORT.md` in the framework is the source). + Never "no support": the open channel is the support, and the better one. Proof ("Who runs it") comes before the comparison, cost before AI. No superlatives ("better than every", "made for"); a claim a reader cannot check is cut, not softened. Add-ons, Basis steps and other developer topics are links into the manual, not sections. diff --git a/README.md b/README.md index 509e609..ff01041 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ architecture boards: what you can build with it, who runs it, where it is the shorter path to a UI5 app on SAP and where it is not, what your organization keeps, where it runs, what it costs, and how to start with one real screen. -**→ https://abap2ui5.github.io/test-docs/** +**→ https://abap2ui5.github.io/why/** A standalone site, independent of the [documentation](https://abap2ui5.github.io/docs/) and the @@ -17,7 +17,7 @@ page; its one script is the cost calculator's. ```bash npm ci -npm run dev # http://localhost:4321/test-docs/ +npm run dev # http://localhost:4321/why/ npm run check # build + tests, what CI runs ``` diff --git a/astro.config.mjs b/astro.config.mjs index 84d4ea9..253a094 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,5 +1,5 @@ // abap2UI5 for decision makers - one static page, published by GitHub Pages at -// https://abap2ui5.github.io/test-docs/ (.github/workflows/pages.yml). +// https://abap2ui5.github.io/why/ (.github/workflows/pages.yml). // // Static output only: no client component, no island. The page's one script // is the cost calculator's public/calc.js, loaded as a file; the policy is @@ -9,7 +9,7 @@ import { defineConfig } from "astro/config"; export default defineConfig({ site: "https://abap2ui5.github.io", - base: "/test-docs/", + base: "/why/", trailingSlash: "ignore", build: { format: "directory", diff --git a/public/calc.js b/public/calc.js index 900681e..7362491 100644 --- a/public/calc.js +++ b/public/calc.js @@ -28,7 +28,7 @@ if (root) { }); const picked = [...root.querySelectorAll("input[name=calc-release]:checked")].map((i) => i.dataset.choice); echo("release", picked.length ? picked.join(", ") : "no system at all"); - echo("support", root.querySelector("input[name=calc-support]:checked")?.dataset.choice || "No support"); + echo("support", root.querySelector("input[name=calc-support]:checked")?.dataset.choice || "In the open"); const zero = new Intl.NumberFormat("en-US", { style: "currency", currency: currency.value, maximumFractionDigits: 0 }).format(0); root.querySelectorAll("[data-amount]").forEach((el) => { el.textContent = zero; }); }; diff --git a/src/components/CostCalculator.astro b/src/components/CostCalculator.astro index 7dc4b36..8fd2a8d 100644 --- a/src/components/CostCalculator.astro +++ b/src/components/CostCalculator.astro @@ -4,7 +4,7 @@ * and the page says why on each one. * * It works without its script: the sheet is in the page with the default - * values, and /test-docs/calc.js (public/calc.js, a file of its own because the + * values, and /why/calc.js (public/calc.js, a file of its own because the * policy allows scripts from this origin only, never inline) makes the sliders * speak, hides the sheet until Calculate and echoes the choices into it. */ const DOCS = "https://abap2ui5.github.io/docs/"; @@ -37,7 +37,7 @@ const lines = [ ["Platform", ": the same class runs on every one of them, from 7.02 up", "release"], ["Runtime", ", every one of them served by your own ABAP stack", "roundtrips"], ["Frontend hosting", "the UI5 your system already ships, and no frontend deployment", null], - ["Support", ": GitHub issues and Slack are open to everybody either way", "support"], + ["Support", ": Slack and GitHub issues are open to anyone at any time; more by mail, planned together", "support"], ["Upgrades", " of releases, each one an abapGit pull", "term"], ]; --- @@ -55,7 +55,7 @@ const lines = [ ))}
Support - +
{sliders.slice(5).map(([id, text, stops, at, unit]) => (
{echo[id as string]}
@@ -67,7 +67,7 @@ const lines = [
{lines.map(([what, note, key]) => (
- {what}{key ? <>{key === "release" ? "S/4HANA on-premise" : key === "support" ? "Community support" : echo[key as string]}{note} : note} + {what}{key ? <>{key === "release" ? "S/4HANA on-premise" : key === "support" ? "In the open" : echo[key as string]}{note} : note} €0
))} diff --git a/src/components/Stats.astro b/src/components/Stats.astro index 94c9ac9..612f63f 100644 --- a/src/components/Stats.astro +++ b/src/components/Stats.astro @@ -7,7 +7,7 @@ const tiles = [ ["0 €", "license fees, for any number of users, systems and apps"], ["1 class", "per app: no frontend project, no extra platform"], ["7.02 → Cloud", "every SAP release, from one source"], - ["Since 2022", "open source on GitHub, MIT, every release listed"], + ["Since 2023", "open source on GitHub, MIT, every release listed"], ["25+", "contributors, from several companies"], ]; --- diff --git a/src/layouts/Page.astro b/src/layouts/Page.astro index 797ab9c..3407874 100644 --- a/src/layouts/Page.astro +++ b/src/layouts/Page.astro @@ -62,7 +62,7 @@ const updated = new Date().toISOString().slice(0, 10);
abap2UI5 - Examples + Try it out @@ -79,7 +79,7 @@ const updated = new Date().toISOString().slice(0, 10); Samples Playground GitHub - Edit this page + Edit this page
diff --git a/src/pages/index.astro b/src/pages/index.astro index d2410bf..394b6c8 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -19,7 +19,7 @@ import CostCalculator from "../components/CostCalculator.astro"; const DOCS = "https://abap2ui5.github.io/docs/"; const PLAYGROUND = "https://abap2ui5.github.io/playground/"; const GITHUB = "https://github.com/abap2UI5/abap2UI5"; -const GITHUB_PAGE = "https://github.com/abap2UI5/test-docs"; +const GITHUB_PAGE = "https://github.com/abap2UI5/why"; const SLACK = "https://abapgit.slack.com/join/shared_invite/zt-46tqufaht-QlrxTzlDqlx85CWbeUnOqg#/shared-invite/email"; const MAIL = "contact@abap2UI5.org"; const base = import.meta.env.BASE_URL; @@ -41,11 +41,10 @@ const useCases = [ const companies = [ ["Emineo", "https://emineo.ch/", "In production", "Apps for warehouse management and production, on desktop and mobile devices"], ["Swiss Krono", "https://www.swisskrono.com/", "In production", "Apps for warehouse management, on mobile devices"], - ["Nuve Platform", "https://www.nuveplatform.com", "Partner", "On-demand SAP systems with abap2UI5 set up automatically"], - ["Logali Group", "https://logaligroup.com", "Partner", "Webinars and certifications for abap2UI5"], + ["Logali Group", "https://logaligroup.com", "Training", "Webinars and certifications for abap2UI5"], ]; const facts = [ - ["Since December 2022", "open source on GitHub, MIT licensed"], + ["Since January 2023", "open source on GitHub, MIT licensed"], ["25+ contributors", "from several companies, every change public"], ["Release 1.148", "October 2026; a release most months, each one listed"], ["Every release tested", "on NetWeaver 7.02, Standard ABAP and ABAP Cloud, in the browser on the oldest and the newest UI5"], @@ -55,15 +54,14 @@ const references = [ ["2026", "Field Service Management Mobile Logging using abap2UI5", "Decabase Blog", "https://blog.decabase.com/field-service-management-mobile-logging-using-abap2ui5-2c18e4ed455d"], ["2026", "Fiori-type Apps built 100% with ABAP", "Logali Group", "https://logaligroup.com/magia-en-el-servidor-local-apps-tipo-fiori-creadas-100-con-codigo-abap/"], ["2025", "Featured on SAP Developer News", "YouTube", "https://www.youtube.com/watch?v=vKrpkDe2mkU&list=PL6RpkC85SLQAVBSQXN9522_1jNvPavBgg&t=90s"], + ["2024", "Highlighted in the Boring Enterprise Nerdletter", "YouTube", "https://www.youtube.com/watch?v=I81z6W_BTIA&t=1010s"], ["2023", "Showcased at SAP TechEd 2023", "YouTube", "https://www.youtube.com/watch?v=kLbF0ooStZs&t=3052s"], ["2023", "Part of the SAP Developer Code Challenge", "SAP Community", "https://groups.community.sap.com/t5/application-development/sap-developer-code-challenge-open-source-abap-week-2/m-p/260727#M1372"], ]; const moreReferences = [ ["2026", "UI5 in ABAP Cloud (without RAP or Fiori Elements)", "Decabase Blog", "https://blog.decabase.com/ui5-in-abap-cloud-without-rap-or-fiori-elements-4e8a70d961c3"], ["2025", "Webinar on Launchpad Integration", "YouTube", "https://www.youtube.com/watch?v=t5g3F3PHsbw&list=PLLpkZ_86h4quGSfsjohDHt9CrpjXdeA1P"], - ["2024", "Automated Deployment of abap2UI5", "Nuve Platform", "https://www.nuveplatform.com/blog/deploy-abap2ui5"], ["2024", "Webinar on creating UI5 interfaces from ABAP using abap2UI5", "YouTube", "https://www.youtube.com/watch?v=N2OAdxf7Lng"], - ["2024", "Highlighted in the Boring Enterprise Nerdletter", "YouTube", "https://www.youtube.com/watch?v=I81z6W_BTIA&t=1010s"], ["2024", "Webinar on developing UI5 apps with abap2UI5", "YouTube", "https://www.youtube.com/watch?v=0izPA2xrPPI"], ["2024", "Featured on SAP Developer News", "YouTube", "https://youtu.be/7n16u-Rx8IY?t=7"], ["2024", "Cust&Code Videos featuring abap2UI5", "YouTube", "https://www.youtube.com/watch?v=SD1vIt_ty0k"], @@ -90,8 +88,8 @@ const compare = [ ["Lock-in", "None: apps are plain ABAP classes in your system", "SAP's programming model", "Low", "Apps live in the vendor's runtime and repository"], ["Source code", "Open, every line, on GitHub", "Closed", "UI5 open, your app yours", "Closed"], ["Standard transactional apps", "Possible, but hand-built; the standard route is RAP", "Its home ground: list report and object page from annotations", "Hand-built", "From the vendor's templates", "even"], - ["Offline & real-time", "Not built for it: every click is a request to SAP", "Offline with extra tooling", "Possible, with effort", "Depends on the vendor", "even"], - ["Support", "Community, best effort; no vendor SLA on offer today", "SAP support", "SAP support for UI5; your app is yours", "The vendor's SLA, in the contract", "even"], + ["Offline", "Not built for it: an app needs its connection to SAP", "Offline with extra tooling", "Possible, with effort", "Depends on the vendor", "even"], + ["Support", "Slack and GitHub issues, open to anyone at any time; more by mail, planned together. No SLA in a contract", "SAP support", "SAP support for UI5; your app is yours", "The vendor's SLA, in the contract", "even"], ]; const useIt = [ ["The team is ABAP.", "No JavaScript, no UI5 tooling, no Business Application Studio. An app is one class, written, debugged, transported and tested like every other class."], @@ -101,9 +99,9 @@ const useIt = [ ]; const notIt = [ ["You want the SAP standard transactional app.", "A list report with an object page on S/4HANA, from a released CDS model: RAP and Fiori Elements are the shorter path, and the one SAP supports."], - ["The app must work offline or in real time.", "Every click is one request to the SAP system. A collaborative editor or a device that works without a network is not what it is for."], + ["The app must work offline.", "Every click is one request to the SAP system. A device that works without a network is not what it is for; live data on a connected screen is."], ["Your guideline mandates SAP's programming model.", "abap2UI5 is clean core in the technical sense, but it is not RAP. The usual outcome is a scope, not a replacement."], - ["You need a vendor with an SLA.", "Support is the community's, on a best-effort basis. Nobody offers a support contract for the framework today; partners offer setup and training."], + ["Your rules need an SLA on paper.", "Support happens in the open: anyone can write in the Slack channel or open an issue or a pull request on GitHub, at any time, and that is where answers come fastest. If you need more than that, write to the maintainers by mail and plan it together; what is not on offer is an SLA in a contract."], ]; /* Governance: an app is an ABAP class, against a tool that stores apps as @@ -127,9 +125,9 @@ const governance = [ const cost = [ ["Framework license", "MIT, commercial use included, for any number of users, systems and apps", "0"], ["Platform, runtime, hosting", "One service on the SAP system you already run; the UI5 your system already ships", "0"], - ["Support", "GitHub issues and Slack, open to everybody, best effort", "0"], + ["Support", "Slack and GitHub issues, open to anyone at any time; pull requests welcome", "0"], ["Developer time", "An ABAP developer writes the screen, as for any class; this is the bill", "your rate"], - ["Partner services", "Setup, training, app development, if you want them", "optional"], + ["Training and consulting", "Webinars, certification, app development by a consultancy, if you want them", "optional"], ]; const strings = [ ["Nothing to sign up for", "The examples run in the browser with no account. The framework is an abapGit pull; there is no trial period and no demo to book."], @@ -148,11 +146,11 @@ const ai = [ const objections = [ ["It is an open-source project. What if the maintainers stop?", `The installed version keeps running, the source is in your system in full, readable and changeable, and the apps stay ordinary ABAP classes. Nothing is called at runtime that is not in your system, apart from the UI5 library, which your system ships. A build renamed into your own namespace is one workflow run (Namespaces, Renaming).`], - ["Who do we call when something breaks?", `Your developer, for the app, as for any class in the system; the community, for the framework, on GitHub and in Slack, on a best-effort basis. Nobody offers a support contract for the framework today (Support); the partners above offer setup, training and development. Security reports are acknowledged within three business days (Security).`], + ["Who do we call when something breaks?", `Your developer, for the app, as for any class in the system. For the framework, write in the Slack channel or open an issue on GitHub: anyone can, at any time, no account with the project needed, and in the open is where it gets answered fastest and where everybody sees the answer. Pull requests are welcome the same way. If your organization needs more, closer cooperation, a workshop or a support arrangement, write to ${MAIL} and plan it together (Support). Security reports are acknowledged within three business days (Security).`], ["Is this another UI5 wrapper that will not survive the next UI5 release?", `The frontend is UI5 itself, not a copy of it, and every change is run in the browser on the oldest supported release and on UI5 2.x. Releases are tagged and listed (Release Notes); what is on its way out is announced and tracked until removal (Deprecations).`], - ["Every click is a request to SAP. Is that fast enough?", `A request carries only what changed and is served by the ABAP stack that already serves everything else. For worklists, forms and cockpits it is the same round trip a SAP GUI screen makes, in a browser. It is not built for a real-time collaborative editor or an app that holds its state offline (Performance).`], + ["Every click is a request to SAP. Is that fast enough?", `A request carries only what changed and is served by the ABAP stack that already serves everything else. For worklists, forms and cockpits it is the same round trip a SAP GUI screen makes, in a browser. It is not built for an app that holds its state offline (Performance).`], ["Our clean-core guideline names RAP. Where does this leave us?", "Side by side. abap2UI5 passes the ABAP Cloud checks and modifies nothing in the standard, so it is clean core in the technical sense, but it is not SAP's programming model. The usual outcome is a scope: standard transactional screens in RAP, the tools around them in abap2UI5, both in one launchpad."], - ["Why should we trust it over a product we pay for?", "Because you can check everything a vendor asks you to believe. The source is open, every test is public, and an app is a class in your own system that keeps running whatever happens to the project. What you give up is a contract with an SLA; what you keep is the code."], + ["Why should we trust it over a product we pay for?", "Because you can check everything a vendor asks you to believe. The source is open, every test is public, and an app is a class in your own system that keeps running whatever happens to the project. What you do not get is an SLA in a contract; what you get is the code, and a maintainer you can reach."], ["Is anybody actually running this in production?", `Two companies say so on this page, and every release is tested against Standard ABAP and ABAP Cloud, downported to NetWeaver 7.02 and run in the browser on the oldest and the newest supported UI5 release (verified on).`], ]; @@ -166,7 +164,8 @@ const week = [ const doors = [ ["play", "Ten minutes, nothing installed", "Browse the example apps for the screen closest to yours, and change one in the playground.", `${PLAYGROUND}samples/`, "Example apps"], ["code", "One afternoon, one developer", "Send them the Quickstart: install, first app, transport.", `${DOCS}get_started/quickstart`, "Quickstart"], - ["chat", "One question, one person", `Ask in Slack, where the people who run it in production answer, or write to ${MAIL}.`, `mailto:${MAIL}`, "Contact"], + ["chat", "A question, any time", "Write in the Slack channel, where the people who run it in production answer, or open a GitHub issue. Anyone can, at any time.", SLACK, "Slack"], + ["partner", "More than a question", `A workshop, closer cooperation, a support arrangement: write to ${MAIL} and plan it together.`, `mailto:${MAIL}`, "Mail"], ]; const give = [ ["Add your company.", `One row under Who runs it is what answers is anybody actually running this? for the next reader.`], @@ -182,7 +181,7 @@ const give = [

UI5 apps for SAP, built by the team you already have

-

abap2UI5 lets ABAP developers build UI5 apps as one ABAP class: no JavaScript, no extra platform, no license fee. Open source since 2022, in production on the desktop and on the warehouse floor.

+

abap2UI5 lets ABAP developers build UI5 apps as one ABAP class: no JavaScript, no extra platform, no license fee. Open source since 2023, in production on the desktop and on the warehouse floor.

See example apps Where it fits @@ -219,8 +218,8 @@ const give = [

Who runs it

-

In production, in the open, since 2022

-

Two companies run it in production and say so here; two build their business on it. The project itself is public down to every change, so the figures below are not ours to make up.

+

In production, in the open, since 2023

+

Two companies run it in production and say so here; one offers training and certification for it. The project itself is public down to every change, so the figures below are not ours to make up.

    {companies.map(([name, href, tag, text]) =>
  • {name}{tag}{text}
  • )}
    {facts.map(([b, t]) =>
  • {b}{t}
  • )}

Talks, articles and videos

diff --git a/src/styles/global.css b/src/styles/global.css index 610d0a4..1a1c252 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -1,14 +1,15 @@ -/* abap2UI5 for decision makers - the one stylesheet. A light, fresh product - * page: a bright hero washed with the logo's coral, sky and mint, white bands - * for the argument, the brand red of the logo for what to do next, and nothing - * that needs a script. Light and dark follow the reader's system. */ +/* abap2UI5 for decision makers - the one stylesheet. A product page for a + * business reader: ink and white, a quiet hero, the brand red of the logo kept + * for what to do next and for one accent per block - not for every figure. + * Fresh through type and spacing, not through colour. Nothing that needs a + * script. Light and dark follow the reader's system. */ @font-face { font-family: "Inter"; font-style: normal; font-weight: 100 900; font-display: swap; - src: url("/test-docs/fonts/inter-roman-latin.woff2") format("woff2"); + src: url("/why/fonts/inter-roman-latin.woff2") format("woff2"); } :root { @@ -27,10 +28,11 @@ --neutral-soft: #eef1f5; --sky: #0ea5e9; --sky-soft: #e6f6fd; - --hero-bg: #fffdfb; - --wash-coral: rgba(255, 138, 128, .30); - --wash-sky: rgba(56, 189, 248, .22); - --wash-mint: rgba(52, 211, 153, .18); + --hero-bg: #f8f9fb; + --wash: rgba(208, 60, 74, .07); + --wash-ink: rgba(15, 23, 42, .05); + --dark-card: #111a2e; + --dark-card-text: #e5eaf2; --header-bg: rgba(255, 255, 255, .86); --radius: 14px; --shadow: 0 1px 2px rgba(15, 23, 42, .06), 0 8px 24px rgba(15, 23, 42, .06); @@ -57,9 +59,10 @@ --sky: #38bdf8; --sky-soft: #10283a; --hero-bg: #0f1522; - --wash-coral: rgba(240, 96, 109, .22); - --wash-sky: rgba(56, 189, 248, .14); - --wash-mint: rgba(52, 211, 153, .10); + --wash: rgba(240, 96, 109, .10); + --wash-ink: rgba(255, 255, 255, .03); + --dark-card: #172033; + --dark-card-text: #e5eaf2; --header-bg: rgba(15, 21, 34, .86); --shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 8px 24px rgba(0, 0, 0, .25); } @@ -125,16 +128,14 @@ code { font-family: var(--mono); font-size: .9em; } /* ---- the hero ------------------------------------------------------------ */ .hero { background: - radial-gradient(900px 460px at 88% -10%, var(--wash-coral), transparent 62%), - radial-gradient(760px 440px at -8% 108%, var(--wash-sky), transparent 62%), - radial-gradient(620px 320px at 48% 118%, var(--wash-mint), transparent 62%), - var(--hero-bg); + radial-gradient(900px 500px at 92% -20%, var(--wash), transparent 65%), + linear-gradient(180deg, var(--hero-bg), var(--bg)); color: var(--text); padding: 88px 0 104px; } .hero .wrap { display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); gap: 56px; align-items: center; } .hero h1 { margin: 0; color: var(--ink); font-size: clamp(36px, 5vw, 58px); font-weight: 750; letter-spacing: -.035em; } -.hero h1 em { font-style: normal; background: linear-gradient(90deg, var(--brand), #f08a4b); -webkit-background-clip: text; background-clip: text; color: transparent; } +.hero h1 em { font-style: normal; color: var(--brand); } .hero .lede { margin: 22px 0 0; max-width: 560px; font-size: 19px; line-height: 1.6; color: var(--muted); } .hero .lede b { color: var(--ink); } .hero .actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 34px; } @@ -148,7 +149,7 @@ code { font-family: var(--mono); font-size: .9em; } } .window-bar { display: flex; align-items: center; gap: 7px; padding: 11px 14px; background: var(--bg-alt); border-bottom: 1px solid var(--line); } .window-bar i { width: 11px; height: 11px; border-radius: 50%; background: var(--line); } -.window-bar i:first-child { background: #ff5f57; } .window-bar i:nth-child(2) { background: #febc2e; } .window-bar i:nth-child(3) { background: #28c840; } +.window-bar i { background: var(--line); } .window-bar span { margin-left: 10px; font: 12px var(--mono); color: var(--muted); } .window img { display: block; width: 100%; } @media (max-width: 920px) { @@ -168,7 +169,8 @@ code { font-family: var(--mono); font-size: .9em; } } .stats li { padding: 22px 22px 20px; border-left: 1px solid var(--line); } .stats li:first-child { border-left: 0; } -.stats b { display: block; font-size: 28px; line-height: 1.1; font-weight: 750; letter-spacing: -.03em; color: var(--brand); } +.stats b { display: block; font-size: 28px; line-height: 1.1; font-weight: 750; letter-spacing: -.03em; color: var(--ink); } +.stats li:first-child b { color: var(--brand); } /* one accent: the price */ .stats span { display: block; margin-top: 6px; font-size: 14px; line-height: 1.45; color: var(--muted); } @media (max-width: 700px) { .stats ul { grid-template-columns: repeat(2, minmax(0, 1fr)); } @@ -252,7 +254,7 @@ code { font-family: var(--mono); font-size: .9em; } .table table.two { min-width: 0; } .table table.two td { color: var(--ink); } .table td.us { color: var(--ink); background: color-mix(in srgb, var(--good-soft) 55%, transparent); } -.table thead th.us { color: var(--good); } +.table thead th.us { color: var(--good); text-transform: none; letter-spacing: 0; font-size: 14px; } /* the name keeps its case */ .table.compare td { color: var(--muted); } .table.compare td.us { color: var(--ink); font-weight: 550; } .table.compare td.us::before { content: "✓ "; color: var(--good); font-weight: 800; } @@ -271,7 +273,7 @@ code { font-family: var(--mono); font-size: .9em; } .table tbody th { width: auto; padding: 14px 18px 8px; border-top: 0; font-size: 16px; } .table td { padding: 10px 18px; } .table td::before { content: attr(data-th); display: block; font-size: 12px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); } - .table td.us::before, .table.compare td.us::before { content: attr(data-th); color: var(--good); } + .table td.us::before, .table.compare td.us::before { content: attr(data-th); color: var(--good); text-transform: none; letter-spacing: 0; } .table.compare td.us.even::before { content: attr(data-th); color: var(--muted); } } @@ -297,12 +299,12 @@ code { font-family: var(--mono); font-size: .9em; } .steps-basis li::before { content: counter(b); position: absolute; left: 0; top: 9px; display: grid; place-items: center; width: 26px; height: 26px; border-radius: 50%; background: var(--brand-soft); color: var(--brand); font-weight: 700; font-size: 13px; } /* the cost */ -.price { padding: 32px; border-radius: var(--radius); background: linear-gradient(135deg, var(--brand) 0%, #f08a4b 100%); color: #fff; box-shadow: var(--shadow); } +.price { padding: 32px; border-radius: var(--radius); background: var(--dark-card); color: var(--dark-card-text); box-shadow: var(--shadow); } .price .big { display: block; font-size: 64px; line-height: 1; font-weight: 800; letter-spacing: -.04em; color: #fff; } -.price .big small { font-size: 20px; font-weight: 600; color: rgba(255, 255, 255, .85); letter-spacing: 0; } +.price .big small { font-size: 20px; font-weight: 600; color: rgba(255, 255, 255, .7); letter-spacing: 0; } .price ul { margin: 22px 0 0; padding: 0; list-style: none; } -.price li { padding: 9px 0 9px 28px; position: relative; border-top: 1px solid rgba(255, 255, 255, .25); color: #fff; } -.price li::before { content: "✓"; position: absolute; left: 0; color: #fff; font-weight: 800; } +.price li { padding: 9px 0 9px 28px; position: relative; border-top: 1px solid rgba(255, 255, 255, .14); color: var(--dark-card-text); } +.price li::before { content: "✓"; position: absolute; left: 0; color: var(--brand-strong); font-weight: 800; } .sheet { padding: 8px 24px; border-radius: var(--radius); background: var(--card); border: 1px solid var(--line); } .sheet-line { display: flex; justify-content: space-between; gap: 18px; padding: 12px 0; border-top: 1px solid var(--line); } @@ -370,7 +372,7 @@ code { font-family: var(--mono); font-size: .9em; } .faq details > p { margin: 0; padding: 0 48px 24px 4px; max-width: 820px; color: var(--muted); } /* who runs it: the companies and the references */ -.logos { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; margin: 36px 0 0; padding: 0; list-style: none; } +.logos { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; margin: 36px 0 0; padding: 0; list-style: none; } @media (max-width: 1000px) { .logos { grid-template-columns: repeat(2, minmax(0, 1fr)); } } @media (max-width: 560px) { .logos { grid-template-columns: minmax(0, 1fr); } } .logos li { padding: 22px; border-radius: var(--radius); background: var(--card); border: 1px solid var(--line); } @@ -410,12 +412,11 @@ code { font-family: var(--mono); font-size: .9em; } /* the ways in */ .start { background: - radial-gradient(800px 380px at 100% 0%, var(--wash-coral), transparent 62%), - radial-gradient(700px 360px at 0% 100%, var(--wash-sky), transparent 62%), - var(--hero-bg); + radial-gradient(800px 420px at 100% 0%, var(--wash), transparent 65%), + linear-gradient(180deg, var(--bg), var(--hero-bg)); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); } -.doors { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; margin-top: 24px; } +.doors { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; margin-top: 24px; } @media (max-width: 1000px) { .doors { grid-template-columns: repeat(2, minmax(0, 1fr)); } } @media (max-width: 560px) { .doors { grid-template-columns: minmax(0, 1fr); } } .door { display: block; padding: 24px; border-radius: var(--radius); border: 1px solid var(--line); background: var(--card); color: var(--text); text-decoration: none; box-shadow: var(--shadow); transition: border-color .15s, transform .15s; } @@ -436,7 +437,7 @@ code { font-family: var(--mono); font-size: .9em; } .strings span { display: block; margin-top: 8px; font-size: 14px; line-height: 1.55; color: var(--muted); } /* giving back: short, after everything else */ -.section.give { padding: 48px 0; background: var(--brand-soft); border-top: 1px solid var(--line); } +.section.give { padding: 48px 0; background: var(--bg-alt); border-top: 1px solid var(--line); } .give-title { font-size: 20px !important; } .give-text { margin: 10px 0 0; max-width: 820px; color: var(--ink); } .give-list { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px 24px; margin: 18px 0 0; padding: 0; list-style: none; font-size: 14px; color: var(--text); } diff --git a/test/site.test.mjs b/test/site.test.mjs index 7d34905..2a641d0 100644 --- a/test/site.test.mjs +++ b/test/site.test.mjs @@ -8,7 +8,7 @@ import fs from "node:fs"; import path from "node:path"; const DIST = path.join(import.meta.dirname, "..", "dist"); -const BASE = "/test-docs/"; +const BASE = "/why/"; const html = fs.readFileSync(path.join(DIST, "index.html"), "utf8"); test("the eight section ids are the addresses the redirect and the README link to", () => { @@ -25,7 +25,7 @@ test("the page runs one script, as a file from this origin, and its policy says // the cost calculator's public/calc.js - and no inline script beside it const scripts = [...html.matchAll(/]*)>([\s\S]*?)<\/script>/gi)]; assert.equal(scripts.length, 1, "one