Wire it all up...

This commit is contained in:
2026-02-13 18:09:10 -03:00
parent 36ff5d0c1a
commit 05a27fc19c
29 changed files with 3219 additions and 1078 deletions

View File

@@ -0,0 +1,27 @@
{
"name": "mail-landing",
"image": "mcr.microsoft.com/devcontainers/javascript-node:1-22-bookworm",
"onCreateCommand": "npm install -g pnpm@10",
"postCreateCommand": "pnpm install",
"forwardPorts": [3000],
"portsAttributes": {
"3000": {
"label": "Preview",
"onAutoForward": "notify"
}
},
"customizations": {
"vscode": {
"extensions": [
"esbenp.prettier-vscode",
"stylelint.vscode-stylelint",
"dbaeumer.vscode-eslint"
],
"settings": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
},
"remoteUser": "node"
}

View File

@@ -1,9 +1,16 @@
# Avoid sending secrets or dev tooling into the build context # Avoid sending secrets or dev tooling into the build context
# config/ and plugins/ are included (no secrets; PHP configs read from ENV at runtime) # config/ and plugins/ are included (no secrets; PHP configs read from ENV at runtime)
.devcontainer
.pnpm-store
.vscode
.woodpecker
node_modules node_modules
scripts
.git .git
.prettierrc .prettierrc
.prettierignore .prettierignore
*.md *.md
.env .env
.env.* .env.*
stylelint.config.js
eslint.config.js

6
.gitignore vendored
View File

@@ -1,6 +1,10 @@
.pnpm-store
.env .env
.env.* .env.*
!.env.example !.env.example
node_modules node_modules
pnpm-lock.yaml .eslintcache
.stylelintcache
dist

View File

@@ -1,2 +1,5 @@
.pnpm-store
node_modules node_modules
pnpm-lock.yaml pnpm-lock.yaml
dist

View File

@@ -1,15 +1,15 @@
{ {
"semi": false, "semi": false,
"singleQuote": true, "singleQuote": true,
"tabWidth": 2, "tabWidth": 4,
"trailingComma": "none", "trailingComma": "none",
"overrides": [ "overrides": [
{ {
"files": "*.yml", "files": "*.yml",
"options": { "options": {
"tabWidth": 4, "tabWidth": 4,
"proseWrap": "preserve" "proseWrap": "preserve"
} }
} }
] ]
} }

View File

@@ -1 +1 @@
{} {}

View File

@@ -1,4 +1,4 @@
FROM nginx:alpine FROM nginx:alpine
COPY nginx/conf.d/ /etc/nginx/conf.d/ COPY nginx/conf.d/ /etc/nginx/conf.d/
COPY src/ /usr/share/nginx/html/ COPY dist/ /usr/share/nginx/html/

View File

@@ -1 +1,23 @@
# Simple Package (Docker) # mail-landing
Static landing site for mail.mifi.holdings (HTML/CSS/JS), built to `dist/` and served with nginx in production.
## Dev Container (local development and preview)
1. **Open in Dev Container**
In Cursor/VS Code: _Command Palette_**Dev Containers: Reopen in Container** (or **Clone Repository in Container Volume** when opening the repo).
The first time will build the container (Node 22 + pnpm) and run `pnpm install`.
2. **Preview the site**
In the container terminal:
- **Quick preview** (serves `src/` as-is, no build):
`pnpm preview`
- **Production-like preview** (build then serve `dist/`):
`pnpm preview:prod`
Port **3000** is forwarded; open **http://localhost:3000** in the host browser (or use the “Preview” port notification).
3. **Other commands**
- `pnpm build` — build `src/``dist/` (minify JS/CSS, inline critical CSS)
- `pnpm lint` / `pnpm format` — lint and format
- `pnpm docker:build` — build production image

View File

@@ -1,34 +1,25 @@
services: services:
service: mail-landing:
image: git.mifi.dev/...:${IMAGE_TAG:-latest} image: git.mifi.dev/mifi-holdings/mail-landing:latest
container_name: service container_name: mifi-mail-landing
environment:
- ENV_NAME=value
healthcheck:
test:
[
'CMD',
'/usr/local/bin/healthcheck.sh',
'--connect',
'--innodb_initialized'
]
retries: 10
start_period: 20s
networks:
- network
volumes:
- volume:/var/lib/...
- other_volume:/var/lib/...
depends_on:
- other service
restart: unless-stopped restart: unless-stopped
networks:
- marina-net
labels:
- 'traefik.enable=true'
- 'traefik.docker.network=marina-net'
- 'traefik.http.routers.mail-landing.rule=Host(`mail.mifi.holdings`)'
- 'traefik.http.routers.mail-landing.entrypoints=websecure'
- 'traefik.http.routers.mail-landing.middlewares=gzip@file,security-supermax-with-analytics@file'
- 'traefik.http.routers.mail-landing.tls=true'
- 'traefik.http.routers.mail-landing.tls.certresolver=letsencrypt'
- 'traefik.http.services.mail-landing.loadbalancer.server.port=80'
healthcheck:
test: ['CMD-SHELL', 'wget --spider -q http://localhost/ || exit 1']
interval: 20s
timeout: 3s
retries: 3
networks: networks:
network: marina-net:
external: true external: true
volumes:
volume:
external: true
other_volume:
external: false

20
eslint.config.js Normal file
View File

@@ -0,0 +1,20 @@
import prettierConfig from 'eslint-config-prettier/flat'
export default [
{
files: ['src/**/*.js'],
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'script',
globals: {
window: 'readonly',
document: 'readonly',
dataLayer: 'writable'
}
},
rules: {
'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }]
}
},
prettierConfig
]

