dumbtv
dumbtv가 ADB로 Fire TV에서 광고·추적·업데이트 알림을 제거, 루트·앱 변경 없이, 무료 오픈소스
Microsoft 365 테넌트의 과다 공유·보안 점수·도메인 스푸핑 여부를 한 환경 또는 전체 클라이언트에서 확인합니다
See what your Microsoft 365 tenants over-share, how their posture scores and who can spoof their domains, for one environment or a whole client book.
보안HTML · PythonGitHub @JeffWouters온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Static marketing site for the SeQontrol platform. Hand-written HTML/CSS/JS — no build step, no
package manager, no external network requests at runtime (no CDN, no font host, no analytics). The
three typefaces are self-hosted under assets/fonts/.
Open index.html in a browser, or serve the folder:
python -m http.server 8080
index.html Home — pitch, products, platform, pricing preview, MSP, honesty section
platform.html The shared platform layer + the trust/security answers
pricing.html Every price, plus the per-product capability tabs and metering rules
for-msps.html Provider model, commercials, land-and-expand motion
contact.html Contact + a form that composes a mailto (no backend, nothing submitted)
limits.html What the platform deliberately does not do
about.html security.html privacy.html terms.html Company and legal
exposure-report.html spoofing-report.html surface-report.html The three free assessments
vs-cipp.html vs-grc-platforms.html vs-m365-governance.html vs-secure-score.html Comparisons
404.html Not-found page (noindex, carries the same chrome)
products/
index.html Product overview + at-a-glance comparison table
sharecare.html securityportal.html webscan.html mailtrust.html the four shipped products
conditionalaccessportal.html complianceportal.html coming soon
postureportal.html dredd.html in development
coming.html A redirect stub. The four unreleased products shared this page behind
anchors until 2026-08-23; it was in the sitemap and half the site linked
to it, so it redirects rather than 404s.
guides/
index.html dmarc-without-breaking-mail.html evidence-auditors-accept.html
what-copilot-can-reach.html
css/styles.css Single stylesheet
js/site.js Mobile nav, current-page marker, footer year, audience doors, tabs, contact form
assets/ Logo extractions, on-dark variants, icons, OG card
fonts/ Sora, Manrope and JetBrains Mono (latin + latin-ext WOFF2) with their OFL licences
Screenshots/ Published crops + their lossless WebP twins. The raw captures live here too
and are gitignored — they still carry a real third-party domain.
favicon.ico Multi-resolution icon at the root, where browsers look for it
.github/workflows/deploy.yml Verify, then publish to GitHub Pages
CNAME, .nojekyll, robots.txt, sitemap.xml
Most of the site is generated. Editing generated HTML directly is the one way to lose work here: the
next generator run overwrites it, silently and completely. If a change belongs to something in
tools/, make it there.
Order matters — each step depends on the one above it:
python tools/build_legal.py # pricing + trust pages; leaves <!--CAPS:key--> markers
python tools/build_guides.py # the guides, from the same page chrome
python tools/build_licensing.py # fills those markers with capability tabs and matrices
python tools/build_prices.py # price blocks on the product pages, from tools/prices.py
python tools/build_nav.py # nav, footer, product cards, skip links, contact address
python tools/build_seo.py # titles, metadata, canonicals, JSON-LD, CSP, sitemap
python tools/build_images.py # lossless WebP twins + <picture> wrappers
python tools/verify.py # the gate CI runs on every push
tools/
verify.py Pre-deploy checks — links, markup, a11y, SEO, editorial rules, shipped JS
_chrome.py The page shell, split out of contact.html and shared by the generators
build_legal.py Pricing and the legal/trust pages; owns CONTACT and the money figures
build_licensing.py Per-product capability tabs and matrices, injected into pricing.html
build_guides.py The guides section
prices.py The list prices quoted OUTSIDE pricing.html, as data (writes nothing)
build_prices.py Fills the <!--PRICE:key--> and <!--PRICE-TIERS:key--> blocks from prices.py
build_nav.py Everything repeated on every page, derived from one PRODUCTS list
build_seo.py Metadata, canonicals, JSON-LD, the CSP meta tag, sitemap.xml
build_images.py PNG -> lossless WebP (69% smaller) and the <picture> markup
build_frameworks.py The compliance framework catalogue as DATA (7 families, 24
frameworks). Writes nothing; pricing quotes its count.
extract_logo.py Re-keys the supplied logo JPEG into transparent PNGs
make_logo_variants.py On-dark variants, sized copies, OG card
make_icons.py favicon.ico and the PNG icon set
crop_screenshots.py redact_screenshots.py Raw capture -> publishable crop
Only build_images.py needs anything installed (pip install Pillow), and only when a screenshot
changes — the WebP files are committed, so CI never runs it.
The three artwork scripts need Pillow too. extract_logo.py has to run first because the other
two read what it produces; after that the order does not matter.
python tools/extract_logo.py # supplied JPEG -> transparent symbol/wordmark/tagline
python tools/make_logo_variants.py # on-dark variants, sized copies, OG card
python tools/make_icons.py # favicon.ico and the icon set
Until 2026-08-31 the last two both wrote favicon-32.png, apple-touch-icon.png and
icon-512.png from the same source symbol, recoloured differently — so whichever ran last decided
what the site shipped, and the order was a contract nothing enforced. make_logo_variants.py no
longer writes icons at all. The two scripts now have no output file in common.
make_icons.py kept the job because its treatment is the one the site links and the only one that
answers for a light browser strip: it lifts the navy shield to pure white for contrast against
chrome it does not control, puts the apple-touch icon on an opaque #0b0f17 tile because iOS
composites transparency onto black and does not pad, and writes both favicon.ico and the navy
favicon-32-light.png that sits behind prefers-color-scheme: light. A white shield disappears on
a light strip, and the other script had no answer for that.
None of the three runs in CI, and none of them run at import: they rewrite tracked binaries, so
they only act when invoked directly. extract_logo.py reads artwork that is deliberately not in
this repo; point SEQONTROL_LOGO_SRC at it. Both preview images go to SEQONTROL_PREVIEW_DIR
(default Z: mp) rather than into the site.
The repository root is the site: no build step, so the whole checkout is published as-is.
.github/workflows/deploy.yml runs tools/verify.py first and only deploys if it passes — pull
requests are verified but never published.
The repository must be public unless you pay for GitHub. GitHub Pages on a private repository requires Pro, Team or Enterprise. On the free plan, enabling Pages on a private repo fails with
HTTP 422: Your current plan does not support GitHub Pages for this repository, and the deploy job dies atconfigure-pages. Repository visibility and site visibility are separate things: a public repo means the source is readable, which for a hand-written marketing site is mostly the same content the site already serves — but read the "What goes public" note below before flipping it.
git init -b main
git add .
git commit -m "SeQontrol.com: initial site"
gh repo create seqontrol.com --public --source=. --remote=origin --push
The workflow sets enablement: true on configure-pages, so Pages is switched on automatically by the
first successful run — no visit to Settings needed. To do it by hand instead:
Settings → Pages → Build and deployment → Source: GitHub Actions.
A public repository publishes this README alongside the site, and this file is candid by design: it records the ShareCare catalog mismatch, that scan cadence is sold but not yet enforced, and pointers to internal pricing material. None of that is secret, but none of it is written for customers either. Before making the repository public, either trim the "Things to change before it goes live" section and the internal cross-references, or keep the repo private and pay for Pages.
CNAME contains seqontrol.com, which is also what sitemap.xml, robots.txt and the og:image URL
assume. Point the domain at GitHub:
Eight apex records — four A and four AAAA — plus one CNAME for www. Each value below is literal and
complete; enter them exactly as written, one record per row.
| Record | Name | Value |
|---|---|---|
| A | @ | 185.199.108.153 |
| A | @ | 185.199.109.153 |
| A | @ | 185.199.110.153 |
| A | @ | 185.199.111.153 |
| AAAA | @ | 2606:50c0:8000::153 |
| AAAA | @ | 2606:50c0:8001::153 |
| AAAA | @ | 2606:50c0:8002::153 |
| AAAA | @ | 2606:50c0:8003::153 |
| CNAME | www | <owner>.github.io |
The AAAA values differ only in the fourth group (8000, 8001, 8002, 8003); :: is IPv6's own
notation for a run of zero groups and is part of the address, not an abbreviation of mine.
Check these against GitHub's current documentation before you commit them. They are GitHub's published Pages addresses and have been stable for years, but hosting IPs are not a promise, and a stale A record is a site that silently stops resolving. Search GitHub Docs for "Managing a custom domain for your GitHub Pages site".
Then tick Enforce HTTPS in Settings → Pages once the certificate is issued (can take a few minutes to an hour). If your DNS provider supports ALIAS/ANAME at the apex, use that instead of the A and AAAA records — one record that follows GitHub's changes beats eight that do not.
Then verify the domain — see Security. Pointing a domain at GitHub without verifying it leaves it claimable by any GitHub user on the day this repository stops publishing.
Not using a custom domain? Delete CNAME, and update the absolute URLs in sitemap.xml,
robots.txt and the og:image meta tag to your Pages URL. Every internal link in the site is
relative (no leading / anywhere — tools/verify.py would flag one), so a project site served from
https://<owner>.github.io/<repo>/ works without further changes. Those three absolute URLs are the
only place the domain is hard-coded. Delete the DNS records above in the same change: a domain left
pointing at GitHub with no repository claiming it is exactly the takeover described under Security.
.nojekyll stops GitHub running the content through Jekyll. Nothing here starts with an underscore
today, but the file costs nothing and removes a whole class of surprise.tools/ and .github/ are uploaded with the site. They are a few KB, are never executed by Pages,
and keeping them in the artifact means the deployed tree matches the repository exactly.sitemap.xml submitted to both (2026-10-14). After every deploy to main, the indexnow job in
deploy.yml sends the pages that changed to IndexNow (Bing, Yandex and others; Google reads the
sitemap instead). 365386801392052bf24fb6390765394a.txt at the root is the IndexNow key file: it
must stay there and contain only the key in tools/indexnow.py. Running the workflow by hand
sends every page.The apex points at GitHub's shared Pages addresses and www is a CNAME to jeffwouters.github.io.
Those records say "GitHub, serve this domain". They do not say which repository. While this
repository publishes with CNAME set, it holds the claim. The claim ends the moment publishing stops:
Pages switched off, the repository renamed, transferred or deleted, made private on the free plan (the
HTTP 422 case under One-time setup), or the plan downgraded. The DNS records still point at GitHub, and
nothing claims the domain. Unless the domain is verified on the owning account, any GitHub user can
then add seqontrol.com to a repository of their own, and GitHub will issue them a valid certificate.
On a security vendor's domain that is a phishing page with a padlock: a copy of exposure-report.html
asking an MSP admin for Entra consent, or a /.well-known/security.txt that sends vulnerability reports
to someone else.
Verification is an account setting plus one DNS record, so only the account owner can do it. State on
2026-09-15: verified. The owner added the domain on the JeffWouters account and the challenge TXT record in the
Microsoft 365 zone; the Pages API reports protected_domain_state: verified. The steps below stay as the record of how,
and for re-verifying if the repository ever moves to another account or organisation.
Verification belongs to the account that owns the repository (JeffWouters, a personal account), not
to the repository.
GitHub → profile picture → Settings → Pages (under "Code, planning, and automation").
Add a domain → enter seqontrol.com (the apex, not www) → Add domain.
GitHub shows a TXT record. Create it in the zone exactly as shown. The zone is hosted by Microsoft 365
(ns1.bdm.microsoftonline.com).
| Record | Name | Value |
|---|---|---|
| TXT | _github-pages-challenge-JeffWouters | the code GitHub shows (unique to the account, so it cannot be written here) |
Some DNS panels want the full name, _github-pages-challenge-JeffWouters.seqontrol.com. Others
append the zone themselves. Enter the zone once, not twice.
Wait until the record resolves, then click Verify:
dig +short TXT _github-pages-challenge-jeffwouters.seqontrol.com @1.1.1.1
Resolve-DnsName -Type TXT _github-pages-challenge-jeffwouters.seqontrol.com -Server 1.1.1.1
Leave the TXT record in place permanently. It is the proof of ownership, not a one-time token. Do not tidy it away once the page says Verified.
Confirm it took. GitHub's Settings → Pages lists the domain as Verified, and the repository's Pages API should agree:
gh api repos/JeffWouters/seqontrol.com/pages --jq .protected_domain_state # want: verified
Verification protects the domain and its immediate subdomains, so www.seqontrol.com is covered by the
same record. Deeper names such as a.b.seqontrol.com are not. Verification is per account or
organisation: if this repository ever moves to an organisation, verify the domain there as well.
CNAME file stays in the repository, containing seqontrol.com. Every deploy re-asserts the claim
from it. Deleting it releases the domain on the next publish while DNS still points at GitHub.seqontrol.com, with Enforce HTTPS on.www record stays a CNAME to jeffwouters.github.io: this account's own Pages host, never
another account's.185.199.108.153–185.199.111.153 or 2606:50c0:8000::153–2606:50c0:8003::153, the
retired Pages addresses 192.30.252.153 and 192.30.252.154, and any CNAME to a *.github.io host.
Delete every one that no repository on this account currently serves.*.seqontrol.com record may point at GitHub. GitHub's own documentation
warns that a wildcard record is an immediate takeover risk even when the domain is verified. There is
none today (a random subdomain returns NXDOMAIN), so keep it that way.www CNAME. Only then make the change. That covers switching Pages off, making the
repository private without a paid plan, renaming, transferring or deleting it, and downgrading the
plan. Put the records back only once a site is publishing again. Keep the TXT record throughout.A CAA record limits which certificate authorities may issue certificates for the domain. It is not a takeover fix, because GitHub would issue a hijacker's certificate from the same CA it uses for this site. It is defence in depth against mis-issuance by every other CA. GitHub Pages certificates come from Let's Encrypt:
| Record | Name | Value |
|---|---|---|
| CAA | @ | 0 issue "letsencrypt.org" |
| CAA | @ | 0 issue "digicert.com" |
The second line is required as soon as the app is live: app.seqontrol.com, api.seqontrol.com and the portal
subdomains (*.app.seqontrol.com) run on Azure Static Web Apps and Container Apps, whose managed certificates come from
DigiCert. Without it their next renewal fails.
CAA applies to every name below it too. Before adding it, list every other host in the zone that serves
TLS on a seqontrol.com name, and add an issue line for each of their CAs, or that host's next renewal
fails. If the DNS host does not offer the CAA record type, skip this step. It is not worth moving the
zone for.
Nothing is loaded from another origin today, and the CSP written by build_seo.py (script-src 'self')
enforces that in the browser. If that ever changes (the ANALYTICS hook in build_seo.py exists for
exactly that), every script or stylesheet loaded by absolute URL must carry Subresource Integrity.
tools/verify.py (check_subresource_integrity) fails the build when one does not:
<script defer src="https://cdn.example/[email protected]/lib.min.js"
integrity="sha384-..." crossorigin="anonymous"></script>
/latest/, /js/script.js)
breaks the day the vendor ships. If a vendor only serves a moving URL, self-host the file under js/
instead, and it stops being third-party.curl -s <url> | openssl dgst -sha384 -binary | openssl base64 -A.crossorigin="anonymous" is required. Without it the browser cannot check a cross-origin file
against its hash, and it blocks the file.script-src (or style-src) in build_seo.py
still has to name it, or the browser refuses the file regardless of its hash.The site speaks as SeQontrol only (since 2026-10-14). JeffOps, the registered entity behind
it, is named only where the law needs a party: the privacy notice (the controller) and the terms (the
other party). Nowhere else: no footer credit, no link to jeffops.com, no parent organisation in the
structured data. The contact address is [email protected]. verify.py fails any other page that
mentions JeffOps.
Light page, navy chrome (since 2026-10-10). The body is a cool light grey with white cards; the header, the heroes, the closing call-to-action panel and the footer are navy. This replaced the dark-only theme, whose neutrals came from the platform shell. The palette is SeQontrol's own:
| Token | Light | Navy chrome | Role |
|---|---|---|---|
--bg | #EEEFF5 | #151C4F | page / chrome background |
--bg-deep | #E4E5EE | #10163F | .band, footer |
--surface | #FFFFFF | #1E2660 | cards, panels |
--text | #151C4F | #FFFFFF | body text (Deep Navy on light) |
--muted | #3D4466 | #D2D4E6 | secondary text |
--dim | #565C7C | #A9ADC8 | captions, hints |
--brand | #6C4FDF | Qontrol Purple — fills and primary buttons | |
--accent | #4B33B8 | #C3B8F5 | links, eyebrows, labels |
Two token sets, one vocabulary. :root holds the light values; .on-navy and everything that is
always navy (.site-header, .hero, .phero, .cta > .wrap, .site-footer, .navy-panel)
re-declares the same custom properties with navy values. Write a component against the tokens and it
works on either ground with no second set of rules.
The supplied logo arrived flattened to JPEG, so the original alpha was gone and the transparency
checkerboard (30px cells) was baked in as real pixels. tools/extract_logo.py re-keys it:
| Asset | What it is |
|---|---|
seqontrol-symbol.png | Faithful extraction of the cloud + shield + magnifier, original colours |
seqontrol-wordmark.png | Faithful extraction of "SeQontrol" |
seqontrol-tagline.png | Faithful extraction of "Secure. Compliant. Confident." |
*-on-dark.png | Site variants — see below |
seqontrol-symbol-on-dark-{64,128}.png, seqontrol-wordmark-on-dark-320.png, seqontrol-tagline-on-dark-320.png | Sized copies, so a 32px slot does not pull a 644px image |
../favicon.ico | Multi-resolution icon (16→256) at the site root, where browsers look for it |
favicon-32.png, apple-touch-icon.png, icon-512.png | The same treatment as PNG |
og-card.png | 1200×630 social card, lockup on --bg |
Why the on-dark variants exist. The delivered artwork is drawn for a light page: the wordmark and
the shield are navy #1a1b4b, which is all but invisible on --bg #0b0f17. The variants keep the
purple exactly as supplied and lift only the navy to --text #e5e7eb, interpolating between the two
brand colours so anti-aliasing survives. The site uses the on-dark variants everywhere; the faithful
extractions are kept as the source of truth for print, light backgrounds and hand-off.
If you would rather the site carried the artwork unaltered, swap the -on-dark filenames back to the
plain ones in the header/footer — the wordmark will be very hard to read.
The icons match the site lockup: white shield, purple cloud, ring, magnifier and check, on a transparent background. The delivered artwork paints the shield navy because it is drawn for a white page; the icons lift it to pure white the same way the header lockup does, and leave every purple pixel untouched (interpolating between the two brand colours, so the anti-aliased rim stays clean).
The catch is the mirror of the old one: a white shield disappears on a light tab strip. So the navy
original is emitted as favicon-32-light.png and linked behind media="(prefers-color-scheme: light)".
Browsers honouring that swap to it; everything else falls back to favicon.ico, which is the white-shield
version. Do not "simplify" by deleting one of the two links — that is what the pair is for.
apple-touch-icon.png is opaque on a dark tile (#0b0f17): iOS ignores alpha and composites
home-screen icons onto black, and a white shield needs a dark ground anyway. Regenerate everything with
tools/make_icons.py.
Audited and fixed; re-check with tools/verify.py, which parses every page and fails on missing
lang, multiple or missing h1, skipped heading levels, duplicate ids, images without alt,
unlabelled form fields, missing landmarks, th without scope, unreachable scroll containers and
vague link text. It currently reports clean across all 12 pages — keep it that way when editing.
What that involved, and why, so it does not get undone:
--dim at 5.20:1 (light theme, on --bg-deep; see Brand and theme). In the dark theme --dim was #6b7490, which measured 3.71–4.22:1 and carried hints,
captions and the footer line — all small text. It was lifted at the same hue to #7c87a3. White on
--brand is 5.55:1 and on --brand-dark 4.87:1, which is why hover lifts only that far.
::placeholder is set explicitly because the browser default fails here.h3, footer column
headings are h2 (they follow the page's last section heading, so h4 would skip), and the hero's
step panel has a visually hidden h2 so its steps can be h3. Callout headings are styled by
container (.note h2, .note h3) rather than by element, so the level can follow document structure
without changing how anything looks — do not restyle these by element.header / nav[aria-label="Main"] / main / footer on every page, plus aside
where there is a complementary column.scope="col", matrix row labels are th scope="row", and group
rows are th scope="colgroup". thead th and tbody th are styled separately so row headers do not
inherit the uppercase column-header treatment..table-scroll has tabindex="0" — without it a keyboard user cannot
scroll a wide table at all. The generated matrices also carry role="group" and a label.aria-selected, roving tabindex, arrow/Home/End keys,
and panels that stay reachable. They degrade to stacked content without JavaScript.aria-hidden glyphs paired with .sr-only "Included" / "Not included", so
the matrices are not read as a grid of meaningless symbols.prefers-reduced-motion. Focus is never removed; the ring is
--focus at 6.32:1.Be exact about consent and onboarding. Each product that reads a tenant has its own Entra app and its
own admin consent, scoped to that product's permissions — the rule verify.py enforces, citing the code
(AppHost.cs, Connectors/Program.cs, Dredd's GraphDirectoryRefResolver). Adding a product is therefore a
second consent, through the same onboarding flow — never a second integration project — and revoking one
revokes exactly one. Never write that the admin "consents once", that adding a product "never asks for new
permissions", or that products share "one connection": verify.py refuses those phrasings, and since
2026-10-10 it collapses whitespace first, so a claim wrapped across two source lines is caught too. The
adjacent planes do not ride the Graph consent: Exchange admin needs a certificate and its own role,
Power Platform a pre-registered service principal, Azure/Log Analytics a workspace and reader role, DNS its
own OAuth2 flow per provider. Write-back is a separate consent round trip. platform.html carries the full
step-by-step table; do not compress it back into "connect once". (Until 2026-10-10 this bullet described
one Entra app for the whole platform, contradicting verify.py and every page; if the code has since
moved to a single app, change the rule, this bullet and the pages together.)
Security stands above compliance. The key principle, stated in the #principle section on
the home page (the full argument sits in its "Why security comes first" disclosure), reinforced in the CompliancePortal scope area and on the
platform trust list. The argument: a compliance control can be waived, which turns the report green
without changing the estate — so the security finding is treated as the fact and compliance as an
interpretation laid over it. A waived finding stays visible and scored, and its exception expires.
Do not let later copy imply compliance is the goal or that a green report equals a secure estate.
The three portals are grouped, with no bundle name. The group headline is the agreed phrase:
"Three products. One platform. Zero compromise." — on products/index.html. The homepage heads its product section "One platform. Products you switch on and off.", above the three availability groups.
CompliancePortal scope. Every mention says it covers the technical implementation of controls on
the platforms SeQontrol supports — not a whole-company compliance programme, and not an audit opinion.
The full carve-out (policies, HR process, physical security, vendor management, anything on an
unsupported platform) is a prominent box near the top of its section on products/coming.html.
Contrast, measured. On light grounds --text is 12.77:1 at worst, --muted 7.55, --dim 5.20,
--accent 6.79; white on --brand is 5.50. In navy chrome --muted is 8.44 at worst, --dim 5.61,
--accent 6.78. --brand as text on --bg-deep is only 4.38, so links and labels use --accent —
do not swap them.
No green, yellow, red or amber. A SeQontrol brand rule. Status chips use shape instead of traffic lights: a filled purple chip for something you can buy, an outlined chip for built-but-not-released, a dashed slate chip for still in development. Ticks in the matrices are purple.
Fonts: Sora (display), Manrope (text), JetBrains Mono (labels, figures). Self-hosted WOFF2 variable
fonts, latin and latin-ext subsets, SIL Open Font License 1.1 (licences beside the files). The CSP's
font-src 'self' already allows them; nothing is fetched from another origin. Arrows (→) fall outside
the latin subset and render from the system font, which is intended.
Product accent tones live in :root as --t-sharecare, --t-security, --t-condaccess,
--t-webscan, --t-compliance, --t-posture, --t-mailtrust, --t-dredd, --t-soon, chosen from
purples, blues, teal, magenta, plum and slate (see the brand rule above). Each clears 4.5:1 as text on
every light ground, because kickers and arrow links use the tone as text colour; the navy set carries
lighter versions. A card picks one with a tone-* class.
No CIS framework references anywhere, per instruction. Named frameworks are limited to SOC 2, ISO 27001, NIST CSF / 800-53, PCI-DSS, HIPAA, GDPR, NIS 2 and Essential Eight, plus "a first-party framework of our own". Google Cloud and AWS coverage is described as read-only connectors without naming a benchmark.
No customer references of any kind. No logos, testimonials, counts, revenue or traction claims — and no statements about their absence either. Where trust needs building, the site does it with concrete product limits (which planes are read-only, what Dredd has not shipped, Microsoft-first scope) rather than by discussing the customer base. Keep it that way when editing.
Prices live on pricing.html, and are quoted beside each product from one data file.
pricing.html carries every figure, the discount bands and the worked bills. Since 2026-10-10 the
products index table and the ShareCare page also show prices, because that is where a buyer reading
about a product asks what it costs. Those figures are never typed into the page: build_prices.py
renders them from tools/prices.py into <!--PRICE:key--> / <!--PRICE-TIERS:key--> blocks.
verify.py enforces both halves: check_prices fails if any figure in a generated block is missing
from pricing.html, and the content rule still refuses a $ figure or a discount percentage anywhere
else on those pages (GENERATED_PRICE_PAGES), and anywhere at all on every other page. So a price
change is two edits in one commit — build_legal.py and prices.py — and CI fails if you make only
one. The next step is rendering pricing.html's own tables from prices.py too, which would remove
the second edit. To add a tier block to another product page: an entry in prices.TIERS, the marker
on the page, and the page in GENERATED_PRICE_PAGES.
There is no suite discount, and that is a decision rather than an omission. Each product is priced on its own. Volume bands on the buyer's own estate are a different thing and are published.
The licensing matrices are a commitment, so keep them true to the catalog. The per-product tables
in pricing.html were built from the seeded entitlement catalog (Billing's CatalogSeeder.cs), not
invented: ShareCare and MailTrust ride the Visibility/Governance/Automation ladder, CompliancePortal
is banded by frameworks in scope with retention and attestation moving between bands, and
SecurityPortal has a single tier because it is scan-only. Rebuild them with python tools/build_licensing.py
rather than hand-editing 100-odd cells. One row states the intended ladder rather than the seeded
one — see the ShareCare caveat below.
The "What each licence includes" section is a tab set, one tab per product, each carrying the
technologies covered, the metering unit, and the capability matrix. It is progressive: the markup
ships with the tab strip hidden by CSS and every panel visible, and site.js adds data-tabs-ready
to flip it into tabs. With JavaScript off the whole thing renders stacked and nothing is lost — do not
"tidy" this by hiding panels in CSS. Tabs are keyboard-driven (arrows, Home/End) and deep-linkable:
pricing.html#mailtrust opens that product.
Technology lists are per product and use .tag.on for supported, .tag.off (dashed) for roadmap
or manual-only — e.g. ShareCare's Google Workspace, MailTrust's unsupported DNS providers. Keep the
roadmap items visibly distinct rather than dropping them; that distinction is the honesty.
Availability is not documented here, on purpose. tools/build_nav.py holds the PRODUCTS
list, and that is the source of truth: the nav badge, the product-page status line, the cards on
index.html and products/index.html, and the availability group headings are all rendered from
it. This bullet used to restate the list and had drifted twice — most recently claiming
CompliancePortal was available, omitting WebScan entirely, and describing full pages for products
whose pages no longer exist. Read PRODUCTS; do not copy it into prose.
Three states, not two, and the distinction is deliberate: None means running today, "Soon"
means built but not released, "In dev" means real work is still happening. Collapsing the last
two tells a reader that something close and something months out are the same distance away.
The four unreleased products share products/coming.html, anchored per product, rather than each
keeping a page that implies you could buy it.
verify.py enforces the part prose cannot: check_availability fails when a product card's
generated badge disagrees with the availability heading it sits under.
Every product has its own page, including the four you cannot buy. Those four open with a
state note — a banner under the lede saying plainly that the product is not for sale — and
build_nav.py writes it from PRODUCTS, so it is the same fact as the nav badge and the Status
line rather than a fourth copy someone has to remember. A page per product reads better than the
shared roadmap page it replaced, but it also looks exactly like a page for something orderable,
which is the risk that banner exists to close. Do not hand-write availability copy into it.