From 695d96ad698c28b05593456f21f0de7bc67c69c1 Mon Sep 17 00:00:00 2001 From: Manu Garg Date: Mon, 9 Feb 2026 23:36:25 -0800 Subject: [PATCH] Redesign web app as pacparser homepage + tester Light theme with dark charcoal header (#1a2332), green accent (#16a34a), Rubik body font, and pacparser brand in JetBrains Mono. Dark code editor textarea replaces the plain white box. Header now clearly identifies the page as the pacparser library homepage with feature tags and links to GitHub, PyPI, and docs. --- web/index.html | 149 ++++++---- web/style.css | 719 ++++++++++++++++++++++++++++++++++--------------- 2 files changed, 602 insertions(+), 266 deletions(-) diff --git a/web/index.html b/web/index.html index ebbee59..2d21427 100644 --- a/web/index.html +++ b/web/index.html @@ -4,107 +4,156 @@ - Pacparser - PAC File Parser Library & Tester + Pacparser โ€” PAC File Parser Library + + +
-

๐ŸŒ PAC File Tester

-

Test your Proxy Auto-Config files online - 100% client-side, no data sent to servers

+
+ โฌก +

pacparser

+
+

A C library to parse Proxy Auto-Config (PAC) files

+

+ Embeds QuickJS to evaluate PAC scripts and implements all standard PAC + helper functions. Ships with a C API, Python bindings, and the + pactester CLI. Licensed under LGPL. +

+
+ C Library + Python Bindings + QuickJS Engine + pactester CLI + LGPL +
+
+
+
+ Online Tester +
+
+

Evaluate your PAC file directly in the browser โ€” no install needed, 100% client-side.

+
-

PAC File

+

+ 01 + PAC File +

- +
+
+ + + + proxy.pac +
+ +
- +
-

Test Configuration

+

+ 02 + Test Parameters +

- - + +
- - - Used for myIpAddress() function + +
-

DNS Mappings (optional)

-

Provide hostname to IP address mappings for dnsResolve() and isResolvable() - functions

+
+
+

DNS Mappings optional

+

Mock dnsResolve() and isResolvable() โ€” map hostnames to IP addresses

+
+ +
- + โ†’ - - + +
- - -
-
- - -
-

About Pacparser

-
-

- Pacparser is a library to parse proxy auto-config (PAC) files. - PAC files are a widely used method for configuring web browsers to select a proxy server. - Pacparser makes it easy to use PAC files in your own programs. -

-
-
- +
@@ -113,4 +162,4 @@

Results

- \ No newline at end of file + diff --git a/web/style.css b/web/style.css index f1a0a82..5221853 100644 --- a/web/style.css +++ b/web/style.css @@ -1,17 +1,34 @@ +@import url('https://fonts.googleapis.com/css2?family=Rubik:wght@400;500;600&family=JetBrains+Mono:wght@400;500;600&display=swap'); + :root { - --primary-color: #2563eb; - --primary-hover: #1d4ed8; - --success-color: #10b981; - --error-color: #ef4444; - --warning-color: #f59e0b; - --bg-color: #f8fafc; - --card-bg: #ffffff; - --border-color: #e2e8f0; - --text-primary: #1e293b; - --text-secondary: #64748b; - --text-muted: #94a3b8; - --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); - --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --bg: #eef2f7; + --surface: #ffffff; + --surface-2: #f8fafc; + --border: #d1d9e6; + --border-dark: #b0bdd0; + --header-bg: #0f172a; + --header-border: #1e293b; + --tagline-color: #94a3b8; + --desc-color: #cbd5e1; + --accent: #16a34a; + --accent-dim: rgba(22, 163, 74, 0.1); + --accent-dark: #15803d; + --success: #16a34a; + --success-bg: #f0fdf4; + --error: #dc2626; + --error-bg: #fef2f2; + --warning: #d97706; + --warning-bg: #fffbeb; + --text-1: #0f172a; + --text-2: #475569; + --text-3: #94a3b8; + --code-bg: #111c2d; + --code-text: #cdd9e9; + --code-border: #253347; + --mono: 'JetBrains Mono', 'Fira Code', 'Courier New', monospace; + --sans: 'Rubik', -apple-system, BlinkMacSystemFont, sans-serif; + --radius: 6px; + --radius-lg: 10px; } * { @@ -21,389 +38,659 @@ } body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + font-family: var(--sans); + background: var(--bg); min-height: 100vh; - padding: 2rem 1rem; - color: var(--text-primary); + color: var(--text-1); line-height: 1.6; + padding: 2rem 1rem 4rem; } .container { - max-width: 900px; + max-width: 820px; margin: 0 auto; - background: var(--card-bg); - border-radius: 16px; - box-shadow: var(--shadow-lg); + background: var(--surface); + border-radius: var(--radius-lg); + border: 1px solid var(--border); overflow: hidden; + box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08); } +/* โ”€โ”€ HEADER โ”€โ”€ */ header { - background: linear-gradient(135deg, var(--primary-color) 0%, #6366f1 100%); - color: white; - padding: 2.5rem 2rem; - text-align: center; + background: var(--header-bg); + border-bottom: 1px solid var(--header-border); + padding: 2.5rem 2.5rem 2.25rem; } -header h1 { - font-size: 2rem; - margin-bottom: 0.5rem; +.brand { + display: flex; + align-items: center; + gap: 0.6rem; + margin-bottom: 0.75rem; +} + +.brand-icon { + width: 28px; + height: 28px; + background: var(--accent); + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + font-size: 0.8rem; + color: white; font-weight: 700; + flex-shrink: 0; + font-family: var(--mono); } -.subtitle { - font-size: 0.95rem; - opacity: 0.95; - max-width: 600px; - margin: 0 auto; +.brand h1 { + font-family: var(--mono); + font-size: 1.75rem; + font-weight: 600; + color: #e8edf5; + letter-spacing: -0.02em; +} + +.tagline { + font-size: 1rem; + color: var(--tagline-color); + font-weight: 400; + margin-bottom: 0.75rem; +} + +.description { + font-size: 0.875rem; + color: var(--desc-color); + max-width: 560px; + line-height: 1.7; + margin-bottom: 1.5rem; +} + +.description strong { + color: #e2e8f0; + font-weight: 500; +} + +.description code { + font-family: var(--mono); + font-size: 0.82em; + color: #7dd3a8; + background: rgba(125, 211, 168, 0.1); + padding: 0.1em 0.4em; + border-radius: 3px; +} + +.feature-tags { + display: flex; + flex-wrap: wrap; + gap: 0.4rem; + margin-bottom: 1.5rem; +} + +.tag { + font-family: var(--mono); + font-size: 0.68rem; + padding: 0.22em 0.65em; + border: 1px solid #475569; + border-radius: 3px; + color: #cbd5e1; + background: rgba(255, 255, 255, 0.05); + letter-spacing: 0.03em; +} + +.header-links { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; +} + +.header-link { + display: inline-flex; + align-items: center; + gap: 0.4rem; + padding: 0.45rem 0.875rem; + border-radius: var(--radius); + border: 1px solid #2e3f57; + color: #7d93ad; + text-decoration: none; + font-size: 0.8rem; + font-weight: 500; + background: rgba(255, 255, 255, 0.03); + transition: all 0.15s; +} + +.header-link:hover { + border-color: var(--accent); + color: #7dd3a8; + background: rgba(22, 163, 74, 0.06); } +/* โ”€โ”€ MAIN โ”€โ”€ */ main { - padding: 2rem; + padding: 2rem 2.5rem; +} + +.tester-header { + display: flex; + align-items: center; + gap: 1rem; + margin-bottom: 0.5rem; } +.tester-header-line { + flex: 1; + height: 1px; + background: var(--border); +} + +.tester-title { + font-family: var(--mono); + font-size: 0.68rem; + font-weight: 500; + letter-spacing: 0.15em; + text-transform: uppercase; + color: var(--accent); + white-space: nowrap; +} + +.tester-subtitle { + text-align: center; + color: var(--text-3); + font-size: 0.825rem; + margin-bottom: 1.75rem; +} + +/* โ”€โ”€ SECTIONS โ”€โ”€ */ .section { - margin-bottom: 2rem; + background: var(--surface-2); + border: 1px solid var(--border); + border-radius: var(--radius-lg); padding: 1.5rem; - background: var(--bg-color); - border-radius: 12px; - border: 1px solid var(--border-color); + margin-bottom: 1rem; } -.section h2 { - font-size: 1.5rem; - margin-bottom: 1rem; - color: var(--text-primary); +.section-title { + display: flex; + align-items: center; + gap: 0.6rem; + font-size: 0.875rem; font-weight: 600; + color: var(--text-1); + margin-bottom: 1.25rem; } -.section h3 { - font-size: 1.1rem; - margin-bottom: 0.75rem; - color: var(--text-primary); - font-weight: 600; +.section-number { + font-family: var(--mono); + font-size: 0.65rem; + color: var(--accent); + background: var(--accent-dim); + padding: 0.15em 0.5em; + border-radius: 3px; + font-weight: 500; } +/* โ”€โ”€ CODE EDITOR โ”€โ”€ */ .input-group { display: flex; flex-direction: column; - gap: 1rem; + gap: 0.75rem; } -textarea { +.editor-wrap { + border: 1px solid var(--code-border); + border-radius: var(--radius); + overflow: hidden; + transition: border-color 0.15s, box-shadow 0.15s; +} + +.editor-wrap:focus-within { + border-color: #3d5a7a; + box-shadow: 0 0 0 3px rgba(22, 163, 74, 0.08); +} + +.editor-bar { + display: flex; + align-items: center; + gap: 0.375rem; + padding: 0.5rem 0.875rem; + background: #0d1825; + border-bottom: 1px solid var(--code-border); + user-select: none; +} + +.editor-dot { + width: 10px; + height: 10px; + border-radius: 50%; + flex-shrink: 0; +} + +.editor-dot.red { + background: #ff5f57; +} + +.editor-dot.yellow { + background: #febc2e; +} + +.editor-dot.green { + background: #28c840; +} + +.editor-label { + font-family: var(--mono); + font-size: 0.7rem; + color: #3d5572; + margin-left: 0.25rem; +} + +textarea#pacFile { + display: block; width: 100%; - padding: 1rem; - border: 2px solid var(--border-color); - border-radius: 8px; - font-family: 'Courier New', Courier, monospace; - font-size: 0.9rem; + padding: 1rem 1.1rem; + border: none; + background: var(--code-bg); + color: var(--code-text); + font-family: var(--mono); + font-size: 0.875rem; + line-height: 1.75; resize: vertical; - background: white; - transition: border-color 0.2s; + min-height: 220px; + outline: none; + caret-color: #7dd3a8; + tab-size: 4; } -textarea:focus { - outline: none; - border-color: var(--primary-color); +textarea#pacFile::placeholder { + color: #6b82a0; + font-style: italic; } .file-upload { display: flex; align-items: center; - gap: 1rem; + gap: 0.75rem; +} + +.file-name { + font-family: var(--mono); + font-size: 0.78rem; + color: var(--text-2); } +/* โ”€โ”€ INPUTS โ”€โ”€ */ .input-row { - margin-bottom: 1.5rem; + margin-bottom: 1.25rem; +} + +.input-row:last-child { + margin-bottom: 0; } .input-row label { - display: block; + display: flex; + align-items: baseline; + gap: 0.5rem; + font-size: 0.825rem; font-weight: 600; - margin-bottom: 0.5rem; - color: var(--text-primary); + color: var(--text-1); + margin-bottom: 0.4rem; +} + +.label-hint { + font-size: 0.75rem; + font-weight: 400; + color: var(--text-3); } .input-row input[type="text"] { width: 100%; - padding: 0.75rem 1rem; - border: 2px solid var(--border-color); - border-radius: 8px; - font-size: 1rem; - transition: border-color 0.2s; + padding: 0.625rem 0.875rem; + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius); + color: var(--text-1); + font-family: var(--mono); + font-size: 0.875rem; + transition: border-color 0.15s, box-shadow 0.15s; + outline: none; } .input-row input[type="text"]:focus { - outline: none; - border-color: var(--primary-color); + border-color: var(--accent); + box-shadow: 0 0 0 3px rgba(22, 163, 74, 0.1); } -.input-row small { - display: block; - margin-top: 0.5rem; - color: var(--text-muted); - font-size: 0.875rem; +.input-row input[type="text"]::placeholder { + color: var(--text-3); + font-style: normal; } +/* โ”€โ”€ DNS SECTION โ”€โ”€ */ .dns-section { - margin-top: 1.5rem; + margin-top: 1.25rem; + padding-top: 1.25rem; + border-top: 1px solid var(--border); +} + +.dns-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 1rem; + margin-bottom: 0.875rem; +} + +.dns-header h4 { + font-size: 0.825rem; + font-weight: 600; + color: var(--text-1); + margin-bottom: 0.2rem; } .help-text { - color: var(--text-secondary); - font-size: 0.9rem; - margin-bottom: 1rem; + font-size: 0.775rem; + color: var(--text-3); + line-height: 1.45; } .dns-mapping { display: flex; align-items: center; - gap: 0.75rem; - margin-bottom: 0.75rem; + gap: 0.5rem; + margin-bottom: 0.5rem; } -.dns-host, .dns-ip { +.dns-host, +.dns-ip { flex: 1; - padding: 0.75rem; - border: 2px solid var(--border-color); - border-radius: 8px; - font-size: 0.9rem; - transition: border-color 0.2s; + padding: 0.575rem 0.75rem; + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius); + color: var(--text-1); + font-family: var(--mono); + font-size: 0.825rem; + outline: none; + transition: border-color 0.15s, box-shadow 0.15s; } -.dns-host:focus, .dns-ip:focus { - outline: none; - border-color: var(--primary-color); +.dns-host:focus, +.dns-ip:focus { + border-color: var(--accent); + box-shadow: 0 0 0 3px rgba(22, 163, 74, 0.1); +} + +.dns-host::placeholder, +.dns-ip::placeholder { + color: var(--text-3); } .arrow { - color: var(--text-muted); - font-size: 1.2rem; - font-weight: bold; + color: var(--text-3); + font-size: 0.9rem; + flex-shrink: 0; } +/* โ”€โ”€ BUTTONS โ”€โ”€ */ .btn-primary { - background: linear-gradient(135deg, var(--primary-color) 0%, #6366f1 100%); - color: white; - padding: 1rem 2.5rem; + display: inline-flex; + align-items: center; + gap: 0.6rem; + padding: 0.75rem 2rem; + background: var(--accent); + color: #ffffff; border: none; - border-radius: 8px; - font-size: 1.1rem; + border-radius: var(--radius); + font-family: var(--sans); + font-size: 0.9rem; font-weight: 600; cursor: pointer; - transition: transform 0.2s, box-shadow 0.2s; - box-shadow: var(--shadow); + transition: all 0.15s; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12); } .btn-primary:hover { - transform: translateY(-2px); - box-shadow: var(--shadow-lg); + background: var(--accent-dark); + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(22, 163, 74, 0.25); } .btn-primary:active { transform: translateY(0); + box-shadow: none; +} + +.btn-arrow { + transition: transform 0.15s; + font-size: 1em; +} + +.btn-primary:hover .btn-arrow { + transform: translateX(3px); } .btn-secondary { - background: white; - color: var(--primary-color); - padding: 0.75rem 1.5rem; - border: 2px solid var(--primary-color); - border-radius: 8px; - font-size: 0.95rem; - font-weight: 600; + display: inline-flex; + align-items: center; + gap: 0.4rem; + padding: 0.45rem 0.875rem; + background: var(--surface); + color: var(--text-2); + border: 1px solid var(--border); + border-radius: var(--radius); + font-size: 0.8rem; + font-weight: 500; cursor: pointer; - transition: all 0.2s; + transition: all 0.15s; + font-family: var(--sans); } .btn-secondary:hover { - background: var(--primary-color); - color: white; + border-color: var(--border-dark); + color: var(--text-1); +} + +.btn-add { + display: inline-flex; + align-items: center; + padding: 0.375rem 0.75rem; + background: var(--surface); + color: var(--accent); + border: 1px solid var(--border); + border-radius: var(--radius); + font-size: 0.775rem; + font-weight: 500; + cursor: pointer; + transition: all 0.15s; + white-space: nowrap; + font-family: var(--sans); +} + +.btn-add:hover { + border-color: var(--accent); + background: var(--accent-dim); } .btn-remove { - background: var(--error-color); - color: white; - width: 32px; - height: 32px; - border: none; - border-radius: 6px; - font-size: 1.2rem; + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + background: transparent; + color: var(--text-3); + border: 1px solid var(--border); + border-radius: var(--radius); + font-size: 0.7rem; cursor: pointer; - transition: all 0.2s; + transition: all 0.15s; flex-shrink: 0; } .btn-remove:hover { - background: #dc2626; - transform: scale(1.1); + background: var(--error-bg); + border-color: #fca5a5; + color: var(--error); } +/* โ”€โ”€ ACTION โ”€โ”€ */ .action-section { text-align: center; - margin: 2rem 0; + padding: 1.25rem 0 1.5rem; } +/* โ”€โ”€ RESULTS โ”€โ”€ */ #results { - font-family: 'Courier New', Courier, monospace; + font-family: var(--mono); } .result-success { - background: #d1fae5; - border-left: 4px solid var(--success-color); - padding: 1.5rem; - border-radius: 8px; - margin-bottom: 1rem; + background: var(--success-bg); + border: 1px solid #bbf7d0; + border-left: 3px solid var(--success); + padding: 1.25rem 1.5rem; + border-radius: var(--radius); } .result-error { - background: #fee2e2; - border-left: 4px solid var(--error-color); - padding: 1.5rem; - border-radius: 8px; - margin-bottom: 1rem; + background: var(--error-bg); + border: 1px solid #fecaca; + border-left: 3px solid var(--error); + padding: 1.25rem 1.5rem; + border-radius: var(--radius); } .result-warning { - background: #fef3c7; - border-left: 4px solid var(--warning-color); - padding: 1.5rem; - border-radius: 8px; - margin-bottom: 1rem; + background: var(--warning-bg); + border: 1px solid #fde68a; + border-left: 3px solid var(--warning); + padding: 1.25rem 1.5rem; + border-radius: var(--radius); } .result-label { - font-weight: 700; + font-size: 0.67rem; + letter-spacing: 0.1em; + text-transform: uppercase; + color: var(--text-3); margin-bottom: 0.5rem; - font-size: 1.1rem; + font-weight: 500; } .result-value { - font-size: 1.2rem; - margin: 0.5rem 0; + font-size: 1rem; + font-weight: 500; + color: var(--text-1); word-break: break-all; + line-height: 1.6; } .debug-info { - background: #f1f5f9; - padding: 1rem; - border-radius: 6px; margin-top: 1rem; - font-size: 0.85rem; + padding: 0.875rem 1rem; + background: var(--surface-2); + border-radius: var(--radius); + border: 1px solid var(--border); } .debug-info h4 { - margin-bottom: 0.5rem; - color: var(--text-secondary); + font-size: 0.67rem; + letter-spacing: 0.1em; + text-transform: uppercase; + color: var(--text-3); + margin-bottom: 0.6rem; + font-weight: 500; } .debug-info ul { list-style: none; - padding-left: 1rem; + padding: 0; } .debug-info li { - margin-bottom: 0.25rem; - color: var(--text-secondary); + font-size: 0.8rem; + color: var(--text-2); + padding: 0.15rem 0; + line-height: 1.5; } +.debug-info li::before { + content: 'ยท '; + color: var(--accent); +} + +/* โ”€โ”€ FOOTER โ”€โ”€ */ footer { - background: var(--bg-color); - padding: 1.5rem; + border-top: 1px solid var(--border); + padding: 1.1rem 2.5rem; text-align: center; - color: var(--text-secondary); - font-size: 0.9rem; - border-top: 1px solid var(--border-color); + font-size: 0.78rem; + color: var(--text-3); } footer a { - color: var(--primary-color); + color: var(--text-2); text-decoration: none; - font-weight: 600; + font-weight: 500; + transition: color 0.15s; } footer a:hover { - text-decoration: underline; + color: var(--accent); } +/* โ”€โ”€ RESPONSIVE โ”€โ”€ */ @media (max-width: 640px) { body { - padding: 1rem 0.5rem; + padding: 0 0 3rem; + } + + .container { + border-radius: 0; + border-left: none; + border-right: none; } header { - padding: 1.5rem 1rem; + padding: 1.75rem 1.25rem 1.5rem; } - header h1 { - font-size: 1.5rem; + .brand h1 { + font-size: 1.4rem; } main { - padding: 1rem; + padding: 1.25rem; } .section { - padding: 1rem; + padding: 1.1rem; } .dns-mapping { flex-wrap: wrap; } - .dns-host, .dns-ip { - min-width: 100%; + .dns-host, + .dns-ip { + min-width: calc(50% - 1.5rem); + flex: none; + } + + .dns-header { + flex-direction: column; + gap: 0.5rem; } .btn-primary { width: 100%; + justify-content: center; } - .links-grid { - grid-template-columns: 1fr; + footer { + padding: 1rem 1.25rem; } -} - -/* About Section Styles */ -.about-section { - background-color: var(--card-bg); - border-top: 1px solid var(--border-color); -} - -.about-content { - color: var(--text-color); - line-height: 1.6; -} - -.links-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 1rem; - margin-top: 1.5rem; -} - -.btn-outline { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0.75rem 1rem; - border: 1px solid var(--primary-color); - border-radius: 6px; - color: var(--primary-color); - text-decoration: none; - font-weight: 500; - transition: all 0.2s; -} - -.btn-outline:hover { - background-color: var(--primary-color); - color: white; - transform: translateY(-2px); - box-shadow: 0 4px 6px rgba(0,0,0,0.1); -} - -.icon { - margin-right: 0.5rem; - font-size: 1.2em; -} +} \ No newline at end of file