View File

@@ -1,21 +1,40 @@
{ {
"name": "...", "name": "mail-landing",
"version": "1.0.0", "type": "module",
"private": true, "version": "1.0.0",
"packageManager": "pnpm@10.29.3", "private": true,
"scripts": { "packageManager": "pnpm@10.29.3",
"format": "prettier --write .", "scripts": {
"format:check": "prettier --check .", "build": "node scripts/build.js",
"lint": "yamllint .woodpecker/ci.yml .woodpecker/build.yml .woodpecker/deploy.yml docker-compose.yml", "docker:build": "docker build --platform linux/amd64 -t git.mifi.dev/mifi-holdings/mail-landing:latest .",
"docker:build": "docker build --platform linux/amd64 -t git.mifi.dev/.../...:latest .", "docker:push": "docker push git.mifi.dev/mifi-holdings/mail-landing:latest",
"docker:push": "docker push git.mifi.dev/.../...:latest" "format": "prettier --write .",
}, "format:check": "prettier --check .",
"devDependencies": { "lint": "pnpm run lint:yaml && pnpm run lint:js && pnpm run lint:css",
"prettier": "^3.4.2", "lint:css": "stylelint \"src/**/*.css\"",
"yaml-lint": "^1.7.0" "lint:js": "eslint src/",
}, "lint:yaml": "yamllint .woodpecker/ci.yml .woodpecker/build.yml .woodpecker/deploy.yml docker-compose.yml",
"repository": { "lint:fix": "pnpm run lint:fix:js && pnpm run lint:fix:css && pnpm run lint:fix:yaml",
"type": "git", "lint:fix:js": "eslint src/ --fix",
"url": "https://github.com/.../...git" "lint:fix:css": "stylelint \"src/**/*.css\" --fix",
} "lint:fix:yaml": "yamllint .woodpecker/ci.yml .woodpecker/build.yml .woodpecker/deploy.yml docker-compose.yml --fix",
"preview": "serve src -l 3000",
"preview:prod": "pnpm build && serve dist -l 3000"
},
"devDependencies": {
"beasties": "^0.4.1",
"clean-css": "^5.3.3",
"eslint": "^10.0.0",
"eslint-config-prettier": "^10.1.8",
"prettier": "^3.4.2",
"serve": "^14.2.4",
"stylelint": "^17.3.0",
"stylelint-config-standard": "^40.0.0",
"terser": "^5.46.0",
"yaml-lint": "^1.7.0"
},
"repository": {
"type": "git",
"url": "https://github.com/mifi-holdings/mail-landing.git"
}
} }

2283
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

78
scripts/build.js Normal file
View File

@@ -0,0 +1,78 @@
/**
* Build script: copy src → dist, minify JS/CSS, inline critical CSS (Beasties).
* Run with: pnpm build
*/
import {
rmSync,
mkdirSync,
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'
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 })
for (const e of entries) {
const full = join(dir, e.name)
if (e.isDirectory()) getFiles(full, files)
else files.push(full)
}
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 })
const distFiles = getFiles(distDir)
// 2. Minify 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)
}
// 3. Minify 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)
}
// 4. Inline critical CSS with Beasties for all HTML files (no browser; works in CI)
const htmlFiles = distFiles.filter((f) => extname(f) === '.html')
const beasties = new Beasties({
path: distDir,
preload: 'default',
logLevel: 'warn'
})
for (const htmlFile of htmlFiles) {
const html = readFileSync(htmlFile, 'utf8')
const inlined = await beasties.process(html)
writeFileSync(htmlFile, inlined)
}
console.log('Build complete: dist/')
}
main().catch((err) => {
console.error(err)
process.exit(1)
})

View File

@@ -1,93 +0,0 @@
:root {
--accordion-bg: #fff;
}
@media (prefers-color-scheme: dark) {
:root {
--accordion-bg: #24264a;
}
}
.accordion {
margin: 1.5rem 0 1rem 0;
border-radius: var(--radius);
overflow: hidden;
box-shadow: 0 1px 6px 0 rgba(90,100,140,0.06);
background: var(--accordion-bg);
}
.accordion-section {
border-top: 1px solid #eee;
background: var(--accordion-bg);
}
@media (prefers-color-scheme: dark) {
.accordion-section {
border-top: 1px solid #28284b;
}
}
.accordion-section:first-child {
border-top: none;
}
.accordion-trigger {
display: flex;
align-items: center;
cursor: pointer;
width: 100%;
background: none;
border: none;
font-size: 1.12rem;
padding: 1.1rem 1.2rem;
text-align: left;
font-weight: 600;
transition: background 0.2s;
color: var(--text-main);
outline: none;
gap: 0.6em;
position: relative;
}
.accordion-trigger:hover, .accordion-trigger:focus {
background: var(--background);
}
.accordion-trigger .icon {
margin-right: 0.5em;
font-size: 1.3em;
opacity: 0.86;
transition: transform 0.2s;
}
.accordion-trigger[aria-expanded="true"] .icon {
transform: rotate(90deg);
}
.accordion-content {
max-height: 0;
overflow: hidden;
background: var(--surface);
transition: max-height 0.3s cubic-bezier(.7,0,.3,1);
font-size: 1rem;
padding: 0 1.5rem;
color: var(--text-main);
text-align: left;
}
.accordion-section.open .accordion-content {
padding: 1.2rem 1.5rem 1.3rem 1.5rem;
max-height: 1000px;
transition: max-height 0.5s cubic-bezier(.7,0,.3,1);
}
@media (max-width: 600px) {
.accordion-trigger {
font-size: 1rem;
padding: 0.93rem 0.8rem;
}
.accordion-section.open .accordion-content {
padding: 0.7rem 0.8rem 0.9rem 0.8rem;
}
}

