Accessibility fixes
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/deploy Pipeline was successful

This commit is contained in:
2026-02-16 12:57:44 -03:00
parent 635594aea8
commit c71ec612bb
10 changed files with 418 additions and 361 deletions

View File

@@ -1,8 +1,8 @@
{
"semi": false,
"semi": true,
"singleQuote": true,
"tabWidth": 4,
"trailingComma": "none",
"trailingComma": "all",
"overrides": [
{
"files": "*.yml",

View File

@@ -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,
];

View File

@@ -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);
});

View File

@@ -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;

View File

@@ -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();
}
});
});

View File

@@ -0,0 +1,4 @@
(function () {
const year = new Date().getFullYear();
document.getElementById('current-year').textContent = `${year}`;
})();

View File

@@ -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 });
})();

View File

@@ -95,283 +95,248 @@
</script>
</head>
<body>
<div class="container faq">
<nav aria-label="Breadcrumb">
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li aria-current="page">Email Setup & Help</li>
</ol>
</nav>
<h1 class="text-center">Welcome to Email from mifi Ventures</h1>
<div class="intro">
<strong>Let&apos;s get your inbox ready! 📬</strong><br />
<p>
Friendly help for setting up your email—works with Outlook,
Apple Mail, Thunderbird, phones, and more.
</p>
</div>
<a href="#main-content" class="skip-link">Skip to main content</a>
<div class="content">
<main id="main-content" class="container faq">
<nav aria-label="Breadcrumb">
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li aria-current="page">Email Setup & Help</li>
</ol>
</nav>
<h1 class="text-center">Welcome to Email from mifi Ventures</h1>
<div class="intro">
<strong>Let&apos;s get your inbox ready! 📬</strong><br />
<p>
Friendly help for setting up your email—works with
Outlook, Apple Mail, Thunderbird, phones, and more.
</p>
</div>
<section
class="general-settings"
aria-label="General Email Settings"
>
<h2>General Settings (All Clients)</h2>
<table>
<tr>
<td>Email Address</td>
<td>your.name@yourdomain.com</td>
</tr>
<tr>
<td>Username</td>
<td>your.name@yourdomain.com</td>
</tr>
<tr>
<td>Password</td>
<td>(your email password)</td>
</tr>
<tr>
<td>Incoming Server</td>
<td><b>mail.mifi.holdings</b></td>
</tr>
<tr>
<td>Outgoing Server</td>
<td><b>mail.mifi.holdings</b></td>
</tr>
<tr>
<td>IMAP Port</td>
<td>993 (SSL/TLS)</td>
</tr>
<tr>
<td>POP3 Port</td>
<td>995 (SSL/TLS)</td>
</tr>
<tr>
<td>SMTP Port</td>
<td>587 (STARTTLS) or 465 (SSL/TLS)</td>
</tr>
<tr>
<td>Authentication</td>
<td>Required (use same as incoming)</td>
</tr>
<tr>
<td>Encryption</td>
<td>SSL/TLS or STARTTLS</td>
</tr>
</table>
<span class="tip"
>Tip: Always use your <b>full email address</b> as your
username!</span
<section
class="general-settings"
aria-label="General Email Settings"
>
</section>
<div class="accordion" id="helpAccordion">
<!-- Outlook -->
<section class="accordion-section">
<button class="accordion-trigger" aria-expanded="false">
<span class="icon"></span> Microsoft Outlook
</button>
<div class="accordion-content">
<ol>
<li>Go to <b>File → Add Account</b></li>
<li>Enter your full email address</li>
<li>
Choose <b>Advanced options</b> → check “Set up
manually”
</li>
<li>Select <b>IMAP</b> (recommended) or POP</li>
<li>
Incoming server:
<code>mail.mifi.holdings</code>, port
<b>993</b> (SSL/TLS)
</li>
<li>
Outgoing server:
<code>mail.mifi.holdings</code>, port
<b>587</b> (STARTTLS) or <b>465</b> (SSL/TLS)
</li>
<li>
Username: full email address; Password: your
password
</li>
<li>Click <b>Connect</b></li>
</ol>
<span class="tip"
>If sending fails, make sure “Require logon using
SPA” is <b>unchecked</b>.</span
>
</div>
<h2>General Settings (All Clients)</h2>
<table>
<tr>
<td>Email Address</td>
<td>your.name@yourdomain.com</td>
</tr>
<tr>
<td>Username</td>
<td>your.name@yourdomain.com</td>
</tr>
<tr>
<td>Password</td>
<td>(your email password)</td>
</tr>
<tr>
<td>Incoming Server</td>
<td><b>mail.mifi.holdings</b></td>
</tr>
<tr>
<td>Outgoing Server</td>
<td><b>mail.mifi.holdings</b></td>
</tr>
<tr>
<td>IMAP Port</td>
<td>993 (SSL/TLS)</td>
</tr>
<tr>
<td>POP3 Port</td>
<td>995 (SSL/TLS)</td>
</tr>
<tr>
<td>SMTP Port</td>
<td>587 (STARTTLS) or 465 (SSL/TLS)</td>
</tr>
<tr>
<td>Authentication</td>
<td>Required (use same as incoming)</td>
</tr>
<tr>
<td>Encryption</td>
<td>SSL/TLS or STARTTLS</td>
</tr>
</table>
<span class="tip"
>Tip: Always use your <b>full email address</b> as your
username!</span
>
</section>
<!-- Apple Mail -->
<section class="accordion-section">
<button class="accordion-trigger" aria-expanded="false">
<span class="icon"></span> Apple Mail (macOS, iOS)
</button>
<div class="accordion-content">
<ol>
<li>
Add Account &rarr; <b>Other Mail Account</b>
</li>
<li>Enter your name, email, and password</li>
<li>
Incoming/Outgoing server:
<code>mail.mifi.holdings</code>
</li>
<li>
IMAP port: <b>993</b> (SSL); SMTP port:
<b>587</b> (STARTTLS) or <b>465</b> (SSL)
</li>
<li>Use full email address for username</li>
</ol>
</div>
</section>
<!-- Thunderbird -->
<section class="accordion-section">
<button class="accordion-trigger" aria-expanded="false">
<span class="icon"></span> Thunderbird
</button>
<div class="accordion-content">
<ol>
<li>Menu → Account Settings → Add Mail Account</li>
<li>Fill in your name, email, and password</li>
<li>
Click “Configure manually” and use settings
above
</li>
</ol>
</div>
</section>
<!-- Mobile -->
<section class="accordion-section">
<button class="accordion-trigger" aria-expanded="false">
<span class="icon"></span> iOS / Android Mail / Gmail
App
</button>
<div class="accordion-content">
<ul>
<li>Add Account → Other</li>
<li>Enter your email and password</li>
<li>
Manual setup: <code>mail.mifi.holdings</code>,
correct ports, SSL/TLS required
</li>
<li>
Gmail app: tap profile → Add account → Other,
fill in details, use IMAP
</li>
</ul>
</div>
</section>
<!-- FAQ -->
<section class="accordion-section">
<button class="accordion-trigger" aria-expanded="false">
<span class="icon"></span> FAQ / Troubleshooting
</button>
<div class="accordion-content">
<div class="faq-q">Q: My email wont send?</div>
<div class="faq-a">
Check that youre using your full email address for
both incoming and outgoing username, and that the
port is 587 or 465.
<div class="accordion" id="helpAccordion">
<!-- Outlook -->
<section class="accordion-section">
<button class="accordion-trigger" aria-expanded="false">
<span class="icon"></span> Microsoft Outlook
</button>
<div class="accordion-content">
<ol>
<li>Go to <b>File → Add Account</b></li>
<li>Enter your full email address</li>
<li>
Choose <b>Advanced options</b> → check “Set
up manually”
</li>
<li>Select <b>IMAP</b> (recommended) or POP</li>
<li>
Incoming server:
<code>mail.mifi.holdings</code>, port
<b>993</b> (SSL/TLS)
</li>
<li>
Outgoing server:
<code>mail.mifi.holdings</code>, port
<b>587</b> (STARTTLS) or
<b>465</b> (SSL/TLS)
</li>
<li>
Username: full email address; Password: your
password
</li>
<li>Click <b>Connect</b></li>
</ol>
<span class="tip"
>If sending fails, make sure “Require logon
using SPA” is <b>unchecked</b>.</span
>
</div>
<div class="faq-q">Q: SSL/TLS errors?</div>
<div class="faq-a">
Ensure SSL or STARTTLS is enabled for both incoming
and outgoing mail.
</div>
<div class="faq-q">Q: Still stuck?</div>
<div class="faq-a">
Contact
<a href="mailto:postmaster@mifi.holdings"
>postmaster@mifi.holdings</a
>.<br />
Please include any error messages, your mail app,
and a screenshot if you can!
</div>
</div>
</section>
</section>
<!-- Pro Tips -->
<section class="accordion-section">
<button class="accordion-trigger" aria-expanded="false">
<span class="icon"></span> Pro Tips & Advanced
</button>
<div class="accordion-content">
<ul>
<li>
<b>IMAP syncs</b> your mail everywhere—choose
IMAP unless you know you want POP3.
</li>
<li>
Your login is always your
<b>full email address</b>.
</li>
<li>
Check your Spam/Junk folder for misfiled good
emails.
</li>
<li>
Advanced: IMAP path prefix =
<b>(leave blank)</b>; SMTP authentication is
always required.
</li>
</ul>
</div>
</section>
</div>
<div class="footer">
Email from mifi Ventures &middot; Help Page &ndash; &copy; 2025
mifi Ventures, LLC
</div>
<!-- Apple Mail -->
<section class="accordion-section">
<button class="accordion-trigger" aria-expanded="false">
<span class="icon"></span> Apple Mail (macOS, iOS)
</button>
<div class="accordion-content">
<ol>
<li>
Add Account &rarr; <b>Other Mail Account</b>
</li>
<li>Enter your name, email, and password</li>
<li>
Incoming/Outgoing server:
<code>mail.mifi.holdings</code>
</li>
<li>
IMAP port: <b>993</b> (SSL); SMTP port:
<b>587</b> (STARTTLS) or <b>465</b> (SSL)
</li>
<li>Use full email address for username</li>
</ol>
</div>
</section>
<!-- Thunderbird -->
<section class="accordion-section">
<button class="accordion-trigger" aria-expanded="false">
<span class="icon"></span> Thunderbird
</button>
<div class="accordion-content">
<ol>
<li>
Menu → Account Settings → Add Mail Account
</li>
<li>Fill in your name, email, and password</li>
<li>
Click “Configure manually” and use settings
above
</li>
</ol>
</div>
</section>
<!-- Mobile -->
<section class="accordion-section">
<button class="accordion-trigger" aria-expanded="false">
<span class="icon"></span> iOS / Android Mail /
Gmail App
</button>
<div class="accordion-content">
<ul>
<li>Add Account → Other</li>
<li>Enter your email and password</li>
<li>
Manual setup:
<code>mail.mifi.holdings</code>, correct
ports, SSL/TLS required
</li>
<li>
Gmail app: tap profile → Add account →
Other, fill in details, use IMAP
</li>
</ul>
</div>
</section>
<!-- FAQ -->
<section class="accordion-section">
<button class="accordion-trigger" aria-expanded="false">
<span class="icon"></span> FAQ / Troubleshooting
</button>
<div class="accordion-content">
<div class="faq-q">Q: My email wont send?</div>
<div class="faq-a">
Check that youre using your full email address
for both incoming and outgoing username, and
that the port is 587 or 465.
</div>
<div class="faq-q">Q: SSL/TLS errors?</div>
<div class="faq-a">
Ensure SSL or STARTTLS is enabled for both
incoming and outgoing mail.
</div>
<div class="faq-q">Q: Still stuck?</div>
<div class="faq-a">
Contact
<a href="mailto:postmaster@mifi.holdings"
>postmaster@mifi.holdings</a
>.<br />
Please include any error messages, your mail
app, and a screenshot if you can!
</div>
</div>
</section>
<!-- Pro Tips -->
<section class="accordion-section">
<button class="accordion-trigger" aria-expanded="false">
<span class="icon"></span> Pro Tips & Advanced
</button>
<div class="accordion-content">
<ul>
<li>
<b>IMAP syncs</b> your mail
everywhere—choose IMAP unless you know you
want POP3.
</li>
<li>
Your login is always your
<b>full email address</b>.
</li>
<li>
Check your Spam/Junk folder for misfiled
good emails.
</li>
<li>
Advanced: IMAP path prefix =
<b>(leave blank)</b>; SMTP authentication is
always required.
</li>
</ul>
</div>
</section>
</div>
</main>
</div>
<script>
// 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()
}
})
})
</script>
<footer class="footer">
Email from mifi Holdings &middot; Help Page &middot; &copy;
2025<span id="current-year"></span>
mifi Ventures, LLC
</footer>
<script defer src="/assets/js/current-year.js"></script>
<script defer src="/assets/js/accordion.js"></script>
</body>
</html>

