@charset "utf-8";
/* ==========================================================================
   BlurMyPhoto — shell.css · loaded by every page
   Concept: the redaction bar. Light paper, hairline rules, one black
   rectangle doing all the talking.

   This file used to be the back half of app.css. It was split so that the
   eighteen prose pages — the guides and the articles, which are the pages
   that earn — stop paying for ~19KB of editor chrome they never render.
   Tool pages load /assets/tool.css on top of this one.

   Sections: fonts · 01 tokens · 02 reset · 03 type · 04 layout ·
   05 skip link · 06 header/nav · 07 crumbs · 08 redaction · 09 buttons ·
   10 prose · 11 ads · 22 footer · 23 focus · 24 motion · 25 print
   ========================================================================== */

/* --- 00 · FONTS — self-hosted -----------------------------------------------
   Previously three families pulled from fonts.googleapis.com, which put a
   render-blocking stylesheet on a third-party origin in front of first paint:
   two extra DNS lookups and two TLS handshakes before a single glyph could be
   requested. They are now four woff2 files on this origin, latin subset,
   preloaded from the page head.

   The privacy dividend is real and not incidental: fonts.googleapis.com and
   fonts.gstatic.com are gone from the CSP, gone from section 7 of /privacy/,
   and gone from the list of parties that see a visitor's IP address.
   -------------------------------------------------------------------------- */
