-
Notifications
You must be signed in to change notification settings - Fork 0
Add webcam feed extractor tool #33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,340 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <!DOCTYPE html> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <html lang="en"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <head> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <meta charset="UTF-8"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <title>Webcam Feed Extractor</title> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <link rel="preconnect" href="https://fonts.googleapis.com"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <style> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| :root { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| color-scheme: light; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --bg: #f5f7fb; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --card: #ffffff; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --primary: #2155f5; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --text: #101828; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --muted: #667085; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --border: #e4e7ec; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --success: #12b76a; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| box-sizing: border-box; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| margin: 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| padding: 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| body { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| font-family: "Inter", system-ui, -apple-system, sans-serif; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| background: var(--bg); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| color: var(--text); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| min-height: 100vh; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| padding: 32px 16px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| display: flex; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| justify-content: center; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .container { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| width: min(900px, 100%); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| header { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| margin-bottom: 24px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| h1 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| font-size: clamp(2rem, 3vw, 2.6rem); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| font-weight: 700; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| margin-bottom: 12px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| p { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| color: var(--muted); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| line-height: 1.6; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .card { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| background: var(--card); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| border-radius: 20px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| border: 1px solid var(--border); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| padding: 24px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| box-shadow: 0 20px 50px rgba(15, 23, 42, 0.08); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| form { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| display: grid; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| grid-template-columns: 1fr auto; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gap: 12px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| margin-top: 16px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| label { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| font-weight: 600; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| margin-bottom: 8px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| display: block; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| input[type="url"] { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| padding: 12px 14px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| border-radius: 12px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| border: 1px solid var(--border); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| font-size: 1rem; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| width: 100%; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| button { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| padding: 12px 18px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| border-radius: 12px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| border: none; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| background: var(--primary); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| color: white; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| font-weight: 600; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cursor: pointer; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| transition: transform 0.15s ease, box-shadow 0.2s ease; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| button:hover { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| transform: translateY(-1px); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| box-shadow: 0 10px 18px rgba(33, 85, 245, 0.2); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| button:disabled { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cursor: not-allowed; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| background: #98a2b3; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| box-shadow: none; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| transform: none; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .helper { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| margin-top: 16px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| background: #f0f4ff; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| padding: 14px 16px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| border-radius: 12px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| font-size: 0.95rem; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .results { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| margin-top: 24px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| display: grid; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gap: 12px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .result-item { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| padding: 14px 16px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| border-radius: 12px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| border: 1px solid var(--border); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| display: flex; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| justify-content: space-between; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| align-items: center; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gap: 12px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| background: #fbfcff; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .result-item a { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| color: var(--primary); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| text-decoration: none; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| word-break: break-all; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .tag { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| font-size: 0.8rem; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| background: #eef4ff; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| color: #1d3ed1; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| padding: 4px 8px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| border-radius: 999px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| font-weight: 600; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .status { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| margin-top: 16px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| color: var(--muted); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .status.success { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| color: var(--success); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| font-weight: 600; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .status.error { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| color: #b42318; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| font-weight: 600; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @media (max-width: 720px) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| form { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| grid-template-columns: 1fr; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| button { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| width: 100%; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .result-item { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| flex-direction: column; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| align-items: flex-start; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </style> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </head> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <body> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div class="container"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <header> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <h1>Webcam Feed Extractor</h1> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <p>Paste a webcam page URL and this tool will scan the HTML (including embedded iframes) to find any <strong>.m3u8</strong> HLS streams.</p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </header> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <section class="card"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <label for="pageUrl">Webcam page URL</label> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <form id="lookupForm"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <input | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id="pageUrl" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name="pageUrl" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type="url" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| placeholder="https://www.camsecure.co.uk/swanage_lifeboat_webcam.html" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| required | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <button type="submit" id="lookupButton">Find feeds</button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </form> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div class="helper"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Tip: The extractor uses a read-only fetch proxy to bypass CORS. It only searches the HTML for direct links to <code>.m3u8</code> files. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div id="status" class="status"></div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div id="status" class="status"></div> | |
| <div id="status" class="status" aria-live="polite"></div> |
Copilot
AI
Feb 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The results container should include aria-live="polite" to announce when new feed links are discovered and rendered to screen reader users. This follows the accessibility pattern consistently used in other tools like rice-cooking-calculator and hybrid-petrol-calculator.
| <div id="results" class="results"></div> | |
| <div id="results" class="results" aria-live="polite"></div> |
Copilot
AI
Feb 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The script tag should use type="module" attribute to follow the repository convention established in RULES.md lines 14-18 and consistently used in other tools like rice-cooking-calculator, hybrid-petrol-calculator, and christmas-timer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Preserve target scheme when building proxy URL
The proxy URL builder strips the input scheme and always prefixes http://, so any webcam page (or iframe) that is HTTPS-only and does not redirect from HTTP will fail to load through the proxy. This makes the extractor silently miss feeds on sites that serve only HTTPS content. Consider preserving the original scheme (or explicitly supporting https:// in the proxy path) so HTTPS-only pages can be fetched.
Useful? React with 👍 / 👎.
Copilot
AI
Feb 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's an extra space before the closing parenthesis in the querySelectorAll call. This should be doc.querySelectorAll("*") without the trailing space.
| doc.querySelectorAll("*" ).forEach((node) => { | |
| doc.querySelectorAll("*").forEach((node) => { |
Copilot
AI
Feb 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The iframe sources are fetched sequentially in a for-of loop. For better performance, consider using Promise.all or Promise.allSettled to fetch all iframes concurrently. This would be especially beneficial when there are multiple iframes to process.
| let iframeLinks = []; | |
| for (const iframeUrl of iframeSources) { | |
| try { | |
| const iframeResponse = await fetch(proxyUrl(iframeUrl)); | |
| if (!iframeResponse.ok) { | |
| continue; | |
| } | |
| const iframeHtml = await iframeResponse.text(); | |
| iframeLinks = iframeLinks.concat(extractM3u8Links(iframeHtml, iframeUrl)); | |
| } catch (error) { | |
| console.warn("Iframe fetch failed", iframeUrl, error); | |
| } | |
| } | |
| const iframePromises = iframeSources.map(async (iframeUrl) => { | |
| try { | |
| const iframeResponse = await fetch(proxyUrl(iframeUrl)); | |
| if (!iframeResponse.ok) { | |
| return []; | |
| } | |
| const iframeHtml = await iframeResponse.text(); | |
| return extractM3u8Links(iframeHtml, iframeUrl); | |
| } catch (error) { | |
| console.warn("Iframe fetch failed", iframeUrl, error); | |
| return []; | |
| } | |
| }); | |
| const iframeResults = await Promise.all(iframePromises); | |
| const iframeLinks = iframeResults.reduce((all, links) => all.concat(links), []); |
Copilot
AI
Feb 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The JavaScript code inside the script tag uses 4-space indentation, but the repository convention in RULES.md line 18 states "The top-level inside that tag is not indented." All top-level JavaScript statements (lines 208-337) should have no indentation. This convention is consistently followed in other tools like rice-cooking-calculator (line 436), hybrid-petrol-calculator (line 260), and christmas-timer (line 290).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The input font size uses
1rembut the repository convention in RULES.md line 13 and consistently followed in other tools (e.g., rice-cooking-calculator line 82, hybrid-petrol-calculator line 65) is to explicitly use16pxfor inputs and textareas.