View File

@@ -1,106 +0,0 @@
:root {
--content-bg: #f8fafc;
--faq-a: #333;
--table-bg: transparent;
}
@media (prefers-color-scheme: dark) {
:root {
--content-bg: #21223a;
--faq-a: #b7badf;
--table-bg: #252745;
}
}
.general-settings {
background: var(--surface);
border-radius: var(--radius);
padding: 1.2rem 1.1rem 1.1rem 1.1rem;
margin-bottom: 2.1rem;
box-shadow: var(--shadow);
position: relative;
z-index: 1;
}
.general-settings h2 {
font-size: 1.14rem;
margin: 0 0 0.6rem 0;
font-weight: 700;
letter-spacing: -0.5px;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 1em;
background: var(--table-bg);
}
td {
padding: 0.38em 0.5em;
border: none;
font-size: 1rem;
}
td:first-child {
color: var(--text-muted);
font-weight: 500;
width: 44%;
white-space: nowrap;
}
.tip {
background: #eef2ff;
color: var(--accent-light);
border-radius: 0.7em;
font-size: 0.98em;
padding: 0.48em 0.8em;
display: inline-block;
margin: 0.3em 0 0.2em 0;
text-align: center;
}
@media (prefers-color-scheme: dark) {
.tip {
background: #232555;
color: #a5b4fc;
}
}
.faq-q {
font-weight: 600;
margin-top: 0.8em;
color: var(--accent);
}
.faq-a {
margin: 0.1em 0 0.6em 0.3em;
color: var(--faq-a);
}
a {
color: var(--accent);
text-decoration: underline;
}
@media (max-width: 600px) {
.container {
padding: 1.1rem 0.5rem 1rem 0.5rem;
}
h1 {
font-size: 1.36rem;
}
.general-settings {
padding: 0.8rem 0.6rem 0.8rem 0.6rem;
}
.general-settings h2 {
font-size: 1rem;
}
td {
font-size: 0.98em;
}
}

View File

@@ -1,145 +0,0 @@
:root {
--accent: #4f46e5;
--accent-light: #6366f1;
--background: #f7fafc;
--button-bg: #2bc4fa;
--button-hover: #22a0ca;
--button-text: #181a20;
--text-main: #23243a;
--text-muted: #64748b;
--radius: 1.25rem;
--shadow: 0 2px 12px 0 rgba(20,30,60,0.09);
--surface: rgba(255,255,255,0.94);
}
@media (prefers-color-scheme: dark) {
:root {
--accent: #a5b4fc;
--accent-light: #818cf8;
--background: #15181c;
--button-bg: #2bc4fa;
--button-hover: #22a0ca;
--button-text: #181a20;
--shadow: 0 2px 16px 0 rgba(8,8,24,0.24);
--surface: rgba(30,34,42,0.9);
--text-main: #f6f7fa;
--text-muted: #aab2bd;
}
}
html, body {
margin: 0;
padding: 0;
background: var(--background);
color: var(--text-main);
font-family: 'Inter', 'Segoe UI', Arial, sans-serif;
font-size: 17px;
min-height: 100vh;
}
body {
display: flex;
flex-flow:column nowrap;
align-items: center;
justify-content: center;
min-height: 100dvh;
}
.container {
display: flex;
flex-grow: 1;
flex-flow: column nowrap;
justify-content: center;
margin: 1rem;
max-width: 370px;
text-align: center;
}
.card {
background: var(--surface);
border-radius: 1.5rem;
box-shadow: var(--shadow);
padding: 3rem 2rem;
}
.container.wide {
max-width: 580px;
}
.emoji {
font-size: 3rem;
margin-bottom: 1rem;
}
h1 {
font-size: 2rem;
margin-bottom: 0.5rem;
font-weight: 700;
letter-spacing: -1px;
}
.intro {
color: var(--text-muted);
margin-bottom: 1.7rem;
text-align: center;
}
p {
color: var(--text-muted);
font-size: 1.1rem;
line-height: 1.5;
margin-bottom: 2rem;
}
.button {
display: block;
padding: 0.75rem 1.5rem;
margin-bottom: 0.75rem;
background: var(--button-bg);
color: var(--button-text);
border-radius: 999px;
font-weight: 600;
text-decoration: none;
transition: background 0.2s;
box-shadow: 0 1px 4px 0 rgba(43,196,250,0.11);
}
.button:hover {
background: var(--button-hover);
}
a {
color: var(--accent);
text-decoration: underline;
}
a:hover {
text-decoration: none;
}
footer {
color: #bbb;
font-size: 0.94em;
letter-spacing: 0.01em;
margin-top: 2.5em;
text-align: center;
}
footer p {
font-size: 0.8em;
margin: 0.5rem 0 1rem;
}
.badges {
align-items: center;
display: flex;
flex-flow: row nowrap;
gap: 1rem;
justify-content: center;
}
@media (max-width: 400px) {
.container {
padding: 2rem 0.5rem;
}
}