@font-face {
  font-family: 'Archivo'; font-style: normal; font-weight: 800; font-display: swap;
  src: url('/assets/fonts/archivo-800.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
                 U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
                 U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Instrument Sans'; font-style: normal; font-weight: 400 600; font-display: swap;
  src: url('/assets/fonts/instrument-sans-var.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
                 U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
                 U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Space Mono'; font-style: normal; font-weight: 400; font-display: swap;
  src: url('/assets/fonts/space-mono-400.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
                 U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
                 U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Space Mono'; font-style: normal; font-weight: 700; font-display: swap;
  src: url('/assets/fonts/space-mono-700.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
                 U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
                 U+2212, U+2215, U+FEFF, U+FFFD;
}

/* --- 01 · TOKENS ---------------------------------------------------------- */
:root {
  color-scheme: light;

  /* Palette — fixed, do not extend. */
  --paper: #EDF0F3; --panel: #FFFFFF; --bar: #0B0C0E; --ink: #16181C;
  --ink-soft: #616B78; --line: #CFD6DE; --stamp: #C8102E;
  /* --line at 1.47:1 is a hairline between blocks, which is all it has to be.
     Where a line is the whole outline of a control it has to be seen, so this
     one clears the 3:1 WCAG asks for non-text contrast: 3.59 on --panel,
     3.14 on --paper. */
  --line-strong: #7E8896;

  /* Text and hairlines sitting ON --bar: paper at reduced alpha (>= 8:1). */
  --on-bar-soft: rgba(237, 240, 243, .78);
  --on-bar-line: rgba(237, 240, 243, .20);

  /* Focus ring. --stamp clears 3:1 on --paper and on --bar; on black
     surfaces the ring switches to --paper (17:1) — see section 23. */
  --ring: var(--stamp); --ring-on-bar: var(--paper);
  --ring-w: 3px; --ring-gap: 2px;

  /* Families */
  --font-display: "Archivo", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-body: "Instrument Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: "Space Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* Fluid type scale — 360px first, desktop is the adaptation. */
  --fs-900: clamp(2.4rem, 1.55rem + 3.7vw, 4.25rem);
  --fs-800: clamp(1.95rem, 1.4rem + 2.4vw, 3.1rem);
  --fs-700: clamp(1.55rem, 1.28rem + 1.2vw, 2.15rem);
  --fs-600: clamp(1.3rem, 1.18rem + .55vw, 1.6rem);
  --fs-500: clamp(1.1rem, 1.04rem + .3vw, 1.28rem);
  --fs-400: clamp(1rem, .98rem + .12vw, 1.0625rem);
  --fs-300: .9375rem; --fs-200: .8125rem; --fs-100: .75rem;

  /* Space */
  --s-1: .25rem; --s-2: .5rem; --s-3: .75rem; --s-4: 1rem; --s-5: 1.5rem;
  --s-6: 2rem; --s-7: 3rem; --s-8: 4.5rem; --s-9: 6.5rem;

  /* Geometry — 3px radius is the ceiling anywhere on the site. */
  --wrap-max: 1080px; --wrap-narrow: 720px;
  --wrap-pad: clamp(1rem, 4.5vw, 2rem);
  --radius: 3px; --hair: 1px solid var(--line); --tap: 44px;

  /* Motion */
  --ease: cubic-bezier(.22, .75, .2, 1); --dur: .24s;
}

/* --- 02 · RESET ----------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

html {
  -webkit-text-size-adjust: 100%; text-size-adjust: 100%;
  scroll-behavior: smooth; scroll-padding-top: var(--s-6);
}

body {
  min-height: 100svh;
  background: var(--paper); color: var(--ink);
  font-family: var(--font-body); font-size: var(--fs-400); font-weight: 400;
  line-height: 1.65; overflow-wrap: break-word;
  text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased;
}

img, svg, video, canvas { display: block; max-width: 100%; }
img, video { height: auto; }
input, button, select, textarea { font: inherit; color: inherit; letter-spacing: inherit; }
button { background: none; border: 0; }
ul[class], ol[class] { list-style: none; padding: 0; }
table { border-collapse: collapse; }
[hidden] { display: none !important; }
::selection { background: var(--bar); color: var(--paper); }

/* --- 03 · TYPOGRAPHY ------------------------------------------------------ */
h1, h2 {
  font-family: var(--font-display); font-weight: 800;
  letter-spacing: -.03em; line-height: 1.04; text-wrap: balance;
}
h3, h4, h5, h6 {
  font-family: var(--font-body); font-weight: 600;
  letter-spacing: -.012em; line-height: 1.25; text-wrap: balance;
}
h1 { font-size: var(--fs-900); }
h2 { font-size: var(--fs-800); margin-top: var(--s-7); }
h3 { font-size: var(--fs-600); margin-top: var(--s-6); }
h4 { font-size: var(--fs-500); margin-top: var(--s-5); }
h5, h6 { font-size: var(--fs-400); margin-top: var(--s-5); }
h1 + p, h2 + p, h3 + p, h4 + p { margin-top: var(--s-3); }

p, ul, ol, dl, pre, blockquote, figure { margin-top: var(--s-4); }
main p { max-width: 68ch; }
main :is(ul, ol):not([class]) { padding-left: 1.15rem; max-width: 66ch; }
main :is(ul, ol):not([class]) li + li { margin-top: var(--s-2); }
main :is(ul, ol):not([class]) li::marker { color: var(--ink-soft); }
/* An unclassed list inside main is a prose list. A list inside a navigation
   component is not, and it needs saying here: those lists carry no class of
   their own, their wrapper does, and :not([class]) outranks the wrapper's own
   selector however that wrapper is written.

   Left alone the prose rules did two things to every one of them. The indent
   put the breadcrumb 18px right of the h1 it belongs to and took 18px the step
   row could not spare. The li + li margin then dropped every item after the
   first by 8px, which staggered the breadcrumb, lifted the current step chip
   clear of the rest of the row, and broke the hairline between related links
   into a dashed one. */
main :is(.crumbs, .toc, .related, .step-nav) :is(ul, ol) { padding-left: 0; max-width: none; }
main :is(.crumbs, .toc, .related, .step-nav) :is(ul, ol) li + li { margin-top: 0; }

strong, b { font-weight: 600; }
small { font-size: var(--fs-300); }
abbr[title] { text-decoration: underline dotted; text-underline-offset: 3px; cursor: help; }
code, pre, samp { font-family: var(--font-mono); font-size: .9em; }

pre {
  background: var(--panel); border: var(--hair); border-radius: var(--radius);
  padding: var(--s-4); overflow-x: auto;
}
blockquote { border-left: 4px solid var(--bar); padding-left: var(--s-4); font-size: var(--fs-500); }

/* Links. Hovering a prose link redacts it — the site's one flourish. */
a {
  color: var(--ink);
  text-decoration-line: underline; text-decoration-thickness: 1px;
  text-decoration-color: var(--line); text-underline-offset: 3px;
  transition: color var(--dur) var(--ease), background-color var(--dur) var(--ease),
    text-decoration-color var(--dur) var(--ease);
}
a:hover { text-decoration-color: var(--bar); }

main p a:hover, main li:not([class]) a:hover,
.lede a:hover, .note a:hover, .warn a:hover {
  background: var(--bar); color: var(--paper);
  text-decoration-color: transparent;
  padding: .06em .14em; margin: -.06em -.14em; border-radius: 2px;
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
}

/* --- 04 · LAYOUT & UTILITIES ---------------------------------------------- */
.wrap {
  width: 100%; max-width: var(--wrap-max);
  margin-inline: auto; padding-inline: var(--wrap-pad);
}
.wrap--narrow { max-width: var(--wrap-narrow); }

/* The tool mount and a bare breadcrumb trail carry their own column, so the
   working area always lines up with the prose above and below it however the
   individual page happened to wrap things. Nesting one inside a .wrap is
   harmless: both resolve to the same 720px border box. */
#tool-mount,
main > .crumbs {
  width: 100%; max-width: var(--wrap-narrow);
  margin-inline: auto; padding-inline: var(--wrap-pad);
}

/* Breathing room under the header, which no page was giving itself. */
main { padding-block-start: clamp(1.25rem, 3vw, 2.25rem); }

.section { padding-block: clamp(2.25rem, 6vw, 4rem); }
.section > :first-child { margin-top: 0; }

.rule { height: 0; border: 0; border-top: var(--hair); margin-block: var(--s-6); }

.sr-only {
  position: absolute !important; width: 1px; height: 1px;
  padding: 0; margin: -1px; border: 0; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

.mono {
  font-family: var(--font-mono); font-size: var(--fs-100); font-weight: 400;
  text-transform: uppercase; letter-spacing: .14em; line-height: 1.4;
  color: var(--ink-soft);
}

/* --- 05 · SKIP LINK ------------------------------------------------------- */
.skip-link {
  position: absolute; top: -5rem; left: var(--s-3); z-index: 120;
  display: inline-flex; align-items: center; min-height: var(--tap);
  padding: var(--s-2) var(--s-4); border-radius: var(--radius);
  background: var(--bar); color: var(--paper);
  font-weight: 600; text-decoration: none;
  transition: top .16s var(--ease);
}
.skip-link:focus { top: var(--s-3); }

/* --- 06 · HEADER, BRAND, NAV ---------------------------------------------- */
.site-head { background: var(--panel); border-bottom: var(--hair); }

.site-head__inner {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: 0; padding-block: var(--s-3) 0;
}

.brand {
  display: inline-flex; align-items: center; gap: .55rem;
  min-height: var(--tap); color: var(--ink); text-decoration: none;
}
.brand__mark {
  flex: none; width: 30px; height: 14px; border-radius: 2px;
  background: var(--bar); box-shadow: inset -4px 0 0 var(--stamp);
}
.brand__name {
  font-family: var(--font-display); font-weight: 800;
  font-size: 1.3rem; letter-spacing: -.03em; line-height: 1;
}
.brand__redact {
  background: var(--bar); color: var(--panel);
  padding: .04em .16em; border-radius: 2px;
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
}
.brand:hover .brand__redact { background: var(--stamp); }

/* Horizontally scrollable strip on narrow screens. No JS, no hamburger. */
.site-nav {
  display: flex; align-items: stretch; gap: var(--s-4); width: 100%;
  margin-inline: calc(var(--wrap-pad) * -1); padding-inline: var(--wrap-pad);
  overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch; scrollbar-width: none;
}
.site-nav::-webkit-scrollbar { display: none; }

.site-nav a {
  position: relative; display: inline-flex; align-items: center;
  min-height: var(--tap); padding-block: var(--s-3);
  white-space: nowrap; text-decoration: none;
  font-size: var(--fs-300); font-weight: 500; color: var(--ink-soft);
}
/* Redaction-style underline. */
.site-nav a::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: .55rem;
  height: 3px; background: var(--bar); border-radius: 2px;
  transform: scaleX(0); transform-origin: 0 50%;
  transition: transform .18s var(--ease);
}
.site-nav a:hover, .site-nav a:focus-visible,
.site-nav a[aria-current="page"] { color: var(--ink); }
.site-nav a:hover::after, .site-nav a:focus-visible::after,
.site-nav a[aria-current="page"]::after { transform: scaleX(1); }
.site-nav a[aria-current="page"] { font-weight: 600; }

@media (max-width: 859.98px) {
  /* Fade hint at the right edge so the strip reads as scrollable. */
  .site-nav {
    -webkit-mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 2.75rem), transparent 100%);
    mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 2.75rem), transparent 100%);
  }
}
@media (min-width: 860px) {
  .site-head__inner {
    flex-direction: row; align-items: center; justify-content: space-between;
    gap: var(--s-6); padding-block: var(--s-2);
  }
  .site-nav {
    width: auto; margin-inline: 0; padding-inline: 0;
    overflow: visible; gap: clamp(.9rem, 1.6vw, 1.6rem);
  }
}

