From e392bfb910ab52b34d8b91ed6cce9e4258466382 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 24 Oct 2026 18:38:08 +0000 Subject: [PATCH] Make the manual developer-only; the case for decision makers moves out abap2UI5 for decision makers is its own site (abap2UI5/test-docs, https://abap2ui5.github.io/test-docs/). What the manual carried for them goes there instead of standing twice: - docs/evaluate.md, resources/cost_calculator, resources/who_uses and resources/references are removed; redirect stubs keep their addresses (the Evaluate ones keep the #fragment - the new page has the same ids) - the cost calculator's code, styles and test, and the landing layout that only the Evaluate page used, go with them - the front door is the developer's: "See how it works" instead of "Evaluate", the security card links Security, the price card is gone, and the fourth tile hands the page for decision makers over - In a Nutshell, Hello World and the Quickstart link the new site - /test-docs/ is a neighbouring deployment for check:cross-site Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0134Dawy9Fb26HLtDiHWfnT1 --- AGENTS.md | 18 +- docs/.vitepress/config.mjs | 22 +- docs/.vitepress/theme/cost-calculator.js | 114 -------- docs/.vitepress/theme/cost-model.js | 60 ---- docs/.vitepress/theme/index.js | 5 - docs/evaluate.md | 310 --------------------- docs/get_started/about.md | 6 +- docs/get_started/hello_world.md | 12 +- docs/get_started/quickstart.md | 2 +- docs/index.md | 90 ++---- docs/public/evaluate.html | 23 ++ docs/public/get_started/evaluate.html | 22 +- docs/public/resources/cost_calculator.html | 23 ++ docs/public/resources/references.html | 23 ++ docs/public/resources/who_uses.html | 23 ++ docs/resources/cost_calculator.md | 66 ----- docs/resources/references.md | 32 --- docs/resources/who_uses.md | 31 --- scripts/build-site.mjs | 55 +--- scripts/lib/cross-site.mjs | 1 + scripts/lib/facts.mjs | 2 +- scripts/site-css/docs.css | 144 +--------- scripts/site-js/site.js | 5 - test/cost-calculator.test.mjs | 217 --------------- 24 files changed, 161 insertions(+), 1145 deletions(-) delete mode 100644 docs/.vitepress/theme/cost-calculator.js delete mode 100644 docs/.vitepress/theme/cost-model.js delete mode 100644 docs/evaluate.md create mode 100644 docs/public/evaluate.html create mode 100644 docs/public/resources/cost_calculator.html create mode 100644 docs/public/resources/references.html create mode 100644 docs/public/resources/who_uses.html delete mode 100644 docs/resources/cost_calculator.md delete mode 100644 docs/resources/references.md delete mode 100644 docs/resources/who_uses.md delete mode 100644 test/cost-calculator.test.mjs diff --git a/AGENTS.md b/AGENTS.md index 0fd7f6dc..5a81a288 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,12 +15,12 @@ person reads the page. Do not put "as an AI, …" prose back into `docs/`. | Path | Purpose | | --- | --- | -| `docs/` | The pages. Sidebar and nav live in `docs/.vitepress/config.mjs`. Two pages are not chapters: `index.md` (`layout: home`, the front door) and `evaluate.md` (`layout: landing`, the page for the person who approves abap2UI5 - drawn like the front door, with no sidebar, outline or crumb trail, and its own address so a link to it does not say "manual"; its frontmatter says why) | +| `docs/` | The pages. Sidebar and nav live in `docs/.vitepress/config.mjs`. One page is not a chapter: `index.md` (`layout: home`, the front door). **The manual is for developers.** The case for the person who approves abap2UI5 - what it can build, how it compares, what it costs, who runs it - is a site of its own, [abap2UI5/test-docs](https://github.com/abap2UI5/test-docs) at https://abap2ui5.github.io/test-docs/; link it, never argue it here a second time | | `docs/public/` | Static assets — **and** the generated `llms.txt`, `llms-full.txt` and per-page `.md`, which are gitignored | | `scripts/check-examples.mjs` | Extracts every fenced ABAP block that builds a view, compiles it against the real framework and lints the view it produces | | `scripts/link-samples.mjs` | Generates the *Working Samples* block on a page from its `samples:` frontmatter plus the catalogue of the repository each class belongs to — the prefix decides: `z2ui5_cl_smp_app_*` is `abap2UI5/samples` (read from its `SAMPLES.md` in a checkout), `z2ui5_cl_smpc_*` samples-controls and `z2ui5_cl_smps_*` samples-stack (read from their `catalogue.json`, a checkout or the published copy). Checks the link in both directions for the samples repository (the `" @docs` line), the class's existence for the other two, and resolves the source links a page writes by hand against whichever checkout is here | | `scripts/generate-llms.mjs` | Builds `llms.txt` / `llms-full.txt` / per-page markdown from the sidebar. Runs inside `docs:build`, so the deploy publishes them | -| `scripts/lib/facts.mjs` | The counted figures the front door and the Evaluate page print under the hero and in `

` - unit tests, specs, browser suites, gates, GitHub stars. Read at build time from the framework README's `facts:start` block (`A2UI5_HOME` via `git show`, else raw GitHub) and the GitHub API; this repository types no number of its own, and a read that fails costs the line, never the build, and says so in the build log | +| `scripts/lib/facts.mjs` | The counted figures the front door prints under the hero and in `

` - unit tests, specs, browser suites, gates, GitHub stars. Read at build time from the framework README's `facts:start` block (`A2UI5_HOME` via `git show`, else raw GitHub) and the GitHub API; this repository types no number of its own, and a read that fails costs the line, never the build, and says so in the build log | | `scripts/generate-api-reference.mjs` | Generates the client API reference — the block in `docs/resources/api.md` and `docs/public/api/client-api.json` — from `z2ui5_if_client` on the framework branch this site tracks (`main`); `--check` is the freshness gate | | `scripts/lib/client-interface.mjs` | Where `z2ui5_if_client` is fetched from (the ref comes from `lib/release.mjs`, shared with `check-api-names.mjs`; `A2UI5_HOME` reads the same ref out of a local clone of the framework instead, with `git show`, never its working tree) and the full parser `generate-api-reference.mjs` renders from; pinned by `test/client-interface.test.mjs` | | `scripts/check-version.mjs` | The release number in the bar's menu, the deprecations page and the changelog, against the newest release tag of the framework | @@ -49,7 +49,6 @@ person reads the page. Do not put "as an AI, …" prose back into `docs/`. | `scripts/check-images.mjs` | Every image under `docs/public`: a screenshot is WebP, a deliverable is one of the PNGs the logo page hands out, none is over its budget, and the build can measure each of them - `scripts/lib/images.mjs` is the one reader both this gate and `build-site.mjs` use, so a format the build cannot size fails here rather than shifting the page there | | `scripts/lib/html.mjs` | `stripComments( )`: every finished page leaves the build without the HTML comments its sources are written with - 418 kB across the site, a tenth of every page's compressed weight, read by nobody - outside ` + + +

+ This page moved to + abap2UI5 for decision makers. +

+ + diff --git a/docs/public/get_started/evaluate.html b/docs/public/get_started/evaluate.html index fb85b272..7743ba12 100644 --- a/docs/public/get_started/evaluate.html +++ b/docs/public/get_started/evaluate.html @@ -1,29 +1,23 @@ Page moved - abap2UI5 - - + + - +

This page moved to - Evaluate abap2UI5. + abap2UI5 for decision makers.

diff --git a/docs/public/resources/cost_calculator.html b/docs/public/resources/cost_calculator.html new file mode 100644 index 00000000..4f8b1f00 --- /dev/null +++ b/docs/public/resources/cost_calculator.html @@ -0,0 +1,23 @@ + + + + + + Page moved - abap2UI5 + + + + + + +

+ This page moved to + What it costs. +

+ + diff --git a/docs/public/resources/references.html b/docs/public/resources/references.html new file mode 100644 index 00000000..724ef654 --- /dev/null +++ b/docs/public/resources/references.html @@ -0,0 +1,23 @@ + + + + + + Page moved - abap2UI5 + + + + + + +

+ This page moved to + Who runs it. +

+ + diff --git a/docs/public/resources/who_uses.html b/docs/public/resources/who_uses.html new file mode 100644 index 00000000..96dd30f0 --- /dev/null +++ b/docs/public/resources/who_uses.html @@ -0,0 +1,23 @@ + + + + + + Page moved - abap2UI5 + + + + + + +

+ This page moved to + Who runs it. +

+ + diff --git a/docs/resources/cost_calculator.md b/docs/resources/cost_calculator.md deleted file mode 100644 index 4bfa3d41..00000000 --- a/docs/resources/cost_calculator.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -outline: [2, 4] -description: What abap2UI5 costs for your users, systems, apps and support, worked out slider by slider - why every line of the sheet comes to the same number, what that number gets you, and who paid for it. ---- -# Cost Calculator - -Set the sliders to your landscape, tick your systems and your support, pick a currency, press Calculate, and read off what abap2UI5 will cost you. - -
-
-
250 users
-
Your systems
-
3 systems
-
10 apps
-
5 developers
-
100,000 roundtrips a day
-
Support
-
3 years
-
-
-

- -
- - diff --git a/docs/resources/references.md b/docs/resources/references.md deleted file mode 100644 index 8d58d549..00000000 --- a/docs/resources/references.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -outline: [2, 4] ---- -# References - -## 2026 -- Field Service Management Mobile Logging using abap2UI5 [(Decabase Blog - 22.08.2026)](https://blog.decabase.com/field-service-management-mobile-logging-using-abap2ui5-2c18e4ed455d) -- UI5 in ABAP Cloud (without RAP or Fiori Elements) [(Decabase Blog - 17.08.2026)](https://blog.decabase.com/ui5-in-abap-cloud-without-rap-or-fiori-elements-4e8a70d961c3) -- Fiori-type Apps built 100% with ABAP [(Logali Group - 23.04.2026)](https://logaligroup.com/magia-en-el-servidor-local-apps-tipo-fiori-creadas-100-con-codigo-abap/) - -## 2025 -- Webinar on Launchpad Integration [(YouTube - 30.07.2025)](https://www.youtube.com/watch?v=t5g3F3PHsbw&list=PLLpkZ_86h4quGSfsjohDHt9CrpjXdeA1P) -- Featured on SAP Developer News [(YouTube - 21.03.2025)](https://www.youtube.com/watch?v=vKrpkDe2mkU&list=PL6RpkC85SLQAVBSQXN9522_1jNvPavBgg&t=90s) - -## 2024 -- Automated Deployment of abap2UI5 [(Blog - 17.12.2024)](https://www.nuveplatform.com/blog/deploy-abap2ui5) -- Webinar on creating UI5 interfaces from ABAP using abap2UI5 [(YouTube - 12.12.2024)](https://www.youtube.com/watch?v=N2OAdxf7Lng) -- Highlighted in the Boring Enterprise Nerdletter [(YouTube)](https://www.youtube.com/watch?v=I81z6W_BTIA&t=1010s) [(Newsletter - 11.12.2024)](https://boringenterprisenerds.substack.com/p/72-abap2ui5-aancos-crystal-ball-sapta) -- Webinar on developing UI5 apps with abap2UI5 [(YouTube - 07.11.2024)](https://www.youtube.com/watch?v=0izPA2xrPPI) -- Featured on SAP Developer News [(YouTube - 14.06.2024)](https://youtu.be/7n16u-Rx8IY?t=7) -- Cust&Code Videos featuring abap2UI5 [(YouTube - 20.05.2024)](https://www.youtube.com/watch?v=SD1vIt_ty0k) -- Running abap2UI5 Backend in Browser [(LinkedIn - 02.04.2024)](https://www.linkedin.com/pulse/running-abap2ui5-backend-browser-lars-hvam-petersen-l8zff/?trackingId=4mhMb1v%2FSoa8SmDSiuCEpg%3D%3D) -- Highlighted in the Boring Enterprise Nerdcast [(YouTube - 29.01.2024)](https://youtu.be/svDZKFBvqR8?t=1050) - -## 2023 -- Featured on SAP Developer News [(YouTube - 15.12.2023)](https://www.youtube.com/watch?v=CfH9L03WUCg&t=350s) -- Join the Advent of Code 2023 with abap2UI5 [(SAP Community - 27.11.2023)](https://blogs.sap.com/2023/11/27/preparing-for-advent-of-code-2023/) -- Showcased at SAP TechEd 2023 [(YouTube - 02.11.2023)](https://www.youtube.com/watch?v=kLbF0ooStZs&t=3052s) -- Part of the SAP Developer Code Challenge [(SAP Community - 17.05.2023)](https://groups.community.sap.com/t5/application-development/sap-developer-code-challenge-open-source-abap-week-2/m-p/260727#M1372) -- Highlighted in the Boring Enterprise Nerdletter [(YouTube)](https://www.youtube.com/watch?v=G62exySitCo&list=PLlxj8-g1r2GlVYXVQnnV5izKwKtEn6KIp&t=1008s) [(Newsletter - 08.03.2023)](https://boringenterprisenerds.substack.com/p/34-abap2ui5-sap-cva-burnout-c2c-shortwave) -- Featured on SAP Developer News [(YouTube - 26.01.2023)](https://www.youtube.com/watch?v=6BDK55xYttM) -- See abap2UI5 on ABAP Open Source Projects [(dotabap.org)](https://dotabap.org/) diff --git a/docs/resources/who_uses.md b/docs/resources/who_uses.md deleted file mode 100644 index b757d610..00000000 --- a/docs/resources/who_uses.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -outline: [2, 4] -description: Companies, workshops and integrations that use abap2UI5, with the system release and the use case, and how to add your own. ---- -# Who Uses abap2UI5? - -We'd love to hear how you use abap2UI5. Whether in customer projects, integrations, workshops, open-source contributions, or any other initiative, sharing your scenario can inspire others to try the project and help the abap2UI5 community grow. - -## Customer Projects -**Does your company use abap2UI5?**
-Showcase how abap2UI5 apps reshape real-world workflows. Add your scenario here: - -| Company | System | Use Case | -| ------------------- | ------------- | ------------- | -| [Emineo](https://emineo.ch/) | ABAP 7.57 | Apps for EWM and PP, running on desktop and mobile devices | -| [Swiss Krono](https://www.swisskrono.com/) | ABAP 7.55 | Apps for EWM, running on mobile devices | - -## Integrations and Workshops -**Do you host workshops, build integrations, or use abap2UI5 in creative ways?**
-Inspire the community by sharing your initiatives so others can learn from your contributions: - -| Company | Description | -| ------------- | ------------- | -| [Nuve Platform](https://www.nuveplatform.com) | On-demand SAP systems with automated abap2UI5 setups | -| [Logali Group](https://logaligroup.com) | Webinars and certifications for abap2UI5 | - -
- -_Ready to showcase your project? Add to this list directly or reach out at . We'd love to hear what you're building._ - -Thank you for being part of the abap2UI5 community! ❤️ diff --git a/scripts/build-site.mjs b/scripts/build-site.mjs index 7b9ce0de..2c942f7a 100644 --- a/scripts/build-site.mjs +++ b/scripts/build-site.mjs @@ -323,10 +323,6 @@ const BAR_DOCS = withRelease(marked('data-site="docs"')); /* The ids the bar brings with it, which no heading of a page may also take. */ const BAR_IDS = new Set([...BAR_DOCS.matchAll(/\bid="([^"]+)"/g)].map((m) => m[1])); const BAR_HOME = withRelease(marked(`href="${HOME}"`)); -/* The bar with no section marked: the Evaluate page is neither the front - door nor a chapter, and a bar that said "Documentation" in bold over it - would put the reader the page is written for into the manual. */ -const BAR_PLAIN = withRelease(BAR); /* THE OTHER HALF OF THE PAIR IS THE BORROWED MARKUP'S, so it is checked rather * than assumed. `data-site` above says which page to come back to; `data-back`, @@ -989,50 +985,6 @@ const home = ({ body, fm, page }) => { `; }; -/* ---- the landing page ------------------------------------------------ - * - * The one chapter drawn like the front door: `layout: landing` in the - * frontmatter (docs/evaluate.md says why). The hero's name, tagline, buttons - * and note come from the frontmatter the way the front door's do; its H1 is - * the page's own, taken out of the body and printed once in the hero; the - * image is a screenshot rather than a mark, so it is drawn at the width a - * screenshot needs. The H2 sections become bands (no hinge, so no cards), - * the tiles stand after the last of them, and the figures under the hero. */ -const landing = ({ body, fm, page }) => { - const h = fm.hero || {}; - const img = h.image || {}; - const h1 = (body.match(/]*>([\s\S]*?)<\/h1>/) || [, ''])[1].replace(/<[^>]+>/g, ''); - const rest = body.replace(/]*>[\s\S]*?<\/h1>\s*/, ''); - const shot = img.src ? (() => { - const at = BASE + String(img.src).replace(/^\//, ''); - const box = measure(path.join(DOCS, 'public', at.slice(BASE.length))); - const size = box && box.w ? ` width="${box.w}" height="${box.h}"` : ''; - return `
${esc(img.alt || '')}
`; - })() : ''; - return `
-
-
- ${h.name ? `

${esc(h.name)}

` : ''} -

${h1}

- ${h.tagline ? `

${esc(h.tagline)}

` : ''} -
${(h.actions || []).map((a) => ` - ${esc(a.text)}`).join('')} -
- ${h.note ? `

${esc(h.note)}

` : ''} -
- ${shot} -
- ${factsLine('hero-facts')} -
${bands(rest)}
${(fm.features || []).map(tile).join('')} -
- -
`; -}; - /* ---- the ABAP, tokenised by the file the catalogue tokenises with ------- * * Not "the same colours" - the same PROGRAM. `abap-highlight.mjs` is what @@ -1415,7 +1367,6 @@ for (const page of pages) { const name = fm.title || titleOf(src, page); const route = routeOf(page); const isHome = fm.layout === 'home'; - const isLanding = fm.layout === 'landing'; /* The theme's own title template, and its own rule for the preview: a chapter previews as itself, the front door as the project. A name two chapters share carries the section that tells them apart. */ @@ -1448,13 +1399,13 @@ for (const page of pages) { sentence cannot be taken from. */ description: isHome ? (fm.description || SITE_DESC) : (describe(src) || SITE_DESC), }), - bar: isHome ? BAR_HOME : isLanding ? BAR_PLAIN : BAR_DOCS, - main: isHome ? home({ body, fm, page }) : isLanding ? landing({ body, fm, page }) : chapter({ body, page, route }), + bar: isHome ? BAR_HOME : BAR_DOCS, + main: isHome ? home({ body, fm, page }) : chapter({ body, page, route }), /* The front door sets its "More on …" lines in italic, in the first screen; unpreloaded, the italic arrived a round trip after the roman and those four lines were drawn twice. A chapter has italic here and there and fetches it when it needs it. */ - italic: isHome || isLanding, + italic: isHome, }); const to = path.join(OUT, 'docs', page.replace(/\.md$/, '.html')); diff --git a/scripts/lib/cross-site.mjs b/scripts/lib/cross-site.mjs index db49fea6..9453d368 100644 --- a/scripts/lib/cross-site.mjs +++ b/scripts/lib/cross-site.mjs @@ -64,6 +64,7 @@ export const NEIGHBOURS = [ '/linter/', // the linter's rule reference '/layout-editor/', // the layout editor, published by abap2UI5/layout-editor's pages workflow '/web-abap2UI5-build/', // a live build of the frontend, credited on Sponsor + '/test-docs/', // abap2UI5 for decision makers, published by abap2UI5/test-docs ]; /** What a retired deployment was replaced BY, so the error can say it. */ diff --git a/scripts/lib/facts.mjs b/scripts/lib/facts.mjs index 32340f50..08387472 100644 --- a/scripts/lib/facts.mjs +++ b/scripts/lib/facts.mjs @@ -1,5 +1,5 @@ /* - * facts — the counted figures the front door and the Evaluate page print. + * facts — the counted figures the front door prints. * * This repository owns no number about the framework, and keeps none by hand: * the one page that carried four figures of its own (`resources/samples.md`) diff --git a/scripts/site-css/docs.css b/scripts/site-css/docs.css index c34bc86b..77269b4e 100644 --- a/scripts/site-css/docs.css +++ b/scripts/site-css/docs.css @@ -806,30 +806,13 @@ html { scroll-padding-top: 62px; } has always drawn it. */ .hero-image { flex: 1 1 auto; min-width: 0; display: flex; justify-content: center; transform: translate(-32px, -32px); } .hero-image img { display: block; width: 200px; height: auto; border: 0; border-radius: 0; } -/* The landing page's hero carries a screenshot where the front door has the - mark: drawn at the width a screenshot needs, framed like the ones in the - chapters, and not shifted up and left the way the mark is. */ -.landing .hero-image.hero-shot { transform: none; justify-content: flex-end; padding-left: 32px; } -.landing .hero-image.hero-shot img { - width: 100%; max-width: 520px; height: auto; - border: 1px solid var(--line); border-radius: 6px; -} -/* Its tables are tables - a header row, cells that wrap - not the front - door's two-column fact sheet, which is what `.home table` is styled as. */ -.landing .vp-doc table { max-width: none; } -.landing .vp-doc table thead { display: table-header-group; } -.landing .vp-doc table td:first-child { width: auto; white-space: normal; } -/* The bands follow one another as sections of one argument, so each one - after the first opens on the hairline the H2 carries and a little air. */ -.landing .band + .band { padding-top: 8px; } -.landing .bands .tiles { margin-top: 8px; } - /* The three things to do next - build a first app, find a sample, take one to production. Under the example rather than under the hero (build-site.mjs places them), so the row is one band of the grid below and takes its spacing from the grid's row gap. */ -/* Four tiles, one per task - build, find, ship, evaluate - and the fourth is - the one for the reader who builds nothing. Two by two under 1100px, where +/* Four tiles, one per task - build, find, ship, and hand the page for the + decision maker over - the fourth is the one for the reader who builds + nothing. Two by two under 1100px, where four 13px details in a row stop being readable. */ .tiles { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; @@ -1016,9 +999,6 @@ html { scroll-padding-top: 62px; } @media (max-width: 860px) { .hero { display: block; } .hero-image { display: none; } - /* ...but the landing page's screenshot stays, under the text: it is the - one picture the reader of that page gets. */ - .landing .hero-image.hero-shot { display: flex; padding: 24px 0 0; justify-content: flex-start; } .tiles { grid-template-columns: minmax(0, 1fr); } } @@ -1287,124 +1267,6 @@ html { scrollbar-gutter: stable; } p, li, blockquote { orphans: 3; widows: 3; } } -/* ---- the cost calculator ---------------------------------------------- - * - * resources/cost_calculator: a sheet of sliders whose every line comes to - * the same number. Plain form controls in the page's own type, the accent - * on the slider's thumb and on the total, and nothing that needs the - * script - the page reads the same with the sliders dead. */ -.cost { margin: 20px 0; } -.cost-row { - display: grid; grid-template-columns: minmax(0, 11em) minmax(0, 1fr) minmax(0, 13em); - gap: 6px 16px; align-items: center; - padding: 10px 0; border-bottom: 1px solid var(--line); font-size: 14px; line-height: 1.55; -} -.cost-row label { font-weight: 600; } -.cost-row input[type="range"] { width: 100%; margin: 0; accent-color: var(--accent); } -.cost-row output { text-align: right; font-variant-numeric: tabular-nums; color: var(--fg-dim); } -.cost-row select { - grid-column: 2 / -1; justify-self: end; font: inherit; padding: 4px 8px; - border: 1px solid var(--line); border-radius: 6px; background: var(--bg); color: inherit; -} -/* A choice rather than a scale: radio buttons or checkboxes, across the - columns the slider and its readout would take. The links that used to sit - beside them are down in the sheet now - an input row is for choosing, and a - link in one is a way out of the page before it has said anything. */ -.cost-row .cost-label { font-weight: 600; } -.cost-row .cost-choices { grid-column: 2 / -1; display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; } -.cost-row .cost-choices label { display: inline-flex; align-items: center; gap: 6px; margin-right: 12px; font-weight: 400; } -.cost-row .cost-choices input[type="radio"], -.cost-row .cost-choices input[type="checkbox"] { margin: 0; accent-color: var(--accent); } -/* The sheet is what the button hands out: hidden until Calculate is pressed, - the inputs greyed while it shows, and the button then offers a new round. */ -.cost-actions { margin: 16px 0; } -.cost-calculate { - font: inherit; font-size: 13px; font-weight: 600; line-height: 34px; padding: 0 16px; - border: 1px solid transparent; border-radius: 6px; cursor: pointer; - background: var(--accent); color: var(--accent-fg, #fff); -} -.cost-calculate:hover { filter: brightness(1.08); } -.cost-calculate:disabled { cursor: progress; opacity: .7; } -.cost-inputs[data-locked] .cost-row { opacity: .5; } -/* ON A PHONE A ROW IS TWO LINES: the label with its readout on the first and - the slider the whole width of the second - the shape a phone's own settings - give a slider - where the readout used to take a third line of its own. A - choice's boxes go under their label rather than beside it: nine systems in - the column beside "Your systems" left the label wrapped to a word a line and - the boxes to a two-word column. The button is the row's whole width, and - the height of a finger. */ -@media (max-width: 620px) { - .cost-row { grid-template-columns: minmax(0, 1fr) auto; } - .cost-row output { grid-column: 2; grid-row: 1; } - .cost-row input[type="range"] { grid-column: 1 / -1; grid-row: 2; } - .cost-row .cost-choices { grid-column: 1 / -1; } - .cost-calculate { width: 100%; line-height: 44px; } -} -/* A thumb rather than a pointer: each box the size of a target and the word - beside it a row the height of one, the slider a band a finger can land on, - and the currency list a size iOS does not zoom the page in on - a control - set under 16px is one it magnifies on focus and leaves magnified. Nothing - here moves under a mouse. */ -@media (pointer: coarse) { - .cost-row .cost-choices label { min-height: 32px; } - .cost-row .cost-choices input[type="radio"], - .cost-row .cost-choices input[type="checkbox"] { width: 18px; height: 18px; } - .cost-row input[type="range"] { height: 32px; } - .cost-row select { font-size: 16px; } -} -.cost-sheet { margin: 20px 0 0; padding: 0 16px; border: 1px solid var(--line); border-radius: 8px; background: var(--bg-sunken); } -.cost-line { - display: flex; justify-content: space-between; align-items: baseline; gap: 16px; - padding: 10px 0; border-bottom: 1px solid var(--line); font-size: 14px; line-height: 1.55; -} -.cost-line:last-child { border-bottom: 0; } -.cost-line small { display: block; font-size: 13px; color: var(--fg-dim); } -.cost-amount { font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; } -.cost-total { - display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; - margin: 16px 0 24px; padding: 16px 20px; border: 1px solid var(--accent); border-radius: 8px; -} -.cost-total > div { display: flex; flex-direction: column; gap: 4px; font-size: 13px; color: var(--fg-dim); } -.cost-total output { font-size: 28px; line-height: 1.15; font-weight: 600; color: var(--accent); font-variant-numeric: tabular-nums; } -.cost-total > div:first-child output { font-size: 40px; } -/* The one line the sheet cannot total. The answer above it is ten zeros, so - the way to say that somebody still paid for them is a panel of its own - - the accent border the total carries, on the quieter background, so it reads - as the other half of the answer rather than as a footnote or a plea. The - four ways are a list without markers, two columns where there is room, each - led by what it is: three of them cost an evening and one costs money, and - they are in that order on purpose. */ -.cost-give { - margin: 28px 0 0; padding: 4px 24px 20px; border: 1px solid var(--accent); - border-radius: 8px; background: var(--bg-sunken); -} -.cost-give h2 { margin: 16px 0 10px; padding: 0; border: 0; } -.cost-give p { font-size: 14px; } -.cost-give > :last-child { margin-bottom: 0; } -/* Named down to the element, because both themes give a list in an article a - marker and an indent of its own. */ -.cost-give ul { - display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 24px; - margin: 16px 0; padding: 0; list-style: none; - font-size: 13px; line-height: 1.6; color: var(--fg-dim); -} -.cost-give ul li { margin: 0; padding: 0; } -/* The lead of a way is a line of its own - and only the lead: a strong later - in the sentence (the tag a post carries) is a word inside it. */ -.cost-give ul li strong:first-child { display: block; } -.cost-give ul li strong { color: var(--fg); font-weight: 600; } -@media (max-width: 620px) { .cost-give ul { grid-template-columns: 1fr; } } -/* The note is the sheet's last line rather than a paragraph under it: the - three figures answer the question, and the sentence that says why they are - all the same character belongs inside the same border. It spans the grid, - sits on a hairline under the figures, and is the size of a caption - a note - on the total, not a fourth number. */ -.cost-total-note { - grid-column: 1 / -1; margin: 4px 0 0; padding-top: 14px; - border-top: 1px solid var(--line); font-size: 13px; line-height: 1.6; color: var(--fg-dim); -} -@media (max-width: 620px) { .cost-total { grid-template-columns: 1fr; } } - /* ---- the front door's example folds on a phone -------------------------- * * The example is 137 lines, and on 390px of width that was 4,000 of the diff --git a/scripts/site-js/site.js b/scripts/site-js/site.js index 3f524704..d5088bd1 100644 --- a/scripts/site-js/site.js +++ b/scripts/site-js/site.js @@ -14,7 +14,6 @@ import { setUpPlayground } from './playground.js'; import { setUpCodeLines, watchCodeLines } from './code-lines.js'; import { markDirective, setUpLinkToSelection } from './link-to-selection.js'; -import { setUpCostCalculator } from './cost-calculator.js'; import { entryOf, forgetOnReload, handOff, lastVisited, rememberHere, rememberScroll, restoreScroll, takeHandoff } from './site-memory.js'; /* ---- a refresh starts over --------------------------------------------- @@ -51,10 +50,6 @@ forgetOnReload([SECTIONS_KEY, SEARCH_KEY]); setUpPlayground(); setUpLinkToSelection(); -/* The cost calculator's sliders and the sheet under them (cost-calculator.js): - two delegated listeners, and nothing to do on any page but that one. */ -setUpCostCalculator(); - /* A number beside every line of every listing, and an address for it - #B2L42 is line 42 of the second listing on the page. `watchCodeLines` hangs the document's listeners, `setUpCodeLines` numbers what is on this page. */ diff --git a/test/cost-calculator.test.mjs b/test/cost-calculator.test.mjs deleted file mode 100644 index 690bc409..00000000 --- a/test/cost-calculator.test.mjs +++ /dev/null @@ -1,217 +0,0 @@ -/* - * The cost calculator (resources/cost_calculator): a sheet of sliders whose - * every line comes to zero, because that is what abap2UI5 costs. - * - * Two things are worth holding. The arithmetic - theme/cost-model.js, the - * half with no DOM in it - so that the joke stays true in code: every line - * is zero and so is the sum, for any setting of any slider. And the page - * against the script: the readouts and the amounts are written into the - * markup, so what the button reveals is what the page already says, and a - * slider whose written readout is not what the script would write for its - * starting position would flicker to the truth on load. - * - * npm test - */ -import { test } from 'node:test'; -import assert from 'node:assert/strict'; -import { readFileSync } from 'node:fs'; -import { fileURLToPath } from 'node:url'; -import { dirname, join } from 'node:path'; -import { amount, line, listed, reading, stopsOf, total } from '../docs/.vitepress/theme/cost-model.js'; - -const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..'); -const PAGE = readFileSync(join(ROOT, 'docs', 'resources', 'cost_calculator.md'), 'utf8'); -const HOME = readFileSync(join(ROOT, 'docs', 'index.md'), 'utf8'); - -test('a reading is the stop at the position, with its unit', () => { - assert.equal(reading(stopsOf('1|5|10'), 0, 'user|users'), '1 user'); - assert.equal(reading(stopsOf('1|5|10000'), 2, 'user|users'), '10,000 users'); - assert.equal(reading(stopsOf('1|5|10000'), '2', 'user|users'), '10,000 users', 'a slider\'s value is a string'); - assert.equal(reading(stopsOf('Community|Premium'), 1), 'Premium'); - assert.equal(reading(stopsOf('1|5'), 9, 'year|years'), '5 years', 'past the end is the end'); - assert.equal(reading(stopsOf('1|5'), -1, 'year|years'), '1 year'); - assert.equal(reading(stopsOf('1|5'), 'x'), '1'); - assert.equal(reading(stopsOf(''), 3), ''); -}); - -test('choices read out as a list', () => { - assert.equal(listed([], 'no system at all'), 'no system at all'); - assert.equal(listed(['S/4HANA on-premise']), 'S/4HANA on-premise'); - assert.equal(listed(['NetWeaver 7.50', 'BTP ABAP Environment']), 'NetWeaver 7.50 and BTP ABAP Environment'); - assert.equal(listed(['A', 'B', 'C']), 'A, B and C'); -}); - -test('an amount is the currency\'s own zero', () => { - assert.equal(amount(0, 'EUR'), '€0'); - assert.equal(amount(0, 'USD'), '$0'); - assert.equal(amount(0, 'JPY'), '¥0'); - /* A code and its number are joined with a no-break space, so they never - part at a line's end. */ - assert.equal(amount(0, 'CHF'), 'CHF\u00a00'); - assert.equal(amount(0, 'XYZ'), 'XYZ\u00a00', 'a code the runtime does not know still reads'); -}); - -test('every line is zero, and so is the sum, whatever the sliders say', () => { - assert.equal(line('250,000 users'), 0); - assert.equal(line(), 0); - assert.equal(total({}), 0); - assert.equal(total({ users: 250000, systems: 50, support: 'Enterprise Plus', term: 10 }), 0); - for (let i = 0; i < 100; i++) { - assert.equal(total({ a: Math.random() * 1e9, b: -1, c: Infinity, d: 'many', e: NaN }), 0); - } -}); - -test('the page and the script agree on what every slider says at load', () => { - const sliders = [...PAGE.matchAll( - //g, - )]; - assert.ok(sliders.length >= 6, `${sliders.length} sliders`); - const ids = new Set(); - for (const [, id, min, max, value, stops, unit] of sliders) { - ids.add(id); - const all = stopsOf(stops); - assert.equal(Number(min), 0, id); - assert.equal(Number(max), all.length - 1, `${id}: max is the last stop`); - assert.ok(Number(value) <= Number(max), `${id}: starts on a stop`); - const said = reading(all, Number(value), unit || ''); - const out = PAGE.match(new RegExp(`([^<]*)`)); - assert.ok(out, `${id} has a readout`); - assert.equal(out[1], said, `${id}: the readout at load is what the script would write`); - for (const echo of PAGE.matchAll(new RegExp(`([^<]*)`, 'g'))) { - assert.equal(echo[1], said, `${id}: an echo at load is what the script would write`); - } - } - /* A choice made with radio buttons or checkboxes: what is ticked at load, - read out as a list, is what the sheet echoes - and the group's name is an - echo target like a slider's id. */ - const groups = new Map(); - for (const [, kind, name, choice, on] of PAGE.matchAll(//g)) { - if (!groups.has(name)) groups.set(name, { kind, chosen: [] }); - assert.equal(groups.get(name).kind, kind, `${name} is one kind of choice`); - if (on) groups.get(name).chosen.push(choice); - } - assert.ok(groups.size >= 2, `${groups.size} groups`); - for (const [name, { kind, chosen }] of groups) { - ids.add(name); - if (kind === 'radio') assert.equal(chosen.length, 1, `${name}: exactly one choice is made at load`); - const none = PAGE.match(new RegExp(`aria-labelledby="${name}-label" data-none="([^"]*)"`))?.[1]; - for (const echo of PAGE.matchAll(new RegExp(`([^<]*)`, 'g'))) { - assert.equal(echo[1], listed(chosen, none), `${name}: the echo at load is what is ticked at load`); - } - } - for (const [, id] of PAGE.matchAll(/data-echo="([^"]+)"/g)) assert.ok(ids.has(id), `an echo of ${id} has a slider or a choice`); - for (const [, id] of PAGE.matchAll(/