View File

@@ -57,24 +57,38 @@
</script>
</head>
<body>
<div class="container text-center">
<div class="emoji">📮</div>
<h1>This is just a mailbox.</h1>
<p>
You&apos;ve reached <b>mail.mifi.holdings</b>.<br />
There&apos;s nothing exciting here&apos;s nothing exciting
here—just some gears whirring and mail being sorted.<br />
Looking for your messages?
</p>
<a class="button" href="/help">Email Setup Help</a>
<a class="button" href="https://webmail.mifi.holdings"
>Go to Webmail</a
>
<a
class="button"
href="https://postmaster.mifi.holdings/users/login.php"
>Change/Forgot Password</a
>
<a href="#main-content" class="skip-link">Skip to main content</a>
<div class="content">
<main id="main-content" class="container text-center">
<div class="emoji">📮</div>
<h1>This is just a mailbox.</h1>
<p>
You&apos;ve reached <b>mail.mifi.holdings</b>.<br />
There&apos;s nothing exciting here&apos;s nothing exciting
here—just some gears whirring and mail being sorted.<br />
Looking for your messages?
</p>
<a class="button" href="/help">Email Setup Help</a>
<a
class="button"
href="https://webmail.mifi.holdings"
target="_blank"
>Go to Webmail</a
>
<a
class="button"
href="https://postmaster.mifi.holdings/users/login.php"
target="_blank"
>Change/Forgot Password</a
>
</main>
</div>
<footer class="footer">
Email from mifi Holdings &middot; &copy; 2025<span
id="current-year"
></span>
mifi Ventures, LLC
</footer>
<script defer src="/assets/js/current-year.js"></script>
</body>
</html>

View File

@@ -2,7 +2,7 @@ export default {
extends: ['stylelint-config-standard'],
overrides: [
{
files: ['src/**/*.css']
}
]
}
files: ['src/**/*.css'],
},
],
};