/* --- 07 · BREADCRUMBS ----------------------------------------------------- */
.crumbs { padding-top: 0; }
.crumbs ol {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0 .55rem;
  list-style: none; margin: 0; padding: 0;
  font-family: var(--font-mono); font-size: var(--fs-100);
  text-transform: uppercase; letter-spacing: .1em; color: var(--ink-soft);
}
.crumbs li { display: flex; align-items: center; gap: .55rem; }
.crumbs li + li::before { content: "/"; color: var(--line); }
.crumbs a, .crumbs [aria-current="page"] {
  display: inline-flex; align-items: center; min-height: var(--tap);
}
.crumbs a { color: var(--ink-soft); text-decoration: none; border-bottom: 1px solid transparent; }
.crumbs a:hover { color: var(--ink); border-bottom-color: var(--bar); background: none; }
.crumbs [aria-current="page"] { color: var(--ink); }

/* --- 08 · THE REDACTION BAR (signature) -----------------------------------
   A solid --bar rectangle over the word that wipes away to the right.
   Reveals on hover / focus-within / tap, and once on load via the
   .redact-ready class app.js puts on <body>, staggered from inline --i.
   -------------------------------------------------------------------------- */
.redact { position: relative; display: inline-block; white-space: nowrap; isolation: isolate; }
.redact__word { position: relative; z-index: 1; }

