/* Adobe Analytics Learning world. Ported faithfully from the source app's own
   shell (header / sidebar / main) and its crimson accent, per the user's
   choice to match the original app's color exactly. Note this means the
   topic's functional accent and the site's personal-brand crimson (header
   credit, About popup) are now the same color, a deliberate tradeoff the
   user chose over a separate topic identity. The app's own author popup is
   dropped here, the universal author-fab + About overlay (chrome.css)
   already covers that site-wide. No on-screen per-page footer, matching how
   Migration actually ships today (its app-level footer didn't carry into
   the Hugo site either). */

:root{
  --bg1:#f8fafc; --bg2:#ffffff; --bg3:#f1f5f9; --card:#ffffff;
  --text1:#0f172a; --text2:#475569; --text3:#5d6b7e; --border:#e2e8f0;
  /* COL-06: --text3 was #94a3b8 and measured 2.45:1 on the page ground --
     it was failing on the Last-updated stamp, the byline separator, the
     prev/next labels and every diagram sub-caption. Now 5.19:1, still a
     clear step lighter than --text2 at 7.24:1.
     --code-chrome-ink is separate BECAUSE the code header is dark in both
     themes: darkening --text3 would have taken .code-lang from 6.75:1 to
     3.19:1. A token used on a fixed-dark surface cannot follow the theme.
     --code-ink is inline <code>, which was #fb7185 (old rose-400) at
     2.46:1 -- unreadable, and it appears in prose across the corpus. */
  --code-chrome-ink:#94a3b8; --code-ink:#ba2142;
  --accent:#ba2142; --accent2:#951833; --accent-light:rgba(186,33,66,.10); --on-accent:#ffffff;
  /* One neutral for BOTH themes, and deliberately not redefined for dark.
     It carries the page-nav button, which previously used --bg2 and so
     tracked the theme to its extremes: a white disc on a white page, a black
     one on a black page. Picked by measurement to sit equally on either
     ground -- 4.17:1 on light (#f8fafc), 4.09:1 on dark (#0f172a) -- so it is
     always visible and never the loudest thing in the corner. */
  --fab-quiet:#6b7a90; --fab-quiet-hover:#5c6a7e; --on-fab-quiet:#ffffff;
  --success:#10b981; --warning:#f59e0b; --info:#3b82f6;
  /* -ink = the same semantic, dark enough to carry TEXT at AA. The bare
     token stays the cross-world constant (COL-03) and keeps doing tints,
     borders and icons. Never set small type in the bare token. */
  --success-ink:#0a6e50; --warning-ink:#8a5700; --info-ink:#1d4ed8; --danger-ink:#a4161a;
  --label-ls:.085em;
  /* --header-h is owned by chrome.css, which the global header lives in. It must
     NOT be redeclared here: this file loads after chrome.css, and a media query
     adds no specificity, so a plain :root value here would beat chrome's
     responsive one and pin the header at its desktop height on a phone. */
  --sidebar-w:280px; --logo-ink:#1E3A8A; --tagline-accent:#1E3A8A;

  /* ---------------------------------------------------------------------
     ACCENT (COL-01 / COL-04 / COL-06, 18 Aug 2026)
     The old accent was #e11d48, which is Tailwind's rose-600 verbatim: a
     framework default rather than a chosen colour. It was also failing.
     The reading column has no background of its own, so prose paints on
     --bg1 (#f8fafc), not on white, and #e11d48 on #f8fafc measures
     4.49:1 -- under the 4.5 AA line for normal text. Every inline link in
     84 signed-off sections was sitting just below the threshold.

     The replacement holds the hue at 347 so this is a deepening rather
     than a rebrand, and drops saturation 77 -> 70 and lightness 50 -> 43:
     hsl(347,70%,43%). Measured 5.93:1 on --bg1 and 6.21:1 on --card. It
     stops well short of the external draft's #96222F (7.82:1), which is
     dark enough to read brownish and lose the clickable signal.

     --accent2 is the deeper companion (8.17:1) used for hovers and for
     the injected doc-note. In DARK the role inverts, because "deeper" on
     a dark ground means less legible: dark --accent2 is a LIGHTER step.
     The old dark --accent2 was #e11d48 -- a naive reuse of the light
     accent -- and measured 3.11:1 on --card, a real AA failure on the
     doc-note prose of every section carrying an Adobe ref-box.

     --on-accent is what goes ON an accent fill. It exists because #fff
     was hardcoded on four accent backgrounds, and white on the dark
     accent measured 3.19:1 before this change and would have been 2.66:1
     after it. It is #fff in light and the dark ink in dark: 6.21:1 and
     6.70:1. Never hardcode #fff on an accent fill again.

     Anything tinted from the accent must be derived from it. Six rules
     carried rgba(225,29,72,...) as a literal and would not have followed
     a token change at all.
     --------------------------------------------------------------------- */

  /* ---------------------------------------------------------------------
     TYPE SCALE (TYP-01, 17 Aug 2026)
     Before this existed the whole reading column was 16px: a section
     heading was the same size as the sentence under it, so a 13-screen
     page gave the eye no landmarks. Each step below has one stated job.
     Change a size HERE, never in a component. The mobile step is a token
     override in the 880px query, not eight per-component overrides.

       --fs-title  page and module titles (h1)
       --fs-h3     section headings, the h3 stack
       --fs-body   body prose everywhere, including inside boxes
       --fs-sm     supporting prose, table cells, notes, ref links
       --fs-label  block titles and box headers
       --fs-meta   breadcrumb, dates, table headers, card descriptions
       --fs-micro  code language, copy button, small badges
       --fs-code   monospace, which runs small at a given px
     --------------------------------------------------------------------- */
  --fs-title:34px; --fs-h3:24px; --fs-body:17px; --fs-sm:15.5px;
  --fs-label:15px; --fs-meta:12.5px; --fs-micro:11.5px; --fs-code:14px;
  --lh-title:1.2; --lh-h3:1.3; --lh-body:1.7;

  /* ---------------------------------------------------------------------
     COLUMN WIDTH (TYP-02)
     One width for prose and components. The first attempt gave prose its
     own narrower measure and left components at 800px; that produced a
     540px strip of text with a 440px void beside it on a 1440px screen,
     and was rejected on sight. The void was the fault, not the measure:
     the reclaimed space all landed on one side because the prose was
     left-aligned inside the old band.

     The measured problem is still real and still unsolved here. From
     52,686 characters of this site's own prose, rendered in the real
     font: 800px = 111.6 characters per line, against a comfortable
     65 to 75. The fix is being prototyped behind `body.world-learning` on one
     section, where a narrower column, larger body type and the in-page
     spine land together, because all three trade against the same
     horizontal budget and cannot be judged separately.
     --------------------------------------------------------------------- */
  --column:760px; --title-indent:17px;
  /* CMP-12: the width an inline SVG diagram must keep so its authored text
     stays legible. 61 of the 65 use a 700-unit viewBox, so at 700px they
     render at scale 1.0 -- authored 11px is 11px. Below the tablet break
     the box scrolls to honour this rather than shrinking the figure to 4px. */
  --diagram-min:700px;

  /* ---------------------------------------------------------------------
     DIAGRAM PALETTE (19 Aug 2026)
     Five hues for figures, so a diagram can use colour to mean something
     without every module inventing its own. Amit's ruling: there is no rule
     to stick to one colour and no rule to use many -- use colour where it
     makes a difference, and bright is allowed.

     Each hue comes as three values with one job each:
       --dia-N        the saturated line. Strokes, arrows, and SOLID fills
                      on shapes that carry NO text. Free to be vivid.
       --dia-N-soft   the tint. The fill behind text.
       --dia-N-ink    the text. Readable on its own soft tint, measured
                      5.46:1 or better in light and 7.99:1 or better in dark.

     The pattern that keeps this safe: a shape carrying TEXT takes soft fill
     + line stroke + ink text. A shape carrying NO text can be solid --dia-N
     at full strength. That is why brightness costs nothing here -- the
     vivid values never sit behind letters.

     A = crimson (the site accent, and the subject of a figure)
     B = blue    C = green    D = amber    E = violet
     --------------------------------------------------------------------- */
  --dia-a:#ba2142; --dia-a-soft:rgba(186,33,66,.10);  --dia-a-ink:#951833;
  --dia-b:#2563eb; --dia-b-soft:rgba(37,99,235,.10);  --dia-b-ink:#1d4ed8;
  --dia-c:#10b981; --dia-c-soft:rgba(16,185,129,.12); --dia-c-ink:#0a6e50;
  --dia-d:#f59e0b; --dia-d-soft:rgba(245,158,11,.14); --dia-d-ink:#8a5700;
  --dia-e:#7c3aed; --dia-e-soft:rgba(124,58,237,.10); --dia-e-ink:#5b21b6;
  --rail-w:250px; --rail-gap:40px; /* COL-06: white on the old #16a34a measured 3.30:1 on the pocket-map tab,
     which is the loudest control on every one of the 116 sections. 5.02:1 now. */
  --pocket:#15803d; --pocket-dark:#126b35;

  /* ---------------------------------------------------------------------
     SPACING SCALE (TYP-03, 18 Aug 2026)
     Headings were carrying the spacing they had when they were 16px: 16px
     above, against a 12px gap between two paragraphs. So a 24px heading got
     less air than an ordinary paragraph break and stopped reading as a rest
     stop on a 14-screen page.

     The ratio is the point. A heading takes --sp-xl above and --sp-sm below,
     four to one, so it separates from what came before and binds to what
     follows. Component margins were also drifting between 14, 16, 20, 24
     and 44px with no reason behind any of them; they now share one step.
     --------------------------------------------------------------------- */
  --sp-xs:6px; --sp-sm:12px; --sp-md:18px; --sp-lg:30px; --sp-xl:48px;
}
[data-theme="dark"]{
  /* --bg3 was #334155, which is too light a surface for the dark inks that
     land on it: --text2/--text3 measured 4.04:1 and the accent 3.89:1 on
     the search field, table headers, flow steps and pocket-map labels.
     At #283340 those become 5.00:1 and 4.81:1. Darkening the SURFACE is
     safer than lightening the ink, which is shared with lighter grounds. */
  --bg1:#0f172a; --bg2:#1e293b; --bg3:#283340; --card:#1e293b;
  --text1:#f1f5f9; --text2:#94a3b8; --text3:#94a3b8; --border:#334155;
  --code-chrome-ink:#94a3b8; --code-ink:#fda4af;
  --accent:#f27893; --accent2:#f7a1b4; --accent-light:rgba(242,120,147,.10); --on-accent:#0f172a; --logo-ink:#ffffff; --tagline-accent:#818cf8;
  /* -ink flips direction on a dark ground: lighter, not darker. */
  --success-ink:#34d399; --warning-ink:#fbbf24; --info-ink:#60a5fa; --danger-ink:#f98a8a;
  /* Diagram palette, dark. The lines brighten and the inks go LIGHTER, the
     same inversion --accent2 and the semantic -ink tokens use. */
  --dia-a:#f27893; --dia-a-soft:rgba(242,120,147,.14); --dia-a-ink:#f7a1b4;
  --dia-b:#60a5fa; --dia-b-soft:rgba(96,165,250,.14);  --dia-b-ink:#93c5fd;
  --dia-c:#34d399; --dia-c-soft:rgba(52,211,153,.14);  --dia-c-ink:#6ee7b7;
  --dia-d:#fbbf24; --dia-d-soft:rgba(251,191,36,.14);  --dia-d-ink:#fcd34d;
  --dia-e:#a78bfa; --dia-e-soft:rgba(167,139,250,.14); --dia-e-ink:#c4b5fd;
}

body.world-learning{margin:0;background:var(--bg1);color:var(--text1);font-family:'IBM Plex Sans',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;line-height:var(--lh-body)}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
/* Inline content links (lesson + glossary body): persistent underline so a link reads as a link without relying on color alone. Crimson accent preserved; nav, cards, breadcrumb, prev/next and section lists are outside .section and unaffected. */
.section a{text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:2px}
.section a:hover{text-decoration-thickness:2px}
.section .ref-box a{text-decoration:none}
.section .ref-box a:hover{text-decoration:underline}
/* A content link is already marked twice over -- accent colour and a persistent
   underline -- so setting it bold is a third signal for the same thing. In
   running prose it reads as shouting, and in a table whose first column is all
   links it reads as a heavy block, which is where Amit caught it on 21 Aug 2026.
   Bold is stripped wherever the two are combined, in either nesting order.

   The 44 cases then in the corpus were unwrapped in the markup as well, so this
   rule is not covering anything up today. It exists because the habit will
   recur across the sections still to be written, and a rule catches it once
   where sixteen file edits would have to be repeated every time.

   Deliberately scoped to .section, the same scope that gives content links
   their underline. Navigation keeps its weight: a bold nav item is a
   "you are here" signal, not decoration, and flattening it would cost
   wayfinding. Only .section is prose. */
.section strong a, .section b a{font-weight:400}
.section a strong, .section a b{font-weight:inherit}

/* ---------- Header ---------- */
/* The header moved to chrome.css as the .site-* component on 2 Sep 2026. It is
   one shared thing now, used by every world, so its rules cannot live inside
   a single world stylesheet. */

/* ---------------------------------------------------------------------------
   SEARCH OVERLAY
   The markup and the JavaScript are shared with the migration world, but the
   styling is not: that world's rules live in world-shell.css, which this world
   never loads. So the component is restyled here on this world's own tokens
   rather than the other world's, which is the same reason each world carries
   its own stylesheet at all.
   --------------------------------------------------------------------------- */
