Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions web/dds_web.css
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
56 changes: 50 additions & 6 deletions web/dds_web.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,22 @@
</script>
<script src="coi-serviceworker.js"></script>
<link rel="stylesheet" type="text/css" href="dds_web.css">
<title>
DDS Web
</title>
<title>DDS Web — Double Dummy Solver</title>
</head>
<body onload="pageLoad()">
<h1>
Enter a 52-card deal using only these pips: <span id=valid-pips></span><br/>
</h1>
<header class="site-header">
<h1>DDS Web</h1>
<p class="site-tagline">Double dummy bridge solver in your browser</p>
<p class="site-repo">
<a href="https://github.com/dds-bridge/dds"
target="_blank"
rel="noopener noreferrer">Source on GitHub</a>
</p>
</header>

<p class="deal-entry-hint">
Enter a 52-card deal using only these pips: <span id=valid-pips></span>
</p>

<button type="button" onclick="clearTestData()">Clear entries</button>
<button type="button" onclick="chooseDealFile()">Import deal…</button>
Expand Down Expand Up @@ -162,6 +170,42 @@ <h1>
</div>
</div>

<section id="faq" class="faq" aria-labelledby="faq-heading">
<h2 id="faq-heading">FAQ</h2>
<details>
<summary>What is DDS Web?</summary>
<p>
DDS Web is a browser demo of
<a href="https://github.com/dds-bridge/dds"
target="_blank"
rel="noopener noreferrer">DDS</a>,
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.
</p>
</details>
<details>
<summary>Does my deal leave my browser?</summary>
<p>
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.
</p>
</details>
<details>
<summary>How do I play through a contract?</summary>
<p>
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.
</p>
</details>
</section>

<script src="dds_web_wasm_bin.js"></script>
<script src="dds_web_wasm.js"></script>
<script src="dds_web_deal_import.js"></script>
Expand Down
46 changes: 46 additions & 0 deletions web/tests/test_web_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,52 @@
COI_PATH = WEB_ROOT / "coi-serviceworker.js"


class DdsWebHtmlProfessionalizationTest(unittest.TestCase):
def test_visible_page_title(self) -> None:
# Document <title> 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*</title>", 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)<a\b[^>]*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)<a\b[^>]*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)<h2[^>]*>\s*FAQ\s*</h2>")
# 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 <meta charset> in the first 1024 bytes. Without it,
Expand Down
Loading