.redact::after {
  content: ""; position: absolute; z-index: 2;
  top: -.06em; right: -.14em; bottom: -.06em; left: -.14em;
  background: var(--bar); border-radius: 2px;
  transform: scaleX(1); transform-origin: 100% 50%;
  transition: transform .4s var(--ease), opacity .4s var(--ease);
  pointer-events: none;
}
/* Fading as well as wiping matters: a scaleX(0) box still rounds up to a
   one-pixel rule on some displays, which reads as a stray caret in a heading. */
.redact:hover::after, .redact:active::after,
.redact:focus-within::after { transform: scaleX(0); opacity: 0; }

body.redact-ready .redact::after {
  animation: redact-lift .6s var(--ease) both;
  animation-delay: calc(260ms + var(--i, 0) * 110ms);
}
@keyframes redact-lift {
  from { transform: scaleX(1); opacity: 1; }
  to { transform: scaleX(0); opacity: 0; }
}

/* No scripting means the load reveal never fires, so uncover the word. */
@media (scripting: none) {
  .redact::after { transform: scaleX(0); opacity: 0; transition: none; }
}

/* --- 09 · BUTTONS — exactly one .btn--primary per screen ------------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  min-height: 48px; min-width: var(--tap); padding: .7rem 1.25rem;
  border: 1px solid transparent; border-radius: var(--radius);
  font-family: var(--font-body); font-size: var(--fs-400); font-weight: 600;
  line-height: 1.2; text-align: center; text-decoration: none;
  cursor: pointer; touch-action: manipulation; -webkit-tap-highlight-color: transparent;
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease),
    border-color var(--dur) var(--ease), transform .08s linear;
}
.btn:active { transform: translateY(1px); }
.btn[disabled], .btn[aria-disabled="true"] { cursor: not-allowed; opacity: .5; transform: none; }

.btn--primary { background: var(--bar); border-color: var(--bar); color: var(--paper); }
.btn--primary:hover:not([disabled], [aria-disabled="true"]) {
  background: var(--ink); border-color: var(--ink);
}

.btn--ghost { background: var(--panel); border-color: var(--line); color: var(--ink); }
.btn--ghost:hover:not([disabled], [aria-disabled="true"]) {
  border-color: var(--bar); background: var(--paper);
}

.btn--text {
  min-height: var(--tap); padding: .5rem .25rem; background: none;
  color: var(--ink); font-weight: 600;
  text-decoration: underline; text-decoration-thickness: 1px;
  text-decoration-color: var(--line); text-underline-offset: 4px;
}
.btn--text:hover:not([disabled]) { text-decoration-color: var(--bar); }

/* Dense rows of secondary controls. The 44px tap target is kept; only the
   horizontal padding and the type get smaller. */