View File

@@ -1,28 +0,0 @@
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";
}
});
});
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();
}
});
});

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -1,146 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Welcome to Email from mifi Ventures</title>
<meta name="description" content="Setup help for users of Email from mifi Ventures">
<link rel="stylesheet" href="/assets/css/style.css">
<link rel="stylesheet" href="/assets/css/settings.css">
<link rel="stylesheet" href="/assets/css/accordion.css">
</head>
<body>
<div class="container wide">
<h1>Welcome to Email from mifi Ventures</h1>
<div class="intro">
<strong>Lets 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="card 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>
<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>
</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>
<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>
<footer>
<p class="badges">
<a href="https://internet.nl/halloffame/mail/" title="internet.nl 100% Email Hall of Fame" class="badge" rel="external"><img src="/assets/media/embed-badge-emailtest.svg" alt="internet.nl 100% Hall of Fame - Email"></a>
<a href="https://internet.nl/halloffame/web/" title="internet.nl 100% Web Hall of Fame" class="badge" rel="external"><img src="/assets/media/embed-badge-websitetest.svg" alt="internet.nl 100% Hall of Fame - Web"></a>
</p>
<p>© mifi Ventures. All rights reserved.</p>
</footer>
</div>
<script src="/assets/js/accordion.js"></script>
</body>
</html>

View File

@@ -1,34 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>mail.mifi.holdings</title>
<link rel="stylesheet" href="/assets/css/style.css">
</head>
<body>
<div class="container">
<div class="card">
<header>
<div class="emoji">📮</div>
<h1>This is just a mailbox.</h1>
</header>
<p>
You've reached <b>mail.mifi.holdings</b>.<br>
There's nothing exciting here—just some gears whirring and mail being sorted.<br>
Looking for your messages?
</p>
<a href="/help" class="button">Email Setup Help</a>
<a href="https://webmail.mifi.holdings" class="button">Go to Webmail</a>
<a href="https://postmaster.mifi.holdings/users/login.php">Change/Forgot Password</a>
</div>
</div>
<footer>
<p class="badges">
<a href="https://internet.nl/halloffame/mail/" title="internet.nl 100% Email Hall of Fame" class="badge" rel="external"><img src="/assets/media/embed-badge-emailtest.svg" alt="internet.nl 100% Hall of Fame - Email"></a>
<a href="https://internet.nl/halloffame/web/" title="internet.nl 100% Web Hall of Fame" class="badge" rel="external"><img src="/assets/media/embed-badge-websitetest.svg" alt="internet.nl 100% Hall of Fame - Web"></a>
</p>
<p>© mifi Ventures. All rights reserved.</p>
</footer>
</body>
</html>

299
src/assets/css/site.css Normal file
View File

