From c71ec612bb4a38fc16fe97278afecb53bceee760 Mon Sep 17 00:00:00 2001 From: mifi Date: Mon, 16 Feb 2026 12:57:44 -0300 Subject: [PATCH] Accessibility fixes --- .prettierrc | 4 +- eslint.config.js | 14 +- scripts/build.js | 78 +++--- src/assets/css/site.css | 59 +++- src/assets/js/accordion.js | 37 +++ src/assets/js/current-year.js | 4 + src/assets/js/ga-init.js | 18 +- src/help/index.html | 507 ++++++++++++++++------------------ src/index.html | 50 ++-- stylelint.config.js | 8 +- 10 files changed, 418 insertions(+), 361 deletions(-) create mode 100644 src/assets/js/accordion.js create mode 100644 src/assets/js/current-year.js diff --git a/.prettierrc b/.prettierrc index 9358f75..782f356 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,8 +1,8 @@ { - "semi": false, + "semi": true, "singleQuote": true, "tabWidth": 4, - "trailingComma": "none", + "trailingComma": "all", "overrides": [ { "files": "*.yml", diff --git a/eslint.config.js b/eslint.config.js index 88e0620..d75f919 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,4 +1,4 @@ -import prettierConfig from 'eslint-config-prettier/flat' +import prettierConfig from 'eslint-config-prettier/flat'; export default [ { @@ -9,12 +9,12 @@ export default [ globals: { window: 'readonly', document: 'readonly', - dataLayer: 'writable' - } + dataLayer: 'writable', + }, }, rules: { - 'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }] - } + 'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], + }, }, - prettierConfig -] + prettierConfig, +]; diff --git a/scripts/build.js b/scripts/build.js index 1e987cb..faf3d10 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -8,71 +8,71 @@ import { readFileSync, writeFileSync, cpSync, - readdirSync -} from 'fs' -import { join, dirname, extname } from 'path' -import { fileURLToPath } from 'url' -import Beasties from 'beasties' -import { minify as minifyJs } from 'terser' -import CleanCSS from 'clean-css' + readdirSync, +} from 'fs'; +import { join, dirname, extname } from 'path'; +import { fileURLToPath } from 'url'; +import Beasties from 'beasties'; +import { minify as minifyJs } from 'terser'; +import CleanCSS from 'clean-css'; -const __dirname = dirname(fileURLToPath(import.meta.url)) -const root = join(__dirname, '..') -const srcDir = join(root, 'src') -const distDir = join(root, 'dist') +const __dirname = dirname(fileURLToPath(import.meta.url)); +const root = join(__dirname, '..'); +const srcDir = join(root, 'src'); +const distDir = join(root, 'dist'); function getFiles(dir, files = []) { - const entries = readdirSync(dir, { withFileTypes: true }) + const entries = readdirSync(dir, { withFileTypes: true }); for (const e of entries) { - const full = join(dir, e.name) - if (e.isDirectory()) getFiles(full, files) - else files.push(full) + const full = join(dir, e.name); + if (e.isDirectory()) getFiles(full, files); + else files.push(full); } - return files + return files; } async function main() { // 1. Clean and copy src → dist - rmSync(distDir, { recursive: true, force: true }) - mkdirSync(distDir, { recursive: true }) - cpSync(srcDir, distDir, { recursive: true }) + rmSync(distDir, { recursive: true, force: true }); + mkdirSync(distDir, { recursive: true }); + cpSync(srcDir, distDir, { recursive: true }); - const distFiles = getFiles(distDir) + const distFiles = getFiles(distDir); // 2. Minify JS - const jsFiles = distFiles.filter((f) => extname(f) === '.js') + const jsFiles = distFiles.filter((f) => extname(f) === '.js'); for (const f of jsFiles) { - const code = readFileSync(f, 'utf8') - const result = await minifyJs(code, { format: { comments: false } }) - if (result.code) writeFileSync(f, result.code) + const code = readFileSync(f, 'utf8'); + const result = await minifyJs(code, { format: { comments: false } }); + if (result.code) writeFileSync(f, result.code); } // 3. Minify CSS - const cleanCss = new CleanCSS({ level: 2 }) - const cssFiles = distFiles.filter((f) => extname(f) === '.css') + const cleanCss = new CleanCSS({ level: 2 }); + const cssFiles = distFiles.filter((f) => extname(f) === '.css'); for (const f of cssFiles) { - const code = readFileSync(f, 'utf8') - const result = cleanCss.minify(code) - if (!result.errors.length) writeFileSync(f, result.styles) + const code = readFileSync(f, 'utf8'); + const result = cleanCss.minify(code); + if (!result.errors.length) writeFileSync(f, result.styles); } // 4. Inline critical CSS with Beasties for all HTML files (no browser; works in CI) - const htmlFiles = distFiles.filter((f) => extname(f) === '.html') + const htmlFiles = distFiles.filter((f) => extname(f) === '.html'); const beasties = new Beasties({ path: distDir, preload: 'default', - logLevel: 'warn' - }) + logLevel: 'warn', + }); for (const htmlFile of htmlFiles) { - const html = readFileSync(htmlFile, 'utf8') - const inlined = await beasties.process(html) - writeFileSync(htmlFile, inlined) + const html = readFileSync(htmlFile, 'utf8'); + const inlined = await beasties.process(html); + writeFileSync(htmlFile, inlined); } - console.log('Build complete: dist/') + console.log('Build complete: dist/'); } main().catch((err) => { - console.error(err) - process.exit(1) -}) + console.error(err); + process.exit(1); +}); diff --git a/src/assets/css/site.css b/src/assets/css/site.css index 00c1889..3d3cd71 100644 --- a/src/assets/css/site.css +++ b/src/assets/css/site.css @@ -44,9 +44,53 @@ body { } body { - display: flex; align-items: center; + display: flex; + flex-direction: column; + gap: 1rem; + justify-content: space-between; +} + +.content { + display: flex; + flex: 1; justify-content: center; + align-items: center; +} + +.footer { + color: #374151; + font-size: 0.94em; + letter-spacing: 0.01em; + padding: 1rem; + text-align: center; + width: 100%; +} + +@media (prefers-color-scheme: dark) { + .footer { + color: #aab2bd; + } +} + +.skip-link { + position: absolute; + top: 0; + left: 0; + padding: 0.75rem 1.25rem; + background: var(--accent); + color: var(--button-text); + font-weight: 600; + text-decoration: none; + z-index: 100; + transform: translateY(-100%); + transition: transform 0.2s; +} + +.skip-link:focus { + transform: translateY(0); + outline: 2px solid var(--accent-light); + outline-offset: 2px; } h1 { @@ -194,9 +238,10 @@ td:first-child { white-space: nowrap; } +/* Tip colors chosen for WCAG 2.2 AAA (≥7:1 contrast) */ .tip { background: #eef2ff; - color: var(--accent-light); + color: #3730a3; border-radius: 0.7em; font-size: 0.98em; padding: 0.48em 0.8em; @@ -207,7 +252,7 @@ td:first-child { @media (prefers-color-scheme: dark) { .tip { background: #232555; - color: #a5b4fc; + color: #c7d2fe; } } @@ -295,14 +340,6 @@ td:first-child { color: var(--faq-a); } -.footer { - margin-top: 2.5em; - text-align: center; - color: #bbb; - font-size: 0.94em; - letter-spacing: 0.01em; -} - @media (width <= 600px) { .container { padding: 1.1rem 0.5rem 1rem; diff --git a/src/assets/js/accordion.js b/src/assets/js/accordion.js new file mode 100644 index 0000000..c3f2a8b --- /dev/null +++ b/src/assets/js/accordion.js @@ -0,0 +1,37 @@ +// Native accessible accordion +document.querySelectorAll('.accordion-trigger').forEach((btn) => { + btn.addEventListener('click', function () { + const section = btn.closest('.accordion-section'); + const expanded = btn.getAttribute('aria-expanded') === 'true'; + document.querySelectorAll('.accordion-section').forEach((s) => { + if (s === section) { + s.classList.toggle('open', !expanded); + btn.setAttribute('aria-expanded', String(!expanded)); + const content = btn.nextElementSibling; + content.style.maxHeight = !expanded + ? content.scrollHeight + 40 + 'px' + : '0px'; + } else { + s.classList.remove('open'); + s.querySelector('.accordion-trigger').setAttribute( + 'aria-expanded', + 'false', + ); + s.querySelector('.accordion-content').style.maxHeight = '0px'; + } + }); + }); + + // Allow arrow navigation + btn.addEventListener('keydown', function (e) { + if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { + const triggers = Array.from( + document.querySelectorAll('.accordion-trigger'), + ); + let idx = triggers.indexOf(e.target); + if (e.key === 'ArrowDown') idx = (idx + 1) % triggers.length; + else idx = (idx - 1 + triggers.length) % triggers.length; + triggers[idx].focus(); + } + }); +}); diff --git a/src/assets/js/current-year.js b/src/assets/js/current-year.js new file mode 100644 index 0000000..00af012 --- /dev/null +++ b/src/assets/js/current-year.js @@ -0,0 +1,4 @@ +(function () { + const year = new Date().getFullYear(); + document.getElementById('current-year').textContent = `–${year}`; +})(); diff --git a/src/assets/js/ga-init.js b/src/assets/js/ga-init.js index 3820ff4..5c27172 100644 --- a/src/assets/js/ga-init.js +++ b/src/assets/js/ga-init.js @@ -1,11 +1,11 @@ -;(function () { - var script = document.currentScript - var id = script && script.getAttribute('data-ga-id') - if (!id) return - window.dataLayer = window.dataLayer || [] +(function () { + var script = document.currentScript; + var id = script && script.getAttribute('data-ga-id'); + if (!id) return; + window.dataLayer = window.dataLayer || []; function gtag() { - window.dataLayer.push(arguments) + window.dataLayer.push(arguments); } - gtag('js', new Date()) - gtag('config', id, { anonymize_ip: true }) -})() + gtag('js', new Date()); + gtag('config', id, { anonymize_ip: true }); +})(); diff --git a/src/help/index.html b/src/help/index.html index 959cdb6..774b832 100644 --- a/src/help/index.html +++ b/src/help/index.html @@ -95,283 +95,248 @@ -
- -

Welcome to Email from mifi Ventures

-
- Let's get your inbox ready! 📬
-

- Friendly help for setting up your email—works with Outlook, - Apple Mail, Thunderbird, phones, and more. -

-
+ +
+
+ +

Welcome to Email from mifi Ventures

+
+ Let's get your inbox ready! 📬
+

+ Friendly help for setting up your email—works with + Outlook, Apple Mail, Thunderbird, phones, and more. +

+
-
-

General Settings (All Clients)

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Email Addressyour.name@yourdomain.com
Usernameyour.name@yourdomain.com
Password(your email password)
Incoming Servermail.mifi.holdings
Outgoing Servermail.mifi.holdings
IMAP Port993 (SSL/TLS)
POP3 Port995 (SSL/TLS)
SMTP Port587 (STARTTLS) or 465 (SSL/TLS)
AuthenticationRequired (use same as incoming)
EncryptionSSL/TLS or STARTTLS
- Tip: Always use your full email address as your - username! -
- -
- -
- -
-
    -
  1. Go to File → Add Account
  2. -
  3. Enter your full email address
  4. -
  5. - Choose Advanced options → check “Set up - manually” -
  6. -
  7. Select IMAP (recommended) or POP
  8. -
  9. - Incoming server: - mail.mifi.holdings, port - 993 (SSL/TLS) -
  10. -
  11. - Outgoing server: - mail.mifi.holdings, port - 587 (STARTTLS) or 465 (SSL/TLS) -
  12. -
  13. - Username: full email address; Password: your - password -
  14. -
  15. Click Connect
  16. -
- If sending fails, make sure “Require logon using - SPA” is unchecked. -
+

General Settings (All Clients)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Email Addressyour.name@yourdomain.com
Usernameyour.name@yourdomain.com
Password(your email password)
Incoming Servermail.mifi.holdings
Outgoing Servermail.mifi.holdings
IMAP Port993 (SSL/TLS)
POP3 Port995 (SSL/TLS)
SMTP Port587 (STARTTLS) or 465 (SSL/TLS)
AuthenticationRequired (use same as incoming)
EncryptionSSL/TLS or STARTTLS
+ Tip: Always use your full email address as your + username!
- -
- -
-
    -
  1. - Add Account → Other Mail Account -
  2. -
  3. Enter your name, email, and password
  4. -
  5. - Incoming/Outgoing server: - mail.mifi.holdings -
  6. -
  7. - IMAP port: 993 (SSL); SMTP port: - 587 (STARTTLS) or 465 (SSL) -
  8. -
  9. Use full email address for username
  10. -
-
-
- - -
- -
-
    -
  1. Menu → Account Settings → Add Mail Account
  2. -
  3. Fill in your name, email, and password
  4. -
  5. - Click “Configure manually” and use settings - above -
  6. -
-
-
- - -
- -
-
    -
  • Add Account → Other
  • -
  • Enter your email and password
  • -
  • - Manual setup: mail.mifi.holdings, - correct ports, SSL/TLS required -
  • -
  • - Gmail app: tap profile → Add account → Other, - fill in details, use IMAP -
  • -
-
-
- - -
- -
-
Q: My email won’t send?
-
- Check that you’re using your full email address for - both incoming and outgoing username, and that the - port is 587 or 465. +
+ +
+ +
+
    +
  1. Go to File → Add Account
  2. +
  3. Enter your full email address
  4. +
  5. + Choose Advanced options → check “Set + up manually” +
  6. +
  7. Select IMAP (recommended) or POP
  8. +
  9. + Incoming server: + mail.mifi.holdings, port + 993 (SSL/TLS) +
  10. +
  11. + Outgoing server: + mail.mifi.holdings, port + 587 (STARTTLS) or + 465 (SSL/TLS) +
  12. +
  13. + Username: full email address; Password: your + password +
  14. +
  15. Click Connect
  16. +
+ If sending fails, make sure “Require logon + using SPA” is unchecked.
-
Q: SSL/TLS errors?
-
- Ensure SSL or STARTTLS is enabled for both incoming - and outgoing mail. -
-
Q: Still stuck?
-
- Contact - postmaster@mifi.holdings.
- Please include any error messages, your mail app, - and a screenshot if you can! -
-
-
+ - -
- -
-
    -
  • - IMAP syncs your mail everywhere—choose - IMAP unless you know you want POP3. -
  • -
  • - Your login is always your - full email address. -
  • -
  • - Check your Spam/Junk folder for misfiled good - emails. -
  • -
  • - Advanced: IMAP path prefix = - (leave blank); SMTP authentication is - always required. -
  • -
-
-
-
- + +
+ +
+
    +
  1. + Add Account → Other Mail Account +
  2. +
  3. Enter your name, email, and password
  4. +
  5. + Incoming/Outgoing server: + mail.mifi.holdings +
  6. +
  7. + IMAP port: 993 (SSL); SMTP port: + 587 (STARTTLS) or 465 (SSL) +
  8. +
  9. Use full email address for username
  10. +
+
+
+ + +
+ +
+
    +
  1. + Menu → Account Settings → Add Mail Account +
  2. +
  3. Fill in your name, email, and password
  4. +
  5. + Click “Configure manually” and use settings + above +
  6. +
+
+
+ + +
+ +
+
    +
  • Add Account → Other
  • +
  • Enter your email and password
  • +
  • + Manual setup: + mail.mifi.holdings, correct + ports, SSL/TLS required +
  • +
  • + Gmail app: tap profile → Add account → + Other, fill in details, use IMAP +
  • +
+
+
+ + +
+ +
+
Q: My email won’t send?
+
+ Check that you’re using your full email address + for both incoming and outgoing username, and + that the port is 587 or 465. +
+
Q: SSL/TLS errors?
+
+ Ensure SSL or STARTTLS is enabled for both + incoming and outgoing mail. +
+
Q: Still stuck?
+
+ Contact + postmaster@mifi.holdings.
+ Please include any error messages, your mail + app, and a screenshot if you can! +
+
+
+ + +
+ +
+
    +
  • + IMAP syncs your mail + everywhere—choose IMAP unless you know you + want POP3. +
  • +
  • + Your login is always your + full email address. +
  • +
  • + Check your Spam/Junk folder for misfiled + good emails. +
  • +
  • + Advanced: IMAP path prefix = + (leave blank); SMTP authentication is + always required. +
  • +
+
+
+
+
- + + + diff --git a/src/index.html b/src/index.html index cba591e..4b18094 100644 --- a/src/index.html +++ b/src/index.html @@ -57,24 +57,38 @@ -
-
📮
-

This is just a mailbox.

-

- You've reached mail.mifi.holdings.
- There's nothing exciting here's nothing exciting - here—just some gears whirring and mail being sorted.
- Looking for your messages? -

- Email Setup Help - Go to Webmail - Change/Forgot Password + +
+
+
📮
+

This is just a mailbox.

+

+ You've reached mail.mifi.holdings.
+ There's nothing exciting here's nothing exciting + here—just some gears whirring and mail being sorted.
+ Looking for your messages? +

+ Email Setup Help + Go to Webmail + Change/Forgot Password +
+
+ Email from mifi Holdings · © 2025 + mifi Ventures, LLC +
+ diff --git a/stylelint.config.js b/stylelint.config.js index aaeff91..48b77a6 100644 --- a/stylelint.config.js +++ b/stylelint.config.js @@ -2,7 +2,7 @@ export default { extends: ['stylelint-config-standard'], overrides: [ { - files: ['src/**/*.css'] - } - ] -} + files: ['src/**/*.css'], + }, + ], +};