.btn--sm { padding: .5rem .8rem; min-height: var(--tap); font-size: var(--fs-300); }

.btn--danger { background: var(--panel); border-color: var(--stamp); color: var(--stamp); }
.btn--danger:hover:not([disabled], [aria-disabled="true"]) {
  background: var(--stamp); color: var(--paper);
}

/* --- 10 · PROSE COMPONENTS ------------------------------------------------ */
.lede {
  max-width: 58ch; margin-top: var(--s-5);
  font-size: var(--fs-500); line-height: 1.55; color: var(--ink);
}

.note, .warn {
  max-width: 68ch; margin-block: var(--s-5); padding: var(--s-4) var(--s-5);
  background: var(--panel); border: var(--hair); border-radius: var(--radius);
}
/* .warn differs from .note by edge weight as well as by the stamp colour. */
.warn { border-left: 4px solid var(--stamp); }
.note > :first-child, .warn > :first-child { margin-top: 0; }
.note p, .warn p { max-width: none; }
.warn > strong:first-child {
  display: block; margin-bottom: .35rem;
  font-family: var(--font-mono); font-size: var(--fs-200); font-weight: 700;
  text-transform: uppercase; letter-spacing: .12em; color: var(--stamp);
}

.kbd {
  display: inline-block; padding: .1em .45em;
  background: var(--panel); border: var(--hair); border-bottom-width: 2px;
  border-radius: var(--radius); color: var(--ink);
  font-family: var(--font-mono); font-size: .85em; line-height: 1.5;
  /* Key names like "Ctrl + Z" should hold together, but .kbd is also used for
     multi-word menu commands, and one of those is wider than a 360px screen.
     Keep words intact, allow the phrase to wrap, and let a single unbreakable
     token fall back to breaking rather than pushing the page sideways. */
  white-space: normal; overflow-wrap: anywhere; max-width: 100%;
}

/* Four-step flow — the only structural numbering on the site. */
.steps {
  counter-reset: step; list-style: none;
  display: grid; gap: var(--s-5); margin-top: var(--s-5); padding: 0;
}
/* The number is positioned, not a grid cell.
   Laying the item out as a grid gave every child a cell of its own, so an item
   written as `<strong>Lead.</strong> then a sentence` dropped the sentence into
   the 2.5rem number column and set it one word to a line. Positioning the
   counter lets inline content of any shape flow as a single block, and the
   measure is capped to match the paragraphs around it. */
.steps > li {
  counter-increment: step; position: relative;
  min-height: 2.5rem;
  padding-left: calc(2.5rem + var(--s-4));
  max-width: calc(62ch + 2.5rem + var(--s-4));
}
.steps > li::before {
  content: counter(step, decimal-leading-zero);
  position: absolute; left: 0; top: 0;
  display: grid; place-items: center; width: 2.5rem; height: 2.5rem;
  background: var(--bar); color: var(--paper); border-radius: var(--radius);
  font-family: var(--font-mono); font-size: var(--fs-200);
  font-weight: 700; letter-spacing: .06em;
}
.steps > li > :first-child { margin-top: 0; }
.steps h3 { margin-top: 0; font-size: var(--fs-500); }
.steps p { max-width: 62ch; }

/* Spec table — styled as <dl class="facts"> or <table class="facts">. */
.facts {
  display: grid; grid-template-columns: minmax(0, 1fr);
  margin-block: var(--s-5); border-top: var(--hair); font-size: var(--fs-300);
}
.facts dt {
  padding-top: var(--s-3);
  font-family: var(--font-mono); font-size: var(--fs-100);
  text-transform: uppercase; letter-spacing: .1em; color: var(--ink-soft);
}
.facts dd { margin: 0; padding-bottom: var(--s-3); border-bottom: var(--hair); }
table.facts { display: table; width: 100%; border-top: 0; }
.facts :is(th, td) {
  padding: var(--s-3) var(--s-3) var(--s-3) 0;
  text-align: left; vertical-align: top; border-bottom: var(--hair);
}
.facts th {
  width: 34%; font-family: var(--font-mono); font-size: var(--fs-100); font-weight: 400;
  text-transform: uppercase; letter-spacing: .1em; color: var(--ink-soft);
}
@media (min-width: 560px) {
  dl.facts { grid-template-columns: minmax(8rem, 30%) minmax(0, 1fr); }
  .facts dt { padding-block: var(--s-3); border-bottom: var(--hair); }
  .facts dd { padding-block: var(--s-3); }
}