@@ -0,0 +1,299 @@
:root {
--accent: #4f46e5;
--accent-light: #6366f1;
--background: #f7fafc;
--button-bg: var(--accent);
--button-hover: var(--accent-light);
--button-text: var(--background);
--radius: 1.25rem;
--shadow: 0 2px 12px 0 rgb(20 30 60 / 9%);
--surface: rgb(255 255 255 / 94%);
--text-main: #23243a;
--text-muted: #64748b;
--table-bg: transparent;
--accordion-bg: #fff;
--content-bg: #f8fafc;
--faq-a: #333;
}
@media (prefers-color-scheme: dark) {
:root {
--accent: #a5b4fc;
--accent-light: #818cf8;
--background: #171923;
--shadow: 0 2px 16px 0 rgb(8 8 24 / 24%);
--surface: rgb(30 34 42 / 90%);
--text-main: #f6f7fa;
--text-muted: #aab2bd;
--table-bg: #252745;
--accordion-bg: #24264a;
--content-bg: #21223a;
--faq-a: #b7badf;
}
}
html,
body {
margin: 0;
padding: 0;
background: var(--background);
color: var(--text-main);
font-family: Inter, 'Segoe UI', Arial, sans-serif;
font-size: 17px;
min-height: 100dvh;
}
body {
display: flex;
align-items: center;
justify-content: center;
}
h1 {
font-size: 2rem;
margin-bottom: 0.5rem;
font-weight: 700;
letter-spacing: -1px;
}
a {
color: var(--accent);
text-decoration: underline;
}
p {
color: var(--text-muted);
margin-bottom: 2rem;
font-size: 1.1rem;
line-height: 1.5;
}
.container {
background: var(--surface);
padding: 3rem 2rem;
border-radius: var(--radius);
box-shadow: var(--shadow);
max-width: 370px;
margin: 1rem;
}
@media (width <= 400px) {
.container {
padding: 2rem 0.5rem;
}
}
.container.faq {
margin: 2.5rem auto 1.5rem;
max-width: 580px;
padding-bottom: 2rem;
}
.text-center {
text-align: center;
}
.emoji {
font-size: 3rem;
margin-bottom: 1rem;
}
.button {
display: block;
padding: 0.75rem 1.5rem;
margin-bottom: 0.75rem;
background: var(--button-bg);
color: var(--button-text);
border-radius: 999px;
font-weight: 600;
text-decoration: none;
transition: background 0.2s;
box-shadow: 0 1px 4px 0 rgb(43 196 250 / 11%);
}
.button:hover {
background: var(--button-hover);
}
.intro {
text-align: center;
margin-bottom: 1.7rem;
color: var(--text-muted);
}
.general-settings {
background: var(--content-bg);
border-radius: var(--radius);
padding: 1.2rem 1.1rem 1.1rem;
margin-bottom: 2.1rem;
box-shadow: 0 1px 6px 0 rgb(90 100 140 / 6%);
position: relative;
z-index: 1;
}
.general-settings h2 {
font-size: 1.14rem;
margin: 0 0 0.6rem;
font-weight: 700;
letter-spacing: -0.5px;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 1em;
background: var(--table-bg);
}
td {
padding: 0.38em 0.5em;
border: none;
font-size: 1rem;
}
td:first-child {
color: var(--text-muted);
font-weight: 500;
width: 44%;
white-space: nowrap;
}
.tip {
background: #eef2ff;
color: var(--accent-light);
border-radius: 0.7em;
font-size: 0.98em;
padding: 0.48em 0.8em;
display: inline-block;
margin: 0.3em 0 0.2em;
}
@media (prefers-color-scheme: dark) {
.tip {
background: #232555;
color: #a5b4fc;
}
}
.accordion {
margin: 1.5rem 0 1rem;
border-radius: var(--radius);
overflow: hidden;
box-shadow: 0 1px 6px 0 rgb(90 100 140 / 6%);
background: var(--accordion-bg);
}
.accordion-section {
border-top: 1px solid #eee;
background: var(--accordion-bg);
}
@media (prefers-color-scheme: dark) {
.accordion-section {
border-top: 1px solid #28284b;
}
}
.accordion-section:first-child {
border-top: none;
}
.accordion-trigger {
display: flex;
align-items: center;
cursor: pointer;
width: 100%;
background: none;
border: none;
font-size: 1.12rem;
padding: 1.1rem 1.2rem;
text-align: left;
font-weight: 600;
transition: background 0.2s;
color: var(--text-main);
outline: none;
gap: 0.6em;
position: relative;
}
.accordion-trigger:hover,
.accordion-trigger:focus {
background: var(--background);
}
.accordion-trigger .icon {
margin-right: 0.5em;
font-size: 1.3em;
opacity: 0.86;
transition: transform 0.2s;
}
.accordion-trigger[aria-expanded='true'] .icon {
transform: rotate(90deg);
}
.accordion-content {
max-height: 0;
overflow: hidden;
background: var(--content-bg);
transition: max-height 0.3s cubic-bezier(0.7, 0, 0.3, 1);
font-size: 1rem;
padding: 0 1.5rem;
color: var(--text-main);
}
.accordion-section.open .accordion-content {
padding: 1.2rem 1.5rem 1.3rem;
max-height: 1000px;
transition: max-height 0.5s cubic-bezier(0.7, 0, 0.3, 1);
}
.faq-q {
font-weight: 600;
margin-top: 0.8em;
color: var(--accent);
}
.faq-a {
margin: 0.1em 0 0.6em 0.3em;
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;
}
h1 {
font-size: 1.36rem;
}
.general-settings {
padding: 0.8rem 0.6rem;
}
.general-settings h2 {
font-size: 1rem;
}
.accordion-trigger {
font-size: 1rem;
padding: 0.93rem 0.8rem;
}
.accordion-section.open .accordion-content {
padding: 0.7rem 0.8rem 0.9rem;
}
td {
font-size: 0.98em;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,9 @@
<svg width="100%" height="100%" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<style>
.block { fill: #0b0b0f; }
@media (prefers-color-scheme: dark) {
.block { fill: #f2f2f2; }
}
</style>
<path class="block" d="M512,96l0,320c0,52.984 -43.016,96 -96,96l-320,0c-52.984,0 -96,-43.016 -96,-96l0,-320c0,-52.984 43.016,-96 96,-96l320,0c52.984,0 96,43.016 96,96Zm-96.011,80.389l53.127,0l0,-56.669l-53.127,0l0,56.669Zm-193.658,55.292c-4.819,-8.296 -11.558,-15.376 -20.217,-21.24c-13.796,-9.344 -29.667,-14.015 -47.612,-14.015c-16.461,0 -30.814,3.913 -43.058,11.739c-7.882,5.038 -14.038,11.753 -18.468,20.146l0,-27.027l-50.091,0l0,219.997l53.127,0l0,-129.124c0,-9.715 1.771,-18.063 5.313,-25.046c3.542,-6.982 8.517,-12.413 14.926,-16.292c6.409,-3.879 13.864,-5.819 22.364,-5.819c8.703,0 16.191,1.94 22.465,5.819c6.274,3.879 11.165,9.293 14.673,16.242c3.508,6.949 5.262,15.314 5.262,25.096l0,129.124l53.127,0l0,-129.124c0,-9.715 1.771,-18.063 5.313,-25.046c3.542,-6.982 8.534,-12.413 14.977,-16.292c6.443,-3.879 13.881,-5.819 22.313,-5.819c8.703,0 16.191,1.94 22.465,5.819c6.274,3.879 11.165,9.293 14.673,16.242c3.508,6.949 5.262,15.314 5.262,25.096l0,129.124l53.127,0l0,-141.774c0,-16.394 -3.559,-30.831 -10.676,-43.311c-7.117,-12.481 -16.815,-22.229 -29.093,-29.245c-12.278,-7.016 -26.209,-10.524 -41.793,-10.524c-17.608,0 -33.141,4.335 -46.6,13.004c-8.624,5.555 -15.884,12.972 -21.779,22.252Zm193.658,189.599l53.127,0l0,-219.997l-53.127,0l0,219.997Z" />
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

11
src/assets/js/ga-init.js Normal file
View File

@@ -0,0 +1,11 @@
;(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)
}
gtag('js', new Date())
gtag('config', id, { anonymize_ip: true })
})()

