From 727b27aeb9f9648f39a9bfbc7a7d04cd540bb0da Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Sat, 17 Oct 2026 20:19:29 +0100 Subject: [PATCH] Give DDS Web a title, GitHub link, and FAQ. Make the demo page read as a product landing surface for first-time visitors instead of only a bare deal-entry form. Co-authored-by: Cursor --- web/dds_web.css | 64 ++++++++++++++++++++++++++++++++++++++ web/dds_web.html | 56 +++++++++++++++++++++++++++++---- web/tests/test_web_html.py | 46 +++++++++++++++++++++++++++ 3 files changed, 160 insertions(+), 6 deletions(-) diff --git a/web/dds_web.css b/web/dds_web.css index 251a7f45..7677bad7 100644 --- a/web/dds_web.css +++ b/web/dds_web.css @@ -5,6 +5,70 @@ https://opensource.org/licenses/MIT */ +.site-header { + margin: 0 0 1rem; +} + +.site-header h1 { + margin: 0 0 0.25rem; + font-size: 1.75rem; + font-weight: 700; +} + +.site-tagline { + margin: 0 0 0.35rem; + color: #333; +} + +.site-repo { + margin: 0; +} + +.site-repo a { + color: #0b57d0; +} + +.deal-entry-hint { + margin: 0 0 0.75rem; + font-size: 1.1rem; + font-weight: 600; +} + +.faq { + max-width: 1100px; + margin: 1.5rem 0 2rem; +} + +.faq h2 { + margin: 0 0 0.5rem; + font-size: 1.25rem; +} + +.faq details { + margin: 0.35rem 0; + border-top: 1px solid #ccc; + padding: 0.4rem 0; +} + +.faq details:last-child { + border-bottom: 1px solid #ccc; +} + +.faq summary { + cursor: pointer; + font-weight: 600; +} + +.faq details p { + margin: 0.5rem 0 0.25rem; + max-width: 42rem; + line-height: 1.45; +} + +.faq code { + font-size: 0.95em; +} + /* Toolbar controls: flat 1px chrome like https://macroxue.github.io/bridge-solver/web/ — Sample deals keeps a CSS chevron as the only visual difference from Clear / Import / Rotate. */ diff --git a/web/dds_web.html b/web/dds_web.html index ae07b51f..88a583e8 100644 --- a/web/dds_web.html +++ b/web/dds_web.html @@ -24,14 +24,22 @@ - - DDS Web - + DDS Web — Double Dummy Solver -

- Enter a 52-card deal using only these pips:
-

+ + +

+ Enter a 52-card deal using only these pips: +

@@ -162,6 +170,42 @@

+
+

FAQ

+
+ What is DDS Web? +

+ DDS Web is a browser demo of + DDS, + a double dummy originally written by Bo Haglund. It runs entirely + client-side via WebAssembly: enter a deal (or import one), + and the page fills a double-dummy table of max tricks by + declarer and strain. +

+
+
+ Does my deal leave my browser? +

+ No. Cards, imports, and solve results stay on your device. + The solver module is downloaded once with the page; nothing + is sent to a server to analyze a deal. +

+
+
+ How do I play through a contract? +

+ After the double-dummy table appears, click a cell to enter + play for that declarer and strain (or that seat on lead for a + partial deal). Drag or click a highlighted card to play it; + each card shows the contract outcome with best play and + defense. Undo with ⌘/Ctrl-Z, or choose Edit hands to leave + play mode. +

+
+
+ diff --git a/web/tests/test_web_html.py b/web/tests/test_web_html.py index 13a988a0..f5a18a4e 100644 --- a/web/tests/test_web_html.py +++ b/web/tests/test_web_html.py @@ -10,6 +10,52 @@ COI_PATH = WEB_ROOT / "coi-serviceworker.js" +class DdsWebHtmlProfessionalizationTest(unittest.TestCase): + def test_visible_page_title(self) -> None: + # Document alone is not enough; the page needs a visible + # product heading so first-time visitors know what they opened. + text = HTML_PATH.read_text(encoding="utf-8") + self.assertRegex( + text, + r'(?is)<h1[^>]*>\s*DDS Web\s*</h1>', + msg="dds_web.html must show a visible DDS Web title", + ) + head_title = re.search(r"(?is)<title>\s*(.*?)\s*", text) + self.assertIsNotNone(head_title) + self.assertIn("DDS Web", head_title.group(1)) + + def test_link_to_github_repository(self) -> None: + text = HTML_PATH.read_text(encoding="utf-8") + self.assertRegex( + text, + r'(?is)]*href="https://github\.com/dds-bridge/dds"[^>]*>', + msg="dds_web.html must link to the dds-bridge/dds repository", + ) + # Prefer opening the repo in a new tab without leaking the opener. + self.assertRegex( + text, + r'(?is)]*href="https://github\.com/dds-bridge/dds"[^>]*' + r'rel="[^"]*\bnoopener\b[^"]*"[^>]*>', + ) + + def test_faq_section_with_common_questions(self) -> None: + text = HTML_PATH.read_text(encoding="utf-8") + self.assertRegex( + text, + r'(?is)<(?:section|div)\b[^>]*id="faq"[^>]*>', + msg="dds_web.html must include an FAQ landmark", + ) + self.assertRegex(text, r"(?is)]*>\s*FAQ\s*

") + # Cover the questions visitors actually ask about a browser solver. + for question in ( + "What is DDS Web?", + "Does my deal leave my browser?", + "How do I play through a contract?", + ): + self.assertIn(question, text) + self.assertNotIn("Why won't solving start?", text) + + class DdsWebHtmlCharsetTest(unittest.TestCase): def test_declares_utf8_charset_within_first_1024_bytes(self) -> None: # Browsers only honor in the first 1024 bytes. Without it,