.faq { margin-top: var(--s-7); }
.faq h3 {
  margin-top: var(--s-5); padding-top: var(--s-4);
  border-top: var(--hair); font-size: var(--fs-500);
}
.faq h3:first-of-type { border-top: 0; padding-top: 0; }
.faq p { margin-top: var(--s-2); }

.related { margin-block: var(--s-7); padding-top: var(--s-4); border-top: 2px solid var(--bar); }
.related h2, .toc h2 {
  margin: 0 0 var(--s-3);
  font-family: var(--font-mono); font-size: var(--fs-200); font-weight: 700;
  text-transform: uppercase; letter-spacing: .14em; color: var(--ink-soft);
}
.related ul { list-style: none; margin: 0; padding: 0; display: grid; }
.related li { border-bottom: var(--hair); }
.related a {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-4);
  min-height: 56px; padding-block: var(--s-3); font-weight: 600; text-decoration: none;
}
.related a::after {
  content: "\2192"; flex: none; color: var(--ink-soft);
  transition: transform .18s var(--ease);
}
.related a:hover, .related a:focus-visible { text-decoration: underline; text-underline-offset: 4px; }
.related a:hover::after { transform: translateX(4px); color: var(--ink); }
@media (min-width: 720px) {
  .related ul { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--s-6); }
}

.toc {
  margin-block: var(--s-6); padding: var(--s-4) var(--s-5);
  background: var(--panel); border: var(--hair); border-left: 4px solid var(--bar);
  border-radius: var(--radius);
}
.toc :is(ul, ol) { list-style: none; margin: 0; padding: 0; display: grid; }
.toc a {
  display: flex; align-items: center; min-height: 40px;
  font-size: var(--fs-300); color: var(--ink); text-decoration: none;
}
.toc a:hover { text-decoration: underline; text-underline-offset: 4px; }

/* --- 10b · FIGURES --------------------------------------------------------
   The site shipped with no <img> on any page, which for a tool about pictures
   was a strange omission: nothing to earn Google Images traffic, and how-to
   prose with no picture of the how. Most of these are diagrams.

   The photographs are the exception, and they follow the rule the original
   comment here set: nothing fabricated. Every covered face was made by running
   /assets/regions.js, the file the tool runs in your browser, over the sample
   photo. The boxes came from the same BlazeFace detector a visitor gets. It
   found two of the three faces and the third was covered by hand; /credits/
   records that, along with the licence. Only the covered version is shown on
   a page.

   width and height are on every <img>, so the aspect-ratio box is reserved
   before the file arrives and the figure cannot shift the text under it.
   -------------------------------------------------------------------------- */
.figure {
  margin: var(--s-7) 0;
}
.figure img {
  display: block; width: 100%; height: auto;
  background: var(--paper);
  border: var(--hair); border-radius: var(--radius);
}
.figure figcaption {
  margin-top: var(--s-3);
  max-width: 62ch;
  font-size: var(--fs-300); line-height: 1.55; color: var(--ink-soft);
}
/* Wide diagrams get to break the narrow prose column on roomy screens. */
@media (min-width: 1000px) {
  .wrap--narrow .figure { width: calc(100% + 2 * var(--s-8)); margin-inline: calc(-1 * var(--s-8)); }
  .wrap--narrow .figure figcaption { margin-inline: var(--s-8); }
}

/* Side-by-side comparisons. Separate <img> elements rather than one wide
   composite: each gets its own alt text and can be indexed on its own, and on
   a narrow screen they stack instead of shrinking to thumbnails where you can
   no longer see what the mode did. */