View File

@@ -1,364 +1,317 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <!-- Google tag (gtag.js) -->
<meta name="viewport" content="width=device-width,initial-scale=1"> <script
<title>Welcome to Email from mifi Ventures</title> async
<meta name="description" content="Setup help for users of Email from mifi Ventures"> src="https://www.googletagmanager.com/gtag/js?id=G-NF64QMKWX6"
<style> ></script>
:root { <script
--accent: #4f46e5; defer
--accent-light: #6366f1; src="/assets/js/ga-init.js"
--background: #f7fafc; data-ga-id="G-NF64QMKWX6"
--surface: #fff; ></script>
--text-main: #1a202c;
--text-muted: #64748b;
--radius: 1.25rem;
--shadow: 0 2px 12px 0 rgba(20,30,60,0.09);
--table-bg: transparent;
--accordion-bg: #fff;
--content-bg: #f8fafc;
--faq-a: #333;
}
@media (prefers-color-scheme: dark) {
:root {
--accent: #a5b4fc;
--accent-light: #818cf8;
--background: #171923;
--surface: #22243a;
--text-main: #e7eaf8;
--text-muted: #b4b9d1;
--shadow: 0 2px 16px 0 rgba(8,8,24,0.24);
--table-bg: #252745;
--accordion-bg: #24264a;
--content-bg: #21223a;
--faq-a: #b7badf;
}
}
html, body {
margin: 0;
padding: 0;
background: var(--background);
color: var(--text-main);
font-family: 'Inter', 'Segoe UI', Arial, sans-serif;
font-size: 17px;
min-height: 100vh;
}
.container {
max-width: 580px;
margin: 2.5rem auto 1.5rem auto;
background: var(--surface);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 2.5rem 2rem 2rem 2rem;
}
h1 {
margin: 0 0 0.5rem 0;
font-size: 2rem;
font-weight: 800;
text-align: center;
letter-spacing: -1px;
}
.intro {
text-align: center;
margin-bottom: 1.7rem;
color: var(--text-muted);
}
.general-settings {
background: var(--content-bg);
border-radius: var(--radius);
padding: 1.2rem 1.1rem 1.1rem 1.1rem;
margin-bottom: 2.1rem;
box-shadow: 0 1px 6px 0 rgba(90,100,140,0.06);
position: relative;
z-index: 1;
}
.general-settings h2 {
font-size: 1.14rem;
margin: 0 0 0.6rem 0;
font-weight: 700;
letter-spacing: -0.5px;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 1em;
background: var(--table-bg);
}
td {
padding: 0.38em 0.5em;
border: none;
font-size: 1rem;
}
td:first-child {
color: var(--text-muted);
font-weight: 500;
width: 44%;
white-space: nowrap;
}
.tip {
background: #eef2ff;
color: var(--accent-light);
border-radius: 0.7em;
font-size: 0.98em;
padding: 0.48em 0.8em;
display: inline-block;
margin: 0.3em 0 0.2em 0;
}
@media (prefers-color-scheme: dark) {
.tip { background: #232555; color: #a5b4fc; }
}
.accordion {
margin: 1.5rem 0 1rem 0;
border-radius: var(--radius);
overflow: hidden;
box-shadow: 0 1px 6px 0 rgba(90,100,140,0.06);
background: var(--accordion-bg);
}
.accordion-section {
border-top: 1px solid #eee;
background: var(--accordion-bg);
}
@media (prefers-color-scheme: dark) {
.accordion-section {
border-top: 1px solid #28284b;
}
}
.accordion-section:first-child {
border-top: none;
}
.accordion-trigger {
display: flex;
align-items: center;
cursor: pointer;
width: 100%;
background: none;
border: none;
font-size: 1.12rem;
padding: 1.1rem 1.2rem;
text-align: left;
font-weight: 600;
transition: background 0.2s;
color: var(--text-main);
outline: none;
gap: 0.6em;
position: relative;
}
.accordion-trigger:hover, .accordion-trigger:focus {
background: var(--background);
}
.accordion-trigger .icon {
margin-right: 0.5em;
font-size: 1.3em;
opacity: 0.86;
transition: transform 0.2s;
}
.accordion-trigger[aria-expanded="true"] .icon {
transform: rotate(90deg);
}
.accordion-content {
max-height: 0;
overflow: hidden;
background: var(--content-bg);
transition: max-height 0.3s cubic-bezier(.7,0,.3,1);
font-size: 1rem;
padding: 0 1.5rem;
color: var(--text-main);
}
.accordion-section.open .accordion-content {
padding: 1.2rem 1.5rem 1.3rem 1.5rem;
max-height: 1000px;
transition: max-height 0.5s cubic-bezier(.7,0,.3,1);
}
.faq-q {
font-weight: 600;
margin-top: 0.8em;
color: var(--accent);
}
.faq-a {
margin: 0.1em 0 0.6em 0.3em;
color: var(--faq-a);
}
.footer {
margin-top: 2.5em;
text-align: center;
color: #bbb;
font-size: 0.94em;
letter-spacing: 0.01em;
}
a {
color: var(--accent);
text-decoration: underline;
}
@media (max-width: 600px) {
.container { padding: 1.1rem 0.5rem 1rem 0.5rem;}
h1 { font-size: 1.36rem; }
.general-settings { padding: 0.8rem 0.6rem 0.8rem 0.6rem; }
.general-settings h2 { font-size: 1rem; }
.accordion-trigger { font-size: 1rem; padding: 0.93rem 0.8rem;}
.accordion-section.open .accordion-content { padding: 0.7rem 0.8rem 0.9rem 0.8rem; }
td { font-size: 0.98em;}
}
</style>
</head>
<body>
<div class="container">
<h1>Welcome to Email from mifi Ventures</h1>
<div class="intro">
<strong>Lets 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"> <meta charset="UTF-8" />
<h2>General Settings (All Clients)</h2> <meta name="viewport" content="width=device-width,initial-scale=1" />
<table> <title>mifi Holdings — Email Setup &amp; Help</title>
<tr><td>Email Address</td><td>your.name@yourdomain.com</td></tr> <meta
<tr><td>Username</td><td>your.name@yourdomain.com</td></tr> name="description"
<tr><td>Password</td><td>(your email password)</td></tr> content="Setup help for users of Email from mifi Holdings"
<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> <link rel="canonical" href="https://mail.mifi.holdings/help" />
<tr><td>IMAP Port</td><td>993 (SSL/TLS)</td></tr> <meta name="robots" content="index, follow" />
<tr><td>POP3 Port</td><td>995 (SSL/TLS)</td></tr> <meta name="author" content="Mike Fitzpatrick (mifi)" />
<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>
<div class="accordion" id="helpAccordion"> <link
rel="icon"
type="image/svg+xml"
href="/assets/images/favicon.svg"
/>
<link
rel="icon"
type="image/x-icon"
href="/assets/images/favicon.ico"
/>
<link
rel="apple-touch-icon"
sizes="180x180"
href="/assets/images/apple-touch-icon.png"
/>
<link rel="stylesheet" href="/assets/css/site.css" />
</head>
<body>
<div class="container faq">
<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>
<!-- Outlook --> <section
<section class="accordion-section"> class="general-settings"
<button class="accordion-trigger" aria-expanded="false"> aria-label="General Email Settings"
<span class="icon"></span> Microsoft Outlook >
</button> <h2>General Settings (All Clients)</h2>
<div class="accordion-content"> <table>
<ol> <tr>
<li>Go to <b>File → Add Account</b></li> <td>Email Address</td>
<li>Enter your full email address</li> <td>your.name@yourdomain.com</td>
<li>Choose <b>Advanced options</b> → check “Set up manually”</li> </tr>
<li>Select <b>IMAP</b> (recommended) or POP</li> <tr>
<li>Incoming server: <code>mail.mifi.holdings</code>, port <b>993</b> (SSL/TLS)</li> <td>Username</td>
<li>Outgoing server: <code>mail.mifi.holdings</code>, port <b>587</b> (STARTTLS) or <b>465</b> (SSL/TLS)</li> <td>your.name@yourdomain.com</td>
<li>Username: full email address; Password: your password</li> </tr>
<li>Click <b>Connect</b></li> <tr>
</ol> <td>Password</td>
<span class="tip">If sending fails, make sure “Require logon using SPA” is <b>unchecked</b>.</span> <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>
<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>
</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>
<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>
<div class="footer">
Email from mifi Ventures &middot; Help Page &ndash; &copy; 2025
mifi Ventures, LLC
</div>
</div> </div>
</section> <script>
// Native accessible accordion
<!-- Apple Mail --> document.querySelectorAll('.accordion-trigger').forEach((btn) => {
<section class="accordion-section"> btn.addEventListener('click', function () {
<button class="accordion-trigger" aria-expanded="false"> const section = btn.closest('.accordion-section')
<span class="icon"></span> Apple Mail (macOS, iOS) const expanded =
</button> btn.getAttribute('aria-expanded') === 'true'
<div class="accordion-content"> document
<ol> .querySelectorAll('.accordion-section')
<li>Add Account &rarr; <b>Other Mail Account</b></li> .forEach((s) => {
<li>Enter your name, email, and password</li> if (s === section) {
<li>Incoming/Outgoing server: <code>mail.mifi.holdings</code></li> s.classList.toggle('open', !expanded)
<li>IMAP port: <b>993</b> (SSL); SMTP port: <b>587</b> (STARTTLS) or <b>465</b> (SSL)</li> btn.setAttribute(
<li>Use full email address for username</li> 'aria-expanded',
</ol> String(!expanded)
</div> )
</section> const content = btn.nextElementSibling
content.style.maxHeight = !expanded
<!-- Thunderbird --> ? content.scrollHeight + 40 + 'px'
<section class="accordion-section"> : '0px'
<button class="accordion-trigger" aria-expanded="false"> } else {
<span class="icon"></span> Thunderbird s.classList.remove('open')
</button> s.querySelector(
<div class="accordion-content"> '.accordion-trigger'
<ol> ).setAttribute('aria-expanded', 'false')
<li>Menu → Account Settings → Add Mail Account</li> s.querySelector(
<li>Fill in your name, email, and password</li> '.accordion-content'
<li>Click “Configure manually” and use settings above</li> ).style.maxHeight = '0px'
</ol> }
</div> })
</section> })
// Allow arrow navigation
<!-- Mobile --> btn.addEventListener('keydown', function (e) {
<section class="accordion-section"> if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
<button class="accordion-trigger" aria-expanded="false"> const triggers = Array.from(
<span class="icon"></span> iOS / Android Mail / Gmail App document.querySelectorAll('.accordion-trigger')
</button> )
<div class="accordion-content"> let idx = triggers.indexOf(e.target)
<ul> if (e.key === 'ArrowDown')
<li>Add Account → Other</li> idx = (idx + 1) % triggers.length
<li>Enter your email and password</li> else idx = (idx - 1 + triggers.length) % triggers.length
<li>Manual setup: <code>mail.mifi.holdings</code>, correct ports, SSL/TLS required</li> triggers[idx].focus()
<li>Gmail app: tap profile → Add account → Other, fill in details, use IMAP</li> }
</ul> })
</div> })
</section> </script>
</body>
<!-- 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>
<div class="footer">
Email from mifi Ventures &middot; Help Page &ndash; &copy; 2025 mifi Ventures, LLC
</div>
</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>
</body>
</html> </html>

