:root{--bg:#14100E;--card:#1E2422;--bd:#2E3634;--tx:#EDEBE6;--mut:#9AA09D;
      --acc:#C4A484;--red:#C87B6E;--amb:#D4A05A;--grn:#7FA37A;
      --serif:'Playfair Display',Georgia,serif;--mono:'DM Mono',Consolas,monospace;
      --font:'DM Sans',system-ui,sans-serif;
      /* The nav CSS copied from What's New references --ink and --accent.
         Neither existed here, so nav{background:var(--ink)} was an invalid
         declaration: the bar fell back to transparent. On a dark page that
         looks correct, which is why it shipped -- in light mode it became
         light text on a light background and the links disappeared.
         An undefined custom property invalidates the declaration silently
         rather than erroring, which is the third time that has bitten this
         site. --ink is fixed, so the nav stays dark in both themes, matching
         every other page. */
      --ink:#1D2322;--accent:#C4A484;
      /* One cloud palette, used by every cloud-coded element on the page:
         status chips, region chips and the write-up cards. They had three
         different palettes, and two of them made Google and Azure the SAME
         blue -- 15 apart in RGB on dark, 6 on light, which is no difference
         at all. A colour whose entire job is telling the clouds apart has to
         actually do it, so Google is green and the three are now 68-133
         apart in every theme. All six pass AA on the surface they sit on. */
      /* THE SITE CLOUD PALETTE. Same hexes as index.html, blog.css,
         intelligence/index.html and build_news_page.py -- this page briefly
         invented its own (#E8A87C/#9DBEE0/#A8C88A), which made it the only
         page where AWS was not sand and Google was not olive.
         --c-*     identity, for edges and fills. Never text.
         --c-*-t   the same identity adjusted per theme, for TEXT, because the
                   identity colours are mid-tones and fail AA on both cards. */
      --c-aws:#C4A484;--c-azure:#5B7B9A;--c-gcp:#8A9A5B;
      --c-aws-t:#D6B896;--c-azure-t:#9DB6CE;--c-gcp-t:#BCC98E}
/* The daily palette. Every other page shifts its ground colour with the day
   of the week; this one was a fixed dark grey, so moving between it and the
   rest of the site looked like leaving the site. Same seven values as the
   What's New page, so a reader crossing between them sees no change. */
html[data-palette="mon"]{--bg:#191E20;--card:#1F2528}
html[data-palette="tue"]{--bg:#1F1D1B;--card:#262421}
html[data-palette="wed"]{--bg:#191F1E;--card:#1F2625}
html[data-palette="thu"]{--bg:#211C1C;--card:#292323}
html[data-palette="fri"]{--bg:#1B1E1B;--card:#212622}
html[data-palette="sat"]{--bg:#1D1D21;--card:#242429}
html[data-palette="sun"]{--bg:#1D1E1B;--card:#232521}

*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--tx);font-family:var(--font);line-height:1.6}
.wrap{max-width:1000px;margin:0 auto;padding:1.6rem 1.2rem 3.5rem}

/* The opening band, matching What's New, the hub, the blog and the portfolio.
   This page went straight from the nav onto the page background, so in light
   mode it was the one page of five whose first screen was pale while every
   other one opened on a dark band. --ink is fixed dark on this page in both
   themes, which is exactly what the others use.
   The text inside is set explicitly rather than inherited: --mut and --tx are
   theme-aware, so in light mode they would paint dark grey on this deliberately
   dark band. */
header.hero{background:var(--ink, #1D2322);padding:2.6rem 1.2rem 2rem}
header.hero .inner{max-width:1000px;margin:0 auto}
header.hero h1{color:#EDEBE6}
header.hero .eyebrow{color:var(--acc, #C4A484)}
header.hero .sub{color:rgba(237,235,230,.74)}
header.hero .fresh{color:rgba(237,235,230,.6)}
header.hero .fresh.stale{color:var(--amb, #D4A05A)}
@media(max-width:720px){
  header.hero{padding:2rem 1.1rem 1.6rem}
}
h1{font-family:var(--serif);font-size:2rem;margin:0 0 .35rem;line-height:1.15}
h2{font-family:var(--serif);font-size:1.25rem;margin:2.4rem 0 .5rem}
.eyebrow{font-family:var(--mono);font-size:.66rem;letter-spacing:.2em;text-transform:uppercase;
         color:var(--acc);margin:0 0 .7rem;display:flex;align-items:center;gap:.5rem}
.dot{width:6px;height:6px;border-radius:50%;background:var(--acc);
     animation:pulse 2.6s ease-in-out infinite}
@keyframes pulse{0%,100%{opacity:.35}50%{opacity:1}}
@media(prefers-reduced-motion:reduce){.dot{animation:none;opacity:.85}}
.sub{color:var(--mut);font-size:.92rem;margin:0 0 1.1rem;max-width:62ch}

/* Freshness is not decoration. The page is only as current as the last
   scheduled run, and a status board that looks live while being hours old is
   worse than one that admits its age, because a reader will act on it. */
.fresh{font-family:var(--mono);font-size:.7rem;color:var(--mut);margin:0 0 1.5rem}
.fresh.stale{color:var(--amb)}

.sum{display:grid;grid-template-columns:repeat(3,1fr);gap:.8rem;margin:0 0 1.8rem}
.scard{background:var(--card);border:1px solid var(--bd);border-radius:12px;padding:.9rem 1rem}
.sc-n{font-family:var(--mono);font-size:.64rem;letter-spacing:.14em;color:var(--mut);
      text-transform:uppercase}
.sc-v{font-family:var(--serif);font-size:1.35rem;margin-top:.2rem;display:flex;
      align-items:center;gap:.45rem}
.pill{width:9px;height:9px;border-radius:50%;flex-shrink:0}
/* Three states, not two. An unreachable feed and a healthy cloud both produce
   an empty incident list, and showing both green turns a monitoring failure
   into false reassurance. */
.ok{background:var(--grn)}.bad{background:var(--red)}.err{background:var(--amb)}
.sc-s{font-size:.76rem;color:var(--mut);margin-top:.1rem}
.sc-t{font-family:var(--mono);font-size:.64rem;color:var(--mut);opacity:.75;margin-top:.35rem}

.inc{background:var(--card);border:1px solid var(--bd);border-left:3px solid var(--red);
     border-radius:12px;padding:1rem 1.1rem;margin:0 0 .9rem}
.top{display:flex;gap:.4rem;align-items:center;flex-wrap:wrap;margin-bottom:.55rem}
.chip{font-family:var(--mono);font-size:.62rem;letter-spacing:.08em;text-transform:uppercase;
      padding:.18rem .45rem;border-radius:4px;border:1px solid var(--bd);color:var(--mut)}

.chip.sev{color:var(--red);border-color:#5A3530}
.ttl{font-family:var(--serif);font-size:1.06rem;line-height:1.35;margin:0 0 .6rem;font-weight:600}
.meta{display:grid;grid-template-columns:7rem 1fr;gap:.3rem .9rem;font-size:.83rem;margin:0 0 .75rem}
.k{font-family:var(--mono);font-size:.64rem;letter-spacing:.08em;text-transform:uppercase;
   color:var(--mut);padding-top:.2rem}
.tags{display:flex;flex-wrap:wrap;gap:.3rem}
.tag{font-size:.72rem;background:#252C2A;border:1px solid var(--bd);border-radius:4px;
     padding:.1rem .4rem;color:#C7CCCA}
.upd{background:#181E1C;border:1px solid var(--bd);border-radius:8px;padding:.7rem .8rem;
     font-size:.82rem;color:#CBD0CE;margin:0 0 .65rem}
.upd .lab{font-family:var(--mono);font-size:.6rem;letter-spacing:.1em;text-transform:uppercase;
          color:var(--mut);display:block;margin-bottom:.35rem}
.foot{display:flex;justify-content:space-between;align-items:center;gap:.6rem;flex-wrap:wrap;
      font-size:.74rem;color:var(--mut);border-top:1px solid var(--bd);padding-top:.6rem}
.foot a{color:var(--acc);text-decoration:none}
.allclear{background:var(--card);border:1px solid var(--bd);border-radius:12px;
          padding:1.4rem;text-align:center;color:var(--mut);font-size:.9rem}

.stat{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:.8rem;margin:.8rem 0 0}
.st{background:var(--card);border:1px solid var(--bd);border-radius:12px;padding:.9rem 1rem}
.st .v{font-family:var(--serif);font-size:1.6rem;color:var(--acc)}
.st .n{font-family:var(--mono);font-size:.62rem;letter-spacing:.12em;text-transform:uppercase;
       color:var(--mut)}
/* The sample size sits under every figure. A median over six incidents is an
   anecdote wearing a statistic's clothes unless it says so. */
.st .w{font-size:.74rem;color:var(--mut);margin-top:.25rem}

table{width:100%;border-collapse:collapse;font-size:.82rem;margin:.7rem 0 0}
th,td{border:1px solid var(--bd);padding:.5rem .6rem;text-align:left;vertical-align:top}
th{background:#232A28;font-family:var(--mono);font-size:.64rem;letter-spacing:.1em;
   text-transform:uppercase;color:var(--mut);font-weight:400}
.note{background:#1A1512;border:1px solid #3A2E24;border-left:3px solid var(--acc);
      border-radius:8px;padding:.85rem 1rem;font-size:.85rem;color:#D6D2CC;margin:.9rem 0 0}
.src{font-family:var(--mono);font-size:.68rem;color:var(--mut);line-height:1.9;margin:.9rem 0 0}
.src a{color:var(--mut)}

@media(max-width:640px){
  .sum{grid-template-columns:1fr}
  .meta{grid-template-columns:1fr;gap:.05rem}
  .k{padding-top:.4rem}
  h1{font-size:1.6rem}
}

/* Tables scroll rather than push the page wide.
   The comparison table is five columns and the sources table carries full
   endpoint URLs; at 430px both were 490px wide, which made the WHOLE document
   scroll sideways -- every heading and paragraph on the page shifted, not just
   the table. Confining the scroll to the table keeps the prose where it is.
   -webkit-overflow-scrolling gives it momentum on iOS. */
.tw{overflow-x:auto;-webkit-overflow-scrolling:touch;margin:.7rem 0 0}
.tw table{margin:0;min-width:34rem}
.tw::-webkit-scrollbar{height:6px}
.tw::-webkit-scrollbar-thumb{background:#39413F;border-radius:3px}
@media(max-width:640px){
  /* The endpoint column is the one that cannot wrap usefully -- a URL broken
     mid-path is unreadable -- so it is allowed to be the reason for the
     scroll rather than being squeezed. */
  .tw td:nth-child(2){white-space:nowrap}
}

/* iPad portrait sits in an awkward band: wide enough for three cards, not
   wide enough for "No active incidents" to stay on one line inside a 235px
   card. Two of the three then run to two lines while the third does not, and
   the row goes ragged. Shrinking the value a step keeps all three on one line
   without dropping to a stacked layout that would waste the width. */
@media (min-width:641px) and (max-width:880px){
  .sc-v{font-size:1.12rem}
}

/* Tap targets. These links were 19px tall -- fine with a mouse, under half
   the 44px a finger needs. The hit area is extended with a pseudo-element
   rather than padding so the visual position of the link does not move: the
   footer row is a flex line and padding would have pushed it taller on every
   card at every width, including desktop where there was no problem. */
.foot a, .src a{position:relative;display:inline-block}
.foot a::after, .src a::after{
  content:"";position:absolute;left:0;right:0;
  top:50%;transform:translateY(-50%);height:44px;
}
@media (hover:hover){
  /* Pointer devices do not need the extension, and an invisible 44px box can
     swallow hovers meant for what sits above or below it. */
  .foot a::after, .src a::after{display:none}
}


/* ── Site chrome ────────────────────────────────────────────────────────
   Copied from the What's New page so this page is navigable like every
   other one. It shipped with no nav, no theme toggle and its own footer,
   which made it somewhere a reader arrives and cannot leave. */
  nav{display:flex;align-items:center;justify-content:space-between;padding:0 3rem;
      position:sticky;top:0;z-index:100;background:var(--ink);color:#EDEBE6;
      /* 64px, matching the portfolio and blog. Sized from padding alone
         this bar came out at 62 and the logo sat two pixels higher than
         on every other page -- invisible alone, obvious when moving
         between sections. */
      height:64px;box-sizing:border-box;padding-top:0;padding-bottom:0}
  .nav-logo{display:flex;align-items:center;gap:.6rem;text-decoration:none;color:#EDEBE6}
  .brand-name{font-family:var(--serif);font-weight:600;font-size:1rem}
  .nav-links{display:flex;gap:1.5rem;list-style:none;align-items:center;margin:0;padding:0}
  .nav-links a{font-size:14px;font-weight:500;color:rgba(237,235,230,.72);text-decoration:none;transition:color .2s}
  .nav-links a:hover{color:var(--accent)}
  /* You are here. Three signals, not one: colour, weight and a rule under
     the word. Colour alone is what the blog had, and it is the signal that
     fails for a reader who cannot separate the accent from the grey. */
  .nav-links a.active{color:var(--accent);font-weight:600}
  .nav-links a.active::after{content:"";display:block;height:2px;
    margin-top:3px;border-radius:2px;background:currentColor;opacity:.85}
  .theme-toggle{display:flex;align-items:center;justify-content:center;background:transparent;
    border:none;border-radius:50%;padding:0;width:32px;height:32px;flex-shrink:0;line-height:1;
    color:rgba(237,235,230,.72);font-family:var(--sans);opacity:.85;cursor:pointer;
    transition:opacity .15s,transform .15s}
  .theme-toggle:hover{opacity:1;transform:scale(1.1)}
  #theme-label{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
  #theme-icon{font-size:1.25rem;line-height:1}
  /* Do not delete the navigation on a phone.
     This used to be `.nav-links li:not(:last-child){display:none}`, which hid
     EVERY item except the last -- so Portfolio, Blog, Intelligence and the
     palette control all vanished and the only thing left in the bar was the
     theme toggle. The blog and home page solve this with a hamburger; these two
     pages just had no navigation at all below 720px.
     Measured: the links need 319px, and the whole bar fits in 390 once the
     brand NAME is dropped and the gaps tighten -- the mark still carries the
     link home, so nothing is lost but the wordmark. */
  @media(max-width:720px){
    nav{padding:.9rem 1rem}
    .nav-logo .brand-name{display:none}
    .nav-links{gap:.8rem}
    .nav-links a{font-size:13px}
    /* Five links, not three. The measurement above was taken when this bar
       held Portfolio, Blog and Intelligence. What's new and Live status were
       added so that every page is one click from every other, and at 390px
       the row stopped fitting -- so the whole PAGE scrolled sideways, which
       is a worse problem than the one the extra links solved.
       The row scrolls inside itself instead: nothing is hidden, nothing is
       collapsed behind a menu, and the page holds still. The scrollbar is
       suppressed because a grey trough under a nav bar reads as damage. */
    /* The row wraps rather than scrolling.
       Scrolling it was the first attempt and it was wrong twice over: the
       theme and palette controls are list items in this same row, so they
       scrolled out of initial view, and the page still gained 66px of
       sideways scroll. Wrapping cannot overflow and cannot hide a control.
       The bar grows to two lines on a phone, which costs a little height and
       keeps every destination and every control on screen. */
    nav{flex-wrap:nowrap}
    .nav-links{flex-wrap:nowrap;min-width:0}
    /* No forced break: the cairn holds the links, so the bar is a mark, a
       page name and the controls -- one row at every phone width. */
    .nav-links a{white-space:nowrap}
  }
  @media(max-width:360px){
    .nav-links{gap:.55rem}
    .nav-links a{font-size:12px}
  }


/* Light mode. The site-wide rules target --text/--text-muted/--border; this
   page's tokens are --tx/--mut/--bd, so the same switch is expressed against
   the names used here rather than renaming everything and risking a miss. */
body.light{--bg:#F7F4EF;--card:#E9E6E0;--bd:#CFCFCE;--tx:#2C2A29;--mut:#6B6A66;
  --c-aws:#C4A484;--c-azure:#5B7B9A;--c-gcp:#8A9A5B;
  --c-aws-t:#705539;--c-azure-t:#3C5570;--c-gcp-t:#515C32;
           --acc:#8A6A46;--red:#A8564A;--grn:#4F7A4A;--amb:#9A6F28}
html[data-palette="mon"] body.light{--bg:#EFF7FB;--card:#E0E9ED}
html[data-palette="wed"] body.light{--bg:#EFF8F7;--card:#DFEAE9}
html[data-palette="thu"] body.light{--bg:#FAF2F2;--card:#EDE4E3}
html[data-palette="fri"] body.light{--bg:#F2F8F3;--card:#E3EAE4}
html[data-palette="sat"] body.light{--bg:#F4F4FA;--card:#E6E6ED}
html[data-palette="sun"] body.light{--bg:#F5F7F2;--card:#E7E9E3}
/* Set the COLOUR wherever the background is set.
   .upd carried #CBD0CE -- a pale grey chosen for a dark card -- and light mode
   changed only its background, leaving pale grey on pale beige at 1.34:1. The
   vendor's own words, which are the substance of this page, were effectively
   invisible. This is the same mistake as the palette menu earlier in this
   site's history, measured then at 1.03:1; overriding a background without its
   colour is apparently a reflex worth distrusting.

   --mut at #6B6A66 also measured 4.35:1 against the card, under the 4.5 floor,
   on four separate elements. The replacements below were solved for rather
   than eyeballed: darkest value clearing 5.5:1 against each background. */
body.light{--mut:#5C5A56}
body.light .upd{background:#F1EDE6;color:#484642}          /* 8.07:1 */
body.light .sub,
body.light .fresh,
body.light .src{color:#64625E}                             /* 5.55:1 on page */
body.light th{background:#E2DED7;color:#5C5A56}
body.light .note{background:#F4EEE5;border-color:#DCCFBC;color:#3A3835}
body.light .tag{background:#DEDAD2;border-color:#C9C4BA;color:#3F3D39}

/* The endpoints are the whole provenance claim -- a reader who wants to check
   the number should be able to reach the source in one tap rather than
   retyping a URL. */
.tw td a{color:var(--acc);text-decoration:none;border-bottom:1px solid rgba(196,164,132,.35)}
.tw td a:hover{border-bottom-color:var(--acc)}

/* Chips in light mode. .chip.aws was hardcoded #E8A87C and .chip.sev resolved
   to the light --red; against the light card they measured 1.63:1 and 4.12:1.
   They were left out of the earlier contrast pass because they are decoration
   in the eye and content in fact -- AWS/ONGOING is what tells you whose
   incident this is and whether it is over.

   Solved against the light card (#E9E6E0) for 5:1 rather than picked. */
body.light .chip{border-color:#C9C4BA;color:#5C5A56}
body.light .chip.sev{color:#8E3A2E;border-color:#D3B0A8}

/* The nav follows the theme, like the rest of the site.
   Measured across every page: home, blog and now all theme their nav -- dark
   bar in dark mode, light bar in light. The three Intelligence pages hardcoded
   a dark bar in both, so switching to light mode left a black slab above a
   cream page and moving between sections looked like moving between sites.
   That was drift, not a decision: these pages were built separately and never
   picked up the themed nav.

   Derived from --bg with color-mix, the same way blog.css derives --nav-bg
   from --surface, so it tracks the daily palette as well as the theme. The
   flat fallback comes first for anything without color-mix. */
nav{background:rgba(29,35,34,.94);
    background:color-mix(in srgb, var(--bg) 94%, transparent);
    -webkit-backdrop-filter:saturate(180%) blur(8px);
    backdrop-filter:saturate(180%) blur(8px);
    color:var(--tx);border-bottom:1px solid var(--bd)}
.nav-logo{color:var(--tx)}
.nav-links a{color:var(--mut)}
.nav-links a:hover{color:var(--acc)}
body.light nav{color:#1C2120}
body.light .nav-logo{color:#1C2120}
body.light .nav-links a{color:rgba(28,33,32,.78)}

/* The theme toggle in light mode. Hardcoded rgba(237,235,230,.85) -- a light
   colour for a dark bar -- and the light-mode rules changed the bar without
   changing it, leaving the control that switches the theme invisible at
   1.08:1 in the theme it switches you into.

   Fourth instance of this exact shape today: .upd, both chip classes, and now
   this. In every case a background was themed and the colour on it was not.
   check_contrast.py now renders both themes and fails on it. */
  body.light .theme-toggle,
  body.light #theme-icon,
  body.light #theme-label{color:#1C2120}

/* The accent on a light card. --acc is #8A6A46 in light mode, which reads at
   3.99:1 against the card -- fine for a large heading, under the bar for a
   0.74rem link. Only the links on cards are darkened; the accent stays as it
   is where it sits on the page ground. */
body.light .foot a{color:#62605C}
body.light .foot a:hover{color:#3F3D39}

/* ── 90-day strip ───────────────────────────────────────────────────────
   One cell per day, flexed so ninety marks compress to fit a phone rather
   than overflowing. At 400px each cell is ~4px, which still reads as a
   pattern even where a single cell is too small to tap. */
.tl{background:var(--card);border:1px solid var(--bd);border-radius:12px;padding:1rem 1.1rem}
.tl-row{display:grid;grid-template-columns:6.5rem 1fr auto;align-items:center;gap:.7rem;padding:.45rem 0}
.tl-n{font-size:.82rem;font-weight:600}
.tl-cells{display:flex;gap:1px;height:26px}
.d{flex:1 1 0;min-width:2px;border-radius:1px}
/* Muted green on purpose: ninety saturated cells read as a decorative bar
   chart, and the eye needs to land on the red. */
.d.ok{background:#4E5F4B;opacity:.5}
.d.bad{background:var(--red)}
.tl-s{font-family:var(--mono);font-size:.66rem;color:var(--mut);white-space:nowrap}
body.light .d.ok{background:#8FA88B;opacity:.55}

/* ── blast radius, inside an incident card ─────────────────────────────── */
.br-bars{display:inline-flex;align-items:center;gap:.25rem;vertical-align:middle}
/* Three segments -- zone, region, several regions -- filled left to right, so
   two incidents whose descriptions share no vocabulary become comparable. */
.b{width:26px;height:6px;border-radius:2px;background:transparent;border:1px solid var(--bd)}
.b.on{background:var(--red);border-color:var(--red)}
.br-w{font-family:var(--mono);font-size:.68rem;color:var(--mut);margin-left:.5rem}

/* ── region grid ───────────────────────────────────────────────────────── */
.regs{display:flex;flex-wrap:wrap;gap:.45rem}
/* The cloud is carried by a solid edge, the count by the fill.
   The fill alone could not do both: at --w scaled to 26% a count-of-one chip
   is tinted about 13%, so every region on the page looked like the same pale
   card and the cloud each one belonged to was invisible. The edge is at full
   strength whatever the count. */
.reg{display:flex;align-items:center;gap:.5rem;padding:.4rem .6rem;border-radius:8px;
     border:1px solid var(--bd);
     border-left:3px solid var(--c, var(--acc));
     background:color-mix(in srgb, var(--c, var(--acc)) calc(var(--w) * 26%), transparent)}
.reg.aws{--c:var(--c-aws)}.reg.azure{--c:var(--c-azure)}
.reg.gcp{--c:var(--c-gcp)}
.reg-n{font-family:var(--mono);font-size:.72rem}
.reg-c{font-size:.72rem;color:var(--mut)}

/* ── what each vendor publishes ────────────────────────────────────────── */
.dv{background:var(--card);border:1px solid var(--bd);border-radius:12px;padding:1rem 1.1rem}
.dv-head{display:grid;grid-template-columns:repeat(6,1fr);gap:.3rem;margin:0 0 .5rem;padding-left:7.5rem}
.dv-head span{font-family:var(--mono);font-size:.56rem;letter-spacing:.04em;
              text-transform:uppercase;color:var(--mut);line-height:1.3}
.dv-row{display:grid;grid-template-columns:7.5rem 1fr auto;align-items:center;gap:.6rem;padding:.35rem 0}
.dv-n{font-size:.84rem;font-weight:600}
.dv-bars{display:grid;grid-template-columns:repeat(6,1fr);gap:.3rem}
.f{height:12px;border-radius:3px;border:1px solid var(--bd)}
.f.full{background:var(--grn);border-color:var(--grn)}
.f.part{background:repeating-linear-gradient(45deg,var(--acc),var(--acc) 3px,transparent 3px,transparent 6px)}
.f.none{background:transparent}
.dv-s{font-family:var(--mono);font-size:.7rem;color:var(--mut)}

.note-sm{font-size:.78rem;color:var(--mut);line-height:1.6;margin:.9rem 0 0;max-width:66ch}

@media(max-width:640px){
  .tl-row{grid-template-columns:1fr;gap:.25rem;padding:.55rem 0}
  .tl-s{text-align:left}
  .tl-cells{height:22px}
  /* Six column labels do not fit; the legend beneath carries the meaning. */
  
  .dv-row{grid-template-columns:5.5rem 1fr auto;gap:.4rem}
}

/* The count inside a region chip sits on a tinted background, not on the card,
   so --mut measured 3.75:1 against it -- caught by check_contrast.py before
   this shipped rather than by someone noticing. Brightened in dark; light mode
   already cleared the bar. */
.reg-c{color:#C7CCCA}
body.light .reg-c{color:#57565200}
body.light .reg-c{color:#575652}

/* A marked cell is a link to the vendor's page for that incident. Given 90
   cells in a row, each is far narrower than a finger -- so the row grows on
   hover/focus rather than the cell, and the whole strip becomes easier to
   hit without changing the layout around it. */
a.d{display:block;cursor:pointer}
a.d:hover,a.d:focus-visible{outline:2px solid var(--tx);outline-offset:1px;z-index:1}
.tl-cells:hover{height:32px;transition:height .12s ease}
.tl-cells{transition:height .12s ease}
@media(prefers-reduced-motion:reduce){.tl-cells{transition:none}}

/* The region code beside the vendor's place name on an incident card. */
.rc{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.86em;color:var(--mut)}

/* The disclosure chart, two layouts.
   Wide is cloud-major with column headers; narrow is criterion-major with
   every row named. The narrow one exists because the headers were previously
   hidden on phones, leaving six unlabelled bars -- a score with nothing to
   attach it to. */
.dv-m{display:none}
.dv-mrow{display:grid;grid-template-columns:1fr auto;align-items:center;
  gap:.5rem;padding:.4rem 0;border-bottom:1px solid var(--bd)}
.dv-mrow:last-of-type{border-bottom:0}
.dv-mn{font-size:.8rem;line-height:1.3}
.dv-cs{display:flex;gap:.5rem;flex-shrink:0}
.dv-c{display:flex;align-items:center;gap:.25rem;font-family:var(--mono);
  font-size:.6rem;color:var(--mut);letter-spacing:.02em}
.dv-c .f{width:14px;height:10px;flex:0 0 auto}
.dv-tot{margin:.7rem 0 0;font-family:var(--mono);font-size:.66rem;color:var(--mut)}
/* The key. Three fill states that nothing explained was the same failure as
   hiding the headers, only smaller. */
.dv-key{display:flex;flex-wrap:wrap;gap:.4rem 1rem;margin:.85rem 0 0;
  padding-top:.7rem;border-top:1px solid var(--bd);
  font-size:.68rem;color:var(--mut)}
.dv-key span{display:flex;align-items:center;gap:.35rem}
.dv-key .f{width:16px;height:10px;flex:0 0 auto}

/* Last, deliberately. .dv-m{display:none} above is the base state, and this
   overrides it at equal specificity -- so it only wins by coming after.
   Stating the swap inside the earlier media block instead hid BOTH layouts,
   leaving the chart blank on a phone. */
@media (max-width: 640px){
  .dv-w{display:none}
  .dv-m{display:block}
}

/* Timeline cell states.
   .bad is the day an incident began; .cont is a day it was merely still open.
   AWS has had two incidents open since 1 March, so without this every cell in
   its strip was solid red -- ninety days that read as ninety events and are
   two. .unk is a gap in the record, which must not look like a good day. */
.d.bad.cont{background:color-mix(in srgb,var(--red) 42%,transparent)}
.d.unk{background:repeating-linear-gradient(45deg,
  color-mix(in srgb,var(--mut) 30%,transparent) 0 2px,transparent 2px 4px)}
.tl-key{display:flex;flex-wrap:wrap;gap:.35rem 1rem;margin:.8rem 0 .25rem;
  font-size:.68rem;color:var(--mut)}
.tl-key span{display:flex;align-items:center;gap:.35rem}
.tl-key .d{width:12px;height:12px;border-radius:2px;flex:0 0 auto;display:block}

/* ---- Post-incident write-ups -------------------------------------------
   A wall of years rather than three lists by cloud. By cloud it reads as a
   scoreboard; by year it reads as what it is, fifteen years of the industry
   writing down what broke, gaps and all. */
.pm{margin:1rem 0 0}
.pm-year{display:grid;grid-template-columns:3.6rem 1fr;gap:.9rem;
  padding:.75rem 0;border-top:1px solid var(--bd)}
.pm-year:first-child{border-top:0}
.pm-y{font-family:var(--mono);font-size:.82rem;color:var(--mut);
  padding-top:.5rem;letter-spacing:.02em}
.pm-cards{display:grid;gap:.5rem}
.pm-card{display:grid;grid-template-columns:1fr auto;gap:.15rem .7rem;
  width:100%;text-align:left;cursor:pointer;
  padding:.6rem .75rem;border:1px solid var(--bd);border-radius:9px;
  background:var(--card);color:var(--tx);font:inherit;
  border-left:3px solid var(--bd);transition:border-color .14s,transform .14s}
.pm-card:hover,.pm-card:focus-visible{transform:translateX(2px)}
.pm-card.aws{border-left-color:var(--c-aws)}
.pm-card.azure{border-left-color:var(--c-azure)}
.pm-card.gcp{border-left-color:var(--c-gcp)}
.pm-c-cloud{grid-column:1;font-family:var(--mono);font-size:.62rem;
  letter-spacing:.06em;text-transform:uppercase;color:var(--mut)}
.pm-c-shape{grid-column:2;grid-row:1;font-family:var(--mono);font-size:.62rem;
  color:var(--mut);white-space:nowrap}
.pm-c-title{grid-column:1/-1;font-size:.86rem;line-height:1.35}

/* The dialog. Sized to be readable rather than to fill the screen: these are
   several thousand words of vendor prose and a full-width line length makes
   them harder to read, not easier. */
#pm-dialog{width:min(46rem,94vw);max-height:86vh;padding:0;border:0;
  border-radius:14px;background:var(--card);color:var(--tx);
  box-shadow:0 24px 70px rgba(0,0,0,.34)}
#pm-dialog::backdrop{background:rgba(0,0,0,.55)}
.pm-body{padding:1.5rem 1.6rem 1.7rem;overflow-y:auto;max-height:86vh}
.pm-x{position:absolute;top:.5rem;right:.6rem;z-index:2;
  width:2rem;height:2rem;border-radius:50%;cursor:pointer;
  border:1px solid var(--bd);background:var(--card);color:var(--tx);
  font-size:1.1rem;line-height:1}
.pm-x:hover,.pm-x:focus-visible{background:var(--bg)}
.pm-meta{display:flex;align-items:center;gap:.6rem;margin:0 0 .5rem}
.pm-date{font-family:var(--mono);font-size:.72rem;color:var(--mut)}
.pm-none{font-style:italic}
#pm-dialog h3{margin:0 0 1rem;font-size:1.15rem;line-height:1.3;
  padding-right:2rem}
.pm-sec{margin:0 0 1.3rem}
.pm-sec h4{margin:0 0 .4rem;font-size:.78rem;font-family:var(--mono);
  letter-spacing:.04em;text-transform:uppercase;color:var(--mut)}
.pm-body p{margin:0 0 .7rem;font-size:.92rem;line-height:1.62}
.pm-shape,.pm-loading{font-size:.78rem;color:var(--mut);font-style:italic}
.pm-src{margin-top:1.4rem;padding-top:.9rem;border-top:1px solid var(--bd);
  font-size:.76rem;color:var(--mut)}
.pm-src a{color:var(--tx)}
@media (max-width:640px){
  .pm-year{grid-template-columns:1fr;gap:.4rem}
  .pm-y{padding-top:0}
  .pm-card{grid-template-columns:1fr}
  .pm-c-shape{grid-column:1;grid-row:auto}
  .pm-body{padding:1.2rem 1.1rem 1.4rem}
}

/* The collapsed "no record yet" band. It takes the width its days would have
   taken, so the row still reads as 90 days across, but it says once what 89
   hatched cells were saying badly. */
.tl-gap{flex:var(--n) 1 0;display:flex;align-items:center;justify-content:center;
  min-width:0;height:100%;border-radius:3px;overflow:hidden;white-space:nowrap;
  font-family:var(--mono);font-size:.58rem;letter-spacing:.02em;color:var(--mut);
  background:repeating-linear-gradient(45deg,
    color-mix(in srgb,var(--mut) 22%,transparent) 0 2px,transparent 2px 5px)}


/* Key for the region grid. Three colours that mean something need saying once. */
.reg-key{display:flex;flex-wrap:wrap;gap:.4rem 1rem;margin:.8rem 0 .2rem;
  font-size:.68rem;color:var(--mut)}
.reg-key span{display:flex;align-items:center;gap:.4rem}
.reg-key i{width:10px;height:10px;border-radius:2px;flex:0 0 auto;display:block}
.reg-key i.aws{background:var(--c-aws)}
.reg-key i.azure{background:var(--c-azure)}
.reg-key i.gcp{background:var(--c-gcp)}

/* The cloud colour on a chip, last and explicit.
   `body .chip.aws` ties with `body.light .chip` at 0-2-1, so source order
   decides -- and that grey rule sits 175 lines further down, which is why AWS
   and Azure chips both came out grey in light mode while looking perfect in
   dark. The light-mode selector here is 0-3-1 and genuinely outranks it.
   Second time this cascade tie has bitten on this page; the disclosure chart
   lost the same way and hid both its layouts. */
body .chip.aws,body.light .chip.aws{color:var(--c-aws-t)}
body .chip.azure,body.light .chip.azure{color:var(--c-azure-t)}
body .chip.gcp,body.light .chip.gcp{color:var(--c-gcp-t)}
body .chip.aws,body.light .chip.aws,
body .chip.azure,body.light .chip.azure,
body .chip.gcp,body.light .chip.gcp{
  border-color:color-mix(in srgb,currentColor 34%, transparent)}

/* The vendor's own line, on the card. Sits between the title and the shape
   badge; only write-ups with a labelled cause section have one. */
.pm-c-q{grid-column:1/-1;margin-top:.35rem;font-size:.82rem;line-height:1.5;
  color:var(--mut);border-left:2px solid var(--bd);padding-left:.6rem}

/* Android Chrome paints a translucent blue rectangle over anything tappable
   while a finger is down. Nothing on this site ever set
   -webkit-tap-highlight-color, so that default applied to every card, nav
   link and timeline cell -- a blue flash in a palette that has no blue in it.
   Suppressed here, and replaced rather than simply removed: taking the
   highlight away with nothing in its place makes a tap feel like it did not
   register, which is a worse bug than the flash. */
html{-webkit-tap-highlight-color:transparent}
a:active,button:active{opacity:.72}

/* Links inside the small print. The cadence note added one to status-runs.json
   and nothing styled it, so it fell to the browser default blue and measured
   1.78:1 on the dark card -- the note colour was set, the link colour inside
   it was not. Same shape as every other contrast bug on this page. */
.note-sm a{color:var(--tx);text-decoration:underline;
  text-underline-offset:2px;text-decoration-thickness:1px}
.note-sm a:hover,.note-sm a:focus-visible{color:var(--acc)}

/* Feedback star. Copied from the portfolio with every var() given a literal
   fallback -- the Intelligence pages define almost none of the eight tokens
   these rules originally relied on, and an undefined property takes the whole
   declaration with it. */
.fb-btn{--fb-star:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.9' stroke-linejoin='round' stroke-linecap='round'%3E%3Cpolygon points='12 2.8 14.95 9.05 21.6 9.95 16.75 14.5 17.95 21.2 12 18 6.05 21.2 7.25 14.5 2.4 9.95 9.05 9.05'/%3E%3C/svg%3E");
  position:fixed;right:14px;top:50%;transform:translateY(-50%);z-index:900;
  width:40px;height:40px;border-radius:11px;
  background:var(--surface, var(--card, #1E2422));
  color:var(--text, var(--tx, #EDEBE6));
  border:1px solid var(--icon-border, var(--bd, rgba(128,128,128,.32)));
  box-shadow:0 2px 10px rgba(0,0,0,.28);
  display:flex;align-items:center;justify-content:center;cursor:pointer;
  font-size:0;transition:border-color .2s,color .2s,background .2s}
.fb-btn::before{content:'';width:19px;height:19px;background:currentColor;
  -webkit-mask:var(--fb-star) center / contain no-repeat;
  mask:var(--fb-star) center / contain no-repeat}
.fb-btn:hover,.fb-btn:focus-visible{
  border-color:var(--accent-gold, #C4A484);color:var(--accent-gold, #C4A484);
  box-shadow:0 3px 14px rgba(0,0,0,.34)}
.fb-overlay{display:none;position:fixed;inset:0;z-index:901;
  background:rgba(0,0,0,.55);align-items:flex-end;justify-content:flex-start;
  padding:1.75rem}
.fb-overlay.open{display:flex}
.fb-modal{background:var(--bg-dark, #1D2322);
  border:1px solid rgba(196,164,132,.25);border-radius:16px;padding:1.5rem;
  width:300px;max-width:100%;color:var(--text-light, #EDEBE6)}
.fb-title{color:var(--text-light, #EDEBE6);font-size:15px;font-weight:600;
  margin-bottom:.35rem}
.fb-sub{color:#A7B0B4;font-size:12px;margin-bottom:1.25rem}
.fb-stars{display:flex;gap:8px;margin-bottom:.5rem}
.fb-star{width:42px;height:42px;border-radius:50%;
  border:1.5px solid rgba(196,164,132,.3);background:transparent;
  display:flex;align-items:center;justify-content:center;cursor:pointer;
  color:#A7B0B4;font-size:18px;line-height:1}
.fb-star.on{border-color:var(--accent-gold, #C4A484);
  color:var(--accent-gold, #C4A484);background:rgba(196,164,132,.1)}
.fb-labels{display:flex;justify-content:space-between;font-size:11px;
  color:#A7B0B4;margin-bottom:1rem}
.fb-textarea{width:100%;box-sizing:border-box;
  background:var(--border-dark, #2A312F);
  border:1px solid rgba(196,164,132,.2);border-radius:8px;
  color:var(--text-light, #EDEBE6);font-size:13px;padding:.6rem .7rem;
  font-family:inherit;margin-bottom:1rem;min-height:70px}
.fb-textarea::placeholder{color:#A7B0B4}
.fb-textarea:focus{outline:none;border-color:rgba(196,164,132,.5)}
.fb-footer{display:flex;justify-content:space-between;align-items:center}
.fb-skip{background:transparent;border:none;color:#A7B0B4;font-size:13px;
  cursor:pointer}
.fb-send{background:var(--accent-gold, #C4A484);color:#1D2322;border:none;
  border-radius:8px;padding:.45rem 1.1rem;font-size:13px;font-weight:700;
  cursor:pointer}
.fb-send:hover{background:#B09173}
.fb-thanks{text-align:center;padding:1rem 0;
  color:var(--text-light, #EDEBE6);font-size:14px}
.fb-thanks span{display:block;color:var(--accent-gold, #C4A484);font-size:28px;
  margin-bottom:.5rem}
.fb-thanks p{color:#A7B0B4;font-size:13px;margin-top:.35rem}
@media (max-width:640px){
  .fb-btn{width:34px;height:34px;border-radius:10px}
  .fb-btn::before{width:16px;height:16px}
  .fb-overlay{padding:.75rem}
  .fb-modal{width:100%}
}

/* Back to top. Copied from blog.css because the status page does not load it,
   with a literal fallback on every var() -- --orange, --ink and --shadow-md
   are undefined here, and an undefined token takes the whole declaration with
   it rather than falling back. */
.back-top{position:fixed;bottom:1.5rem;left:1.5rem;right:auto;
  width:40px;height:40px;border-radius:50%;
  background:var(--orange, var(--acc, #C4A484));
  color:var(--ink, #1D2322);
  border:none;cursor:pointer;display:flex;align-items:center;
  justify-content:center;opacity:0;transform:translateY(8px);
  transition:opacity .2s, transform .2s;
  box-shadow:var(--shadow-md, 0 4px 16px rgba(0,0,0,.28));
  font-size:1rem;line-height:1;z-index:200;
  touch-action:manipulation;-webkit-tap-highlight-color:transparent;
  pointer-events:none}
.back-top.show{opacity:1;transform:translateY(0);pointer-events:auto}
@media (hover:hover){
  .back-top:hover{filter:brightness(1.06)}
}
.back-top:focus-visible{filter:brightness(1.06)}
.back-top:active{filter:brightness(.94)}
@media (prefers-reduced-motion:reduce){
  .back-top{transition:none}
}
/* The jump to the sources, stacked above the back-to-top. Outlined rather
   than filled so it reads as secondary to it -- one is where you were, the
   other is where the evidence is. Every var() carries a literal for the same
   reason the button above does: an undefined token takes the whole
   declaration with it. */
.to-src{position:fixed;bottom:1.5rem;left:50%;right:auto;
  display:inline-flex;align-items:center;justify-content:center;
  min-height:44px;padding:0 1.05rem;
  /* 44px, not the 32 it was. Apple's own minimum is 44 and this sat well
     under it, which is most of why it took two goes to hit.
     touch-action:manipulation drops the 300ms wait Safari holds in case the
     tap turns into a double-tap zoom -- that delay reads as a press that did
     not register, so the finger comes down again. */
  touch-action:manipulation;-webkit-tap-highlight-color:transparent;
  border-radius:999px;text-decoration:none;
  font-family:var(--mono, ui-monospace, monospace);
  font-size:.66rem;letter-spacing:.08em;text-transform:uppercase;
  color:var(--orange, var(--acc, #C4A484));
  background:var(--card, #171A19);
  border:1px solid var(--orange, var(--acc, #C4A484));
  opacity:0;transform:translate(-50%,8px);
  transition:opacity .2s, transform .2s;
  box-shadow:var(--shadow-md, 0 4px 16px rgba(0,0,0,.28));
  z-index:200;pointer-events:none}
/* Centred on the bottom edge, level with the back-to-top rather than stacked
   over it: the two are peers -- one goes up, one goes down -- and a stack read
   as one control with a label above it. translate carries the centring, so
   both states have to restate it or the pill jumps left as it fades in. */
.to-src.show{opacity:1;transform:translate(-50%,0);pointer-events:auto}
/* Hover only where hovering exists.
   On a touch screen the first tap on an element carrying :hover styles is
   often spent applying that hover rather than following the link, and the
   second tap is the one that works. Gating it behind a hover-capable pointer
   removes the phantom first press without costing a mouse anything. */
@media (hover:hover){
  .to-src:hover{filter:brightness(1.12)}
}
.to-src:focus-visible{filter:brightness(1.12);outline:2px solid currentColor;
  outline-offset:2px}
.to-src:active{filter:brightness(.94)}
@media (prefers-reduced-motion:reduce){
  .to-src{transition:none}
}

/* The brand mark, matching the portfolio and blog exactly.
   These pages rendered a bare <img> with no class, so nothing styled it: it
   came out square with object-fit:fill, i.e. the round mark cropped to a box
   and stretched, while every other page shows a circle. --surface is not
   defined here, so it carries a fallback -- an undefined token would take the
   whole background declaration with it. */
.nav-logo .brand-mark{width:30px;height:30px;border-radius:50%;
  object-fit:contain;display:block;flex:0 0 30px;padding:1px;
  background:var(--surface, var(--card, transparent))}

/* Below 820px, the portfolio's breakpoint. Matching the number matters as
   much as matching the value: a nav that reflows at 720 on one page and 820
   on another shifts under a reader turning a phone sideways. */
@media (max-width: 820px){
  nav, .nav{padding:0 1rem}
}

/* Timeline cells are buttons now, so they need their own reset: a <button>
   brings a border, background and padding that would break a 90-day strip. */
button.d{padding:0;border:0;background:none;font:inherit;cursor:pointer;
  display:block;-webkit-appearance:none;appearance:none}
button.d:hover,button.d:focus-visible{outline:2px solid var(--tx);
  outline-offset:1px;z-index:1}
.tl-key .tl-tz{margin-left:auto;font-style:italic;opacity:.85}

/* The write-up archive, one year at a time. Every card stays in the HTML --
   hidden, not omitted -- so browser search still finds them and the section
   still works with JavaScript off. */
.pm-yrs{display:flex;flex-wrap:wrap;gap:.4rem;margin:1rem 0 .2rem}
.pm-yr{font:inherit;font-size:.76rem;cursor:pointer;padding:.3rem .6rem;
  border-radius:999px;border:1px solid var(--bd);background:transparent;
  color:var(--tx);display:flex;align-items:center;gap:.35rem}
.pm-yr:hover,.pm-yr:focus-visible{background:color-mix(in srgb,currentColor 8%,transparent)}
.pm-yr.is-on{border-color:currentColor;font-weight:600}
.pm-yn{font-family:var(--mono);font-size:.66rem;color:var(--mut)}
.pm-yr.is-on .pm-yn{color:inherit}
.pm-year.is-hidden{display:none}

/* Cloud filter, in the same shape as the year chips. The dot carries the
   cloud's own colour so the row reads as clouds rather than as more years. */
.pm-cls{margin-top:.35rem}
.pm-cl{font:inherit;font-size:.76rem;cursor:pointer;padding:.3rem .6rem;
  border-radius:999px;border:1px solid var(--bd);background:transparent;
  color:var(--tx);display:flex;align-items:center;gap:.4rem}
.pm-cl:hover,.pm-cl:focus-visible{background:color-mix(in srgb,currentColor 8%,transparent)}
.pm-cl.is-on{border-color:currentColor;font-weight:600}
.pm-cl.aws::before,.pm-cl.azure::before,.pm-cl.gcp::before{
  content:"";width:8px;height:8px;border-radius:50%;flex:0 0 auto}
.pm-cl.aws::before{background:var(--c-aws-t)}
.pm-cl.azure::before{background:var(--c-azure-t)}
.pm-cl.gcp::before{background:var(--c-gcp-t)}
.pm-card.is-hidden{display:none}
/* A chip that would show nothing, dimmed rather than hidden -- "AWS 0 in
   2026" is a fact about AWS worth seeing, and a row that reflows as you
   filter is harder to use than one that stays put. */
.pm-yr.is-empty,.pm-cl.is-empty{opacity:.45}
.src-kind{font-family:var(--mono);font-size:.66rem;color:var(--mut)}

/* Timeline window chips. 90 days is the default because the question at the
   top of this page is about now; the years answer the one below it. */
.tl-win{display:flex;flex-wrap:wrap;gap:.4rem;align-items:center;
  margin:.2rem 0 .9rem}
.tl-w{font:inherit;font-size:.76rem;cursor:pointer;padding:.3rem .65rem;
  border-radius:999px;border:1px solid var(--bd);background:transparent;
  color:var(--tx)}
.tl-w:hover,.tl-w:focus-visible{background:color-mix(in srgb,currentColor 8%,transparent)}
.tl-w.is-on{border-color:currentColor;font-weight:600}
.tl-wn{font-family:var(--mono);font-size:.66rem;color:var(--mut);
  margin-left:auto}
/* A YEAR IS 365 CELLS, and they cannot all fit a phone.
   Letting them shrink to fit produced sub-pixel bars: the rows rendered with
   labels and counts and nothing visible between them, which reads as a broken
   chart rather than a dense one. So year cells hold a floor of 2px and the
   row scrolls sideways instead. The 90-day strip is untouched -- it fits. */
.tl.is-year .tl-cells{overflow-x:auto;-webkit-overflow-scrolling:touch;
  gap:1px;padding-bottom:2px}
.tl.is-year .tl-cells .d{min-width:2px;flex:0 0 2px}
.tl.is-year .tl-row{align-items:start}
.pm-more{width:100%;margin-top:.5rem;padding:.5rem;border-radius:9px;
  border:1px dashed var(--bd);background:transparent;color:var(--tx);
  font:inherit;font-size:.78rem;cursor:pointer}
.pm-more:hover,.pm-more:focus-visible{
  background:color-mix(in srgb,currentColor 7%,transparent)}

/* The outage map. Equirectangular, so a region's dot sits where the region
   does; the shaded half is night as computed from the sun's position now. */
.om{margin:.9rem 0 .4rem}
/* No border or panel behind it any more: the map is an ellipse, and a
   rectangle drawn around an ellipse is a box the map does not fill. */
.om-svg{width:100%;height:auto;display:block;background:transparent}
.om-sea{fill:color-mix(in srgb,var(--tx) 6%,transparent)}
/* .pm-cl is display:flex, so without a flex row of its own each chip
   becomes its own line. */
.om-cls{display:flex;flex-wrap:wrap;gap:.4rem;align-items:center;
  margin:.2rem 0 .1rem}
/* The key. Same ink as the map, so a reader checks it against the picture
   rather than holding the caption in their head. */
.om-key{list-style:none;margin:.5rem 0 0;padding:0;display:flex;
  flex-wrap:wrap;gap:.35rem 1.1rem}
.om-key li{display:flex;align-items:center;gap:.35rem;
  font-size:.74rem;color:var(--mut)}
.om-sw{width:18px;height:18px;flex:0 0 auto;overflow:visible}
.om-sw .om-key-live{fill:none;stroke:var(--red);stroke-width:1.7}
.om-sw .om-hit{stroke:var(--amb);stroke-width:1.4;stroke-dasharray:2.6 2.2}
/* The key's "broken now" dot carries the same red rim the map's does, so the
   two swatches differ by the dot as well as by the ring. */
.om-sw .is-live-c{stroke:var(--red);stroke-width:1.3}
/* The vendor collar: an arc per cloud running a region at that place. Colour
   here is presence, which every vendor publishes, so unlike an incident count
   it can be tinted per cloud without implying a ranking. */
/* The arcs take the theme-aware variant, not the base identity colour.
   Against the landmass the base Azure sat at 3.2:1 and simply did not read --
   the palette was picked to sit quietly next to body text, which is the
   opposite of what a 1.5px arc on a grey continent needs. The -t variants are
   the same three hues brightened for a dark page and darkened for a light
   one, so the map gains contrast without the site gaining a fourth palette:
   Azure goes 3.2 -> 6.9, Google 4.7 -> 8.1, AWS 6.1 -> 7.6. */
/* The dot is a pie of the clouds running a region at that place, so cloud
   colour is on every mark on the map rather than only the ones that broke.
   The -t variants are used, not the base identity colours: those were picked
   to sit quietly beside body text, and against the landmass base Azure came
   out at 3.2:1 and did not read. */
.om-pie path,.om-pie circle{stroke:var(--card);stroke-width:.4}
.om-pie .aws{fill:var(--c-aws-t)}
.om-pie .azure{fill:var(--c-azure-t)}
.om-pie .gcp{fill:var(--c-gcp-t)}
/* A place known only from an incident, with no live region behind it. */
.om-orphan{fill:color-mix(in srgb,var(--tx) 45%,transparent)}
/* "Something broke here in the last 90 days" versus "something is broken here
   right now". These were an orange ring and a red ring -- #C56B3F against a
   --red of #C87B6E, near enough the same hue that at eleven pixels they were
   the same mark, and the key showed two swatches a reader could not tell
   apart.
   They now differ in two ways at once, because either alone is fragile: the
   past one is amber and DASHED, the present one is red and solid. Dashed
   reads as "was", and it survives being small, colour-blind, or printed. */
.om-hit{fill:none;stroke:var(--amb);stroke-width:1.4;
  stroke-dasharray:2.6 2.2;stroke-linecap:round}
/* Something broken right now. The pulse is the only motion on the page and it
   means exactly one thing, so it is worth the exception -- and it stops for
   anyone who has asked the browser for less of it. */
/* The alarm rings are animated by SVG's own <animate>, emitted in pm.js, not
   from here.
   The CSS version scaled about the circle's centre, which in SVG needs
   transform-box:fill-box -- Safari 15.4 and later only. On an older iPad the
   ring scaled about the viewBox origin and left the map, which looks exactly
   like an alarm that does not move. SMIL needs none of that.
   What stays here is what the ring looks like, and what it looks like when
   motion is switched off. */
.om-pulse{fill:none;stroke:var(--red);stroke-width:2.2}
/* The dot under an alarm gets a red rim, so it is still marked out when the
   rings are mid-fade and for anyone who has turned motion off. */
.om-dot.is-live .om-pie path,.om-dot.is-live .om-pie circle{
  stroke:var(--red);stroke-width:1.2}
.om-dot.is-live .om-orphan{stroke:var(--red);stroke-width:1.2}
@media (prefers-reduced-motion:reduce){
  /* No movement, but the alarm must not disappear: pm.js emits a single ring
     with no <animate> children, and it is drawn heavier to make up for
     holding still. The strip above the map says the same thing in words. */
  .om-pulse{stroke-width:2.6;opacity:.95}
}

/* The alert strip. Only rendered when something is actually open, because an
   alarm that is always on screen stops being one. */
.om-alert{display:flex;flex-wrap:wrap;align-items:center;gap:.4rem .5rem;
  margin:0 0 .5rem;padding:.5rem .65rem;border-radius:10px;
  border:1px solid color-mix(in srgb,var(--red) 45%,var(--bd));
  background:color-mix(in srgb,var(--red) 9%,transparent)}
.om-alert>b{color:var(--red);font-size:.82rem;letter-spacing:.01em;
  margin-right:.15rem}
.om-alert>b::before{content:"";display:inline-block;width:8px;height:8px;
  margin-right:.45rem;border-radius:50%;background:var(--red);
  animation:om-blink 1.6s ease-in-out infinite}
@keyframes om-blink{0%,100%{opacity:1}50%{opacity:.25}}
@media (prefers-reduced-motion:reduce){
  .om-alert>b::before{animation:none}
}
.om-jump{font:inherit;font-size:.76rem;cursor:pointer;padding:.22rem .55rem;
  border-radius:999px;border:1px solid color-mix(in srgb,var(--red) 40%,var(--bd));
  background:transparent;color:var(--tx);display:inline-flex;
  align-items:center;gap:.35rem}
.om-jump:hover,.om-jump:focus-visible{
  background:color-mix(in srgb,var(--red) 14%,transparent)}
.om-jump i{width:7px;height:7px;border-radius:50%;flex:0 0 auto}
.om-jump i.aws{background:var(--c-aws-t)}
.om-jump i.azure{background:var(--c-azure-t)}
.om-jump i.gcp{background:var(--c-gcp-t)}
.om-k-live{color:var(--red);font-weight:600}
.om-how{margin:.35rem 0 0}
.om-how summary{font-family:var(--mono);font-size:.68rem;letter-spacing:.06em;
  text-transform:uppercase;color:var(--mut);cursor:pointer}
.om-how summary:hover,.om-how summary:focus-visible{color:var(--tx)}
.om-how p{margin:.45rem 0 0}
/* What is at a place: one row per region, grouped by cloud. */
.om-reg{width:100%;border-collapse:collapse;margin:.5rem 0 .2rem;
  font-size:.78rem}
.om-reg td{padding:.28rem .4rem .28rem 0;border-bottom:1px solid var(--bd);
  vertical-align:middle}
.om-reg tr:last-child td{border-bottom:0}
.om-reg code{font-family:var(--mono);font-size:.74rem}
.om-reg .om-z{text-align:right;color:var(--mut);white-space:nowrap}
.om-who .chip{margin-right:.35rem}
.om-open{margin:.6rem 0 .2rem;padding-top:.4rem;
  border-top:1px solid var(--bd)}
.om-open h4{margin:0 0 .3rem;font-size:.8rem;color:var(--red)}

/* Paths, not lines: a meridian on an ellipse is a curve. */
.om-grat path{fill:none;stroke:color-mix(in srgb,var(--tx) 12%,transparent);
  stroke-width:.5}
.om-dot{cursor:pointer}
.om-halo{fill:transparent}
.om-dot{color:var(--mut)}
.om-dot.is-live{color:var(--red)}
.om-dot:hover .om-halo,.om-dot:focus-visible .om-halo{
  fill:color-mix(in srgb,currentColor 22%,transparent)}
.om-dot:focus-visible{outline:none}
.om-core{stroke:var(--card);stroke-width:1}
.om-legend{margin-top:.5rem}
.om-land path{fill:color-mix(in srgb,var(--tx) 13%,transparent);
  stroke:color-mix(in srgb,var(--tx) 30%,transparent);stroke-width:.35}
.om-names text{fill:color-mix(in srgb,var(--tx) 62%,transparent);
  font-family:var(--mono);font-size:5.4px;letter-spacing:.35px;
  text-anchor:middle;paint-order:stroke;
  stroke:var(--card);stroke-width:1.6px;stroke-linejoin:round}
.om-water text{fill:color-mix(in srgb,var(--tx) 34%,transparent);
  font-style:italic;text-anchor:middle}
.om-water .om-ocean{font-size:6px;letter-spacing:1.6px}
.om-water .om-sea-lbl{font-size:4.6px;letter-spacing:.4px}

/* The opening band is dark in BOTH themes, so its text must be light in both.
   These live at the end and name body.light explicitly because the light-mode
   block above redefines --mut and --acc for a pale page: inheriting them
   painted the standfirst and the timestamp at 2.6:1 and the eyebrow at 3.2:1
   on a deliberately dark band. Same trap as the menu panel and the page name
   earlier -- a theme-aware token used on a surface that does not follow the
   theme. */
header.hero h1,
body.light header.hero h1 { color: #EDEBE6; }
header.hero .sub,
body.light header.hero .sub { color: rgba(237,235,230,.78); }
header.hero .fresh,
body.light header.hero .fresh { color: rgba(237,235,230,.64); }
header.hero .fresh.stale,
body.light header.hero .fresh.stale { color: #E8C97A; }
header.hero .eyebrow,
body.light header.hero .eyebrow { color: #D6B896; }
header.hero .dot,
body.light header.hero .dot { background: #D6B896; }