.compare {
  display: grid; gap: var(--s-4); grid-template-columns: 1fr;
}
@media (min-width: 560px) {
  .compare { grid-template-columns: repeat(2, 1fr); }
  .compare--three { grid-template-columns: repeat(3, 1fr); }
}
.compare__item { margin: 0; }
.compare__item img {
  display: block; width: 100%; height: auto;
  background: var(--paper);
  border: var(--hair); border-radius: var(--radius);
}
.compare__label {
  margin: var(--s-2) 0 0;
  font-family: var(--font-mono); font-size: var(--fs-100);
  text-transform: uppercase; letter-spacing: .12em; color: var(--ink-soft);
}
.compare__label b { font-weight: 600; color: var(--ink); letter-spacing: inherit; }

/* --- 11 · AD SLOTS — height reserved so CLS stays at zero ------------------
   Three rules govern everything in this section.

   1. Reserve the height in CSS, always. An ad that arrives into a box already
      the right size cannot move the text under it, which is the whole of the
      site's Cumulative Layout Shift budget.
   2. Label every slot. .ad-slot__note is not decoration and not an apology —
      an ad a reader cannot tell apart from the interface is a policy problem
      as well as a discourtesy, and this site's entire proposition is that it
      does not do sly things.
   3. Nothing sticky goes near the download button. See the data-step guard in
      ads.js; the styling here assumes that guard holds.
   -------------------------------------------------------------------------- */
.ad-slot {
  position: relative;
  display: grid; place-items: center; min-height: 280px;
  margin-block: var(--s-7); padding: var(--s-4);
  background: var(--panel); border: 1px dashed var(--line); border-radius: var(--radius);
  text-align: center; contain: layout paint;
}
.ad-slot__note {
  display: block; max-width: 36ch;
  font-family: var(--font-mono); font-size: var(--fs-100);
  line-height: 1.6; letter-spacing: .06em; color: var(--ink-soft);
}
/* Once a unit is in the box the placeholder label would be talking over it. */
.ad-slot[data-ad-state="filled"] .ad-slot__note { display: none; }
.ad-slot[data-ad-state="filled"] { border-style: solid; }
.ad-slot > ins.adsbygoogle { display: block; width: 100%; }

@media (max-width: 699.98px) { .ad-slot { min-height: 250px; } }

/* In-content units sit inside the prose column and are deliberately quieter
   than the end-of-article slot: no heavy border, a little more air above than
   below, so the eye reads them as a break between sections rather than as the
   end of the piece. The rule above the label does the separating. */
.ad-slot--inline {
  min-height: 280px;
  margin-block: var(--s-7) var(--s-6);
  background: transparent; border: 0; border-top: var(--hair);
  border-radius: 0; padding: var(--s-5) 0 0;
}
.ad-slot--inline .ad-slot__note { color: var(--ink-soft); opacity: .85; }
@media (max-width: 699.98px) { .ad-slot--inline { min-height: 250px; } }

/* The mobile anchor. Capped well under the 30% of viewport height that the
   Coalition for Better Ads treats as the line for a large sticky ad, and
   dismissible, which is the other half of that standard. */
.ad-slot--anchor {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
  display: block;
  min-height: 60px; max-height: 110px; margin: 0;
  padding: var(--s-2) var(--s-3) calc(var(--s-2) + env(safe-area-inset-bottom, 0px));
  border: 0; border-top: var(--hair); border-radius: 0;
  background: var(--panel); contain: layout paint size;
  box-shadow: 0 -2px 12px rgb(0 0 0 / .07);
}
.ad-slot--anchor .ad-slot__note { display: none; }
.ad-slot__close {
  position: absolute; top: -13px; right: var(--s-3);
  display: grid; place-items: center;
  width: 26px; height: 26px; padding: 0;
  font: 700 15px/1 var(--font-mono); color: var(--ink);
  background: var(--panel); border: 1px solid var(--line); border-radius: 50%;
  cursor: pointer;
}
.ad-slot__close:hover { background: var(--paper); }

/* Keep the footer reachable when the anchor is up. */
body.has-anchor-ad { padding-bottom: 110px; }
@media (min-width: 900px) { .ad-slot--anchor { display: none; } body.has-anchor-ad { padding-bottom: 0; } }