View File

@@ -1,103 +1,64 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <!-- Google tag (gtag.js) -->
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <script
<title>mail.mifi.holdings</title> async
<style> src="https://www.googletagmanager.com/gtag/js?id=G-NF64QMKWX6"
:root { ></script>
--background: #f7fafc; <script
--surface: rgba(255,255,255,0.94); defer
--text-main: #23243a; src="/assets/js/ga-init.js"
--text-muted: #64748b; data-ga-id="G-NF64QMKWX6"
--button-bg: #2bc4fa; ></script>
--button-hover: #22a0ca;
--button-text: #181a20; <meta charset="UTF-8" />
--shadow: 0 2px 24px 0 rgba(0,0,0,0.11); <meta name="viewport" content="width=device-width,initial-scale=1.0" />
} <title>mifi Holdings — Mail Services</title>
@media (prefers-color-scheme: dark) { <meta
:root { name="description"
--background: #15181c; content="The mifi Holdings mail services hub—here you can find help with setting up your email, changing your password, and accessing webmail."
--surface: rgba(30,34,42,0.9); />
--text-main: #f6f7fa; <link rel="canonical" href="https://mail.mifi.holdings" />
--text-muted: #aab2bd; <meta name="robots" content="index, follow" />
--button-bg: #2bc4fa; <meta name="author" content="Mike Fitzpatrick (mifi)" />
--button-hover: #22a0ca;
--button-text: #181a20; <link
--shadow: 0 2px 24px 0 rgba(0,0,0,0.18); rel="icon"
} type="image/svg+xml"
} href="/assets/images/favicon.svg"
html,body { />
height: 100%; <link
margin: 0; rel="icon"
padding: 0; type="image/x-icon"
background: var(--background); href="/assets/images/favicon.ico"
color: var(--text-main); />
font-family: 'Inter', Arial, sans-serif; <link
} rel="apple-touch-icon"
body { sizes="180x180"
display: flex; href="/assets/images/apple-touch-icon.png"
align-items: center; />
justify-content: center; <link rel="stylesheet" href="/assets/css/site.css" />
min-height: 100vh; </head>
} <body>
.container { <div class="container text-center">
text-align: center; <div class="emoji">📮</div>
background: var(--surface); <h1>This is just a mailbox.</h1>
padding: 3rem 2rem; <p>
border-radius: 1.5rem; You&apos;ve reached <b>mail.mifi.holdings</b>.<br />
box-shadow: var(--shadow); There&apos;s nothing exciting here&apos;s nothing exciting
max-width: 370px; here—just some gears whirring and mail being sorted.<br />
margin: 1rem; Looking for your messages?
} </p>
.emoji { <a class="button" href="/help">Email Setup Help</a>
font-size: 3rem; <a class="button" href="https://webmail.mifi.holdings"
margin-bottom: 1rem; >Go to Webmail</a
} >
h1 { <a
font-size: 2rem; class="button"
margin-bottom: 0.5rem; href="https://postmaster.mifi.holdings/users/login.php"
font-weight: 700; >Change/Forgot Password</a
letter-spacing: -1px; >
} </div>
p { </body>
color: var(--text-muted);
margin-bottom: 2rem;
font-size: 1.1rem;
line-height: 1.5;
}
a {
display: block;
padding: 0.75rem 1.5rem;
margin-bottom: 0.75rem;
background: var(--button-bg);
color: var(--button-text);
border-radius: 999px;
font-weight: 600;
text-decoration: none;
transition: background 0.2s;
box-shadow: 0 1px 4px 0 rgba(43,196,250,0.11);
}
a:hover {
background: var(--button-hover);
}
@media (max-width: 400px) {
.container { padding: 2rem 0.5rem; }
}
</style>
</head>
<body>
<div class="container">
<div class="emoji">📮</div>
<h1>This is just a mailbox.</h1>
<p>
Youve reached <b>mail.mifi.holdings</b>.<br>
Theres nothing exciting here—just some gears whirring and mail being sorted.<br>
Looking for your messages?
</p>
<a href="/help">Email Setup Help</a>
<a href="https://webmail.mifi.holdings">Go to Webmail</a>
<a href="https://postmaster.mifi.holdings/users/login.php">Change/Forgot Password</a>
</div>
</body>
</html> </html>

8
stylelint.config.js Normal file
View File

@@ -0,0 +1,8 @@
export default {
extends: ['stylelint-config-standard'],
overrides: [
{
files: ['src/**/*.css']
}
]
}