.search-overlay{position:fixed;inset:0;z-index:1200;background:rgba(15,23,42,.45);display:none;justify-content:center;align-items:flex-start;padding:72px 16px 16px}
.search-overlay:not([hidden]){display:flex}
[data-theme="dark"] .search-overlay{background:rgba(0,0,0,.62)}
.search-box{width:100%;max-width:640px;background:var(--bg2);border:1px solid var(--border);border-radius:14px;box-shadow:0 20px 60px rgba(0,0,0,.28);overflow:hidden;display:flex;flex-direction:column;max-height:calc(100vh - 120px)}
.search-in{display:flex;align-items:center;gap:10px;padding:13px 16px;border-bottom:1px solid var(--border)}
.search-in svg{color:var(--text3);flex:none}
/* 16px so iOS does not zoom the page when the field takes focus. */
.search-in input{flex:1;min-width:0;border:0;outline:0;background:transparent;color:var(--text1);font-size:16px;font-family:inherit}
.search-in input::placeholder{color:var(--text3)}
.search-in kbd{font-size:11px;color:var(--text3);border:1px solid var(--border);border-radius:5px;padding:2px 6px;flex:none}
.search-results{overflow-y:auto;padding:6px}
.search-hint{padding:18px 14px;color:var(--text2);font-size:var(--fs-meta)}
.search-results .sresult{display:flex;gap:12px;align-items:flex-start;padding:10px 12px;border-radius:9px;cursor:pointer;text-decoration:none}
.search-results .sresult:hover,.search-results .sresult.sel{background:var(--accent-light);text-decoration:none}
.sbadge{flex:none;font-size:10px;font-weight:700;letter-spacing:.4px;text-transform:uppercase;padding:3px 8px;border-radius:999px;margin-top:2px;white-space:nowrap;color:var(--accent);background:var(--bg2);border:1px solid var(--border)}
.sbody{display:flex;flex-direction:column;gap:3px;min-width:0}
.stitle{font-size:var(--fs-sm);font-weight:600;color:var(--text1)}
.smeta{font-size:var(--fs-micro);color:var(--text2)}
.ssnip{font-size:var(--fs-meta);color:var(--text2);line-height:1.5;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
.sresult mark{background:#fde68a;color:#1f2937;border-radius:3px;padding:0 1px}
.shint{margin:10px 4px 4px;padding:10px 12px;border-top:1px solid var(--border)}
.shint-lead{display:block;font-size:var(--fs-micro);font-weight:700;color:var(--text2);margin-bottom:5px}
.search-results .shint-a{display:block;font-size:var(--fs-meta);padding:3px 0;color:var(--accent);text-decoration:none}
.search-results .shint-a:hover{text-decoration:underline}

/* ---------- Sidebar ---------- */
.lsidebar{position:fixed;top:var(--header-h);left:0;width:var(--sidebar-w);height:calc(100vh - var(--header-h));background:var(--bg2);border-right:1px solid var(--border);overflow-y:auto;z-index:900;transition:transform .3s}
.lsidebar-content{padding:12px 8px}
.lnav-item{margin-bottom:2px}
/* The module row is a <button> now, not a div wrapping a link and a chevron:
   the name toggles the sections rather than navigating to the landing. It
   therefore carries the padding the inner link used to, plus the resets a
   button needs to stop looking like one. `open` means "this module contains
   the page you are reading"; `current` means "this IS the page you are on",
   which only a module landing can be. */
.lnav-header{display:flex;align-items:center;gap:8px;width:100%;box-sizing:border-box;
  padding:9px 10px;border:none;border-radius:6px;background:none;cursor:pointer;
  font-family:inherit;font-size:13px;color:var(--text1);text-align:left}
.lnav-header:hover{background:var(--bg3)}
.lnav-header.open .lnav-title{font-weight:600}
.lnav-header.current{background:var(--accent-light);color:var(--accent)}
.lnav-num{width:22px;height:22px;background:var(--bg3);border-radius:5px;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:600;flex:none}
.lnav-title{flex:1;min-width:0;font-weight:500}
.lnav-chevron{flex:none;color:var(--text3);font-size:12px;line-height:1;transition:transform .2s}
.lnav-header.open .lnav-chevron{transform:rotate(180deg)}
.lnav-subs{display:none;padding:4px 0 4px 26px}
.lnav-subs.open{display:block}
.lnav-sub{display:block;padding:7px 10px;font-size:12px;color:var(--text2);border-radius:5px;text-decoration:none}
.lnav-sub:hover{background:var(--bg3);color:var(--text1);text-decoration:none}
.lnav-sub.active{color:var(--accent);background:var(--accent-light);font-weight:600}
/* The landing page, named for what it actually is and placed where a reader
   who wants it will look. It must stay quieter than the sections: it is the
   fallback for somebody who wants the module's own summary, not the first
   thing anybody should read. Declared AFTER .lnav-sub because the two match
   at identical specificity (0,1,0) on the module Overview, which carries both
   classes, and source order is what decides it.

   It is separated by a HAIRLINE rather than by colour or weight, and that is
   deliberate: --text2 and --text3 are the same #94a3b8 in dark, so a colour
   step that dims nicely in light does nothing at night, and the 600 weight
   this rule first carried made Overview LOUDER than the sections underneath
   it -- the exact inversion the row exists to avoid. A rule separates without
   competing, and it reads the same in both themes. */
.lnav-ovw{display:block;padding:7px 10px 8px;margin-bottom:3px;border-radius:5px 5px 0 0;
  border-bottom:1px solid var(--border);text-decoration:none;
  font-size:12px;font-weight:400;color:var(--text3)}
.lnav-ovw:hover{background:var(--bg3);color:var(--text1);text-decoration:none}
.lnav-ovw.active{color:var(--accent);background:var(--accent-light)}
.lnav-alltop{display:flex;align-items:center;gap:6px;padding:9px 10px;margin-bottom:8px;font-size:12.5px;font-weight:600;color:var(--text2);border-radius:6px;text-decoration:none}
.lnav-alltop:hover{background:var(--bg3);color:var(--text1);text-decoration:none}
/* sidebar category level (phase) */
/* The glossary link, pinned above the phase list. It is a lookup rather than a
   stop on the curriculum, so it is deliberately NOT styled like a phase: no
   chevron, no expandable body, and a count on the right that says what it is
   without a word of explanation. Quiet enough that it does not compete with
   the five phases, present enough that a reader mid-sentence can find it. */
.lnav-glossary{display:flex;align-items:center;gap:8px;padding:8px 12px;margin-bottom:10px;
  border:1px solid var(--border);border-radius:8px;text-decoration:none;
  font-size:var(--fs-sm);font-weight:600;color:var(--text1);
  transition:border-color .15s,background-color .15s,color .15s}
.lnav-glossary:hover{border-color:var(--accent);color:var(--accent);background:var(--accent-light)}
.lnav-glossary.current{border-color:var(--accent);color:var(--accent);background:var(--accent-light)}
.lnav-gloss-n{margin-left:auto;font-size:var(--fs-micro);font-weight:700;color:var(--text3);
  font-variant-numeric:tabular-nums}
.lnav-glossary:hover .lnav-gloss-n,.lnav-glossary.current .lnav-gloss-n{color:var(--accent)}
.lnav-cat{margin-bottom:1px}
/* Same change as the module row above: the phase name toggles its modules
   instead of navigating to the phase landing. */
.lnav-cathead{display:flex;align-items:center;justify-content:space-between;gap:4px;
  width:100%;box-sizing:border-box;padding:10px;border:none;border-radius:6px;
  background:none;cursor:pointer;font-family:inherit;font-size:12.5px;font-weight:700;
  color:var(--text1);text-align:left;line-height:1.3}
.lnav-cathead:hover{background:var(--bg3)}
.lnav-catname{flex:1;min-width:0}
.lnav-catchev{flex:none;color:var(--text3);font-size:10px;line-height:1;transition:transform .2s}
.lnav-cathead.open .lnav-catchev{transform:rotate(180deg)}
.lnav-cathead.open{color:var(--accent)}
.lnav-cathead.current{background:var(--accent-light);color:var(--accent)}
.lnav-catbody{display:none;padding:2px 0 8px 6px}
.lnav-catbody.open{display:block}
.lnav-catbody .lnav-header{font-size:12.5px}

/* ---------- Main ---------- */
/* box-sizing:border-box is load-bearing here, not tidiness. min-height is
   calc(100vh - header) and the padding is 24px top and bottom; under the
   default content-box those 48px sit OUTSIDE the min-height, so the document
   is always 48px taller than the viewport no matter how short the content
   is. Every learning page with content shorter than one screen -- the world
   front page, the five phase landings, the 21 module landings -- carried a
   scrollbar that had nothing to scroll to. Found 2 Sep 2026: the front page
   measured 848 against an 800 viewport, and hiding content changed nothing
   because the height was never coming from the content. */
.lmain{box-sizing:border-box;margin-left:var(--sidebar-w);margin-top:var(--header-h);min-height:calc(100vh - var(--header-h));padding:24px 32px}
.lcontent{max-width:var(--column);margin:0 auto}

/* The world front page only, selected by the one element unique to it. Its job
   is to show the whole map without scrolling, and the generous bottom padding
   that suits a 2000-word lesson is the last thing standing between the map and
   a 650px laptop viewport. Lessons keep their 24px. */
body.world-learning:has(.lhome-foot) .lmain{padding-bottom:8px}
/* ==========================================================================
   PROTOTYPE v2, one section only (M14 s1), behind `proto: true` front matter.

   ONE WIDTH, decided 18 Aug after two rounds of review.
   The two-width version (prose 620, figures 800) is typographically better
   and was noticed as wrong twice, unprompted, in two separate reviews. A
   layout a careful reader keeps reading as broken IS broken, whatever the
   textbook says, so prose and figures share --column again.

   760px at 18px measures ~94 characters against 111.6 at the old 800/16.
   That is a real improvement and an honest admission that it is short of
   the 65-75 target: you cannot have a 70-character measure, full-size
   infographics and one width at this type size. Infographics won, because
   they are the differentiator; at 760 they lose 5% and their internal text
   still renders larger than the 620 version that was rejected.

   THE OVERFLOW BUG, for whoever hits it next: a grid item with a px
   max-width does NOT shrink below its track. `min-width:0` is not enough.
   It needs max-width:min(...,100%). Without it the article ran 109px wider
   than a 390px phone, which stretched the fixed header to the overflowed
   width and made it slide away sideways.
   ========================================================================== */
/* --------------------------------------------------------------------------
   TYP-04, prototype only. IBM Plex, self-hosted, no CDN link in the page.

   Why a typeface at all: the system stack renders as Segoe UI on Windows,
   SF on Mac and Roboto on Android, so it cannot carry an identity. Plex was
   drawn for technical documentation, has character without being fashionable,
   and its mono sibling shares the same skeleton so code and prose stop
   looking like two different documents.

   ONE variable file covers every weight the site uses (400/500/600/650/700)
   in 44.6KB, against four or five static files. The single 800 in the
   stylesheet clamps to 700 and is not used on a section page.


/* TYP-04 site-wide: IBM Plex is the learning world font. */
/* Mono is applied per selector because the base rules hardcode Consolas and
   are declared later in this file. Doubling up the body class beats them on
   specificity without reaching for !important. */
body.world-learning .code-body pre,
body.world-learning .lcontent code,
body.world-learning .lcontent .path,
body.world-learning .shot-brief,
body.world-learning .wf-rail a::before{
  font-family:'IBM Plex Mono',Consolas,monospace;
}

/* scroll-padding belongs on the SCROLLING element. Setting it on body does
   nothing, which is why every jump landed with the heading under the header
   and the reader looking at the paragraph after it. */

/* ---------------------------------------------------------------------------
   TYP-06 + COL-06: the label voice (18 Aug 2026, revised 19 Aug)

   Measured before this work: warn-hdr, info-hdr, pro-tip-hdr, ref-title,
   path-title, diagram-title and shot-title were all 13.5px / 700 / Plex Sans
   with letter-spacing normal -- and four of them were carrying a colour that
   could not be read on its own tint:

       warn-hdr   #f59e0b on its own tint   1.91:1   103 boxes
       ref-title  #10b981 on its own tint   2.25:1    83 boxes
       ref-box a  #10b981 on its own tint   2.25:1
       info-hdr   #3b82f6 on its own tint   3.15:1    65 boxes
       table th   #94a3b8                   2.34:1

   The first attempt set these in mono, uppercase and tracked, on the argument
   that it reads as an engineering field manual. Amit rejected it on 19 Aug,
   and he was right: mono uppercase with letter-spacing is a recognisable
   generated-page tell, and a site whose whole claim is sixteen years of real
   practice cannot afford a typographic signature that says otherwise. It also
   quietly removed the accent from every pro-tip header, which is most of why
   the site stopped looking crimson.

   So the label stays in the site's own face and reads as a label through
   WEIGHT and SIZE rather than through case or tracking, which is how print
   technical publishing has always done it: Plex Sans 700, sentence case,
   --fs-label stepped 13.5px -> 15px so it separates cleanly from 18px body
   prose. No mono, no uppercase, no letter-spacing.

   The colour comes back, in versions that can actually be read. The bare
   --warning / --info / --success are untouched and still do tints, borders
   and icons, so COL-03 holds exactly; the -ink companions carry the text.

   Uppercase survives in exactly two places, where it is a genuine convention
   rather than a style: table column headers and the code-block language
   label. Both keep a little tracking, because uppercase without it is
   cramped. Neither is in mono.
   --------------------------------------------------------------------------- */

/* The seven labels take their size and weight from their own component rules
   below; this is only the size step and the shared reset of the first
   attempt's case and tracking. */
body.world-learning .lcontent .warn-hdr,
body.world-learning .lcontent .info-hdr,
body.world-learning .lcontent .pro-tip-hdr,
body.world-learning .lcontent .ref-title,
body.world-learning .lcontent .path-title,
body.world-learning .lcontent .diagram-title,
body.world-learning .lcontent .shot-title,
body.world-learning .lcontent .lcrumb{
  font-family:'IBM Plex Sans',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
  text-transform:none;
  letter-spacing:normal;
}
/* Table headers and the code label: uppercase by convention, sans, tracked. */
body.world-learning .code-lang{letter-spacing:var(--label-ls)}
/* The ref-box is the deliberate handoff, and its links stay green. The green
   just has to be one a reader can see. */
body.world-learning .lcontent .ref-box a{color:var(--success-ink)}

html:has(body.world-learning){scroll-behavior:smooth; scroll-padding-top:calc(var(--header-h) + 24px)}
@media(prefers-reduced-motion:reduce){html:has(body.world-learning){scroll-behavior:auto}}

body.has-rail .lmain{
  display:grid; align-items:start;
  grid-template-columns:minmax(0,1fr) var(--rail-w);
  column-gap:var(--rail-gap);
}
/* border-box so the gutter below cannot push the column past its track */
body.world-learning .lcontent{
  box-sizing:border-box; min-width:0; max-width:min(var(--column),100%);
  margin:0 auto; padding-left:var(--title-indent);
}
/* The h1 carries a crimson accent rule as 14px padding + a 3px border, so
   its TEXT starts 17px right of everything else and the page head read as
   three lines starting in three different places. Rather than indent the
   breadcrumb and date to match (which would then leave the body text out
   of line instead), the column gains a 17px gutter that everything shares
   and the h1 is pulled back into it, so the bar hangs in the margin and
   every line of text on the page starts at exactly the same x. */
body.world-learning .lcontent > h1.lsec-title{margin-left:calc(var(--title-indent) * -1)}

/* ---- the rail ---- */
.wf-rail{
  position:sticky; top:calc(var(--header-h) + 22px);
  max-height:calc(100vh - var(--header-h) - 48px);
  overflow-y:auto; overscroll-behavior:contain;
  padding:0 6px 8px 16px; border-left:1px solid var(--border);
}
.wf-meta{
  font-size:var(--fs-meta); font-weight:600; color:var(--text1);
  margin:0 0 14px; line-height:1.5;
}
.wf-meta span{display:block; font-weight:400; color:var(--text2)}

/* The two navigation devices are deliberately NOT alike. The pocket map is
   green because that colour is doing a job: it carries a reader across 116
   sections and has to catch the eye. The page list is quiet because it only
   moves you inside one page. */
.wf-map-btn{
  display:flex; align-items:center; gap:9px; width:100%; margin:0 0 20px;
  padding:10px 12px; cursor:pointer; text-align:left;
  font-family:inherit; color:#fff; background:var(--pocket);
  border:none; border-radius:8px; box-shadow:0 2px 8px rgba(22,163,74,.28);
}
.wf-map-btn:hover{background:var(--pocket-dark)}
.wf-map-btn svg{flex:none;width:17px;height:17px}
.wf-map-btn b{display:block; font-size:var(--fs-meta); font-weight:700; line-height:1.3}
.wf-map-btn em{display:block; font-style:normal; font-size:var(--fs-micro); opacity:.85; line-height:1.3}
.wf-rail-h{
  font-size:var(--fs-micro); font-weight:700; letter-spacing:.1em;
  text-transform:uppercase; color:var(--text2); margin:0 0 3px;
}
.wf-rail-sub{font-size:var(--fs-micro); color:var(--text2); margin:0 0 9px; line-height:1.4}
.wf-rail ol{list-style:none; margin:0; padding:0; counter-reset:wf -1}
.wf-rail li{counter-increment:wf; margin:0}
.wf-rail a{
  display:flex; gap:8px; padding:4px 6px 4px 0; border-radius:5px;
  font-size:var(--fs-meta); line-height:1.35; color:var(--text2); text-decoration:none;
}
.wf-rail a::before{
  content:counter(wf,decimal-leading-zero); flex:none;
  font-family:Consolas,monospace; font-size:10.5px; color:var(--text3);
  padding-top:2px; font-variant-numeric:tabular-nums;
}
.wf-rail a:hover{color:var(--text1); background:var(--bg3); text-decoration:none}
.wf-rail li.on > a{color:var(--accent); font-weight:650}
.wf-rail li.on > a::before{color:var(--accent)}
.wf-rail li.seen > a{color:var(--text3)}
.wf-rail li.wf-intro > a{font-style:italic}
/* Jump to the path-box. Not a section, so it takes no number and sits below
   a rule: a reader who landed here only wanting "where is this screen" can
   find it without reading anything. It borrows the path-box's own violet so
   the link and its destination are visibly the same thing, and it is
   deliberately NOT the ref-box, which sends people off to Adobe. */
.wf-rail li.wf-path{counter-increment:none; margin-top:9px; padding-top:9px; border-top:1px solid var(--border)}
.wf-rail li.wf-path > a{color:#534AB7; font-weight:650}
.wf-rail li.wf-path > a:hover{color:#3C3489; background:rgba(83,74,183,.08)}
.wf-rail li.wf-path > a::before{
  content:""; width:12px; height:12px; margin-top:3px; border-radius:50%;
  background:radial-gradient(circle at 50% 50%, #534AB7 0 3.2px, transparent 3.4px);
}
[data-theme="dark"] .wf-rail li.wf-path > a{color:#CECBF6}
[data-theme="dark"] .wf-rail li.wf-path > a:hover{color:#E6E4FB; background:rgba(206,203,246,.12)}
[data-theme="dark"] .wf-rail li.wf-path > a::before{
  background:radial-gradient(circle at 50% 50%, #CECBF6 0 3.2px, transparent 3.4px);
}

/* Three columns only: the pocket map lives in the rail, so the floating tab
   would be a duplicate. Everywhere else the tab stays exactly as it was. */
/* Hide the floating tab ONLY where the rail actually exists to carry it.
   Keyed on :has(.wf-rail) rather than .has-rail: 27 sections have too few
   headings to build a rail, and hiding the tab on those left them with no
   pocket map at all, which is the one control that must never disappear. */
@media(min-width:1244px){ body:has(.wf-rail) .wf-tab{display:none} }

/* ---- drawer below the three-column threshold ---- */
.wf-nav-fab{display:none}
.wf-nav-veil{display:none}
@media(max-width:1243px){
  body.has-rail .lmain{grid-template-columns:minmax(0,1fr)}
  /* the green tab is back and permanent here, so the drawer must not carry
     a second copy of the same control */
  .wf-map-btn{display:none}
  .wf-rail{
    /* Above the site header (z-1000). At 945 the fixed header sat on top of
       the drawer and swallowed the "On this page" title, so the panel opened
       looking like it started at item 00. A modal covers the chrome. */
    position:fixed; top:0; right:0; z-index:1051; box-sizing:border-box;
    width:min(330px,88vw); height:100%; max-height:none;
    padding:20px 18px 28px; border-left:1px solid var(--border);
    background:var(--bg2); box-shadow:-8px 0 28px rgba(0,0,0,.16);
    transform:translateX(101%); transition:transform .22s ease; visibility:hidden;
  }
  .wf-rail.open{transform:none; visibility:visible}
  .wf-nav-veil{
    display:block; position:fixed; inset:0; z-index:1050;
    background:rgba(0,0,0,.42); opacity:0; pointer-events:none; transition:opacity .22s;
  }
  .wf-nav-veil.open{opacity:1; pointer-events:auto}
  /* A quiet control, deliberately. It sits directly above the About mark, and
     as a near-black solid disc it once shouted over it: a utility for moving
     inside one page was outweighing the site's own identity. The order of
     loudness should be pocket map, then page nav, then identity, and this is
     the middle one.

     The fix for that was a surface fill, --bg2, and it traded one fault for
     another: --bg2 is #ffffff in light and #1e293b in dark, so the button
     tracked the theme to its extremes and read as a white disc on a white
     page or a black disc on a black one. It either vanished or shouted.

     --fab-quiet is one neutral for BOTH themes, chosen by measurement rather
     than by picking an opposite: 4.17:1 against the light page ground
     (#f8fafc) and 4.09:1 against the dark one (#0f172a). Near-identical
     presence either way, clearly a control, and never the loudest thing in
     the corner. The icon rides at 4.36:1 on it, past the 3:1 non-text line.
     One value means there is no dark override to keep in step. */
  .wf-nav-fab{
    display:flex; align-items:center; justify-content:center;
    /* Clear of the About mark, which is 88px tall -- not the 80px this note
       used to claim -- and would otherwise sit underneath this. Horizontal
       position comes from the shared --fab-axis so the two controls stack as
       one column; offset is half this button's own width. */
    position:fixed; right:calc(var(--fab-axis) - 22px); bottom:112px; z-index:940;
    width:44px; height:44px; border-radius:50%; cursor:pointer;
    color:var(--on-fab-quiet); background:var(--fab-quiet); border:none;
    box-shadow:0 3px 10px rgba(15,23,42,.18);
  }
  .wf-nav-fab:hover{background:var(--fab-quiet-hover)}
  [data-theme="dark"] .wf-nav-fab{box-shadow:0 3px 10px rgba(0,0,0,.45)}
  .wf-nav-fab svg{width:20px;height:20px}
  .wf-rail a{font-size:var(--fs-sm); padding:7px 6px 7px 0}
}
/* Scoped to beat `body.world-learning .lcontent p`, which is (0,2,2) and
   otherwise wins: the breadcrumb is a <p> inside .lcontent. Before TYP-01
   this rule said 13px and the page rendered it at 16px. */
body.world-learning .lcontent .lcrumb{font-size:var(--fs-meta);color:var(--text2);margin:0 0 10px}
.lcrumb a{color:var(--text2)} .lcrumb a:hover{color:var(--accent)}
/* Size carries the hierarchy, weight does not have to shout as well.
   Both page titles ran at 700, which with a 34px size and a crimson rule
   beside it made the heaviest thing on the page heavier still. Google Cloud
   sets its equivalent at 500 and still reads as more confident, because the
   size alone is doing the work. 600 keeps the mark authoritative without the
   slab look that dates a documentation page. */
.lmod-title{font-size:var(--fs-title);line-height:var(--lh-title);font-weight:600;color:var(--text1);margin:.1em 0 .4em}
.lsec-title{font-size:var(--fs-title);line-height:var(--lh-title);font-weight:600;color:var(--text1);margin:0 0 var(--sp-sm);padding-left:14px;border-left:3px solid var(--accent)}
/* Last-updated stamp. Sits under the section title, aligned to the title text (14px padding + 3px rule). Lives inside .lcontent, so lFillPrintDoc carries it into the print document automatically. */

/* --------------------------------------------------------------------------
   EAT-01: the byline. One bounded band under the title, carrying who wrote
   this, what they have done, when it changed and how long it takes. Bounded
   by two hairlines so it reads as a block of credentials rather than four
   loose scraps of metadata. The name is a link to /about/, because a claim
   a reader can check in one click is worth more than one they cannot.
   -------------------------------------------------------------------------- */
body.world-learning .lcontent .byline{
  display:flex; flex-wrap:wrap; align-items:center; gap:5px 11px;
  margin:0 0 var(--sp-xl); padding:11px 0;
  border-top:1px solid var(--border); border-bottom:1px solid var(--border);
  font-size:var(--fs-meta); color:var(--text2); line-height:1.5;
}
/* The name carries by SIZE, not by looking like a hyperlink. Underlined
   crimson made the byline read as a row of links; plain dark text read as a
   label nobody would click. Setting it a step larger than the metadata
   around it makes it the subject of the block, which is what it is, and the
   underline arrives only on hover. */
.byline .by-who{
  font-size:var(--fs-sm); font-weight:700; color:var(--text1);
  text-decoration:none; letter-spacing:-.008em;
}
.byline .by-who:hover{color:var(--accent); text-decoration:underline;
  text-decoration-thickness:1.5px; text-underline-offset:3px}
.byline-lite{margin-bottom:var(--sp-sm) !important}
.byline .by-cred{color:var(--text2)}
.byline .by-sep{color:var(--text3)}
.byline .by-date,.byline .by-read{color:var(--text2); white-space:nowrap}
@media(max-width:600px){
  /* Three deliberate lines on a phone: who, then the credential, then the
     dates. Left to wrap on its own it produced four ragged lines with a
     separator stranded at the start of one of them. The separators go and
     the read time carries its own, so the last line still reads as a pair. */
  .byline{gap:3px 9px}
  .byline .by-who{flex:1 0 100%}
  .byline .by-cred{flex:1 0 100%; order:2}
  .byline .by-sep{display:none}
  .byline .by-date{order:3}
  .byline .by-read{order:4}
  .byline .by-read::before{content:"\00B7\00A0"; color:var(--text3)}
}
body.world-learning .lcontent .lupdated{font-size:var(--fs-meta);color:var(--text3);font-weight:400;margin:-6px 0 var(--sp-lg);padding-left:0;letter-spacing:normal}
.lupdated time{font-weight:500;color:inherit}
.lcontent p{color:var(--text1);margin:0 0 1em}

/* ---------- Module landing cards (world index) ---------- */
/* Tightened 2 Sep 2026. The front page is a navigator and its third tier -- the
   actual sections -- was landing at 701px, below the fold on any laptop shorter
   than about 780. None of the space was doing work: it was padding on three
   tiers, two 32px connector arrows, and an 88px mark. */
.welcome{display:flex;align-items:center;justify-content:center;gap:16px;padding:6px 16px 2px;margin-top:-4px}
.welcome-icon{width:64px;height:64px;display:flex;align-items:center;justify-content:center;flex:none;color:var(--logo-ink)}
.welcome-text{text-align:left}
.lmod-mark{color:var(--logo-ink);margin-bottom:10px}
.lmod-mark svg{display:block}
.welcome-title{font-size:28px;font-weight:700;margin:0;color:var(--text1);line-height:1.15}
.welcome-sub{font-size:15px;color:var(--text2);max-width:520px;margin:0 auto 10px;text-align:center}
.hm-cue{display:flex;align-items:center;justify-content:center;gap:14px;font-size:14px;font-weight:600;color:var(--text2);letter-spacing:.03em;margin:2px auto 2px}
.hm-cue::before,.hm-cue::after{content:"";height:1px;width:36px;background:var(--border)}
.lhome-support{margin-top:10px}
.welcome-stats{display:flex;justify-content:center;gap:40px;margin-bottom:26px}
.stat-val{font-size:32px;font-weight:800;color:var(--accent)}
.stat-lbl{font-size:12px;color:var(--text3)}
body.world-learning .welcome-btn{display:inline-flex;align-items:center;gap:8px;padding:12px 24px;background:var(--accent);color:var(--on-accent,#fff);border:none;border-radius:6px;font-size:14px;font-weight:700;text-decoration:none}
body.world-learning .welcome-btn:hover{background:var(--accent2);text-decoration:none}
.lmod-section-h{display:flex;align-items:center;justify-content:space-between;margin:30px 0 4px;padding-top:18px;border-top:1px solid var(--border)}
.lmod-section-h span{font-size:13px;font-weight:700;color:var(--text3);text-transform:uppercase;letter-spacing:.5px}
.lglossary-link{font-size:13.5px;font-weight:600;color:var(--accent)}
.lmod-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px;margin:18px 0 8px}
.lmod-card{display:block;border:1px solid var(--border);border-radius:12px;padding:18px;background:var(--card);color:var(--text1);text-decoration:none}
.lmod-card:hover{border-color:var(--accent);text-decoration:none}
.lmod-card .lmc-num{display:inline-block;font-size:11px;font-weight:700;color:var(--accent);background:var(--accent-light);border-radius:999px;padding:2px 9px;margin-bottom:8px}
.lmod-card .lmc-t{font-weight:700;font-size:16px}
.lmod-card .lmc-d{font-size:13.5px;color:var(--text3);margin-top:5px;line-height:1.5}
.lmod-card .lmc-meta{font-size:11.5px;color:var(--text3);margin-top:10px}

/* ---------- Module page: section list ---------- */
.lsec-list{list-style:none;padding:0;margin:18px 0}
.lsec-list li{margin-bottom:6px}
.lsec-list a{display:flex;align-items:center;gap:10px;padding:11px 14px;border:1px solid var(--border);border-radius:9px;color:var(--text1);background:var(--card)}
.lsec-list a:hover{border-color:var(--accent);text-decoration:none}
.lsec-list .lsn{width:24px;height:24px;border-radius:6px;background:var(--bg3);display:flex;align-items:center;justify-content:center;font-size:11.5px;font-weight:700;flex:none}

/* ---------- Lesson prev/next ---------- */
.lpn{display:flex;justify-content:space-between;gap:14px;margin-top:34px;padding-top:18px;border-top:1px solid var(--border)}
/* WEIGHT AND COLOUR, because this block sits at the moment of highest intent
   and used to be the quietest thing on the page: a hairline border, a grey
   micro label and body-weight text, directly under a coloured box of links
   pointing off the site. Readers took the loud thing. The label carries the
   accent now and the title carries 600, which is the least that makes the way
   onward read as the primary action without turning the foot of an article
   into a pair of buttons. */
.lpn a{flex:1;border:1px solid var(--border);border-radius:9px;padding:12px 16px;color:var(--text1);font-size:var(--fs-sm);font-weight:600}
.lpn a:hover{border-color:var(--accent);text-decoration:none}
.lpn a:hover .lpn-lab{color:var(--accent2)}
.lpn .lpn-lab{display:block;font-size:var(--fs-micro);font-weight:700;color:var(--accent);margin-bottom:3px}
.lpn .lpn-next{text-align:right}

/* THE LEFT DRAWER'S SCRIM, and it never existed as anything but an empty div.
   `.loverlay` had exactly one rule on the whole site -- `display:block` when
   the drawer opened -- and no position, no inset, no background and no
   z-index. An empty block element in normal flow is zero pixels tall, so
   `display:block` changed nothing visible and there was never a surface to
   catch a tap. world-learning.js has always bound closeNav to it; the handler
   simply could not fire.

   Reported from live testing on a phone: the left drawer could only be closed
   by pressing the hamburger a second time, while the page-nav drawer and the
   pocket map both closed on an outside tap. Those two work because .wf-nav-veil
   below is a real fixed layer; this now matches it, including the fade, and
   sits just under .lsidebar's z-index:900 so the drawer stays above its own
   scrim. */
.loverlay{display:none}

/* ---------- Mobile drawer ---------- */

@media(max-width:880px){
  .lsidebar{transform:translateX(-100%)}
  body.lnav-open .lsidebar{transform:translateX(0)}
  body.lnav-open .loverlay{
    display:block; position:fixed; inset:0; z-index:899;
    background:rgba(0,0,0,.42);
  }
  .lmain{margin-left:0;padding:18px 16px}
  /* Search collapses to an icon on a phone rather than disappearing. It was
     display:none here, which left the mobile reader with no way into search at
     all: not a narrower entry point, none. On a site this size search is the
     main way anybody finds anything, so it keeps its place in the header and
     the label is what gets dropped. Sized to the same 34px target as the print
     and theme buttons beside it. */
  /* TYP-01: the mobile step of the type scale is a TOKEN override, not a
     per-component one. This block previously redefined eight components
     individually and dropped body prose to 13px, which is too small for
     long-form reading and is the rendering Google indexes. Add a component
     override here only for something the scale genuinely cannot express. */
  :root{
    /* --header-h is set by the 1180 block above, which is where the header
       becomes two rows; it is deliberately not repeated here. */
    --fs-title:27px; --fs-h3:20px; --fs-body:16.5px; --fs-sm:14.5px;
    --fs-label:13.5px; --fs-meta:12px; --fs-micro:11px; --fs-code:13px;
    /* TYP-03: the spacing step scales with the type step, so the heading
       keeps its four-to-one ratio on a phone instead of collapsing. */
    --sp-xs:5px; --sp-sm:10px; --sp-md:15px; --sp-lg:24px; --sp-xl:36px;
  }
  /* The whole chain does not fit a phone: hamburger, mark, amitdusane.com, rule,
     section name and three icon buttons come to more than 375px before padding.
     The WORDMARK is what drops, not the section name. The mark is still the link
     to the site root, so tapping it goes home exactly as it does on desktop, and
     the section name is the half that tells a reader where they actually are. */
  /* Back to a flex row on a phone. The centred grid is a desktop idea: at 375px
     a 34px search icon marooned in the middle would squeeze the section name
     from both sides, so the icons group at the right edge as they did before
     and the identity cluster takes the space that is left. */
  .path-box{padding:12px 14px}
  body.world-learning .lcontent .path{padding:2px 7px}
  .ref-box{padding:12px 13px}
}
/* Print: mirror the lesson into a document with a repeating author header and
   footer (matching the migration guide's print template), and hide the live app. */
.print-doc{display:none}
@media print{
  /* force the light palette regardless of the active theme */
  :root,[data-theme="dark"]{
    --bg1:#ffffff; --bg2:#ffffff; --bg3:#ffffff; --card:#ffffff;
    --text1:#0f172a; --text2:#475569; --text3:#5d6b7e; --border:#e2e8f0;
    --code-chrome-ink:#94a3b8; --code-ink:#ba2142;
    --accent:#ba2142; --accent2:#951833; --accent-light:rgba(186,33,66,.10); --on-accent:#ffffff;
    --success:#10b981; --warning:#f59e0b; --info:#3b82f6;
    --success-ink:#0a6e50; --warning-ink:#8a5700; --info-ink:#1d4ed8; --danger-ink:#a4161a;
    /* The diagram palette has to be reset here too, and forgetting it was a
       real defect: every other token above was already being forced back to
       light, so a page printed from dark mode came out correct EXCEPT for
       its figures, which kept the dark palette. Dark inks are deliberately
       LIGHTER than their light counterparts, so on white paper they washed
       out to near-invisible while every box around them printed normally.
       Fifteen tokens, and all fifteen must be here. */
    --dia-a:#ba2142; --dia-a-soft:rgba(186,33,66,.10);  --dia-a-ink:#951833;
    --dia-b:#2563eb; --dia-b-soft:rgba(37,99,235,.10);  --dia-b-ink:#1d4ed8;
    --dia-c:#10b981; --dia-c-soft:rgba(16,185,129,.12); --dia-c-ink:#0a6e50;
    --dia-d:#f59e0b; --dia-d-soft:rgba(245,158,11,.14); --dia-d-ink:#8a5700;
    --dia-e:#7c3aed; --dia-e-soft:rgba(124,58,237,.10); --dia-e-ink:#5b21b6;
    /* The last two the dark theme touches. .lhead and .lmain are hidden in
       print, but the home map is not, and .hm-brand and .hm-tagline .tl-b
       read from these. Printed from dark, the monogram went white on white
       paper. The rule is now complete: every token the dark theme overrides
       is reset here, so nothing about the active theme can reach paper. */
    --logo-ink:#1E3A8A; --tagline-accent:#1E3A8A;
    /* TYP-01: the print step of the scale, in points. Without this the
       print document inherits the 30px screen title. Paper wants a tighter
       scale than a screen, but it still needs the hierarchy: before this,
       print rendered headings and body prose at the identical size. */
    --fs-title:19pt; --fs-h3:13.5pt; --fs-body:11pt; --fs-sm:9.5pt;
    --fs-label:9.5pt; --fs-meta:8.5pt; --fs-micro:8pt; --fs-code:8.5pt;
    /* Paper sets its own measure through the page margin, so the screen
       column must not apply or every printed line would stop short. */
    --column:none;
  }
  .wf-rail,.wf-nav-fab,.wf-nav-veil,.wf-tab{display:none !important}
  /* EAT-01 in print. The running header on every sheet already says who
     wrote this and where it came from, so the byline must not say it a
     second time: the name goes, and what stays is what the header does not
     carry, the credential and the dates. Without these rules the byline
     inherited the print link colour and rendered the name in crimson. */
  .byline{border-color:#bbb !important; margin-bottom:14pt !important; padding:6pt 0 !important}
  .byline .by-who{display:none !important}
  .byline .by-sep:first-of-type{display:none !important}
  .byline .by-cred,.byline .by-date,.byline .by-read{color:#4b5563 !important}
  .byline a[href]{color:#4b5563 !important; text-decoration:none !important}
  html{color-scheme:light !important;background:#fff !important}
  body{background:#fff !important}
  .print-doc tbody td{color:#1c2129}
  .print-doc .lupdated{font-size:8.5pt;color:#6b7280;margin:-4pt 0 10pt;padding-left:0}
  .print-doc .lupdated time{font-weight:600}
  *{-webkit-print-color-adjust:exact;print-color-adjust:exact}
  .site-head,.lsidebar,.loverlay,.lmain{display:none !important}
  .print-doc tbody td{padding:16px 0}
  .print-doc .lcrumb,.print-doc .lpn{display:none !important}
  .print-doc h1,.print-doc h2,.print-doc h3{break-after:avoid;page-break-after:avoid}
  /* CMP-12 must invert on paper. On screen the figure holds --diagram-min
     and the box scrolls; paper cannot scroll, so the same rules would crop
     every diagram at the page edge and silently lose the right-hand side of
     it. Print lets the figure scale down instead -- which is fine, because
     print resolution is several times the screen and small type survives it. */
  .print-doc .diagram-box,.print-doc .diagram-frame{overflow:visible !important}
  .print-doc .diagram-box > svg,.print-doc .diagram-frame > svg,.print-doc .diagram-content > svg{min-width:0 !important;max-width:100% !important}
  .print-doc .diagram-frame-mixed > div{min-width:0 !important}
  .print-doc .diagram-box > .diagram-title{position:static !important}
  .print-doc .info-box,.print-doc .warn-box,.print-doc .pro-tip,.print-doc .ref-box,.print-doc .path-box,.print-doc .dl-box,.print-doc .tbl-wrap,.print-doc .diagram-box,.print-doc .code-block,.print-doc .dothis,.print-doc .flow-horizontal{break-inside:avoid;page-break-inside:avoid;box-shadow:none !important}
  /* A download link cannot be clicked off paper, so it prints as an address
     somebody can type. Same reasoning as the ref-box rule below, but these
     hrefs are root-relative, so the host has to be written in. */
  .print-doc .dl-list a{display:block !important;background:transparent !important;border:none !important;padding:2px 0 !important}
  .print-doc .dl-name{color:#ba2142 !important}
  .print-doc .dl-list a[href]::after{content:" - amitdusane.com" attr(href);font-size:8pt;font-weight:400;color:#4b5563;word-break:break-all}
  .print-doc .dl-meta{display:none !important}
  .print-doc a[href]{color:#ba2142;text-decoration:none}
  /* The section list on a landing page is navigation, not prose links, and on
     screen it is set in --text1 inside a bordered card. The blanket rule above
     was repainting every one of those rows crimson on paper, so a printed
     landing page came out as a stack of red boxes that looks nothing like the
     page it was printed from. Scoped one level deeper so it wins on
     specificity rather than needing !important. */
  .print-doc .lsec-list a[href]{color:#1c2129}
  .print-doc .ref-box a[href]{color:#10b981}
  /* CMP-10. On paper a link is just coloured text, and the ref-box is the
     whole point of the section ending: it is the handoff to Experience League.
     Printed without its URL it points nowhere. Expanded for ref-box links
     only, because doing it to every inline link would bury the prose in
     parentheses. */
  /* On paper the ::after prints the URL itself, so the screen mark has to be
     undone completely: an 11px inline-block with a mask would clip that text to
     a square. Every property the screen rule sets is reset here, not just the
     content. */
  .print-doc .ref-box a[href^="http"]::after{content:" (" attr(href) ")";font-size:8pt;font-weight:400;color:#4b5563;word-break:break-all;
    display:inline;width:auto;height:auto;margin-left:0;vertical-align:baseline;background:none;-webkit-mask:none;mask:none}
  .print-doc .path{background:#CECBF6;color:#3C3489;padding:3px 9px;border-radius:4px;font-family:Consolas,monospace;font-size:13px}
  /* The path-box heading, printed from dark mode, came out #CECBF6 on white:
     a pale lavender wash where a heading should be. It is the same trap the
     diagram palette note above describes, and the reason it survived the
     token reset is that this colour is not a token -- [data-theme="dark"]
     .path-title sets the hex directly, so forcing --text* back to light does
     nothing for it.

     Why this one and not its neighbours: the dark rule for .path is written
     as [data-theme="dark"] body.world-learning .lcontent .path, and the print
     document copies the article into #printBody, which is NOT inside
     .lcontent -- so that rule stops matching and the chips print correctly.
     .path-title carries no such ancestor, matches anywhere, and followed the
     page onto paper. It is the only rule of its kind that does: the other
     five dark hardcoded colours all belong to chrome that is never copied.

     !important because the dark rule sits ~400 lines later in the file at
     equal specificity, and source order would otherwise decide it. */
  .print-doc .path-title{color:#534AB7 !important}
  .hm-arrow{display:none !important}
  .hm-row-mod,.hm-row-sec{display:none !important}
  .hm-tagline .tl-b{display:inline !important}
  .hm-tagline{font-size:15px !important}
  .welcome-icon{width:70px !important;height:70px !important}
  .welcome-title{font-size:24px !important}
  /* "Do this" on paper. The step number stays, because a walkthrough without
     its numbers is not a walkthrough, but the filled crimson disc becomes a
     thin ring with crimson figures: the same information at a fraction of the
     ink, and it survives a greyscale printer, which a white number knocked
     out of a solid fill does not. */
  .print-doc .dothis-steps > li::before{
    background:transparent !important; color:#ba2142 !important;
    border:1.5px solid #ba2142; line-height:23px;
  }
  .print-doc .dothis-steps > li::after{background:#e2e8f0}
  .print-doc .dothis{border:1px solid #cbd5e1}
  /* --bg3 is forced white on paper, so the two options lose the tint that
     separates them and the card prints as one block of running text. A
     hairline keeps a comparison looking like a comparison. */
  .print-doc .comp-item{border:1px solid #cbd5e1}

  /* TABLES ON PAPER.
     On screen a cell is separated from the one beside it by white space and
     nothing else -- the only rule drawn is border-bottom, so there are no
     vertical lines at all. That survives on screen because a reader can see
     the whole row at once and the column widths are generous. On paper it
     falls apart: the page is narrower, cells wrap onto two and three lines,
     and with no line between them the wrapped text of one column reads as a
     continuation of its neighbour. Which is exactly the report -- values
     mixing, no way to tell where a column ends.

     So paper gets a real grid. Every cell is boxed, the outer edge is drawn
     a shade darker so the table reads as one object, and the header keeps
     its tint. --border is #e2e8f0 on paper, which is too faint to separate
     anything at this size, so these are set in ink rather than in the token.

     EVERY selector here goes through .tbl-wrap, and that is not tidiness, it
     is the whole correctness of the block. .print-doc is itself a <table>:
     a layout table whose thead cell carries the running header, whose tfoot
     cell carries the footer, and whose single tbody cell holds the ENTIRE
     article. So `.print-doc td` is not "a table cell in the content", it is
     the page. Written that way this block drew a border around the whole
     sheet, boxed the header, and set the complete article to 8.5pt -- which
     is exactly the "print looks completely different" report, and none of it
     was anything to do with tables. */
  .print-doc .tbl-wrap{border:1px solid #94a3b8 !important;border-radius:0 !important;overflow:visible !important}
  .print-doc .tbl-wrap table{width:100% !important;border-collapse:collapse !important;font-size:8.5pt !important}
  .print-doc .tbl-wrap th,.print-doc .tbl-wrap td{
    border:1px solid #cbd5e1 !important;
    padding:5px 7px !important;
    font-size:8.5pt !important;
    vertical-align:top !important;
    overflow-wrap:break-word !important;
    word-break:normal !important;
  }
  /* Paper cannot scroll, so a token with no separator in it has to be allowed
     to break somewhere rather than push the column off the sheet. The <wbr>
     breaks the script inserts are preferred over this wherever they exist,
     so a dotted path still comes apart at its dots and this only catches what
     nothing else can. */
  .print-doc .tbl-wrap td{overflow-wrap:anywhere !important}
  .print-doc .tbl-wrap th{background:#f1f5f9 !important;border-color:#94a3b8 !important;color:#334155 !important}
  /* Column widths are deliberately left to the browser. table-layout:fixed
     was tried here and removed: it divides the width evenly, which flatters
     a four-column grid and cramps a two-column table whose first column is a
     key and whose second is a paragraph. The reported problem was cells
     running together, and that is the borders' job, not the layout's. */
  .print-doc .dothis-hdr{background:#f1f5f9}
}

/* ==========================================================================
   Content-component styles, ported from the source app's own stylesheet.
   Class names kept exactly as the source wrote them (pro-tip, cards, ref-box,
   etc.) so the lifted section bodies need zero rewriting, only the literal
   crimson values are recolored to teal; everything else (info/success/
   warning semantic colors) is left as the source intended.
   ========================================================================== */
.section{margin-bottom:8px}
/* Four to one: separate from what came before, bind to what follows. */
/* The section heading repeats 5 to 17 times down a page, so it sets the
   page's rhythm more than the title does. 550 rather than 600: still clearly
   a heading against 17px prose, with the weight stepped back one notch now
   that the prose around it is near-black and no longer needs a heavy heading
   to out-rank it. If this reads too light on a real page, 600 is the number
   to go back to -- it is the one judgement here that needs an eye, not a
   measurement. */
.subsec-title{font-size:var(--fs-h3);line-height:var(--lh-h3);font-weight:550;margin:var(--sp-xl) 0 var(--sp-sm);color:var(--text1)}
/* Reading text is the PRIMARY ink, not the secondary one.
   It ran on --text2 for a long time as a deliberate softening, and measured
   against the two sites Amit benchmarked on 21 Aug 2026 that softening is
   what made the page look faded rather than calm. Body prose contrast:
   Google Cloud 15.27:1, Experience League 15.91:1, us 7.24:1 -- roughly half.
   Grey ink on the light grey ground put the whole page in one narrow tonal
   band with nothing to anchor the eye. On --text1 it measures 17.06:1.
   This is not an accessibility fix; 7.24 already cleared AAA. It is a
   legibility and freshness one. --text2 stays correct for genuinely
   secondary text -- nav, byline, captions, meta -- which is most of its
   other 28 uses. */
body.world-learning .lcontent p{margin-bottom:var(--sp-md);color:var(--text1);font-size:var(--fs-body)}
body.world-learning .lcontent ul,body.world-learning .lcontent ol{margin:var(--sp-md) 0;padding-left:20px;color:var(--text1);font-size:var(--fs-body)}
body.world-learning .lcontent li{margin-bottom:var(--sp-xs);font-size:var(--fs-body)}
body.world-learning .lcontent li strong{color:var(--text1)}

.pro-tip{background:var(--accent-light);border:1px solid rgba(186,33,66,.25);border-radius:6px;padding:14px 16px;margin:var(--sp-lg) 0}
.pro-tip-hdr{display:flex;align-items:center;gap:10px;font-weight:700;color:var(--accent);margin-bottom:8px;font-size:var(--fs-label)}
/* Box prose is body-sized. Scoped so the token actually applies: the
   generic .lcontent p rule outranks a bare `.pro-tip p`, so before this
   these three declarations were dead and the 15px they asked for never
   reached the page. A warn-box is often the most important thing on the
   page, so it gets body size rather than a de-emphasised aside size. */
body.world-learning .lcontent .pro-tip p{margin:0;font-size:var(--fs-body)}
.info-box{background:rgba(59,130,246,.1);border:1px solid rgba(59,130,246,.2);border-radius:6px;padding:14px 16px;margin:var(--sp-lg) 0}
.info-hdr{display:flex;align-items:center;gap:10px;font-weight:700;color:var(--info-ink);margin-bottom:8px;font-size:var(--fs-label)}
body.world-learning .lcontent .info-box p{margin:0;font-size:var(--fs-body)}
.warn-box{background:rgba(245,158,11,.1);border:1px solid rgba(245,158,11,.2);border-radius:6px;padding:14px 16px;margin:var(--sp-lg) 0}
.warn-hdr{display:flex;align-items:center;gap:10px;font-weight:700;color:var(--warning-ink);margin-bottom:8px;font-size:var(--fs-label)}
body.world-learning .lcontent .warn-box p{margin:0;font-size:var(--fs-body)}

.code-block{background:#0d1117;border:1px solid var(--border);border-radius:6px;margin:var(--sp-lg) 0;overflow:hidden}
.code-hdr{display:flex;justify-content:space-between;padding:6px 12px;background:#161b22;border-bottom:1px solid var(--border)}
.code-lang{font-size:var(--fs-micro);color:var(--code-chrome-ink);text-transform:uppercase}
.code-copy{background:none;border:none;color:rgba(255,255,255,.55);cursor:pointer;font-size:var(--fs-micro)}
.code-copy:hover{color:#fff}
.code-body{padding:12px;overflow-x:auto}
.code-body pre{font-family:Consolas,monospace;font-size:var(--fs-code);line-height:1.5;color:#e6edf3;margin:0}
body.world-learning .lcontent code{font-family:Consolas,monospace;font-size:var(--fs-code);background:var(--bg3);padding:2px 5px;border-radius:3px;color:var(--code-ink)}

/* ==========================================================================
   "Do this" -- the light instruction panel, 21 August 2026

   The code block above was carrying three different jobs and only one of
   them was code. Measured across the corpus: 33 blocks are genuinely code,
   19 are hands-on walkthroughs and 63 are transcripts of an Adobe settings
   screen. 86% of all the text inside a near-black block was not code. On
   Marketing Channel Processing Rules every one of the ten blocks was a rule
   configuration, and together they took up 19% of the page's height as dark
   slabs. A reader's eye reads "dark = code = skippable" and slides past --
   which is fatal here, because CLAUDE.md calls the walkthrough the engine of
   a section, the thing that earns the second visit.

   So real code keeps the terminal. Everything the reader is meant to DO
   moves here: a light panel that reads as a recipe rather than as output.

   One component, two body shapes, because Amit's ruling was that these are
   the same family and should not become two things to learn:
     .dothis-steps   a sequence, where order matters   (the 19 walkthroughs)
     .dothis-fields  a finished setup to copy           (the 63 transcripts)

   No new hue. The semantic vocabulary is full -- amber warns, blue informs,
   crimson tips, green points at Adobe, violet is a screen path -- so the
   "you are doing something" signal comes from STRUCTURE: the numbered rail.
   Nothing else on the site looks like it. The only colour spent is the
   accent on the step numbers, which is a job COL-02 had already assigned to
   it and never delivered.
   ========================================================================== */
.dothis{background:var(--card);border:1px solid var(--border);border-radius:8px;margin:var(--sp-lg) 0;overflow:hidden}
.dothis-hdr{display:flex;align-items:baseline;gap:9px;padding:11px 16px;background:var(--bg3);border-bottom:1px solid var(--border);font-weight:700;font-size:var(--fs-label);color:var(--text1)}
.dothis-hdr b{color:var(--accent);flex:none}
/* The one case where the component's own header would contradict its content:
   a rule shown so the reader does NOT build it. The panel stays structurally
   identical, because it is still a rule configuration and belongs beside the
   four correct ones -- only the kicker changes, and it is the loudest element
   in the panel, so that is enough. Amber is already the site's warning hue,
   so this spends no new colour. --warning-ink rather than the bare --warning,
   which is not dark enough to carry text: warn-hdr measured 1.91:1 before the
   -ink tokens existed. */
.dothis--avoid .dothis-hdr b{color:var(--warning-ink)}
/* TWO LAYERS, and the split is load-bearing rather than tidiness.
   Everything structural is declared BARE so it applies wherever the panel
   renders. The `.lcontent`-scoped rules below re-assert only the handful of
   properties that `body.world-learning .lcontent p` and `... li` would
   otherwise win at (0,2,2) -- CLAUDE.md trap 7.

   The print document is why this matters. It is a separate
   `<table class="print-doc">` with content cloned into `#printBody`, and it
   is NOT inside `.lcontent`. The first version of this component scoped its
   structure, so on paper `list-style:none` vanished and the browser numbered
   every li including the group headings, `counter-increment` vanished so the
   counter read 0, and `position:relative` vanished so the absolutely
   positioned step circle escaped its list item and landed on the masthead.
   The bare `::before` and `::after` kept rendering throughout, which is what
   put an orphan crimson "0" over the logo and a stray rule down the page.
   Scope a component's LOOK, never its STRUCTURE. */
.dothis-setup{margin:0;padding:10px 16px;background:var(--bg1);border-bottom:1px solid var(--border);font-size:var(--fs-sm);color:var(--text2)}
.dothis-end{margin:0;padding:11px 16px;background:var(--bg1);border-top:1px solid var(--border);font-size:var(--fs-sm);color:var(--text2)}
body.world-learning .lcontent p.dothis-setup{margin:0;padding:10px 16px;font-size:var(--fs-sm);color:var(--text2)}
body.world-learning .lcontent p.dothis-end{margin:0;padding:11px 16px;font-size:var(--fs-sm);color:var(--text2)}

/* ---- shape one: an ordered sequence ---- */
.dothis-steps{list-style:none;margin:0;padding:16px 16px 4px 0;counter-reset:ds;color:var(--text1)}
.dothis-steps > li{position:relative;padding-left:48px;margin-bottom:16px;counter-increment:ds;font-size:var(--fs-body)}
body.world-learning .lcontent ol.dothis-steps{margin:0;padding:16px 16px 4px 0;color:var(--text1)}
body.world-learning .lcontent ol.dothis-steps > li{margin-bottom:16px;font-size:var(--fs-body)}
.dothis-steps > li::before{content:counter(ds);position:absolute;left:10px;top:1px;width:26px;height:26px;border-radius:50%;background:var(--accent);color:var(--on-accent);font-weight:700;font-size:13px;line-height:26px;text-align:center}
/* The connector is what turns nine boxes into one journey. It stops at the
   last step and at a group break, so the grouping stays legible. */
.dothis-steps > li::after{content:"";position:absolute;left:22px;top:31px;bottom:-16px;width:2px;background:var(--border)}
.dothis-steps > li:last-child::after{display:none}
.dothis-steps > li.ds-group{counter-increment:none;margin:22px 0 12px;padding-left:48px;font-size:var(--fs-sm);font-weight:600;color:var(--text2)}
body.world-learning .lcontent ol.dothis-steps > li.ds-group{margin:22px 0 12px;font-size:var(--fs-sm);font-weight:600;color:var(--text2)}
.dothis-steps > li.ds-group::before,.dothis-steps > li.ds-group::after{display:none;content:none}
.dothis-steps > li.ds-group:first-child{margin-top:0}
/* Action and outcome are separated deliberately. Running them together is
   what made the old block hard to follow: you could not see where the doing
   stopped and the checking began. */
.ds-do{display:block;color:var(--text1);line-height:var(--lh-body)}
.ds-see{display:block;margin-top:6px;padding-left:11px;border-left:2px solid var(--border);color:var(--text2);font-size:var(--fs-sm);line-height:1.55}

/* ---- shape two: a finished setup, as a properties panel ---- */
.dothis-fields{margin:0;padding:4px 16px 10px}
.dothis-fields > div{display:grid;grid-template-columns:minmax(150px,36%) 1fr;gap:12px;padding:9px 0;border-bottom:1px dashed var(--border)}
.dothis-fields > div:last-child{border-bottom:none}
.dothis-fields dt{color:var(--text2);font-size:var(--fs-sm);line-height:1.5}
.dothis-fields dd{margin:0;color:var(--text1);font-size:var(--fs-sm);line-height:1.5}
.dothis-fields ul{margin:0;padding-left:16px}
/* An ordered list inside a field, for the one case where a finished setup
   still contains something sequential: a rule's actions run top to bottom,
   and reversing them sends an empty hit. The panel is a snapshot, but that
   ordering is part of what is being copied. */
.dothis-fields ol{margin:0;padding-left:18px}
.dothis-fields li{font-size:var(--fs-sm);margin-bottom:2px}
body.world-learning .lcontent .dothis-fields dd{margin:0;color:var(--text1);font-size:var(--fs-sm);line-height:1.5}
body.world-learning .lcontent .dothis-fields ul{margin:0;padding-left:16px;color:var(--text1)}
body.world-learning .lcontent .dothis-fields ol{margin:0;padding-left:18px;color:var(--text1)}
body.world-learning .lcontent .dothis-fields li{font-size:var(--fs-sm);margin-bottom:2px}

/* Monospace is a spice here, not the medium: exact values and UI labels only,
   inside a sentence that is set in the reading font at reading size. */
/* overflow-wrap:anywhere because these carry landing URLs and query strings.
   The longest one today happens to fit at 390px; the next one added would not,
   and an overflowing chip pushes the whole grid item wide. */
.dothis code{background:var(--bg3);border:1px solid var(--border);border-radius:4px;padding:1px 6px;font-size:.88em;color:var(--text1);overflow-wrap:anywhere}
body.world-learning .lcontent .dothis code{background:var(--bg3);border:1px solid var(--border);border-radius:4px;padding:1px 6px;font-size:.88em;color:var(--text1);overflow-wrap:anywhere}
.dothis .ds-sep{color:var(--text3);padding:0 2px}

@media(max-width:880px){
  .dothis-fields > div{grid-template-columns:1fr;gap:3px}
  body.world-learning .lcontent ol.dothis-steps > li,
  body.world-learning .lcontent ol.dothis-steps > li.ds-group{padding-left:40px}
  .dothis-steps > li::before{left:6px;width:24px;height:24px;line-height:24px;font-size:12px}
  .dothis-steps > li::after{left:17px}
}

.tbl-wrap{overflow-x:auto;margin:var(--sp-lg) 0;border:1px solid var(--border);border-radius:6px}
/* Inserted by world-learning.js, and only on the tables that still overflow
   at the bottom of the fitting ladder. A phone cuts a wide table off at the
   right edge with no scrollbar showing until the reader already drags it, so
   it reads as broken rather than scrollable. The rule below is a second guard:
   even if the class survived a resize to desktop, it would not show there. */
.tbl-hint{display:none}
@media(max-width:880px){
  body.world-learning .lcontent p.tbl-hint{
    display:flex; align-items:center; gap:6px;
    margin:var(--sp-lg) 0 calc(var(--sp-xs) * -1);
    font-size:var(--fs-micro); color:var(--text3); line-height:1.4;
  }
  body.world-learning .lcontent p.tbl-hint::before{
    content:""; flex:none; width:13px; height:13px;
    background:no-repeat center/13px url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%235d6b7e%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M5%2012h14%22%2F%3E%3Cpath%20d%3D%22M13%206l6%206-6%206%22%2F%3E%3C%2Fsvg%3E");
  }
  .tbl-hint + .tbl-wrap{margin-top:var(--sp-sm)}
}
@media print{ .tbl-hint{display:none !important} }
body.world-learning .lcontent table{width:100%;border-collapse:collapse;font-size:var(--fs-sm)}
body.world-learning .lcontent th,body.world-learning .lcontent td{padding:10px 12px;text-align:left;border-bottom:1px solid var(--border)}
body.world-learning .lcontent th{background:var(--bg3);font-weight:600;font-size:var(--fs-meta);color:var(--text2);text-transform:uppercase;letter-spacing:var(--label-ls)}
/* Table cells are reading text too, so they follow the prose ink above. The
   uppercase th stays on --text2: it is a label, not something anyone reads
   a sentence of. */
body.world-learning .lcontent td{color:var(--text1)}
body.world-learning .lcontent tr:last-child td{border-bottom:none}

.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px;margin:var(--sp-lg) 0}
.card{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:16px}
.card:hover{border-color:var(--accent)}
a.card{display:block;color:inherit}
a.card:hover{border-color:var(--accent)}
.section a.card,.section a.card:hover,.section a.card:focus{text-decoration:none}
.section a.card .card-title,.section a.card .card-desc{text-decoration:none}
a.card .card-title{color:var(--text1)}
a.card .card-desc{color:var(--text2)}
.card-icon{width:36px;height:36px;background:var(--accent-light);border-radius:8px;display:flex;align-items:center;justify-content:center;margin-bottom:10px;font-size:16px}
.card-title{font-size:var(--fs-body);font-weight:600;margin-bottom:6px;color:var(--text1)}
.card-desc{font-size:var(--fs-meta);color:var(--text2)}

.flow{display:flex;align-items:center;justify-content:center;gap:8px;flex-wrap:wrap;padding:20px;background:var(--card);border:1px solid var(--border);border-radius:6px;margin:16px 0}
.flow-step{background:var(--bg3);padding:12px 20px;border-radius:8px;font-size:15px;font-weight:600}
.flow-step.highlight{background:var(--accent-light);border:1px solid rgba(186,33,66,.35);color:var(--accent)}
.flow-arrow{color:var(--accent);font-size:22px;font-weight:700}
.flow-vertical{display:flex;flex-direction:column;align-items:center;gap:4px;text-align:center}
.flow-arrow-down{color:var(--accent);font-size:18px}
.flow-dest{background:var(--accent-light);color:var(--accent);padding:8px 16px;border-radius:4px;font-size:12px;font-weight:500}
.flow-horizontal{display:flex;align-items:center;justify-content:center;gap:14px;flex-wrap:wrap}

.ref-box{background:rgba(16,185,129,.08);border:1px solid rgba(16,185,129,.2);border-radius:6px;padding:14px 16px;margin:var(--sp-lg) 0}
.ref-title{display:flex;align-items:center;gap:10px;font-weight:700;color:var(--success-ink);margin-bottom:10px;font-size:var(--fs-label)}
.ref-box ul{margin:0;padding-left:18px}
body.world-learning .lcontent .ref-box li{margin-bottom:4px;font-size:var(--fs-sm)}
.ref-box a{color:var(--success);text-decoration:none}
.ref-box a:hover{text-decoration:underline}
/* THE MARK THAT SAYS THIS LEAVES THE SITE. Every one of these links carries
   target="_blank", which on a desktop at least opens a visible second tab. On a
   phone there is no tab strip, so leaving amitdusane.com produced no signal of
   any kind -- and Adobe's pages read as a continuation of ours, so testers went
   on believing they were still here and started looking for the map. The link
   text already begins "Adobe Docs:" and that was not enough, because naming the
   publisher is not the same as saying you are about to travel.

   Drawn rather than a typed glyph, for the reason recorded on the other arrows
   on this site: an arrow character carries its font's own metrics and sits off
   the optical centre. currentColor so it follows the link into dark. */
.ref-box a[href^="http"]::after{
  content:""; display:inline-block; width:11px; height:11px;
  margin-left:5px; vertical-align:-1px; background:currentColor;
  -webkit-mask:var(--extlink) center/11px 11px no-repeat;
          mask:var(--extlink) center/11px 11px no-repeat;
  --extlink:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M14 5h5v5M19 5l-8 8M17 14v4a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h4"/></svg>');
}

/* ==========================================================================
   Module downloads, 30 August 2026. Two surfaces, one config block.

   .dl-box below is the full statement, authored once in the module's first
   section. These two are generated on every section of the module and are
   never authored, for the reason Amit gave: on a module whose payload is a
   set of files, a download control buried mid-article is only found by a
   reader who was already scrolling for something else.

   .mdocs-strip sits under the byline, above the first word of prose.
   .mdocs-card is lifted into the top of the in-page spine by
   world-learning.js, which makes it sticky on desktop and one tap from the
   floating button below 1244px. That column was chosen over a fourth
   floating control because it already solves exactly this problem for the
   path-box jump and Ask Amit, and because the order of loudness -- pocket
   map, page nav, identity -- is deliberate and should not gain a fourth
   voice.
   ========================================================================== */
.mdocs-strip{
  display:flex; flex-wrap:wrap; align-items:center; gap:5px 9px;
  margin:0 0 var(--sp-lg); padding:9px 13px;
  background:var(--bg3); border:1px solid var(--border); border-radius:7px;
}
body.world-learning .lcontent p.mdocs-strip{font-size:var(--fs-meta); line-height:1.5}
.mdocs-strip-t{color:var(--text2); font-weight:600}
.mdocs-strip a{color:var(--accent); font-weight:600; text-decoration:none}
.mdocs-strip a:hover{text-decoration:underline}
.mdocs-sep{color:var(--text3)}
/* The tile is .dl-ico at two thirds size. Same glyph, same fill, so the strip,
   the spine card and the full box read as one object in three places. */
.mdocs-ico{
  flex:none; width:20px; height:20px; border-radius:5px;
  background-color:#ba2142; background-repeat:no-repeat; background-position:center; background-size:12px;
  background-image:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23ffffff%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M21%2015v4a2%202%200%200%201-2%202H5a2%202%200%200%201-2-2v-4%22%2F%3E%3Cpath%20d%3D%22M7%2010l5%205%205-5%22%2F%3E%3Cpath%20d%3D%22M12%2015V3%22%2F%3E%3C%2Fsvg%3E");
}
.mdocs-card{
  margin:0 0 20px; padding:11px 12px;
  background:var(--card); border:1px solid var(--accent); border-radius:8px;
}
.mdocs-card-h{
  display:flex; align-items:center; gap:8px; margin:0 0 8px;
  font-size:var(--fs-micro); font-weight:700; letter-spacing:.04em; color:var(--text1);
}
.mdocs-card ul{list-style:none; margin:0; padding:0}
/* The card lives inside .wf-rail, so `.wf-rail li` and `.wf-rail a::before`
   both reach it and it arrived numbered 01 to 05 like a heading list. Those
   numbers belong to the spine, where they mean position on the page; on a
   list of five files they mean nothing and cost a third of the card's width.
   Scoped through .wf-rail so this beats the spine rules on specificity rather
   than on source order, which is the trap two doors down. */
.wf-rail .mdocs-card li{margin:0; counter-increment:none}
.wf-rail .mdocs-card a{
  display:block; gap:0; padding:4px 0; text-decoration:none;
  font-size:var(--fs-meta); font-weight:600; color:var(--accent); line-height:1.35;
}
.wf-rail .mdocs-card a::before{content:none; display:none}
.wf-rail .mdocs-card a:hover{color:var(--accent); background:none; text-decoration:underline}

/* ==========================================================================
   .dl-box, 31 August 2026. The site's first downloadable asset.

   Rule 1 of the rulebook allows a site-level component added once, wired
   into the layout and recorded. This is the second, after .lupdated. It
   exists because Module 19 is the one place where the artifact IS the
   payload: a reader who leaves with a working SDR workbook has something no
   amount of prose replaces.

   It is deliberately NOT a tinted alert box. Every tinted box on this site
   says something about the prose beside it -- warn, info, tip, and the
   ref-box handing the reader to Adobe. This one hands the reader an object,
   so it is built on the neutral card ground that .flow already uses, and
   the accent appears only on the icon tile and the file names. Crimson was
   available as a tint and rejected: .pro-tip already owns crimson, and two
   crimson boxes on one page stop meaning two different things.

   Both list rules are scoped through .lcontent because
   `body.world-learning .lcontent li` is (0,2,2) and beats a bare `.dl-list
   li` outright. Trap 7. The anchor is display:flex on a block li, which is
   what lets the name and the meta line sit apart without a nested grid. */
.dl-box{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:18px 20px;margin:var(--sp-xl) 0}
.dl-title{display:flex;align-items:center;gap:10px;font-weight:700;color:var(--text1);margin-bottom:10px;font-size:var(--fs-label)}
.dl-ico{background-color:#ba2142;background-image:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23ffffff%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M21%2015v4a2%202%200%200%201-2%202H5a2%202%200%200%201-2-2v-4%22%2F%3E%3Cpath%20d%3D%22M7%2010l5%205%205-5%22%2F%3E%3Cpath%20d%3D%22M12%2015V3%22%2F%3E%3C%2Fsvg%3E")}
body.world-learning .lcontent .dl-box p{font-size:var(--fs-sm);color:var(--text2);margin:0 0 14px}
.dl-list{list-style:none;margin:0;padding:0}
body.world-learning .lcontent .dl-list li{margin:0 0 8px;font-size:var(--fs-sm)}
body.world-learning .lcontent .dl-list li:last-child{margin-bottom:0}
body.world-learning .lcontent .dl-list a{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 12px;text-decoration:none;background:var(--bg3);border:1px solid var(--border);border-radius:6px;padding:11px 14px;transition:border-color .15s,background .15s}
body.world-learning .lcontent .dl-list a:hover{border-color:var(--accent);background:var(--accent-light)}
.dl-name{font-weight:600;color:var(--accent)}
.dl-meta{color:var(--text3);font-size:var(--fs-meta);margin-left:auto}

.path-box{background:rgba(167,139,250,.12);border:1px solid rgba(167,139,250,.35);border-radius:8px;padding:16px 18px;margin:var(--sp-lg) 0}
.path-title{display:flex;align-items:center;gap:10px;font-weight:700;color:#534AB7;margin-bottom:10px;font-size:var(--fs-label)}
.path-ico{flex:none;width:30px;height:30px;border-radius:7px;background:#534AB7 url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23ffffff%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M9%2011a3%203%200%201%200%206%200a3%203%200%200%200%20-6%200%22%2F%3E%3Cpath%20d%3D%22M17.657%2016.657l-4.243%204.243a2%202%200%200%201%20-2.827%200l-4.244%20-4.243a8%208%200%201%201%2011.314%200z%22%2F%3E%3C%2Fsvg%3E") center/17px no-repeat}

/* ==========================================================================
   Component icons, 21 August 2026: four tiles replacing four emoji.

   These are not a new design. They are .path-ico above, applied four more
   times -- Amit picked that one out as the icon on the site that works, and
   the reason it works is that it is DRAWN: a 30px tile carrying a white line
   glyph at the same 24-unit viewBox, stroke 2, round caps that every chrome
   icon on the site already uses. The four emoji it replaces were the only
   things not speaking that language, and because the operating system drew
   them they were a different picture on Windows, Mac and Android, in colours
   the site never chose.

   Fills are fixed in both themes rather than following the -ink tokens,
   exactly as .path-ico's violet is. Following the token would flip the amber
   to #fbbf24 in dark and strand a white glyph on a light ground. Measured on
   the fill: white sits between 5.9:1 and 6.7:1 on all four, which is what
   decides whether the glyph is legible. Tile-against-card is 2.3 to 3.0 in
   dark, and that is not the binding constraint -- the label text beside it
   carries the meaning, and .path-ico itself measures 2.15 there and has been
   accepted since August.

   Retires a documented trap with them: the warning emoji depended on the
   U+FE0F variation selector, and without it some platforms rendered a
   monochrome glyph. A tile has no such dependency. */
.warn-ico,.info-ico,.tip-ico,.ref-ico,.dl-ico{flex:none;width:30px;height:30px;border-radius:7px;background-repeat:no-repeat;background-position:center;background-size:17px}
.warn-ico{background-color:#8a5700;background-image:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23ffffff%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M10.36%203.59%202.26%2017.12A1.91%201.91%200%200%200%203.9%2020h16.2a1.91%201.91%200%200%200%201.64-2.88L13.63%203.6a1.91%201.91%200%200%200-3.27%200z%22%2F%3E%3Cpath%20d%3D%22M12%209v4%22%2F%3E%3Cpath%20d%3D%22M12%2017h.01%22%2F%3E%3C%2Fsvg%3E")}
.info-ico{background-color:#1d4ed8;background-image:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23ffffff%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Ccircle%20cx%3D%2212%22%20cy%3D%2212%22%20r%3D%229%22%2F%3E%3Cpath%20d%3D%22M12%208h.01%22%2F%3E%3Cpath%20d%3D%22M11%2012h1v4h1%22%2F%3E%3C%2Fsvg%3E")}
.tip-ico{background-color:#ba2142;background-image:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23ffffff%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Ccircle%20cx%3D%2212%22%20cy%3D%2210%22%20r%3D%225.2%22%2F%3E%3Cpath%20d%3D%22M9.8%2017h4.4%22%2F%3E%3Cpath%20d%3D%22M10.6%2019.6h2.8%22%2F%3E%3C%2Fsvg%3E")}
.ref-ico{background-color:#0a6e50;background-image:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23ffffff%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M3%2018.5a8%208%200%200%201%209%200%208%208%200%200%201%209%200%22%2F%3E%3Cpath%20d%3D%22M3%206.5a8%208%200%200%201%209%200%208%208%200%200%201%209%200%22%2F%3E%3Cpath%20d%3D%22M3%206.5v12%22%2F%3E%3Cpath%20d%3D%22M12%206.5v12%22%2F%3E%3Cpath%20d%3D%22M21%206.5v12%22%2F%3E%3C%2Fsvg%3E")}

/* Mobile sizing sits HERE, after the base rules, not up in the 880px block
   where it used to live. It was written at line 675 against base rules that
   land around 1010, and media queries add no specificity -- so the later
   30px simply won and the override had never once applied. .path-ico
   carried the same dead rule since August: it measured 30px at 390 while
   the stylesheet claimed 26px. Trap 7, second form, in the wild. */
@media(max-width:880px){
  .path-ico,.warn-ico,.info-ico,.tip-ico,.ref-ico,.dl-ico{width:26px;height:26px;background-size:15px}
  .dl-box{padding:14px 15px}
  body.world-learning .lcontent .dl-list a{padding:10px 12px}
  /* Table fitting moved to world-learning.js, which breaks long identifiers
     at their separators and only then steps the type down, per table and
     against a floor. This block briefly carried `overflow-wrap:anywhere`
     instead: it made every table fit, but by breaking words mid-syllable
     wherever the line happened to run out, which reads badly. Nothing to
     declare here now -- the fallback when the script does not run is the
     .tbl-wrap scroller, which is what it has always been. */
}
body.world-learning .lcontent .path-box p{margin:5px 0;font-size:var(--fs-sm)}
body.world-learning .lcontent .path{background:#CECBF6;padding:3px 9px;border-radius:4px;font-family:Consolas,monospace;font-size:var(--fs-code);color:#3C3489}
[data-theme="dark"] .lupdated{color:var(--text3)}
[data-theme="dark"] .path-title{color:#CECBF6}
[data-theme="dark"] body.world-learning .lcontent .path{background:#3C3489;color:#E6E4FB}

/* ---------------------------------------------------------------------------
   CMP-01 / CMP-02 / CMP-12: the diagram stops looking like an alert box.

   136 diagram blocks are the one thing on this site that nothing else in the
   Adobe Analytics space has. They were presented in a 1px bordered card with
   a 13.5px bold title -- the same weight as a warn-box, quieter than a
   pro-tip. The differentiator was the least considered object on the page.

   The distinction here is deliberately STRUCTURAL, not coloured. Every other
   box on a section is a wash of its semantic colour; a figure that is a plain
   plate with air around it and a ruled caption is already unlike all of them,
   and it stays unlike them in print and in dark. It also leaves the accent
   free for the job list in COL-02 rather than spending it here.

   CMP-12, measured at 390px before this change: an inline SVG scales with its
   frame, so a 700-unit viewBox in a 276px box renders at 0.394 and authored
   11px text lands at 4.3px -- against 16.5px body prose. 61 of the 65 inline
   SVGs use a 700 viewBox, so the floor is unambiguous: below the tablet
   breakpoint the figure holds --diagram-min and the BOX scrolls, never the
   page. The caption is sticky so it stays with the figure while it scrolls.
   The 69 HTML/CSS layouts are untouched -- they are real DOM text, they
   reflow, and they were never the problem.
   --------------------------------------------------------------------------- */
/* 24 Aug 2026: the figure's chrome was rebuilt, matching what screenshots got
   on 21 Aug. It arrived in three passes and the end state is what matters.

   The padding is gone. A figure used to sit in a card with its own
   background, border, radius and roughly 60px of padding, with the caption
   fenced off by a second rule underneath it -- decoration competing with the
   drawing, and a good part of the column spent on framing. Every figure now
   gets the full column width.

   The ground and a hairline edge stayed. Stripping the card outright dropped
   the drawing onto --bg1, the page's grey, which read as a hole rather than
   as a surface.

   The edge is an OUTLINE, not a border, and that is the point of it: an
   outline is painted outside the box model and takes part in no layout at
   all, so the figure keeps every pixel. A 1px border would have quietly taken
   2px of content width off each of the 131 figures. outline-offset:-1px pulls
   it just inside the edge so it reads as a border, and it follows the radius.

   And the chrome sits on the DRAWING, not on the box, so the caption stays
   outside the frame exactly as .shot-title sits outside .shot-frame. On the
   box it enclosed the caption too, which fenced a heading inside a panel and
   read as a title bar rather than as a figure caption.

   One consequence to know: 137 shapes across 43 sections fill with
   var(--card), the same value this surface uses, so their interiors are
   invisible and they read by their strokes alone. That is the long-standing
   look; the alternative was the grey ground, and --bg2 resolves to the same
   value in both themes, so there is no third option. */
/* The figure hint. Scoped through .lcontent because `body.world-learning
   .lcontent p` is (0,2,2) and a bare .fig-hint at (0,1,0) loses to it, which
   is trap 7 and would have rendered this at body size. The media query sits
   after the base rule for the same reason in its second form: equal
   specificity, source order decides. */
body.world-learning .lcontent p.fig-hint{display:none}
@media(max-width:880px){
  body.world-learning .lcontent p.fig-hint{
    display:block;margin:0 0 var(--sp-xs);
    font-size:var(--fs-micro);color:var(--text3);line-height:1.45;
  }
  body.world-learning .lcontent p.fig-hint[hidden]{display:none}
}

.diagram-box{margin:var(--sp-xl) 0}
/* .diagram-frame is JS-inserted around every drawing, mirroring .shot-frame.
   It takes the chrome and provides the positioning context the zoom control
   needs.

   Wrapping has a consequence that is easy to miss and cost a measurement to
   find: it breaks every `.diagram-box > svg` and `.diagram-box:has(> svg)`
   selector below, because the svg is no longer a direct child. Those rules
   are CMP-12 -- the --diagram-min floor and the scroller that goes with it --
   so the frame has to take that role over or the floor silently disappears.
   Measured on M07 s5 at 1280px before this line existed: the figure rendered
   at 614px rather than 700, scaling authored 11px text to 9.6px. The
   .diagram-box rules below are kept as the no-JS fallback, where the svg is
   still a direct child and they still apply. */
.diagram-frame{position:relative;overflow-x:auto;overscroll-behavior-x:contain}
.diagram-frame > svg{min-width:var(--diagram-min)}
/* Set by the wrapper when the drawing is not an svg alone. One figure in the
   corpus is mixed -- M02 s2 pairs an svg timeline with three HTML bars -- and
   its labels rendered flush against the outline without this. */
.diagram-frame-mixed{padding:var(--sp-sm)}
/* The svg holds --diagram-min and the frame scrolls to it, but a block child
   sizes to the frame's CONTENT box rather than to an overflowing sibling, so
   the bars stopped 110px short of the timeline they annotate and the figure
   came apart as soon as the reader scrolled. One figure scrolls as one thing. */
.diagram-frame-mixed > div{min-width:var(--diagram-min)}
.diagram-frame,
.diagram-box > svg,
.diagram-box > .diagram-content{
  background:var(--card);
  border-radius:4px;
  outline:1px solid var(--border);
  outline-offset:-1px;
}
.diagram-title{font-weight:700;color:var(--text1);font-size:var(--fs-label)}
body.world-learning .lcontent .diagram-title{margin:0 0 var(--sp-md)}
/* margin:auto centres when there is room and does NOT clip when there is not,
   which justify-content:center does. */
.diagram-content{display:flex;overflow-x:auto;overscroll-behavior-x:contain}
.diagram-content > *{margin-inline:auto}
/* CMP-12 applies at EVERY width, not just on a phone. Measured at 1280px:
   the three-column shell leaves the article about 574px, so a 700-unit
   viewBox rendered at 0.82 and authored text landed at 8.2px -- worse than
   the phone once the phone was fixed. It is a narrow-COLUMN problem, and
   the reading column is narrow on a laptop too. The box scrolls whenever
   the figure does not fit; on a wide screen it never has to. */
.diagram-box:has(> svg){overflow-x:auto;overscroll-behavior-x:contain}
.diagram-box > svg,.diagram-content > svg{min-width:var(--diagram-min)}
.diagram-box:has(> svg) > .diagram-title{position:sticky;left:0}

/* Diagram zoom, on every drawing in the world. The control is
   DESKTOP-INVISIBLE by default and switched on only inside the mobile query
   below, because on a wide screen the figure fits and the scroll never has
   to happen. */
.diagram-zoom-btn{display:none}
/* This query has to sit AFTER the display:none above, not up in the main 880
   block, because the two rules carry identical specificity and source order
   decides. Written the other way round it silently did nothing: trap 7's
   second form, and it caught this change too. */
@media(max-width:880px){
  /* On a phone a zoomable figure FITS, and the button is what makes that
     affordable. CMP-12 holds --diagram-min everywhere else because a figure
     that cannot be read and cannot be enlarged is worse than one that
     scrolls; once there is a way to open it at authored size, shrinking to
     the column is the better trade. Horizontal scrolling inside a page is
     awkward, easy to miss, and left roughly 390px of every figure silently
     off-screen. The reader now sees the whole shape, and taps to read it. */
  .diagram-box{overflow-x:hidden}
  .diagram-frame{overflow-x:hidden}
  .diagram-frame > svg{min-width:0;width:100%}
  .diagram-frame-mixed > div{min-width:0}
  /* Same corner, same size, same everything as .shot-zoom-btn. The control
     used to sit in the caption row because .diagram-box is a scroller and an
     absolutely positioned child scrolled away with the figure; wrapped in
     .diagram-frame, which does not scroll on a phone, that constraint is gone
     and the two components can finally agree. */
  .diagram-zoom-btn{
    position:absolute;top:8px;right:8px;
    display:flex;align-items:center;justify-content:center;
    width:30px;height:30px;padding:0;font-size:15px;line-height:1;
    border:1px solid var(--border);border-radius:6px;
    background:var(--card);color:var(--text2);cursor:pointer;
  }
  .diagram-zoom-btn[hidden]{display:none}
  /* Code wraps rather than scrolling. Measured across all 502 code lines in
     the learning world: 70% already fit a phone, 30% pass 45 characters and
     the longest in the corpus is 85, so the worst case is two lines. Shrinking
     the type would have penalised every line to rescue under a third of them,
     and horizontal scrolling makes code awkward to select and copy. */
  .code-block .code-body > pre{
    white-space:pre-wrap;
    overflow-wrap:break-word;
    overflow-x:visible;
  }
}

.author-quote{background:var(--accent-light);border-left:3px solid var(--accent);padding:16px 20px;margin:20px 0;border-radius:0 6px 6px 0}
.author-quote p{font-size:14px;color:var(--text1);font-style:italic;margin:0}

/* The glossary carries no monogram of its own. It had one, first stacked as a
   58px block above the breadcrumb and then inline with the title, and both were
   wrong: stacked it pushed the first term off a phone screen, and inline it
   dragged the title text off the left edge every paragraph below it aligns to,
   and the print sheet clipped it against the running header. The heading now
   sits as a plain direct child of .lcontent, which is what restores both the
   hanging indent and the normal spacing beneath it.
   The opening line needs its own top margin because .lsec-title leaves only
   12px, which is sized for a lesson page where the byline sits in the gap.
   The scoped form is required: body.world-learning .lcontent p is (0,2,2) and
   beats a bare .gl-intro. */
body.world-learning .lcontent p.gl-intro{margin-top:var(--sp-md)}

/* Glossary. The A to Z strip is a sticky nav pinned directly under the fixed
   header, so a reader two hundred terms down can change letter without
   scrolling back to the top. It is anchors and CSS only, with no JavaScript:
   the ids are authored on the letter headings, because the build-time anchor
   injection in lesson/single.html does not run on this type.
   Letters holding no terms stay in the strip as dimmed spans rather than being
   dropped, so it reads as a complete alphabet with gaps in it instead of an
   arbitrary list of letters. */
.gl-az{
  position:sticky; top:var(--header-h); z-index:5;
  display:flex; flex-wrap:wrap; gap:2px;
  margin:0 0 var(--sp-lg); padding:8px 0;
  background:var(--bg1); border-bottom:1px solid var(--border);
}
.gl-az a,.gl-az .gl-az-off{
  flex:1 1 auto; min-width:25px; text-align:center;
  padding:6px 0; border-radius:4px;
  font-size:var(--fs-label); font-weight:600; line-height:1;
}
/* Scoped through .section so it beats the persistent content-link underline,
   which is (0,1,1) and would otherwise underline all twenty six letters. */
.section .gl-az a{color:var(--accent); text-decoration:none}
.section .gl-az a:hover,.section .gl-az a:focus-visible{background:var(--accent-light); text-decoration:none}
/* A dimmed letter is not interactive, so it is exempt from the AA text rule,
   but it still has to READ as "no terms here" rather than as a rendering
   fault. At .45 it measured 2.41:1 on the dark ground and 1.88:1 on the light
   one, which is too faint to carry that meaning. .75 clears 3:1 in both and
   still reads as clearly inactive beside the crimson letters at 5.93:1. */
.gl-az .gl-az-off{color:var(--text3); opacity:.75}

/* The sticky strip sits in front of whatever the anchor jump lands on, so the
   global scroll-padding-top is not enough on its own here. The two ADD UP:
   html carries scroll-padding-top of calc(--header-h + 24px), so the heading
   lands at that distance PLUS this margin. Only the strip's own height needs
   covering here, which is 44px on desktop and 66px on mobile where it wraps to
   two rows. Setting this to the full header-plus-strip figure pushed the
   heading 116px further down the page than it needed to go. */
.gl-letter{scroll-margin-top:36px}

.glossary-list{margin-bottom:24px}
.glossary-item{padding:11px 0;border-bottom:1px solid var(--border)}
.glossary-item:last-child{border-bottom:none}
.glossary-term{font-weight:600;color:var(--accent);margin-bottom:4px;font-size:var(--fs-sm)}
/* Every term on this page is a link, so a persistent underline distinguishes
   nothing and two hundred of them read as noise. The link is still marked
   without relying on colour: it is the bold opening line of its own row, and
   the page says so in its first sentence. Underline returns on hover and on
   keyboard focus. */
a.glossary-term{display:block; text-decoration:none}
.section a.glossary-term{text-decoration:none}
.section a.glossary-term:hover,.section a.glossary-term:focus-visible{text-decoration:underline; text-decoration-thickness:1px; text-underline-offset:2px}
.glossary-def{font-size:var(--fs-sm);color:var(--text1);line-height:1.6}

@media(max-width:880px){
  /* 26 letters wrap to two rows on a phone rather than scrolling sideways.
     A horizontal scroller inside a page is easy to miss, which is the same
     reason figures got a zoom button instead of one. */
  .gl-az{gap:1px; padding:6px 0}
  .gl-az a,.gl-az .gl-az-off{min-width:22px; padding:7px 0; font-size:var(--fs-meta)}
  .gl-letter{scroll-margin-top:58px}
}

/* The component carries its own card, so it looks the same wherever it is
   used. It was three different things across six placements: bare on three
   pages, wrapped in a .compare-card on one, and inside a .diagram-content
   figure on two. Identical markup, three appearances, and the bare one read
   as unfinished next to the others.

   Tokens rather than fixed colours, which also puts the border on paper:
   print forces --card and --border to their light values, so the card comes
   through without a print rule of its own. */
.comparison-grid{
  display:flex;align-items:center;justify-content:center;gap:20px;flex-wrap:wrap;
  background:var(--card);border:1px solid var(--border);border-radius:8px;
  padding:var(--sp-md);margin:var(--sp-lg) 0;
}
/* Inside a figure that already carries the chrome: one border, never two. */
.diagram-content > .comparison-grid{background:none;border:0;padding:0;margin:0}
.comp-item{background:var(--bg3);padding:16px 24px;border-radius:8px;text-align:center;min-width:150px}
.comp-label{font-weight:600;color:var(--text1);margin-bottom:6px}
.comp-desc{font-size:12px;color:var(--text3)}
.comp-vs{font-weight:700;color:var(--accent);font-size:16px}

.activity-map-demo{background:var(--bg3);border-radius:8px;padding:16px}
.am-page{border:1px solid var(--border);border-radius:6px;overflow:hidden;background:var(--bg1)}
.am-header{background:var(--bg2);padding:10px;text-align:center;font-size:12px;color:var(--text3);border-bottom:1px solid var(--border)}
.am-nav{display:flex;justify-content:center;gap:16px;padding:12px;border-bottom:1px solid var(--border);flex-wrap:wrap}
.am-link{position:relative;font-size:12px;padding:6px 12px;background:var(--bg2);border-radius:4px}
.am-link.hot{background:rgba(239,68,68,.2);color:var(--danger-ink)}
.am-link.warm{background:rgba(245,158,11,.2);color:var(--warning-ink)}
.am-link.cool{background:rgba(34,197,94,.2);color:var(--success-ink)}
.am-bubble{font-size:10px;font-weight:600;margin-left:4px}
.am-content{padding:24px;text-align:center}

/* ---------- Documentation signpost (auto-injected before ref-box) ---------- */
.doc-note{margin:var(--sp-lg) 0 var(--sp-sm);padding:3px 0 3px 14px;background:transparent;border:none;border-left:3px solid var(--accent);border-radius:0}
.doc-note-t{font-weight:700;font-style:italic;color:var(--accent) !important;font-size:11.5px !important;letter-spacing:0;text-transform:none;margin-bottom:2px}
.doc-note p{margin:0;font-size:11.5px !important;font-style:italic !important;color:var(--accent2) !important;line-height:1.45}

/* ---------- How to use this hub (landing page, once) ---------- */
.howto{max-width:680px;margin:24px auto 4px;padding:22px 24px;background:var(--card);border:1px solid var(--border);border-radius:12px;text-align:left}
.howto-t{font-size:18px;font-weight:700;color:var(--text1);margin-bottom:10px}
.howto .howto-lead{font-size:15px;color:var(--text1);font-weight:500;line-height:1.6;margin:0 0 14px}
.howto p{font-size:14px;color:var(--text2);margin:0 0 12px;line-height:1.6}
.howto-pair{display:flex;gap:12px;flex-wrap:wrap;margin:14px 0 4px}
.howto-pair > div{flex:1;min-width:200px;background:var(--bg3);border:1px solid var(--border);border-radius:8px;padding:12px 14px}
.howto-pair .ht-lab{font-weight:700;color:var(--text1);font-size:13px;margin-bottom:4px}
.howto-pair .ht-txt{font-size:13px;color:var(--text2);line-height:1.5}
.howto-together{font-size:13.5px;color:var(--text2);margin:8px 0 0;font-style:italic}

/* ============================================================
   Home-page map. Themed (light + dark). Same pyramid, cascade
   glow. Green shades adapt so the bloom reads in both themes.
   ============================================================ */
.hm{max-width:none;margin:0;padding:0}
.hm{position:relative}
.hm-brand{position:absolute;top:0;right:6px;color:var(--logo-ink);opacity:.55}
.hm-brand svg{display:block}
/* Vertical rhythm, widened 2 Sep 2026. Nine pixels between tiers packed the
   rows into a block that read as noise rather than as a pyramid, and the gaps
   are what say it is one. The budget is not the empty space on a large monitor
   but the 138px left over on a 1280x640 laptop once the blurbs came out; this
   and the taller arrows spend about 87 of it, so the map still does not
   scroll on the smallest desktop anyone reads it on. */
.hm-tier{display:flex;justify-content:center;padding:20px 0}
.hm-tier-cat{padding-top:2px}
.hm-row{display:flex;flex-wrap:wrap;justify-content:center;gap:14px 24px}
.hm-row-mod,.hm-row-sec{display:none}
.hm-row-mod.show,.hm-row-sec.show{display:flex;animation:hmFade .22s ease}
@keyframes hmFade{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}

/* dotted divider + arrow node, themed */
.hm-arrow{display:flex;align-items:center;justify-content:center;height:34px;position:relative}
.hm-arrow::before{content:"";position:absolute;left:10%;right:10%;top:50%;border-top:1.5px dotted var(--text3)}
.hm-arrow span{position:relative;z-index:1;width:22px;height:22px;border-radius:50%;background:var(--bg2);border:1.6px solid #16a34a;display:flex;align-items:center;justify-content:center;box-shadow:0 0 0 3px rgba(34,197,94,.12)}
.hm-arrow span::after{content:"";border-left:4px solid transparent;border-right:4px solid transparent;border-top:6px solid #16a34a}

/* boxless labels, themed text */
.hm-cat,.hm-mod,.hm-sec{display:inline-flex;flex-direction:column;align-items:center;gap:1px;padding:8px 14px;border:1px solid var(--border);background:var(--bg2);color:var(--text2);cursor:pointer;font-family:inherit;text-decoration:none;text-align:center;position:relative;border-radius:10px;transition:border-color .16s,background .16s,color .16s}
@media(hover:hover){.hm-cat:hover,.hm-mod:hover,.hm-sec:hover{border-color:#22c55e;color:var(--text1)}}
.hm-lab{font-size:14px;font-weight:600;white-space:nowrap;line-height:1.3;position:relative;z-index:1}
.hm-cat .hm-lab{font-size:15px;font-weight:700}
.hm-sec .hm-lab{font-size:12.5px;font-weight:500}

/* browsing selection: underline, no glow */
.hm-cat.sel,.hm-mod.sel{color:var(--text1)}
.hm-cat.sel::after,.hm-mod.sel::after{content:"";position:absolute;left:10px;right:10px;bottom:1px;height:2px;border-radius:2px;background:var(--text3)}

/* THE GLOW, themed. Light: crisp green halo + bright text (bloom would smudge on white).
   Dark: diffuse bloom like the overlay. Three shades: cat deep, mod mid, sec light. */
.hm-cat.on,.hm-mod.on{border-color:#16a34a;background:rgba(34,197,94,.13)}
.hm-cat.on .hm-lab,.hm-mod.on .hm-lab{color:var(--text1);font-weight:700}
/* light theme glow (default): tighter, more saturated so it reads on light bg */
.hm-sec.soon,.hm-soonmod{opacity:.45;pointer-events:none}
.hm-soonmod .hm-lab{font-style:italic;color:var(--text3);font-weight:400}

/* DARK theme: brighter greens, diffuse bloom, light text */

/* MORE AIR WHERE THERE IS AIR TO SPEND, and this has to be keyed to the height
   of the window rather than its width. The base above is sized for a 1280x640
   laptop, where the map clears the fold by 51px and not a pixel more. On a
   1440x900 screen the same numbers leave 300px of empty page below it, which
   is what makes the map read as a small suppressed panel floating in space.

   BOTH THRESHOLDS ARE MEASURED, not chosen for looking round. Height alone is
   not enough, because the panel gets taller as it gets narrower: at 920px wide
   the five phases wrap to three rows instead of two, and at 1024x790 the map
   overflowed by a whisker even at two. Both of those were live regressions
   from a first attempt at 881x780. Verified fitting at 1100x880 with 145px to
   spare, 1280x880 with 173px, 1440x900 with 193px and 1600x1000 with 293px;
   verified still tight and correct below the cut at 1280x640, where the base
   rhythm above clears the fold by 51px. A phone is excluded by the width. */
@media(min-width:1100px) and (min-height:860px){
  .hm-tier{padding:34px 0}
  .hm-tier-cat{padding-top:10px}
  .hm-arrow{height:52px}
  .hm-row{gap:16px 26px}
}

@media(max-width:560px){
  .hm-row{gap:8px 12px}
  .hm-cat .hm-lab{font-size:12px}
  .hm-mod .hm-lab{font-size:11.5px}
  .hm-sec .hm-lab{font-size:11px}
  .hm-tier{padding:13px 0}
}

.about-avatar svg{display:block;width:90px;height:90px}

/* home tagline */
.hm-tagline{text-align:left;font-size:16px;font-weight:700;line-height:1.4;margin:5px 0 0;letter-spacing:.01em;white-space:nowrap}
.hm-tagline .tl-a{color:var(--accent)}
.hm-tagline .tl-b{color:var(--tagline-accent)}
@media(max-width:880px){.hm-tagline{font-size:13.5px}.hm-tagline .tl-b{display:block}}

/* home map panel + dark box contrast */
.hm-panel{max-width:1000px;margin:4px auto 0;background:var(--card);border:1px solid var(--border);border-radius:16px;padding:12px 20px 14px}
[data-theme="dark"] .hm-cat,[data-theme="dark"] .hm-mod,[data-theme="dark"] .hm-sec{background:var(--bg3)}
/* COL-06: .18 over the darkened --bg3 left the tag text at 4.12:1. */
[data-theme="dark"] .hm-cat.on,[data-theme="dark"] .hm-mod.on{background:rgba(34,197,94,.10)}
@media(max-width:880px){.hm-panel{padding:14px 12px 18px;border-radius:14px}.hm-cat,.hm-mod,.hm-sec{padding:6px 11px}}

/* Home hero and cue, mobile. THE MARK AND THE TITLE SIZE ARE SET ONLY HERE.
   There used to be a second (max-width:880px) block above declaring 44px and
   21px, and this one -- identical specificity, later in the file -- silently
   beat it and rendered the mark at 70px. The visible symptom was on a phone:
   a 70px mark beside the text left 227px for the title, so "Adobe Analytics
   Learning" broke across two lines and the whole hero read as cramped.

   48px is chosen, not guessed: it leaves 281px for the text column at 390,
   which is enough for the title on ONE line at 20px. The duplicate rules are
   gone from the block above rather than left to fight. */
@media(max-width:880px){
  .welcome{gap:12px}
  .welcome-icon{width:48px;height:48px}
  .welcome-title{font-size:20px;line-height:1.12}
  .hm-cue{font-size:12px;gap:8px;letter-spacing:.02em}
  .hm-cue::before,.hm-cue::after{display:none}
}

/* category placeholder pages */
.cat-page{max-width:760px}

/* --- Module 5 QA: emphasis treatments (pullquote, callout-line, highlighted diagram-box) --- */
.section .pullquote{
  margin:22px 0;padding:18px 22px;
  border-left:4px solid var(--accent,#ba2142);
  background:var(--bg2,#fff1f4);
  border-radius:0 10px 10px 0;
  font-size:19px;line-height:1.5;font-weight:600;
  color:var(--text1,#0f172a);
}
.section .callout-line{
  margin:22px 0;padding:16px 20px;text-align:center;
  border:1px dashed var(--accent,#ba2142);border-radius:10px;
  background:var(--card,#ffffff);
  font-size:17px;font-weight:600;color:var(--accent2,#951833);
}
.section .diagram-box.highlight{
  border:2px solid var(--accent,#ba2142);
  box-shadow:0 2px 14px rgba(186,33,66,.10);
}

/* --- Module 5 QA round 3: purpose-built infographics --- */

/* ITEM 4 — contract: three boxes in one row, outer two point inward at the middle */
.section .tri-row{display:flex;align-items:stretch;justify-content:center;gap:0;flex-wrap:nowrap}
.section .tri-box{flex:1 1 0;min-width:0;background:var(--bg3,#eef2f7);border:1px solid var(--border,#e2e8f0);border-radius:12px;padding:18px 16px;text-align:center;display:flex;flex-direction:column;justify-content:center;gap:6px}
.section .tri-box.mid{background:var(--bg2,#fff1f4);border:2px solid var(--accent,#ba2142)}
.section .tri-box .tri-h{font-weight:700;font-size:15px;color:var(--text1,#0f172a)}
.section .tri-box.mid .tri-h{color:var(--accent2,#951833)}
.section .tri-box .tri-t{font-size:13px;color:var(--text2,#475569);line-height:1.4}
.section .tri-arrow{flex:0 0 auto;align-self:center;color:var(--accent,#ba2142);font-size:26px;line-height:1;padding:0 6px}
@media (max-width:640px){
  .section .tri-row{flex-wrap:wrap}
  .section .tri-box{flex:1 1 100%}
  .section .tri-arrow{transform:rotate(90deg);padding:6px 0}
}

/* ITEM 5 — two scenarios split by a vertical divider; two boxes each, labelled arrows */
.section .scene-split{display:flex;align-items:stretch;gap:0;flex-wrap:nowrap}
.section .scene{flex:1 1 0;min-width:0;padding:4px 22px;display:flex;flex-direction:column;gap:12px}
.section .scene.left{border-right:2px solid var(--border,#e2e8f0)}
.section .scene-lab{font-weight:700;font-size:14px;text-align:center;padding-bottom:4px}
.section .scene.ok .scene-lab{color:#15803d}
.section .scene.bad .scene-lab{color:var(--accent2,#951833)}
.section .scene-flow{display:flex;flex-direction:column;align-items:center;gap:4px}
.section .scene-box{width:100%;max-width:230px;background:var(--bg3,#eef2f7);border:1px solid var(--border,#e2e8f0);border-radius:10px;padding:12px 14px;text-align:center}
.section .scene-box .sb-h{font-weight:700;font-size:14px;color:var(--text1,#0f172a)}
.section .scene.ok .scene-box.filled{background:var(--bg2,#fff1f4);border-color:var(--accent,#ba2142)}
.section .scene-lbl{font-size:12px;font-weight:600;color:var(--text2,#475569);display:flex;align-items:center;gap:6px;line-height:1.2}
.section .scene-lbl.in{color:#15803d}
.section .scene-lbl.out{color:var(--accent2,#951833)}
.section .scene-lbl.late{color:var(--accent2,#951833);opacity:.85}
.section .scene-ar{color:var(--text3,#94a3b8);font-size:18px;line-height:1}
@media (max-width:640px){
  .section .scene-split{flex-wrap:wrap}
  .section .scene{flex:1 1 100%}
  .section .scene.left{border-right:none;border-bottom:2px solid var(--border,#e2e8f0);padding-bottom:16px;margin-bottom:6px}
}

/* ITEM 10 — comparison as a framed white card, two darker panels inside */
/* .compare-card is gone. It was the wrapper that gave ONE of the six
   comparison grids a card, and it hardcoded background:#ffffff, so in dark
   mode that section carried a white slab with dark-backed options sitting on
   it. The card now belongs to .comparison-grid itself, in tokens, which
   removes both the inconsistency and the hardcoded colour. */

/* soft callout (items 11,12) retained */
.section .callout-soft{margin:18px 0;padding:12px 20px;text-align:center;border:1px dashed var(--accent,#ba2142);border-radius:10px;background:transparent;font-size:15px;font-weight:600;color:var(--accent2,#951833)}

/* human-priority (item 9) retained as-is */
.section .human-priority{display:flex;align-items:center;justify-content:center;gap:26px;flex-wrap:wrap;padding:8px 0}
.section .hp-node{display:flex;flex-direction:column;align-items:center;gap:6px;max-width:220px;text-align:center}
.section .hp-face{font-size:46px;line-height:1}
.section .hp-badge{display:inline-block;font-size:12px;font-weight:700;padding:2px 10px;border-radius:999px}
.section .hp-badge.primary{background:var(--accent,#ba2142);color:var(--on-accent,#fff)}
.section .hp-badge.secondary{background:var(--bg3,#eef2f7);color:var(--text2,#475569)}
.section .hp-node.support{opacity:.72}

/* --- shot-box: product screenshots (added 14 Aug 2026) ------------------
   Sibling of .diagram-box. Three things a screenshot needs that a diagram
   does not: a frame that makes its edges obvious, a height cap so a wide
   capture never dominates the page, and a zoom control. The image itself
   is inert. Only the button opens the overlay, and the overlay stays in
   the same tab. */
/* 21 Aug 2026: the chrome came off. A screenshot was sitting inside THREE
   nested borders -- the card, the grey frame, and the image's own -- which
   made the component read as a good piece of interface and a poor piece of
   teaching. The card and the frame are gone. What survives is the one border
   that belongs to the picture, a heading above it and a description below,
   which is what a figure in a book looks like.

   The height cap went up with them, and that was the real complaint. The
   image was clamped to 360px tall at EVERY viewport, so even at 1440px a
   reader had to open the overlay to see anything. 560px lets a landscape
   capture fill the 760px column; the overlay is now for detail rather than
   for legibility. */
.shot-box{margin:var(--sp-xl) 0}
/* 23 Aug 2026: this was --fs-h3 at weight 550, byte-identical to
   .subsec-title, so a figure label rendered as a section heading and set up
   an expectation the in-page spine deliberately does not meet (shots are
   evidence under a heading, not destinations, so they are not listed there).
   Brought into the one label voice the rulebook already specifies and that
   .diagram-title already used. It only became visible now: the
   .shot-pending override below sets --fs-label, so every placeholder looked
   correct and the real style was unseen until Module 1 shipped a real shot. */
.shot-title{font-weight:700;color:var(--text1);margin-bottom:12px;font-size:var(--fs-label)}

.shot-frame{position:relative;display:flex;align-items:flex-start;justify-content:center}
/* CMP-04 / SEO-09: the box is driven by WIDTH alone, and the reason is a
   334px layout shift measured cold at 1280 -- a lazy image with no definite
   width occupied 2px until its bytes arrived. The width attribute keeps the
   width definite, so that stays fixed.

   23 Aug 2026: a screenshot now renders at the size the thing really is.
   Every capture is 2x retina, so the width/height ATTRIBUTES carry the true
   1x size (2880x1800 is a 1440x900 window) while the file keeps its 2x
   pixels and stays sharp. The browser takes its intrinsic size from those
   attributes, so `width:auto` still reserves the box before the bytes land
   and there is no layout shift -- which is what the old `width:100%` was
   there for.

   What went with it: --sw, --sar and the whole --shot-cap ladder. They sized
   by column and by a height ceiling, and neither knew how big the thing
   actually was, so a small dialog and the entire Workspace both filled the
   column. Now a 726px permissions panel occupies 726px and only the genuinely
   wide captures hit the column edge.

   ONE RULE HOLDS THIS TOGETHER: never constrain width and height at the same
   time. `width:100%` plus a `max-height` clamps the height without moving the
   width, which stretches the picture -- that is exactly how the print
   stylesheet distorted every figure. Constrain width, let height follow. */
/* Note there is deliberately NO `width` declaration here. The width/height
   attributes are presentational hints, so width="1440" IS the CSS width until
   an author rule overrides it -- and `width:auto` would override it, sending
   the image back to its intrinsic 2880px and filling the column again, which
   is the bug this whole change exists to kill. Leave width alone, cap it with
   max-width, and let height follow. */
.shot-frame img{display:block;max-width:100%;height:auto;border:1px solid var(--border);border-radius:4px;background:#fff;pointer-events:none}
/* Both .shot-tall and its replacement .shot-side were removed the same day
   they were written, because true sizing dissolved the problem they existed
   for. .shot-tall raised the height ceiling to 700px and so spent ~700px of
   page on a narrow strip; .shot-side moved the note into the dead space
   beside it. Sized truly, those three captures are 390x495, 314x405 and
   496x687 -- small figures that sit under their note without waste, which is
   how a figure works in a book and how Adobe's own documentation sets one.
   Two shapes fewer to choose between, and no class to remember. */

.shot-zoom-btn{position:absolute;top:8px;right:8px;width:30px;height:30px;display:flex;align-items:center;justify-content:center;padding:0;font-size:15px;line-height:1;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text2);cursor:pointer}
.shot-zoom-btn:hover{background:var(--accent,#ba2142);border-color:var(--accent,#ba2142);color:var(--on-accent,#fff)}

.shot-note{margin-top:14px;font-size:var(--fs-sm);color:var(--text2);line-height:1.65}
:root[data-theme="dark"] .shot-frame img{filter:brightness(.9) contrast(1.02)}

/* Overlay: same tab, natural size, scrollable in both directions. */
.shot-ov{position:fixed;top:0;right:0;bottom:0;left:0;z-index:9999;background:rgba(15,23,42,.93);display:none}
.shot-ov.open{display:block}
.shot-ov-scroll{position:absolute;top:0;right:0;bottom:0;left:0;overflow:auto;-webkit-overflow-scrolling:touch;padding:58px 16px 24px}
/* width is set inline by the script, to the capture's TRUE size rather than
   the file's 2x pixels. max-width:none lets it exceed the viewport on a
   phone, where panning is expected; height:auto keeps the ratio, and nothing
   here constrains height, so the overlay cannot distort either. */
.shot-ov-scroll img{display:block;margin:0 auto;max-width:none;height:auto}
/* The [hidden] guard is not optional once an author rule sets display on the
   same element. `display:block` above is (0,1,1) and the UA's [hidden] rule is
   (0,1,0), so the attribute loses and img.hidden = true silently does nothing.
   Without this the srcless image sat above every expanded diagram showing its
   alt text and a broken-image icon -- invisible in light mode, because alt
   text paints in dark ink on the overlay's near-black ground, and obvious the
   moment the theme flipped. */
.shot-ov-scroll img[hidden]{display:none}
/* The ground under a cloned diagram MUST follow the theme, and hardcoding
   #fff here was a live bug. Every figure paints itself from --card, --text1
   and the --dia-* tokens, so in dark mode the artwork resolves to its dark
   palette; dropping that onto a fixed white card produced pale, washed-out
   shapes that looked like a rendering fault. The overlay backdrop is
   near-black in both themes, so the card carries a border to keep its edge
   findable once it is dark-on-dark.

   This is the same lesson as --code-chrome-ink, arrived at from the opposite
   direction: there a token had to stop following the theme because its
   surface was fixed, here a surface has to start following the theme because
   its contents do. What matters is that ground and ink agree. */
.shot-ov-svg{
  margin:0 auto;width:max-content;border-radius:4px;padding:14px;
  background:var(--card);border:1px solid var(--border);
}
.shot-ov-svg[hidden]{display:none}
.shot-ov-close{position:fixed;top:12px;right:14px;z-index:2;width:38px;height:38px;padding:0;border:1px solid rgba(255,255,255,.28);border-radius:8px;background:rgba(15,23,42,.85);color:#fff;font-size:20px;line-height:1;cursor:pointer}
.shot-ov-hint{position:fixed;top:20px;left:16px;z-index:2;color:rgba(255,255,255,.72);font-size:12px;font-weight:600}
body.shot-ov-open{overflow:hidden}

@media (max-width:768px){
  /* The two padding overrides that used to sit here were re-adding inset to
     the card and the frame, and both are gone now. Only the margin and the
     height caps still mean anything on a phone. */
  .shot-box{margin:32px 0}
  /* No height cap on a phone either. max-width:100% already shrinks every
     capture to the column, and a second constraint on height is what breaks
     the aspect ratio. A capture wider than the phone simply scales down and
     the expand button is there for detail. */
}
@media print{
  .shot-zoom-btn{display:none !important}
  .shot-ov{display:none !important}
  /* Screen-only affordances must not reach paper. The diagram control and the
     expand hint both did: the hint had no print rule at all, and the diagram
     button never got the line its screenshot counterpart above has had since
     the shot-box work. A printed page was showing a floating magnifier and an
     instruction to tap it. */
  .diagram-zoom-btn{display:none !important}
  .fig-hint{display:none !important}
  /* Keep a screenshot and its note on one page. This used to fight the
     on-screen height caps; there are none now, and paper reads the same true
     size the screen does, so a figure arrives here already the right shape
     and already small enough to sit inside the text. The blank pages came
     from the opposite: an image forced near page-size could not fit beside
     anything, so break-inside threw the whole box forward. */
  .print-doc .shot-box{break-inside:avoid;page-break-inside:avoid;box-shadow:none !important}
  .print-doc .shot-frame{padding:8px;background:#fff}
  /* The dark-mode dimming has to be switched off explicitly here, and this
     was a live bug Amit found on paper. `:root[data-theme="dark"] .shot-frame
     img` applies a brightness(.9) filter, and printing does not clear the
     theme attribute -- the root is still data-theme="dark" while the paper is
     white. So a print taken from dark mode carried a dimmed screenshot onto a
     white page, for no reason, while the same print from light mode was fine.
     Resetting the frame background was not enough; the filter lives on the
     image. Anything that paints for a THEME needs an explicit print reset,
     because print is not a theme. */
  .print-doc .shot-frame img{filter:none !important}
  /* Paper gets the same number the screen gets: true size, capped to the
     column, width only. The max-height that used to sit here is what
     distorted every printed figure -- it clamped the height while
     `width:100%` held the width, so a 0.79 portrait printed at 1.13. There is
     no height constraint on any surface now, which is why the ratio cannot
     break on any of them. */
  .print-doc .shot-frame img{max-width:100%;height:auto}
}
/* Screenshot placeholder. Deliberately loud: an unfilled slot reaching
   production is exactly the silent failure this site keeps being bitten
   by, so it is impossible to mistake for finished work. Guarded by a
   grep on shot-pending before every commit. */
/* The padding here is re-added deliberately. Taking the chrome off .shot-box
   on 21 Aug stripped the inset a finished screenshot no longer needs, but the
   pending placeholder is a different object: it is a panel of text inside a
   dashed border, and without padding the brief sat hard against the rule.
   Only the pending state carries it. */
.shot-box.shot-pending{border:2px dashed var(--accent,#ba2142);background:transparent;border-radius:10px;padding:18px}
.shot-pending .shot-title{font-size:var(--fs-label);line-height:1.4;margin-bottom:12px}
.shot-pending .shot-frame{background:var(--bg1);border:2px dashed var(--border);border-radius:8px;padding:14px 16px;min-height:150px;align-items:flex-start;justify-content:flex-start}
/* Scoped, because the brief is a <p> inside .lcontent and that rule is (0,2,2)
   while `.shot-pending .shot-brief` is only (0,2,0). Unscoped it rendered at
   body size, 16.5px, and a six-line monospace brief at body size runs wider
   than the column. Trap 7, for the third time in one session. */
body.world-learning .lcontent p.shot-brief{font-size:12.5px;line-height:1.75;color:var(--text2);white-space:pre-wrap;margin:0}
.shot-pending .shot-title::after{content:" — SCREENSHOT PENDING";color:var(--accent,#ba2142);font-weight:700;letter-spacing:.5px}

/* "Created by" prefix, landings only. Sized with the metadata around it so
   the name still steps up out of it. */
.byline .by-pre{font-size:var(--fs-meta); color:var(--text2)}

/* The scale line on module and phase landings: how big this is before you
   start. Sits under the byline as a second line of metadata, quiet, and it
   uses tabular numerals so the figures line up if the eye compares two
   modules across the curriculum. */
/* Scoped, because `body.world-learning .lcontent p` is (0,2,2) and a bare
   .lscale is (0,1,0): unscoped it rendered at body size. Same fault as the
   breadcrumb and the box prose before it. */
body.world-learning .lcontent p.lscale{
  display:flex; flex-wrap:wrap; gap:4px 9px; align-items:baseline;
  margin:0 0 var(--sp-lg);
  font-size:var(--fs-meta); color:var(--text2);
  font-variant-numeric:tabular-nums;
}
.lscale .ls-sep{color:var(--text3)}
/* CMP-11. Honour prefers-reduced-motion across the whole surface, not just the
   two places that were handled piecemeal (scroll-behavior here, the author
   fab's breathe in chrome.css). Vestibular triggers are transitions and
   keyframes generally, so the sweep has to be universal.

   .01ms rather than none is deliberate: animation:none suppresses the
   animationend event, and any script waiting on one would hang instead of
   degrade. A duration this short is imperceptible but still fires. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ==========================================================================
   Ask Amit — the three surfaces, 22 August 2026
   The overlay itself lives in chrome.css because it is universal. These are
   the learning world's entry points into it.
   ========================================================================== */

/* Spine entry, below the path-box jump. Same shape as .wf-path: not a heading,
   so it takes no number and gets a rule above it to separate it from the list
   of places in the page. This is the one that answers the reachability
   problem -- the spine is the only furniture reachable from any scroll
   position without scrolling first. */
.wf-rail li.wf-ask{counter-increment:none; margin-top:7px; padding-top:8px; border-top:1px solid var(--border)}
.wf-rail li.wf-ask > a{color:var(--accent); font-weight:650}
.wf-rail li.wf-ask > a:hover{color:var(--accent2); background:var(--accent-light)}
.wf-rail li.wf-ask > a::before{
  content:""; width:12px; height:12px; margin-top:3px; border-radius:50%;
  background:radial-gradient(circle at 50% 50%, var(--accent,#ba2142) 0 3.2px, transparent 3.4px);
}

/* The bar that ARRIVES, at every width.

   It replaced a permanent line at the foot of the section, on Amit's call:
   a tip pinned to the bottom of every page is only ever seen by somebody who
   already finished, and it sat below the ref-box, which the section anatomy
   says nothing should follow. Removing it dissolved that tension rather than
   arguing with it.

   The behaviour is now identical on a phone and a desktop -- it slides in at
   two thirds of the article, where a question has actually formed, and it can
   be dismissed for the session. What changes with width is only its SHAPE.

   Below 1244 it spans the width, because the spine is a drawer there and this
   is the only one-tap route. env(safe-area-inset-bottom) keeps it clear of the
   iOS home indicator, which is the strip a naive sticky footer always hits.

   At 1244 and up it becomes a compact centred pill. Full width would be absurd
   on a 1440 screen, and centring keeps it clear of the About mark in the
   bottom-right corner without anything having to move: measured at 1244, 1280
   and 1440, the pill ends at least 280px short of the mark. */
.ask-bar{
  display:flex; align-items:center; gap:10px;
  position:fixed; z-index:930;
  bottom:calc(12px + env(safe-area-inset-bottom, 0px));
  padding:11px 14px; border-radius:12px;
  background:var(--card); border:1px solid var(--border);
  box-shadow:0 8px 26px rgba(15,23,42,.18);
  opacity:0; pointer-events:none;
  transition:transform .32s cubic-bezier(.2,.8,.3,1), opacity .22s ease;
}
.ask-bar > a{flex:1; display:flex; align-items:baseline; gap:7px; text-decoration:none; min-width:0}
.ask-bar span{font-size:var(--fs-sm); color:var(--text2); white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
.ask-bar b{flex:none; font-size:var(--fs-sm); font-weight:700; color:var(--accent)}
.ask-bar-x{flex:none; width:30px; height:30px; border:none; border-radius:8px; background:transparent;
           color:var(--text3); font-size:20px; line-height:1; cursor:pointer}
.ask-bar-x:hover{background:var(--bg3); color:var(--text1)}
[data-theme="dark"] .ask-bar{box-shadow:0 8px 26px rgba(0,0,0,.5)}

@media(max-width:1243px){
  .ask-bar{left:12px; right:12px; transform:translateY(calc(100% + 24px))}
  .ask-bar.is-in{transform:none; opacity:1; pointer-events:auto}
  /* The bar spans the full width and the bottom-right corner is already taken:
     at 390 the About mark sits from y766 to y826 and the page-nav button just
     above it. Measured, they collide. So while the bar shows, both lift by
     exactly its height plus its gap. Shifting `bottom` rather than
     `transform` is deliberate -- .author-fab:hover sets transform:none, which
     would cancel a translate mid-hover and make the mark jump. */
  body.ask-bar-in .author-fab{bottom:calc(84px + env(safe-area-inset-bottom, 0px))}
  body.ask-bar-in .wf-nav-fab{bottom:calc(178px + env(safe-area-inset-bottom, 0px))}
  .author-fab,.wf-nav-fab{transition:bottom .32s cubic-bezier(.2,.8,.3,1)}
}

@media(min-width:1244px){
  .ask-bar{left:50%; width:min(430px, calc(100vw - 48px)); transform:translateX(-50%) translateY(calc(100% + 24px))}
  .ask-bar.is-in{transform:translateX(-50%); opacity:1; pointer-events:auto}
}
/* A bar that slides is a bar that moves under the reader's thumb. Honour the
   preference and let it simply appear. */
@media(prefers-reduced-motion:reduce){.ask-bar{transition:none}}

/* ============================================================================
   THE TWO LANDING PAGES, rebuilt 2 Sep 2026 after user testing.

   Almost every tester hit the same three things: they looked for the map and
   it was not on these pages, they expected the page to teach and it did not,
   and they could not tell a phase landing from a module landing. The first was
   a missing partial. The other two are what these rules answer.

   THE TWO SHAPES ARE DELIBERATELY DIFFERENT. A module landing is a numbered
   contents list, because its children are an ordered reading sequence. A phase
   landing is a grid of cards, because its children are four or five
   destinations a reader chooses between. Previously both rendered the same
   <ol class="lsec-list"> under the same heading and were indistinguishable.
   ============================================================================ */

/* ---- the head: what page is this, and how big ---- */
.lland-head{margin:0 0 var(--sp-md)}
/* The line that answers "where am I". It is the smallest text on the page and
   the first thing read, which is the right way round for an orientation label. */
body.world-learning .lcontent p.lland-kicker{
  margin:0 0 4px; font-size:var(--fs-meta); font-weight:700; letter-spacing:.06em;
  text-transform:uppercase; color:var(--accent);
}
.lland-kicker .lk-sep{color:var(--text3); margin:0 6px; font-weight:400}
body.world-learning .lcontent p.lland-tag{
  margin:4px 0 0; font-size:var(--fs-h3); font-weight:400; color:var(--text2); line-height:1.35;
}

/* ----------------------------------------------------------------------------
   MODULE LANDING: the section list.

   Each row carries the section's own description. The old list was title-only,
   so choosing between eight sections meant opening several of them; the line
   that decides it was already written as the section's `description` and was
   simply not being shown anywhere a reader could use it.
   ---------------------------------------------------------------------------- */
.lland-secs{list-style:none; margin:0 0 var(--sp-lg); padding:0; border-top:1px solid var(--border)}
.lland-secs li{border-bottom:1px solid var(--border)}
.lland-secs a,.lland-secs .ls-stub{
  display:flex; align-items:flex-start; gap:14px; padding:13px 10px 13px 4px;
  text-decoration:none; border-radius:8px; transition:background-color .15s;
}
.lland-secs a:hover{background:var(--accent-light); text-decoration:none}
.ls-n{
  flex:none; width:26px; font-size:var(--fs-meta); font-weight:700; color:var(--text3);
  font-variant-numeric:tabular-nums; padding-top:2px;
}
.lland-secs a:hover .ls-n{color:var(--accent)}
.ls-body{display:flex; flex-direction:column; gap:2px; min-width:0; flex:1}
.ls-t{font-size:var(--fs-sm); font-weight:600; color:var(--text1); line-height:1.35}
.lland-secs a:hover .ls-t{color:var(--accent)}
.ls-d{font-size:var(--fs-meta); color:var(--text2); line-height:1.5}
/* Drawn, permanent, and grey rather than accent. It was hidden until hover on
   the reasoning that an arrow on every one of seventeen rows competes with the
   titles it is meant to serve -- right about accent arrows, wrong about what
   removing it costs. Without one, a row is a number, a title and a description
   with no underline and no colour, which is a table of contents in a printed
   document rather than a list of links, and testers read it that way. A phone
   has no hover to correct the impression. Grey competes with nothing; hover
   still takes it to accent and slides it. */
.ls-go{
  flex:none; width:14px; height:14px; margin-top:4px; background:var(--text3);
  transition:background-color .15s, transform .15s;
  -webkit-mask:var(--landarrow) center/14px 14px no-repeat;
          mask:var(--landarrow) center/14px 14px no-repeat;
  --landarrow:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h13M12.5 6l6 6-6 6"/></svg>');
}
.lland-secs a:hover .ls-go{background:var(--accent); transform:translateX(2px)}
.lland-secs li.soon .ls-t,.lland-secs li.soon .ls-d{color:var(--text3)}

/* ----------------------------------------------------------------------------
   PHASE LANDING: the module cards. Two up where there is room, because four or
   five cards in one column is a scroll for no reason.
   ---------------------------------------------------------------------------- */
.lland-mods{display:grid; grid-template-columns:repeat(auto-fit,minmax(min(260px,100%),1fr)); gap:10px; margin:0 0 var(--sp-lg)}
.lland-mod{
  display:flex; align-items:flex-start; gap:13px; padding:15px 16px;
  border:1px solid var(--border); border-radius:12px; background:var(--card);
  text-decoration:none; transition:border-color .15s, background-color .15s, transform .15s;
}
a.lland-mod:hover{border-color:var(--accent); background:var(--accent-light); transform:translateY(-1px); text-decoration:none}
.lm-n{
  flex:none; width:26px; height:26px; border-radius:50%; display:flex; align-items:center; justify-content:center;
  background:var(--accent-light); color:var(--accent);
  font-size:var(--fs-micro); font-weight:800; font-variant-numeric:tabular-nums;
}
.lm-body{display:flex; flex-direction:column; gap:3px; min-width:0; flex:1}
.lm-t{font-size:var(--fs-sm); font-weight:700; color:var(--text1); line-height:1.3}
a.lland-mod:hover .lm-t{color:var(--accent)}
.lm-tag{font-size:var(--fs-meta); color:var(--text2); line-height:1.4}
.lm-meta{font-size:var(--fs-micro); color:var(--text3); margin-top:2px}
.lm-go{
  flex:none; width:14px; height:14px; margin-top:5px; background:var(--accent); opacity:0;
  transition:opacity .15s, transform .15s;
  -webkit-mask:var(--landarrow) center/14px 14px no-repeat;
          mask:var(--landarrow) center/14px 14px no-repeat;
  --landarrow:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h13M12.5 6l6 6-6 6"/></svg>');
}
a.lland-mod:hover .lm-go{opacity:1; transform:translateX(2px)}
.lland-mod.soon{opacity:.55}

/* ---- everything below the map is context, and reads as such ---- */
.lland-about{border-top:1px solid var(--border); padding-top:var(--sp-md); margin-top:var(--sp-md)}
body.world-learning .lcontent h2.lland-h2{
  font-size:var(--fs-label); font-weight:700; letter-spacing:.06em; text-transform:uppercase;
  color:var(--text3); margin:0 0 var(--sp-sm);
}
body.world-learning .lcontent p.lland-next{
  margin-top:var(--sp-md); padding-top:var(--sp-sm); border-top:1px solid var(--border);
  font-size:var(--fs-sm); color:var(--text2);
}
.lland-next a{font-weight:600}

@media(max-width:880px){
  .lland-secs a,.lland-secs .ls-stub{padding:12px 6px 12px 2px; gap:11px}
  .ls-n{width:22px}
  .lland-mods{grid-template-columns:1fr}
}