/* --- 22 · FOOTER ---------------------------------------------------------- */
.site-foot {
  margin-top: var(--s-8);
  padding-block: var(--s-7) calc(var(--s-6) + env(safe-area-inset-bottom, 0px));
  background: var(--bar); color: var(--paper);
}
.foot-free {
  max-width: 22ch; margin: 0 0 var(--s-6); padding-bottom: var(--s-5);
  border-bottom: 1px solid var(--on-bar-line);
  font-family: var(--font-display); font-weight: 800; font-size: var(--fs-700);
  letter-spacing: -.03em; line-height: 1.1; color: var(--paper);
}
.foot-grid { display: grid; gap: var(--s-6); grid-template-columns: minmax(0, 1fr); }
@media (min-width: 620px) {
  .foot-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s-5); }
}
.foot-col ul { list-style: none; margin: 0; padding: 0; }
.foot-h {
  margin: 0 0 var(--s-2);
  font-family: var(--font-mono); font-size: var(--fs-100); font-weight: 700;
  text-transform: uppercase; letter-spacing: .14em; color: var(--on-bar-soft);
}
.site-foot a {
  display: inline-flex; align-items: center; min-height: var(--tap);
  color: var(--paper); font-size: var(--fs-300);
  text-decoration: none; border-bottom: 1px solid transparent;
}
.site-foot a:hover, .site-foot a:focus-visible {
  background: var(--paper); color: var(--bar);
  padding-inline: .3rem; margin-inline: -.3rem; border-radius: 2px;
}
.foot-legal {
  max-width: 78ch; margin-top: var(--s-6); padding-top: var(--s-5);
  border-top: 1px solid var(--on-bar-line);
  font-size: var(--fs-300); line-height: 1.6; color: var(--on-bar-soft);
}

/* --- 23 · FOCUS — one ring, never removed --------------------------------- */
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible,
textarea:focus-visible, summary:focus-visible, canvas:focus-visible,
[tabindex]:focus-visible, .dropzone:focus-within {
  outline: var(--ring-w) solid var(--ring);
  outline-offset: var(--ring-gap);
  border-radius: var(--radius);
}
/* On black surfaces the ring switches to paper for maximum separation. */
.site-foot a:focus-visible, .site-foot button:focus-visible,
.skip-link:focus, .skip-link:focus-visible {
  outline: var(--ring-w) solid var(--ring-on-bar); outline-offset: var(--ring-gap);
}
.stage__canvas:focus-visible { outline-offset: 0; }

/* --- 24 · MOTION ---------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important; animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important; transition-delay: 0ms !important;
  }
  /* The bar is not rendered at all — the word is simply visible. */
  .redact::after { display: none !important; }
  .related a::after { transform: none !important; }
}

/* --- 25 · PRINT — guides print as clean documents -------------------------- */
@media print {
  :root { --paper: #FFFFFF; }
  body { background: #FFFFFF; color: #000000; font-size: 11pt; line-height: 1.45; }

  .skip-link, .site-nav, .crumbs, .ad-slot, .ad-slot--inline, .ad-slot--anchor,
  .ad-slot__close, .dropzone, .stage,
  .toolbar, .modes, .progress, .status, .cold-start, .step-nav, .actionbar,
  .filecard, .export-opts, .region-count, .toc, .btn { display: none !important; }

  .site-head { border-bottom: 1px solid #000000; padding-bottom: 6pt; }
  .brand__mark { box-shadow: none; }
  .brand__redact {
    background: transparent !important; color: #000000 !important;
    box-shadow: inset 0 0 0 1px #000000;
  }
  .redact::after { display: none !important; }

  .site-foot {
    margin-top: 18pt; padding: 6pt 0 0;
    background: transparent; color: #000000; border-top: 1px solid #000000;
  }
  .site-foot .foot-free, .site-foot .foot-grid { display: none; }
  .foot-legal { margin: 0; padding: 0; border: 0; color: #000000; font-size: 8pt; }

  main p, main :is(ul, ol) { max-width: none; }
  main a { color: #000000; text-decoration: underline; }
  main p a[href^="/"]::after { content: " (blurmyphoto.com" attr(href) ")"; font-size: 8pt; }
  main p a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 8pt; }

  h1, h2, h3, h4 { break-after: avoid; page-break-after: avoid; }
  .note, .warn, .steps > li, .facts, .related { break-inside: avoid; page-break-inside: avoid; }
  .section { padding-block: 8pt; }
}
