/* Afrinkong — the design system.
 * ---------------------------------------------------------------------------
 * Every page on this site used to carry its own copy of the tokens, the reset
 * and the type scale, inline, which meant the palette existed in twenty-five
 * places and drifted in each of them. This file is the single definition. Pages
 * still carry their own layout rules inline — that part is genuinely per-page,
 * and inlining it is why the site paints in one request — but nothing below is
 * ever redefined by a page.
 *
 * Naming: `af-` is the system. Page-local classes keep their own prefixes
 * (`wa-` gateway, `fj-` Kamerun, `ct-` country) so it is always obvious from a
 * class name whether a rule is shared or local.
 *
 * The system has to hold for fifty-four countries and twenty-seven categories
 * per country, so nothing here encodes a country, a region or a count.
 */

/* ---- the display face ------------------------------------------------------
 * THE STACK NAMED A FONT THAT ALMOST NOBODY HAD, AND THE FALLBACK WAS NOT
 * NARROW.
 *
 *   --fj-display: "Archivo Narrow", "Roboto Condensed", "Arial Narrow", Arial…
 *
 * Windows and macOS ship Arial Narrow, so they got a condensed grotesque.
 * Android ships Roboto Condensed, so it got one too. iOS and iPadOS ship
 * neither — no Archivo Narrow, no Roboto Condensed, no Arial Narrow — so every
 * headline on this site fell through to Arial and was set in a face that is
 * not condensed at all. Measured: the same headline is 21% wider in Arial than
 * in Archivo Narrow. On the device this audience mostly uses, the display
 * voice was not the display voice.
 *
 * The note this replaces said all three voices were system stacks because "a
 * webfont is a render-blocking request and this site's argument is that it
 * arrives instantly". The reasoning was sound about a <link> to a font
 * service. It does not apply to a self-hosted woff2 with font-display:swap,
 * which blocks nothing: the page paints immediately in the fallback and swaps
 * when the face lands. The two Latin subsets together are 35 KB, against a
 * homepage that carries four megabytes of photographs.
 *
 * The serif and the mono stay system stacks. They are read, not seen, and the
 * fallbacks for both are close enough that nobody would be able to tell.
 *
 * Archivo Narrow is under the SIL Open Font License 1.1 — fonts/OFL.txt — and
 * the files are the ones Google Fonts serves, self-hosted so that no page on
 * this site makes a request to a third party.
 */
@font-face{
  font-family:"Archivo Narrow";
  font-style:normal;
  font-weight:100 900;   /* one variable file covers the 400 and the 700 */
  font-display:swap;
  src:url(/fonts/archivo-narrow-latin.woff2) format("woff2");
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,
    U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,
    U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face{
  font-family:"Archivo Narrow";
  font-style:normal;
  font-weight:100 900;
  font-display:swap;
  src:url(/fonts/archivo-narrow-latin-ext.woff2) format("woff2");
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,
    U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,
    U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
/* WHAT THE PAGE LOOKS LIKE FOR THE HALF-SECOND BEFORE THE FACE ARRIVES.
   font-display:swap means the first paint uses whatever is next in the stack,
   and on iOS that is Arial — 21% wider, so every headline would reflow when
   the real face landed. This is the same Arial with its metrics corrected to
   Archivo Narrow's, so the swap changes the shape of the letters and not the
   size of the box.
   The numbers are measured, not estimated: eight representative strings of
   this site's own headlines, at 400 and at 700, gave a width ratio between
   81.85% and 83.42% with a mean of 82.44. The ascent and descent overrides
   are Archivo Narrow's own 104 and 31 per hundred pixels, divided by the
   size-adjust. Verified after the fact — the corrected fallback matches the
   real face to within 1% on uppercase, which is what nearly all of the display
   type on this site is, and the line box matches exactly. */
@font-face{
  font-family:"Narrow Fallback";
  src:local("Arial"),local("Helvetica Neue"),local("Helvetica"),
      local("Liberation Sans");
  size-adjust:82.4%;
  ascent-override:126.2%;
  descent-override:37.6%;
  line-gap-override:0%;
}

:root{
  /* Paper and ink.
     ---------------------------------------------------------------------
     The site is set on warm paper, not white: it is a printed journal that
     happens to be a website, and pure white reads as software. What it was
     not, until this palette, was warm enough to be a decision — a pale cream
     with a pale peach on it reads as an archive rather than as a place.

     Ivory is the paper. Sand is the second surface, and it is a real surface
     rather than a 6% tint of the ink: a band of sand under a band of ivory
     is a change of ground, which a mix of the two never quite is.

     Deep Forest is the authority — every solid call, every dark ground, the
     border under the masthead. Charcoal is the reading ink. They are close
     enough to belong together and far enough apart that a headline set in
     charcoal on ivory does not read as the same object as a button. */
  --c-bg:#F6F1E7;          /* Warm Ivory  — the paper */
  --c-sand:#E9DDCA;        /* Sand        — the second surface */
  --c-primary:#10251F;     /* Deep Forest — authority, calls, dark grounds */
  --c-ink:#171A17;         /* Charcoal    — the reading ink */
  --c-border:#DCCEB6;      /* a rule that belongs to sand, not to grey */

  /* The African accent, in two weights and for a measurable reason.
     ---------------------------------------------------------------------
     Terracotta #C96A45 is the energy of this palette and it cannot carry
     small text on paper: 3.31:1 against ivory, where AA asks 4.5. The site
     sets its accent at 9 to 11 pixels — every stamp, every nav item, every
     category label — so an accent that fails at that size fails everywhere
     it is actually used.

     So the accent is two colours with one job each, and neither is a
     compromise. Burnt Sienna carries type: 5.39:1 on ivory and 4.52:1 on
     sand, both clear. Terracotta fills: the map's destinations, an active
     tab, a band. Where terracotta carries type it carries charcoal at
     4.71:1, never ivory at 3.31.

     Burnt Sienna is #B94E2E darkened by one step. As given it measures
     4.45:1 on ivory — under 4.5 by four hundredths, which is failing. */
  --c-accent:#8F4128;      /* Burnt Sienna, text weight */
  --c-accent-fill:#C96A45; /* Terracotta, fill weight */

  /* On a dark ground the accent has to go the other way, and terracotta as
     given is 4.31:1 on forest — again just short. Lightened by one step it
     is 4.53:1 on forest and 4.94:1 on charcoal. The dark surfaces below
     re-point --c-accent at this, so every `color:var(--c-accent)` already
     written gets the right one without a second rule. */
  --c-accent-lit:#CB6F4B;

  /* Muted gold does not work on paper — 2.37:1 — and is excellent on forest
     at 6.02:1. It is the metadata voice on every dark ground, and a fill
     (never a text colour) on light ones. Restraint is the point: it appears
     on the dark bands and in the map's legend and nowhere else. */
  --c-gold:#B99A63;

  /* Sage as given is 3.22:1 on ivory, which is a surface colour rather than
     an ink. Darkened to the same rule as the accent it reads as the same
     family and clears the bar: 5.37:1 on ivory, 4.51:1 on sand. */
  --c-muted:#505D4D;

  /* Derived surfaces. Mixed rather than hand-picked, so changing --c-bg or
     --c-primary moves the whole system together instead of leaving orphans. */
  /* The alternating band. It was ivory mixed 6% toward the ink, which is a
     grey shadow of the paper rather than a second ground; sand is the
     colour the palette actually has for this, and against ivory it is a
     change you can see rather than one you can measure. */
  /* THE ZONE GROUND STOPS BEING THE MAP'S MATERIAL.
     A dust section measured 1.19:1 against the page, so the band that is meant
     to say "this is a different part of the argument" said almost nothing — on
     a laptop the two grounds are one ground. It could not simply be darkened,
     because --c-sand is also what the continent is mixed from: the land is 85%
     sand, and taking sand down closes the gap between a destination and the
     land it sits on, which is the reading order the whole map depends on.
     Measured: at #DFCFB4 the zone reads at 1.36 but a destination falls to
     1.25x the coastline, under the 1.3 the map holds.
     So they are two tokens now. The map keeps its material and the page gets a
     ground: 1.36:1, which is a band you can see, and nothing on the map moves. */
  --fj-dust:#DFCFB4;
  /* The two inks that sit on it had to move with it, and this is the finding
     the whole change turned on: the old ground was not 1.19:1 by taste. It was
     pinned there by --c-muted and --c-accent, which measured 4.51 and 4.52
     against it — AA to two decimal places. Any darker ground broke both, on
     every page with a dust band, which is how 69 pieces of text on the home page
     and a button on the atlas went under at once.
     So the metadata voice and the accent take 14% of the forest: 4.55 and 4.65
     on the new ground, 6.19 and 6.33 on the page, and darker links on paper is
     not a cost. */
  --fj-basalt:color-mix(in srgb,var(--c-primary) 95%,#000);
  --fj-onbasalt:color-mix(in srgb,var(--c-bg) 88%,var(--c-primary));
  --fj-onbasalt-dim:color-mix(in srgb,var(--c-bg) 62%,var(--c-primary));
  --fj-rule:1px solid var(--c-border);
  --fj-rule-dark:1px solid color-mix(in srgb,var(--c-bg) 20%,transparent);

  /* Three voices and no more: a condensed grotesque for anything that shouts,
     a serif for anything that has to be read, a mono for anything that labels.
     The display face is served by this site (see the @font-face block above);
     the other two are system stacks, because a serif and a mono are read
     rather than looked at and their fallbacks are indistinguishable.
     "Narrow Fallback" sits after the two condensed faces that some systems do
     have and before plain Arial, which is the one that is not narrow. */
  --fj-display:"Archivo Narrow","Roboto Condensed","Arial Narrow",
    "Narrow Fallback",Arial,"Helvetica Neue",sans-serif;
  --fj-text:Charter,"Iowan Old Style","Source Serif Pro","Palatino Linotype",Palatino,Georgia,serif;
  --fj-mono:"IBM Plex Mono","SFMono-Regular",Menlo,Consolas,"Liberation Mono",monospace;

  /* Spacing is a scale, not a set of numbers typed at the moment of need. */
  --sp-1:6px; --sp-2:12px; --sp-3:20px; --sp-4:32px;
  --sp-5:48px; --sp-6:72px; --sp-7:104px;

  /* One motion vocabulary. Everything that moves uses these, so the whole site
     accelerates the same way. */
  --ease:cubic-bezier(.2,.7,.2,1);
  --t-fast:.2s; --t-mid:.45s; --t-slow:.75s;

  --frame:1240px;      /* the measure everything is set inside */
  --mast:78px;         /* the fixed masthead, so heroes can clear it */
  color-scheme:light;
}

*{margin:0;padding:0;box-sizing:border-box}
html{scroll-behavior:smooth;scroll-padding-top:calc(var(--mast) + 16px)}
body{font-family:var(--fj-text);background:var(--c-bg);color:var(--c-ink);
  line-height:1.66;font-size:17px;-webkit-font-smoothing:antialiased}
img{max-width:100%;display:block}
/* The reset the browser gives `hidden` is a `display:none` at the weakest
   possible specificity, so any component rule that sets `display` silently
   defeats it — a hidden section that is still a flex container is still on the
   page, taking its full height, with everything after it pushed past the fold. */
[hidden]{display:none !important}
a{color:inherit;text-decoration:none}
ul,ol{list-style:none}
::selection{background:var(--c-accent);color:var(--c-bg)}

/* Display type is always uppercase condensed and always tight. `text-wrap:
   balance` is what stops a three-word headline breaking one-two, which at this
   size is the difference between a masthead and an accident. */
h1,h2,h3,h4{font-family:var(--fj-display);font-weight:700;line-height:1.02;
  letter-spacing:-.01em;text-transform:uppercase;text-wrap:balance}

/* ---- accessibility ------------------------------------------------------- */
/* A visible focus ring on keyboard focus only. The site is dense with links;
   without this, tabbing through it is invisible.
 *
 * TWO TONES, BECAUSE NO SINGLE COLOUR CAN PASS ON BOTH GROUNDS.
 *
 * This was one 2px ring in --c-accent. Measured in a browser against the
 * colour actually behind each control, on nine pages:
 *
 *   .af-skip on every dark page      2.26:1
 *   .af-btn, .at-modeb, .at-surprise 2.26:1
 *   .wa-act, .wa-reg on the homepage 1.00:1   — the ring WAS its ground
 *
 * WCAG 1.4.11 wants 3:1 for a focus indicator. The dark accent is 6.21:1 on
 * ivory and 2.26:1 on basalt, and it is re-pointed to the lighter accent only
 * inside a list of dark containers — so whether a control passed depended on
 * whether it happened to sit inside one, which is not a system.
 *
 * There is no single colour that works: ivory is 14:1 on basalt and 1:1 on
 * ivory; the gold is 6:1 on basalt and 2.4:1 on paper. So the ring is drawn
 * twice — a pale inner ring and a dark outer one — and whichever ground
 * it lands on, one of the two is in full contrast against it. This is the
 * ordinary answer to a ring that must work on an unknown background and it is
 * why operating systems draw theirs the same way.
 */
:focus-visible{outline:2px solid var(--c-bg);outline-offset:2px;
  box-shadow:0 0 0 4px var(--c-primary)}
/* THE HALO HAS TO OUTWEIGH A COMPONENT'S OWN box-shadow.
   Several components use box-shadow as a border substitute — .sx-mo and
   .sx-portrait draw their 1px rule that way — at the same specificity as
   the rule above and in a stylesheet that loads after it, so they silently
   erased the focus halo and left a pale ring on pale paper at 1.38:1. Named
   with their element, these are 0,1,1 and win, and a component would now have
   to reach 0,2,0 to take the ring away by accident. */
a:focus-visible,button:focus-visible,input:focus-visible,select:focus-visible,
textarea:focus-visible,summary:focus-visible,[tabindex]:focus-visible{
  outline:2px solid var(--c-bg);outline-offset:2px;
  /* !important, and specificity alone genuinely cannot do this.
     box-shadow is this codebase's underline: `.st-stand a` draws its rule with
     `inset 0 -1px 0 var(--c-border)` and `.st--now .st-stand a` overrides that
     again — 0,1,1 and 0,2,1, in a file that loads after this one. Any
     selector strong enough to beat every such rule would have to be guessed at
     and re-guessed each time somebody adds another. The gate found thirteen of
     seventy-nine stops on one portrait page with the halo replaced by a sand
     underline at 1.38:1, and four with no halo at all at 1:1.
     A decorative shadow suppressed for as long as a control has keyboard focus
     is the correct trade; the underline comes straight back when focus leaves. */
  box-shadow:0 0 0 4px var(--c-primary) !important}
/* Three components replace the ring with an indicator of their own — a
   rectangle around a point on a map is not where the place is. They opt out of
   both tones, or the halo would put back the rectangle they removed. */
.ex-bar input:focus,.at-c:focus,.wa-map-city:focus{box-shadow:none !important}

.af-skip{position:absolute;left:-9999px;top:0;z-index:200;background:var(--c-primary);
  color:var(--c-bg);padding:12px 18px;font-family:var(--fj-mono);font-size:11px;
  letter-spacing:.18em;text-transform:uppercase}
.af-skip:focus{left:0}

/* Motion is decoration here, never information: every transition can be removed
   without losing meaning, so reduced-motion removes all of it. */
/* Delays go with the durations they were covering.
   Zeroing only the durations leaves every transition-delay in the file timing
   an animation that is no longer happening, and one of them is load-bearing.
   The hero stacks twenty-three captions and shows one; the hidden ones are held
   out of the tab order with `visibility`, transitioned as
   `visibility 0s linear .45s` so the element stays reachable for exactly as
   long as its opacity takes to fade. Under reduced motion the opacity snapped
   to zero and the delay ran its full course anyway, so for 450 milliseconds
   after every selection a caption was invisible, focusable, and had a link in
   it — measured: still there at 300ms, gone by 500ms.

   Which is to say the setting that exists to help people who are disorientated
   by movement was the only setting under which the hero put the focus ring on
   something nobody could see. PASS SEVEN never caught it because PASS SEVEN
   does not ask for reduced motion. */
@media(prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{transition-duration:.001ms !important;animation-duration:.001ms !important;
    transition-delay:0s !important;animation-delay:0s !important}
}

/* ---- primitives ---------------------------------------------------------- */
.af-frame{max-width:var(--frame);margin:0 auto;padding:0 44px}

/* The eyebrow. Mono, tracked wide, accent — it labels a thing without competing
   with the thing. */
.af-stamp{display:block;font-family:var(--fj-mono);font-size:11px;letter-spacing:.26em;
  text-transform:uppercase;color:var(--c-accent)}

/* Two buttons and no third. Solid is the one thing to do next; quiet is the
   alternative. A page with three equal buttons has no hierarchy. */
.af-btn{display:inline-block;font-family:var(--fj-mono);font-size:11px;letter-spacing:.18em;
  text-transform:uppercase;padding:13px 22px;border:1px solid var(--c-primary);
  transition:background var(--t-fast) var(--ease),color var(--t-fast) var(--ease),
    border-color var(--t-fast) var(--ease)}
.af-btn i{font-style:normal;margin-left:9px}
.af-btn--solid{background:var(--c-primary);color:var(--c-bg)}
.af-btn--solid:hover{background:var(--c-accent);border-color:var(--c-accent)}
/* The alternative, not the afterthought. It was a sand rule round muted type,
   which on a sand band is a button you have to look for. Forest on ivory is the
   same authority as the solid call with the weight taken out of it. */
.af-btn--quiet{border-color:var(--c-primary);color:var(--c-primary);background:var(--c-bg)}
.af-btn--quiet:hover{background:var(--c-primary);color:var(--c-bg)}

/* The underlined onward link, used wherever a block leads somewhere. */
.af-go{display:inline-block;font-family:var(--fj-mono);font-size:10.5px;letter-spacing:.2em;
  text-transform:uppercase;border-bottom:2px solid var(--c-accent);padding-bottom:4px;
  transition:color var(--t-fast) var(--ease)}
.af-go:hover{color:var(--c-accent)}

/* Numbered section head. The number is the navigation; the sentence is the
   argument. Used on every long page so a visitor always knows where they are. */
.af-head{display:grid;grid-template-columns:150px 1fr;gap:40px;align-items:start;
  margin-bottom:var(--sp-5)}
.af-head-no{border-top:2px solid var(--c-accent);padding-top:12px}
.af-head-no b{display:block;font-family:var(--fj-display);font-size:38px;font-weight:700;
  line-height:1;color:var(--c-accent)}
.af-head-no span{display:block;font-family:var(--fj-mono);font-size:10px;letter-spacing:.18em;
  text-transform:uppercase;color:var(--c-muted);margin-top:8px}
.af-head h2{font-size:clamp(30px,4vw,54px);max-width:24ch}
.af-head h2 em{font-style:normal;color:var(--c-accent)}
/* 44em of a 17px face is 748 pixels and 96 average characters a line. The
   comfortable range for continuous prose is 45-85; past 90 the eye loses
   the start of the next line on the way back. 72ch measures 78 average
   characters in this face — ch is the width of a zero, which here is 1.09
   times the average lowercase letter, so the two are not the same number. */
.af-note{margin-top:16px;color:var(--c-muted);max-width:72ch}
@media(max-width:880px){.af-head{grid-template-columns:1fr;gap:18px;margin-bottom:var(--sp-4)}}

/* ---- the country window -------------------------------------------------- */
/* The signature. A photograph masked into the country's own borders, or the
   outline filled in accent where there is no photograph yet. It is defined
   here, once, because it has to look identical on the gateway and on all
   fifty-four country pages — that is the whole point of it being a signature.
   It degrades to the filled outline rather than to a grey box, so a country
   with no photography is still finished. */
.af-window{position:relative}
.af-window svg{width:100%;height:100%;overflow:visible;display:block}
/* The fill belongs to the component, not to one wrapper: the gateway's hero
   draws the same window without an `.af-window` around it, and a scoped rule
   left it painting black there. */
.af-window-fill{fill:color-mix(in srgb,var(--c-accent) 88%,var(--c-primary))}
.af-window--onbasalt .af-window-fill{fill:var(--c-accent)}
/* One class for the SVG itself, so the build-time window (plate.py) and the
   runtime one (scripts/window.js) cannot drift into two different components. */
.af-window-svg{width:100%;height:100%;display:block;overflow:visible}

/* ---- the plate ----------------------------------------------------------- */
/* What a slot looks like before its photograph exists.
 *
 * Five hundred and sixty-seven of the five hundred and ninety-four image slots
 * in this dataset have no photograph yet, so this is not an edge case — it is
 * most of what the site currently looks like, and it is designed rather than
 * merely survived. The plate occupies exactly the box the photograph will
 * occupy, so swapping one for the other shifts nothing on the page.
 *
 * The ground is the country's own region tone, set per element, which is what
 * stops a page of unresolved slots reading as a grid of identical grey holes:
 * the Sahel and the Cape are different colours before either has a picture.
 * The country's outline sits behind, bled off the corner, at the weight of a
 * watermark. It never says "image missing" — it says what the photograph is of,
 * which is true and is the most useful thing it can say. */
/* The ground is the region's tone taken two thirds of the way to the house ink,
   not the tone itself. Two of the five region tones — the sand of North Africa
   and the ochre of Southern Africa — are light enough that cream type on them
   measured 3.4:1 and 2.5:1, and this component is the empty state on five
   hundred and sixty-seven image slots, so that was unreadable type across most
   of the site. Darkening the tone rather than the palette keeps each region's
   hue doing its job on the map, where it sits on paper and is never typed on.
   Worst case is now 5.4:1. */
.af-plate{position:relative;overflow:hidden;display:flex;flex-direction:column;
  justify-content:flex-end;padding:clamp(14px,4%,26px);
  background:color-mix(in srgb,var(--plate-tone,var(--c-primary)) 66%,var(--c-primary));
  color:var(--c-bg);isolation:isolate}
/* A wash of paper over the tone, so five saturated inks become five grounds a
   headline can sit on. */
.af-plate::after{content:"";position:absolute;inset:0;z-index:-1;
  background:linear-gradient(160deg,rgba(247,242,231,.16),rgba(247,242,231,0) 62%)}
.af-plate-shape{position:absolute;right:-14%;top:-10%;width:78%;height:auto;z-index:-1;
  opacity:.2;fill:var(--c-bg)}
.af-plate-eye{font-family:var(--fj-mono);font-size:9px;letter-spacing:.22em;
  text-transform:uppercase}
.af-plate-say{font-family:var(--fj-display);font-weight:700;text-transform:uppercase;
  line-height:1.02;letter-spacing:-.01em;margin-top:8px;
  font-size:clamp(17px,2.6cqw,30px);text-wrap:balance}
.af-plate-where{font-family:var(--fj-mono);font-size:9px;letter-spacing:.22em;
  text-transform:uppercase;margin-top:10px}
/* The caption is sized against the plate rather than the viewport: the same
   component is a 240px card on one page and a 1152px band on another, and a
   headline that ignores its own box is only ever right in one of them. */
@supports(container-type:inline-size){
  .af-plate{container-type:inline-size}
}
/* Behind a headline the page already has: the outline and the ground, nothing
   else. The shape moves to the middle and grows, because at full bleed a
   watermark in the corner reads as an accident rather than as a decision. */
.af-plate--ground .af-plate-shape{right:auto;left:50%;top:50%;
  transform:translate(-30%,-50%);width:auto;height:150%;opacity:.14}

/* ---- surfaces ------------------------------------------------------------ */
.af-zone{padding:var(--sp-7) 0}
.af-zone--dust{background:var(--fj-dust)}
/* Every dark ground re-points the accent at its lit variant, so the rules
   written against --c-accent do not need to know which surface they landed on.
   Any new dark panel joins this list and inherits the same correction. */
/* Dark grounds repoint two tokens, not one. --c-muted is a grey chosen against
   cream paper; on basalt it measures 2.4:1, so any metadata that wandered onto a
   dark panel was unreadable. Re-pointing it here means the hundred-odd rules
   already written against --c-muted keep working and get the right grey for the
   surface they landed on. Any new dark panel joins this list. */
/* .tf-hero, .tf-page and .wo-page joined late and the delay cost exactly what
   the note above predicts: /trans-afrique and /wonders are basalt from edge to
   edge and were still using the paper accent, so every .af-stamp on them
   measured 2.31:1 against a 4.5 bar. It went unseen because neither page was
   in tools/browser-checks.js either — they are in it now.

   The content wrappers rather than the <body>. Both pages carry a sticky
   masthead that is genuinely ivory, and remapping at the body would have
   handed the light plate the dark-ground tokens: .jn-routes is written against
   --c-muted, which would have become muted gold on cream at 2.37:1. Trading
   one contrast failure for another is not a fix. */
.af-zone--basalt,.af-dark,.st--now,.st-nowcard,.sx-nowsec,.sx-now,
.wa-zone.basalt,.wa-foot,.fj-zone.basalt,.fj-foot,.foot,.pl-foot,.af-foot,
.tq-hero,.tq-band,.fj-transect-step.lv6,.ct-dark,.at-dark,.wa-open,.wa-motion,
.tf-hero,.tf-page,.wo-page{
  --c-accent:var(--c-accent-lit);
  /* Muted gold is where the restraint in this palette lives. It is unusable on
     paper — 2.37:1 — and excellent on forest at 6.02:1, so it appears only
     here: the metadata voice on every dark ground, and nowhere on a light one.
     That is the whole of its job, and it is why a gold appearing at all reads
     as a change of ground rather than as decoration. */
  --c-muted:var(--c-gold);
  /* The map's third tier does NOT re-point here, and the reason is worth
     leaving: it looked as though it had to. "An operator of ours" is deep
     forest, and on a deep forest hero a coastal one like Namibia should read
     as a bite taken out of the continent. Gold was the obvious substitute —
     this palette's mark of authority on a dark surface, which is what the tier
     means. Rendered, gold measures 1.63:1 against the land it sits on and the
     tier stopped being a tier.

     Forest is 9.84:1 against the land, and the bite never happens, because
     every country on this map is stroked in ivory: Namibia's coast is drawn
     whether the sea behind it is paper or forest. The fix was already there.
     A tier has to contrast with what it sits on, not with what surrounds it. */
}
/* And this is the whole of where gold appears: the label above a section on a
   dark ground, and the column headings in a dark footer. One or two marks per
   band. Gold on every paragraph would be a theme; gold on the thing that names
   the section is a material. 6.15:1 on basalt. */
.af-zone--basalt .af-stamp,.wa-zone.basalt .af-stamp,.fj-zone.basalt .af-stamp,
.wa-zone.basalt .wa-head-no span,.af-zone--basalt .af-head-no span,
.wa-foot .wa-foot-col b,.fj-foot .fj-foot-col b,.foot .foot-col b,
.pl-foot .pl-foot-col b{color:var(--c-gold)}

.af-zone--basalt{background:var(--fj-basalt);color:var(--fj-onbasalt)}
.af-zone--basalt h2,.af-zone--basalt h3{color:var(--c-bg)}
.af-zone--basalt .af-note{color:var(--fj-onbasalt-dim)}
.af-zone--basalt .af-head-no span{color:var(--fj-onbasalt-dim)}
@media(max-width:880px){.af-zone{padding:var(--sp-6) 0}}

@media(max-width:560px){.af-frame{padding:0 20px}}

/* ---- explore: the universal index ---------------------------------------- */
/* Cmd/Ctrl-K from any page on the site. Lives in the design system rather than
   in a page's stylesheet because it is on every page, including the 404 — it is
   the one component whose whole job is that the site has a single way in. */

.ex-key{margin-left:6px;display:inline-flex;align-items:center;gap:7px;
  font-family:var(--fj-mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--c-muted);background:none;border:1px solid var(--c-border);
  padding:6px 10px;cursor:pointer;white-space:nowrap;
  transition:border-color var(--t-fast) var(--ease),color var(--t-fast) var(--ease)}
.ex-key:hover{border-color:var(--c-accent);color:var(--c-accent)}
.ex-key kbd{font-family:inherit;font-size:9px;letter-spacing:.08em;
  border:1px solid var(--c-border);padding:1px 5px;color:var(--c-muted)}
/* The keycap hint goes at 900, not 700: between those two widths the nav is
   still on and the pair together pushed the key past the right edge of a
   768 tablet on the busiest masthead. The word carries it alone. */
@media(max-width:900px){.ex-key kbd{display:none}}

html.ex-open{overflow:hidden}
.ex{position:fixed;inset:0;z-index:120;display:flex;align-items:flex-start;
  justify-content:center;padding:max(6vh,48px) 20px 20px}
.ex-veil{position:absolute;inset:0;background:color-mix(in srgb,var(--c-primary) 62%,transparent)}
.ex-panel{position:relative;width:min(680px,100%);max-height:82svh;display:flex;
  flex-direction:column;background:var(--c-bg);border:2px solid var(--c-primary);
  box-shadow:0 30px 80px color-mix(in srgb,var(--c-primary) 34%,transparent)}
.ex-h{position:absolute;left:-9999px}
.ex-bar{display:flex;align-items:center;gap:12px;padding:16px 18px;
  border-bottom:2px solid var(--c-primary)}
.ex-bar input{flex:1 1 0;min-width:0;font-family:var(--fj-text);font-size:19px;
  background:transparent;color:var(--c-ink);border:0;padding:4px 0}
/* The one field where the ring is genuinely redundant: the dialog it sits in
   opens with this input already focused and nothing else in it is tabbable
   until a result is highlighted, so a ring here marks the only place focus can
   be. The bar's own 2px rule under it is the boundary, and it is drawn in
   --c-primary against the page ground at 12:1. */
.ex-bar input:focus{outline:none}
.ex-esc{font-family:var(--fj-mono);font-size:9px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--c-muted);background:none;border:1px solid var(--c-border);padding:6px 9px;
  cursor:pointer}
.ex-esc:hover{border-color:var(--c-primary);color:var(--c-primary)}
.ex-status{padding:10px 18px 0;font-family:var(--fj-mono);font-size:9px;letter-spacing:.2em;
  text-transform:uppercase;color:var(--c-muted);min-height:1.2em}
.ex-list{overflow-y:auto;padding:8px 0 14px;flex:1 1 auto;overscroll-behavior:contain}
.ex-group{padding:16px 18px 6px;font-family:var(--fj-mono);font-size:9px;letter-spacing:.22em;
  text-transform:uppercase;color:var(--c-accent)}
.ex-row{display:block;padding:11px 18px;border-left:3px solid transparent}
.ex-row b{display:block;font-family:var(--fj-display);font-size:18px;text-transform:uppercase;
  line-height:1.1;font-weight:700}
.ex-row span{display:block;margin-top:3px;font-size:14.5px;color:var(--c-muted)}
.ex-row:hover,.ex-row.is-on{background:var(--fj-dust);border-left-color:var(--c-accent)}
.ex-row.is-on b{color:var(--c-accent)}
.ex-none{padding:18px;font-size:16px;color:var(--c-muted)}
.ex-foot{padding:12px 18px;border-top:1px solid var(--c-border);font-family:var(--fj-mono);
  font-size:9px;letter-spacing:.14em;line-height:1.8;text-transform:uppercase;
  color:var(--c-muted)}
@media(max-width:640px){
  .ex{padding:0}
  .ex-panel{width:100%;max-height:100svh;height:100svh;border-width:0}
  .ex-row{padding:14px 16px}
}
@media(pointer:coarse){.ex-key,.ex-esc{min-height:44px}}
/* The key sits between the routes and the page's primary call, and stays when
   the routes go. On a phone it is the label that carries it: there is no Cmd
   key to hint at, so the kbd hint is dropped and the word does the work. */
.ex-key{margin-left:auto}
.pl-routes+.ex-key,.at-routes+.ex-key,.jn-routes+.ex-key,.mt-routes+.ex-key,
.wa-routes+.ex-key,.fj-routes+.ex-key,.routes+.ex-key{margin-left:6px}
@media(max-width:1010px){
  .wa-routes+.ex-key,.fj-routes+.ex-key,.routes+.ex-key{margin-left:auto}
}
@media(max-width:700px){
  .pl-routes+.ex-key,.at-routes+.ex-key,.jn-routes+.ex-key,.mt-routes+.ex-key{margin-left:auto}
}
/* The key at phone widths. A masthead 320 pixels wide holds a wordmark, an index
   and a call to action only if one of them stops using words — and the index is
   the one that must survive, because it is the only control that reaches the
   whole site. So the glyph carries it and the label is dropped, with the
   accessible name still on the button. */
.ex-key-eye{width:15px;height:15px;flex:none;display:none}
.ex-key-word{display:inline}
@media(max-width:560px){
  .ex-key-eye{display:block}
  .ex-key-word{display:none}
  .ex-key{padding:9px 11px;min-width:44px;justify-content:center}
}
/* Below 420px the call to action gives up letter-spacing before the index gives
   up existing. Every masthead on the site is covered by one rule rather than
   six, because they all put a wordmark, the index and one button on a bar that
   is now three hundred and twenty pixels wide. */
@media(max-width:420px){
  header .btn,header .af-btn,header a[class*="btn"]{
    padding:9px 10px;font-size:8.5px;letter-spacing:.08em;white-space:nowrap}
  header .btn i,header .af-btn i,header a[class*="btn"] i{display:none}
  .wa-mast-in,.pl-mast,.at-mast,.jn-mast,.mt-mast,.mast-in,.fj-mast-in{gap:10px}
  .af-frame{padding:0 14px}
  .mark b,.wa-mark b,.pl-mark b{font-size:17px}
  .mark>span:last-child,.wa-mark span{display:none}
  .wa-mast-in{padding-left:14px;padding-right:14px}
  .jn-mast,.at-mast,.mt-mast{padding-left:10px;padding-right:10px;gap:8px}
  .mark b,.wa-mark b,.pl-mark b,.mt-mark b,.jn-mark b,.at-mark b{font-size:15px;
    letter-spacing:0}
  header .btn,header .af-btn,header a[class*="btn"]{padding:9px 8px;font-size:8px}
  .pl-mast{padding:0 14px}
  .ex-key{margin-left:auto}
}

/* ---- a finger is not a mouse pointer ------------------------------------- */
/* WCAG 2.5.8 asks for 24x24 CSS pixels. Measured on a 390px phone, twelve of
   the eighteen page families had targets at 23, 20, 17 and 13 pixels tall — all
   of them wide enough and none of them tall enough, because they are set in
   mono at 9 to 11px with a rule under them and no padding.

   The widths are fine, so only the block padding changes, and only where a
   finger is the pointer: on a mouse these keep the tighter setting the design
   was drawn at. A link sitting inside a sentence is exempt under the same
   success criterion and is left alone. */
@media(pointer:coarse){
  .af-go{padding-top:6px;padding-bottom:9px}
  .af-stamp{padding-block:2px}
}

/* AN ADOPTED PHOTOGRAPH RESERVES ITS BOX
   adopt.py used to leave width and height off every photograph it placed, and
   its note explained why: these slots are styled `width:100%; aspect-ratio:N`
   with no height, so the attributes make both dimensions definite, aspect-ratio
   is dropped, and the picture renders at its own shape rather than the slot's.
   Measured at the time: 479x1280 where 479x638 was wanted.

   height:auto is what resolves it. With only one dimension definite the
   attributes become a ratio hint, aspect-ratio still decides the shape, and the
   box is held before the file arrives — the same fix the feel cards and the 122
   country photographs already carry. Scoped to data-illustration so it reaches
   the adopted photographs and nothing else on these pages. */
img[data-illustration]{height:auto}


/* ---- the country atlas ----------------------------------------------------- */
/* TWO MAPS, ONE GRAMMAR, FIFTY-FOUR COUNTRIES.
   Built by tools/tourism/countrymap.py, which records where every layer comes
   from and why the layers this repository has no coordinates for — mountains,
   parks, numbered route markers — are not drawn rather than approximated.

   The locator answers "where in Africa is this" and carries no labels, no
   water and no cities: a locator that has to be read has failed. The atlas is
   the country cropped out of the continental projection rather than
   renormalised into its own box, which is the whole difference between an
   atlas plate and a silhouette — it keeps its real proportion, its real
   orientation, and its real relationship to everything it touches. */
.cm-loc{width:100%;height:auto;display:block}
.cm-loc-rest path{fill:color-mix(in srgb,var(--c-primary) 12%,transparent);stroke:none}
.cm-loc-here{fill:var(--c-accent);stroke:none}

.cm-atlas{width:100%;height:auto;display:block}
/* Sized off --u, the frame's own width in viewBox units, which the generator
   writes onto the svg. The crop differs per country — Eswatini's frame is 60
   units across and Algeria's 300 — so a fixed stroke is a hairline on one and a
   rope on the other, and a fixed font size is a caption on one and a word
   filling the map on the other. */
/* Sea and land have to be told apart at a glance or the plate is one beige
   field with a border network drawn on it — which is what the first version
   was, because both were translucent washes a few per cent apart. So: the sea
   is a real tint and the land is OPAQUE paper laid over it. That way the coast
   is a hard edge the eye finds without being pointed at it, which is the whole
   job of a coastline on a map of a coastal country. */
.cm-sea{fill:color-mix(in srgb,#2C6E7F 16%,var(--c-bg));stroke:none}
.cm-near path{fill:color-mix(in srgb,var(--c-primary) 7%,var(--c-bg));
  stroke:color-mix(in srgb,var(--c-primary) 26%,transparent);
  stroke-width:calc(var(--u) * .0035)}
.cm-water .cm-river{fill:none;stroke:color-mix(in srgb,#2C6E7F 62%,transparent);
  stroke-width:calc(var(--u) * .004);stroke-linecap:round;stroke-linejoin:round}
.cm-water .cm-lake{fill:color-mix(in srgb,#2C6E7F 34%,var(--c-bg));stroke:none}
/* Opaque like the land it sits in, not a wash. Left translucent it took its
   colour from the sea rect underneath and Kenya came out grey-mauve — the
   country reading colder than the countries around it, which is backwards. */
.cm-here{fill:color-mix(in srgb,var(--c-accent) 15%,var(--c-bg));
  stroke:var(--c-primary);stroke-width:calc(var(--u) * .006);stroke-linejoin:round}
/* THE `* 1px` ON EVERY TYPE RULE IS LORE-BEARING, DO NOT TIDY IT AWAY.
   --u is written as a bare number so the geometry properties can use it:
   stroke-width takes a <number> in SVG, so calc(var(--u) * .006) is valid
   there. font-size and letter-spacing take a <length>, and a unitless calc
   result is not one — the declaration is thrown out and the text falls back to
   the inherited 16px, which inside an svg means SIXTEEN USER UNITS, a tenth of
   Kenya's frame. That is what made the first two renders look like headings
   over the map, and shrinking the numbers could never have fixed it. Inside an
   svg a CSS px IS a user unit, so `* 1px` both makes the value legal and keeps
   it frame-relative. */
.cm-near-name{font-family:var(--fj-mono);font-size:calc(var(--u) * .013 * 1px);
  letter-spacing:calc(var(--u) * .0016 * 1px);
  fill:color-mix(in srgb,var(--c-primary) 50%,transparent)}
/* Seychelles and Mauritius: no polygon in the continental outline, so the
   country is a marked position rather than a shape. Ringed, not dotted, so it
   never reads as one of the cities. */
.cm-here-pt circle{fill:var(--c-accent)}
.cm-here-pt .cm-here-ring{fill:none;stroke:var(--c-accent);
  stroke-width:calc(var(--u) * .004)}
.cm-here-pt text{font-family:var(--fj-mono);font-size:calc(var(--u) * .018 * 1px);
  letter-spacing:calc(var(--u) * .002 * 1px);fill:var(--c-primary);
  text-anchor:middle}
.cm-loc-here .cm-loc-ring{fill:none;stroke:var(--c-accent);stroke-width:6}
.cm-loc-here circle{fill:var(--c-accent)}
.cm-city circle{fill:var(--c-primary)}
.cm-city text{font-family:var(--fj-mono);font-size:calc(var(--u) * .0135 * 1px);
  letter-spacing:calc(var(--u) * .001 * 1px);fill:var(--c-primary)}
.cm-scale line{stroke:var(--c-primary);stroke-width:calc(var(--u) * .004);fill:none}
.cm-scale text{font-family:var(--fj-mono);font-size:calc(var(--u) * .0125 * 1px);
  letter-spacing:calc(var(--u) * .001 * 1px);fill:var(--c-primary)}
.cm-north line{stroke:var(--c-primary);stroke-width:calc(var(--u) * .004);fill:none}
.cm-north text{font-family:var(--fj-mono);font-size:calc(var(--u) * .015 * 1px);
  letter-spacing:0;fill:var(--c-primary)}
/* On a dark ground the same plate inverts rather than being redrawn. */
.af-dark .cm-sea,.tf-page .cm-sea,.wa-zone.basalt .cm-sea{
  fill:color-mix(in srgb,#2C6E7F 30%,var(--c-primary))}
.af-dark .cm-near path,.tf-page .cm-near path,.wa-zone.basalt .cm-near path{
  fill:color-mix(in srgb,var(--c-bg) 9%,var(--c-primary));
  stroke:color-mix(in srgb,var(--c-bg) 22%,transparent)}
.af-dark .cm-here,.tf-page .cm-here,.wa-zone.basalt .cm-here{
  fill:color-mix(in srgb,var(--c-accent-lit) 20%,transparent);stroke:var(--c-bg)}
.af-dark .cm-near-name,.tf-page .cm-near-name,.wa-zone.basalt .cm-near-name{
  fill:color-mix(in srgb,var(--c-bg) 52%,transparent)}
.af-dark .cm-city circle,.af-dark .cm-city text,.af-dark .cm-scale line,
.af-dark .cm-scale text,.af-dark .cm-north line,.af-dark .cm-north text,
.tf-page .cm-city circle,.tf-page .cm-city text,.tf-page .cm-scale line,
.tf-page .cm-scale text,.tf-page .cm-north line,.tf-page .cm-north text{
  fill:var(--c-bg);stroke:var(--c-bg)}
.af-dark .cm-scale line,.tf-page .cm-scale line,
.af-dark .cm-north line,.tf-page .cm-north line{fill:none}
.af-dark .cm-loc-rest path,.tf-page .cm-loc-rest path{
  fill:color-mix(in srgb,var(--c-bg) 12%,transparent)}
.af-dark .cm-loc-here,.tf-page .cm-loc-here{fill:var(--c-accent-lit)}


/* ---- paper ----------------------------------------------------------------
 * WHAT HAPPENS WHEN SOMEBODY PRINTS THIS.
 *
 * Two of fourteen stylesheets had print rules, and neither was one of the ones
 * people print. A traveller printing a crossing's itinerary, a country
 * portrait or a page of rates to take to somebody got: a basalt page, which is
 * to say a black rectangle costing most of a cartridge, with a sticky masthead
 * stamped across the top of every sheet and every link's destination lost.
 *
 * THE TOKENS DO THE WORK. Every surface on this site is written against
 * --c-bg, --c-primary, --c-muted and the basalt pair, so re-pointing those six
 * for print turns every dark page light without touching a single component
 * rule. That is the entire return on having had a token system: one block here
 * corrects fourteen stylesheets, including the four written after it.
 */
@media print {
  :root{
    --c-bg:#fff;
    --c-primary:#111;
    --c-muted:#444;
    --c-border:#bbb;
    --fj-basalt:#fff;
    --fj-onbasalt:#111;
    --fj-onbasalt-dim:#444;
    /* Burnt sienna prints as mud on a monochrome printer and as an expensive
       flat on a colour one. Darkened, it stays legible either way. */
    --c-accent:#6E3220;
    --c-accent-fill:#6E3220;
    --c-accent-lit:#6E3220;
    --c-gold:#5a4a2e;
    --fj-rule:1px solid #bbb;
    --fj-rule-dark:1px solid #bbb;
  }
  body{background:#fff;color:#111}

  /* THE DARK SURFACES, BY NAME. Re-pointing --fj-basalt whitens everything
     painted with it, but several surfaces are painted with --c-primary
     directly — which is the INK token and cannot be turned white without
     taking the text with it. Measured in print emulation, .wa-open, .tf-close
     and .pl-foot were still coming out at 0.006 luminance, which is a black
     rectangle on paper. This is the same list the light/dark token swap uses
     further up the file, pointed the other way. */
  .af-zone--basalt,.af-dark,.st--now,.st-nowcard,.sx-nowsec,.sx-now,
  .wa-zone.basalt,.wa-foot,.fj-zone.basalt,.fj-foot,.foot,.pl-foot,.af-foot,
  .tq-hero,.tq-band,.fj-transect-step.lv6,.ct-dark,.at-dark,.wa-open,.wa-motion,
  .tf-hero,.tf-page,.tf-close,.wo-page,.wo-body,.tr-body{
    background:#fff !important;color:#111 !important}

  /* Chrome that means nothing on paper. A sticky masthead prints on top of
     the first line of every sheet, and a skip link is a link to a place the
     reader is already looking at. */
  /* !important, and it is earned. The mastheads are declared display:flex in
     journey.css and the others, which load AFTER this file at the same
     specificity — so a plain display:none here loses every tie and the
     sticky bar prints across the top of sheet one. This is the case the
     keyword exists for. */
  .af-skip,.jn-mast,.wa-mast,.at-mast,.pl-mast,.mt-mast,.fj-mast,.mast,
  .wo-bar,.pi-jump,.tf-series,.wa-now-turn,.jn-enq-copy,.ex-bar,
  .af-btn,.wa-act,.at-surprise,.at-modeb{display:none !important}

  /* position:fixed prints once, at the top of the first sheet, over whatever
     is there. The band's photograph is decoration; on paper it is a hazard. */
  .wa-seam-pic,.wa-win-fixed,[style*="position:fixed"]{display:none}
  .wa-seam,.wa-open{clip-path:none}

  /* A printed link whose destination is invisible is a dead end on paper.
     Internal ones are expanded to a full URL; a reader with a sheet of paper
     has no site to be relative to. */
  main a[href^="/"]::after{content:" (afrinkong.com" attr(href) ")";
    font-size:.82em;color:#555;word-break:break-all}
  main a[href^="http"]::after{content:" (" attr(href) ")";
    font-size:.82em;color:#555;word-break:break-all}
  main a[href^="#"]::after,main a[href^="mailto"]::after{content:""}

  /* Nothing splits across a sheet if it can be helped, and no heading is left
     stranded at the foot of one. */
  figure,article,.tr-block,.wo-card,.ct-compare,.hw-step{break-inside:avoid}
  h1,h2,h3{break-after:avoid}
  img{max-width:100%;height:auto}

  /* Motion has no meaning on paper and reveal-on-scroll would print blank. */
  *{transition:none !important;animation:none !important;opacity:1 !important}
}

/* ---- the boundary between two companies on one domain ----------------------
 * MOVED HERE FROM kamerun.css, AND THE MOVE IS THE FIX.
 *
 * This bar marks where Afrinkong ends and its operator begins, so it belongs
 * to neither brand's stylesheet — it belongs to the file both of them load.
 * In kamerun.css it reached /about, /pricing and /services and missed
 * /cameroon and /contact, which do not load that file: those two carry their
 * CSS inline. So on the operator's own landing page and on its enquiry page
 * — the two where a reader is most likely to be about to spend money with
 * somebody whose name does not match the domain — the bar has been
 * completely unstyled since it shipped. Plain text at the full width of the
 * page, which the measure gate reported as 184 characters.
 *
 * It also explains why the max-width added to fix that measure did nothing:
 * it was added to a file those two pages never read.
 */
.af-operator-bar{display:flex;flex-wrap:wrap;align-items:baseline;
  justify-content:space-between;gap:6px 24px;
  max-width:var(--frame);margin:0 auto;padding:11px 28px;
  border-bottom:var(--fj-rule)}
/* Capped where a sentence reads. Left to fill the bar it ran to 184
   characters on the two widest pages — the measure gate caught it, which is
   what the measure gate is for. */
.af-operator-say{margin:0;font-size:13px;line-height:1.5;color:var(--c-muted);
  max-width:78ch}
.af-operator-say b{font-weight:700;color:var(--c-primary)}
.af-operator-go{display:flex;flex-wrap:wrap;gap:4px 18px;margin:0}
.af-operator-go a{display:inline-flex;align-items:center;min-height:24px;
  font-family:var(--fj-mono);font-size:9px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--c-accent);
  border-bottom:1px solid color-mix(in srgb,var(--c-accent) 40%,transparent);
  transition:border-color var(--t-fast) var(--ease)}
.af-operator-go a:hover{border-bottom-color:var(--c-accent)}
@media(max-width:760px){
  .af-operator-bar{padding:10px 18px}
  .af-operator-say{font-size:12.5px}
}

/* ---- the colophon, and the footer four pages did not have ----------------- */
/* THE COLOPHON RULE WAS WRITTEN THREE TIMES AND REACHED NEITHER OF THE PAGES
   THAT NEEDED IT MOST.
   Identical copies of `.af-co-link` live in places.css, journey.css and (scoped
   to .wa-foot) gateway.css. /atlas, /meet, /journey and /places load none of
   the first two, so the day those pages gained a colophon the three statements
   would have rendered as default browser links — underlined blue, on basalt.
   Canonical here, in the sheet every page on the site already links. The two
   copies stay where they are only as ground-specific overrides. */
.af-co-links{display:flex;flex-wrap:wrap;gap:4px 18px;margin-top:10px}
.af-co-link{font-family:var(--fj-mono);font-size:9px;letter-spacing:.18em;
  text-transform:uppercase;color:var(--c-muted);display:inline-flex;
  align-items:center;min-height:24px;border-bottom:1px solid transparent;
  transition:color var(--t-fast) var(--ease),border-color var(--t-fast) var(--ease)}
.af-co-link:hover{color:var(--c-accent);border-bottom-color:var(--c-accent)}
.af-co-brand{font-weight:700}
.af-co-where{display:block;margin-top:5px;text-transform:none}

/* ===== THE SHELL. Phase 5 + 6. ==========================================
   One masthead for 1,597 pages, replacing ten.

   Written here rather than in a new stylesheet because afrinkong.css is
   already linked by 1,596 of the 1,597 pages, so the shell costs no extra
   request anywhere.

   TWO ROWS, AND THE SECOND IS CONTEXTUAL. Row one is the platform: the mark,
   the two areas, About. Row two is the children of the area THIS page is in,
   so a visitor always sees the room they are standing in. No dropdown, because
   the shell has to work with no JavaScript and a hover menu fails touch and
   keyboard both.

   FOUR BREAKPOINTS, NOT THIRTY-ONE. 900 / 760 / 560 are three of the four
   canonical widths from tokens.css; the shell uses no others. */
.af-shell{position:sticky;top:0;z-index:80;background:var(--c-bg);
  border-bottom:2px solid var(--c-primary)}
.af-shell-in{max-width:var(--frame);margin:0 auto;padding:12px 44px;
  display:flex;align-items:center;gap:30px}

.af-shell-mark{display:flex;align-items:center;gap:10px;margin-right:auto;
  font-family:var(--fj-display);font-size:19px;font-weight:700;
  text-transform:uppercase;line-height:1;letter-spacing:-.01em;
  white-space:nowrap;color:var(--c-primary)}
.af-shell-mark span{display:none}

/* The two areas. Larger and heavier than the row beneath them, because they
   are the architecture and their children are the contents. */
.af-shell-nav{display:flex;gap:26px}

/* THE AREAS ARE DISCLOSURES, NOT LINKS.
   An area is not a page. This used to be an anchor pointing at the first of
   its own children, so on /places "Explore" led to the page you were already
   standing on.
   <details> because it opens with no JavaScript, is keyboard operable by
   default and announces its expanded state. The panel is absolutely
   positioned, so the seven things under Explore cost the masthead no height at
   all — which is what lets them be present on every page without the band
   clearance problem that removed them in the first place. */
.af-shell-area{position:relative}
.af-shell-area>summary{list-style:none;cursor:pointer;display:flex;
  align-items:center;gap:6px;min-height:24px;
  font-family:var(--fj-mono);font-size:11px;letter-spacing:.2em;
  text-transform:uppercase;color:var(--c-muted);padding:8px 0;
  border-bottom:2px solid transparent;transition:color .18s,border-color .18s}
.af-shell-area>summary::-webkit-details-marker{display:none}
/* The affordance. A caret that turns, so the control says it opens before it
   is pressed — without it a disclosure reads as a dead label. */
.af-shell-area>summary::after{content:"";width:5px;height:5px;
  border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;
  transform:translateY(-2px) rotate(45deg);transition:transform .18s}
.af-shell-area[open]>summary::after{transform:translateY(1px) rotate(225deg)}
.af-shell-area>summary:hover{color:var(--c-primary)}
.af-shell-area.is-here>summary{color:var(--c-primary);
  border-bottom-color:var(--c-accent)}
.af-shell-area[open]>summary{color:var(--c-primary)}

/* ONE DISCLOSURE PANEL, NOT TWO.
   This and the phone menu are the same component — a list of destinations
   dropped from a summary — so they share these rules rather than restating
   them. They were briefly two: the desktop copy carried padding 10px/16px and
   min-height 24px against the phone's 11px/18px and 44px, which is a smaller
   tap target reached by the same finger on a touch laptop. Nothing rendered
   differently, because 15px type in 20px of padding clears 24px without ever
   consulting the minimum — so the divergence was invisible and would have
   stayed invisible until somebody tightened the type. Two interpretations of
   one component is the failure this shell exists to prevent; keep them here. */
.af-shell-area-menu,.af-shell-menu-in{position:absolute;top:100%;z-index:90;
  background:var(--c-bg);border:2px solid var(--c-primary);padding:6px 0 10px}
.af-shell-area-menu a,.af-shell-menu-in a{display:block;padding:11px 18px;
  min-height:44px;font-size:15px;color:var(--c-primary);
  border-top:var(--fj-rule)}
.af-shell-area-menu a:hover,.af-shell-menu-in a:hover{background:var(--c-sand)}
.af-shell-area-menu a[aria-current="page"],
.af-shell-menu-in a[aria-current="page"]{color:var(--c-accent)}

/* Where they legitimately differ: which edge they hang from, and how wide.
   Explore drops from a control in the middle of the row and holds a fixed
   width; the phone menu hangs off the right edge and takes the viewport. */
.af-shell-area-menu{left:0;min-width:230px}
.af-shell-area-menu a:first-child{border-top:0}

.af-shell-util{display:flex;gap:20px}
.af-shell-util a{font-family:var(--fj-mono);font-size:10px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--c-muted);padding:8px 0}
.af-shell-util a:hover{color:var(--c-accent)}

/* Row two. Scrolls in its own container above 760 rather than wrapping, so the
   masthead height never changes between two pages of the same family. */
.af-shell-sub{max-width:var(--frame);margin:0 auto;padding:0 44px 10px;
  display:flex;gap:22px;overflow-x:auto;scrollbar-width:none}
.af-shell-sub::-webkit-scrollbar{display:none}
.af-shell-sub a{font-family:var(--fj-text);font-size:14px;white-space:nowrap;
  color:var(--c-muted);padding:2px 0;border-bottom:1px solid transparent}
.af-shell-sub a:hover{color:var(--c-primary);border-bottom-color:var(--c-border)}
.af-shell-sub a[aria-current="page"]{color:var(--c-primary);
  border-bottom-color:var(--c-accent)}

/* PRODUCT IDENTITY, BENEATH THE PLATFORM BAR RATHER THAN INSTEAD OF IT.
   Trans Afrique, the Journey Fund, the builder and the atlas keep their
   character; what they stop keeping is their own idea of what a masthead is.
   Sand ground so the band reads as a different level rather than a second
   navigation. */
.af-shell-prod{background:var(--c-sand);border-top:var(--fj-rule)}
.af-shell-prod{max-width:none;padding:0}
.af-shell-prod{display:flex;align-items:baseline;gap:24px;flex-wrap:wrap;
  padding:9px 44px}
/* 24px minimum, measured rather than assumed. This shipped at 22px tall — a
   15px display line in a 9px-padded band — and the browser suite caught it on
   nineteen pages. WCAG 2.2 asks 24 of anything that is not a link inside a
   sentence, and a product name in a navigation band is not that. The band's own
   padding does not change; the link grows into it. */
.af-shell-prod-n{display:inline-flex;align-items:center;min-height:24px;
  font-family:var(--fj-display);font-size:15px;font-weight:700;
  text-transform:uppercase;letter-spacing:.02em;color:var(--c-primary)}
.af-shell-prod-nav{display:flex;gap:18px;flex-wrap:wrap}
.af-shell-prod-nav a{font-family:var(--fj-mono);font-size:10px;
  letter-spacing:.14em;text-transform:uppercase;color:var(--c-muted);padding:4px 0}
.af-shell-prod-nav a:hover{color:var(--c-accent)}
.af-shell-prod-nav a[aria-current="page"]{color:var(--c-primary)}

/* THE OPERATOR'S SHELL. A different company's front door on the same domain,
   and it must not wear Afrinkong's navigation. It used to share the .fj-mast
   class with 55 Afrinkong tourism pages while carrying entirely different
   links, so a change to one changed both. */
.af-shell--operator{border-bottom-color:var(--c-accent)}
.af-shell--operator .af-shell-nav{margin-left:auto;gap:22px}
.af-shell--operator .af-shell-nav a{font-family:var(--fj-mono);font-size:11px;
  letter-spacing:.16em;text-transform:uppercase;color:var(--c-muted);padding:8px 0}
.af-shell--operator .af-shell-nav a:hover{color:var(--c-accent)}

/* THE PHONE MENU. <details> because it opens with no JavaScript, is keyboard
   operable by default, and announces its own expanded state — none of which is
   true of a button plus a class plus aria somebody has to keep in step. */
.af-shell-menu{display:none;margin-left:auto;position:relative}
.af-shell-menu>summary{list-style:none;cursor:pointer;
  font-family:var(--fj-mono);font-size:10px;letter-spacing:.2em;
  text-transform:uppercase;color:var(--c-primary);
  padding:11px 0;min-height:44px;display:flex;align-items:center}
.af-shell-menu>summary::-webkit-details-marker{display:none}
.af-shell-menu[open]>summary{color:var(--c-accent)}
/* The shared panel rules are above, with the area menu. Only the anchoring,
   the width and the group eyebrow are the phone menu's own. */
.af-shell-menu-in{right:0;min-width:min(78vw,290px)}
.af-shell-menu-in b{display:block;font-family:var(--fj-mono);font-size:9.5px;
  letter-spacing:.2em;text-transform:uppercase;color:var(--c-accent);
  padding:14px 18px 6px}
.af-shell-menu-in span{display:block}

@media(max-width:900px){
  .af-shell-in{padding:11px 26px;gap:20px}
  .af-shell-sub{padding:0 26px 9px}
  .af-shell-prod{padding:8px 26px}
  .af-shell-nav{gap:20px}
  .af-shell-util{display:none}
}
@media(max-width:760px){
  .af-shell-in{padding:9px 22px}
  .af-shell-sub{padding:0 22px 8px}
  .af-shell-prod{padding:7px 22px;gap:14px}
  /* One row: mark and Menu. The areas and their children all live in the
     disclosure, as a list, because two scrolling rows on a phone is two
     things to miss. */
  .af-shell-nav,.af-shell-sub{display:none}
  .af-shell-menu{display:block}
  .af-shell-mark{font-size:17px}
}
@media(max-width:560px){
  .af-shell-in{padding:8px 18px}
  .af-shell-prod{padding:6px 18px}
  .af-shell-mark{font-size:15px;gap:8px}
  .af-shell-prod-n{font-size:13px}
}

/* /atlas, /meet, /journey and /places ended at </main>: no footer, no way
   onward, and no statement anywhere on the page of who the company is. Basalt
   rather than a country tone, because none of the four is about one country
   and a fixed ground is one the dark-surface tokens are already right for. */
.af-foot{background:var(--fj-basalt);color:var(--fj-onbasalt);
  margin-top:var(--sp-7);padding:var(--sp-6) 0 var(--sp-5)}
.af-foot-in{max-width:var(--frame);margin:0 auto;padding:0 44px}
.af-foot-brand{display:inline-block;font-family:var(--fj-display);font-size:26px;
  font-weight:700;text-transform:uppercase;color:var(--c-bg)}
.af-foot-brand span{display:block;font-family:var(--fj-mono);font-size:9.5px;
  font-weight:400;letter-spacing:.28em;color:var(--c-accent);margin-top:7px}
.af-foot-nav{display:flex;flex-wrap:wrap;gap:6px 26px;margin-top:var(--sp-5);
  padding-top:20px;border-top:var(--fj-rule-dark)}
.af-foot-nav a{display:inline-flex;align-items:center;min-height:28px;
  font-size:14.5px;color:var(--fj-onbasalt-dim);
  border-bottom:1px solid transparent;transition:color var(--t-fast) var(--ease)}
.af-foot-nav a:hover{color:var(--c-bg)}
.af-foot-co{margin-top:22px;padding-top:18px;border-top:var(--fj-rule-dark);
  font-family:var(--fj-mono);font-size:9px;letter-spacing:.12em;line-height:1.7;
  color:var(--fj-onbasalt-dim)}
.af-foot-co b{font-weight:400;color:var(--c-bg)}
.af-foot-co .af-co-brand{font-weight:700;color:var(--c-bg)}
/* On basalt the paper grey is 2.31:1. The dark-ground list below re-points
   --c-muted to gold for exactly this reason, and .af-foot joins it. */
.af-foot .af-co-link{color:var(--fj-onbasalt-dim)}
.af-foot .af-co-link:hover{color:var(--c-bg);border-bottom-color:var(--c-accent)}
@media(max-width:560px){.af-foot-in{padding:0 20px}}

/* The country pages have a footer and had no colophon in it: the company was
   named in their JSON-LD, which is to say to machines and not to readers. */
.fj-foot-co{margin-top:0;padding:0 0 26px;font-family:var(--fj-mono);
  font-size:9px;letter-spacing:.12em;line-height:1.7;color:var(--fj-onbasalt-dim)}
.fj-foot-co b{font-weight:400;color:var(--c-bg)}
.fj-foot-co .af-co-brand{font-weight:700;color:var(--c-bg)}
.fj-foot .af-co-link{color:var(--fj-onbasalt-dim)}
.fj-foot .af-co-link:hover{color:var(--c-bg);border-bottom-color:var(--c-accent)}

/* ---- the mark -------------------------------------------------------------
 * THE COMPANY HAD A LOGO AND THE SITE NEVER SHOWED IT.
 *
 * images/brand/ has carried a mark and a full lockup since the beginning, and
 * the only place either of them reached a reader was the favicon and the
 * home-screen icon — the two sizes at which a logo is too small to be one.
 * Every footer on 1,597 pages set the company's name in type and nothing else.
 *
 * The mark rather than the lockup, everywhere. The lockup is the mark plus the
 * wordmark plus the tagline; every footer here already prints the wordmark and
 * the tagline in the display and mono faces, so the lockup would print both a
 * second time, in a third face, one of which is a photograph of type.
 *
 * One class, one file, one custom property for the size. `--af-emblem` is set
 * inline per use — 60 on the homepage, 52 in the brand blocks, 24 in the
 * places bar — because the size is a property of the place it is used, not of
 * the mark, and a modifier class per size is a class per size.
 */
/* max-width:none matters and is not tidying. The global img rule sets
   max-width:100%, and in the masthead lockup the mark sits in an `auto` grid
   track: the track sizes itself from the image, the image's max-width resolves
   against the track, and the pair settle at zero. The mark computed 0px wide
   and 34px tall on the homepage and simply was not there. An explicit width
   with no percentage cap breaks the circle. */
.af-emblem{display:block;width:var(--af-emblem,52px);height:var(--af-emblem,52px);
  max-width:none;flex:0 0 auto}
/* Inline against a line of text: on the baseline rather than above it, and
   sized against the line it sits in. */
.af-emblem--inline{display:inline-block;vertical-align:-.34em;margin-right:9px}

/* The brand blocks: mark above the wordmark, which is how a mark and a
   wordmark stack when the wordmark is set in type and not drawn. */
.af-foot-brand,.wa-foot-brand,.fj-foot-brand{display:block}
.af-foot-brand .af-emblem,.wa-foot-brand .af-emblem,.fj-foot-brand .af-emblem{
  margin-bottom:14px}
.af-foot-brand b,.wa-foot-brand b,.fj-foot-brand b{display:block;font-weight:inherit}

/* THE MASTHEAD LOCKUP: THE MARK BESIDE THE NAME, NOT ABOVE IT.
   Every masthead on this site is a fixed 78px bar carrying two lines of type —
   a mono eyebrow over the page's name — and the marks are laid out as flex
   columns to stack them. A mark added to that column would be a third row in a
   bar with room for two.

   Beside them instead, spanning both lines, which is the shape a mark and a
   wordmark have always had. `a.af-lockup` rather than `.af-lockup` on purpose:
   each page family declares its own .xx-mark as display:flex in its own
   stylesheet, and those load after this file — an element plus a class beats a
   class whatever the order, which is the only way to state this once instead
   of five times. */
a.af-lockup{display:grid;grid-template-columns:auto 1fr;
  column-gap:12px;align-items:center;line-height:1}
a.af-lockup .af-emblem{grid-row:1 / span 2;align-self:center;margin:0}
a.af-lockup > i,a.af-lockup > b,a.af-lockup > span{grid-column:2}
/* No lazy loading on the mark in a masthead: it is above the fold on every
   page it appears on, and a lazy logo is a logo that arrives late. */
.af-emblem--mast{width:var(--af-emblem,34px);height:var(--af-emblem,34px)}

/* THE MASTHEAD RUNS OUT OF ROOM BEFORE THE PHONE DOES.
   The mark costs 34px and a 12px gap, and four mastheads had no 46px to give:
   at 320 the atlas overflowed its own document by 14 pixels, the places index
   by 11, /404 by 8 and Trans Afrique by 5 — the right-hand call pushed past
   the edge. Measured, not assumed: /journey and the homepage were unaffected
   and are left alone.

   What goes is the eyebrow, not the mark. Every one of these mastheads reads
   AFRINKONG in 9.5px mono over the page's name, and beside the mark that
   eyebrow is the redundant half — the mark says Afrinkong, which is what a
   mark is. It is hidden from the eye and kept for a screen reader, because
   alt="" on the mark means the company's name would otherwise be announced
   nowhere in the masthead. The result on a phone is [mark] PAGE NAME, which is
   a cleaner lockup than three things in 320 pixels.

   The mark also comes down a size between there and 900, where the same
   right-hand call was 11 pixels over on the places index at 768. */
@media(max-width:560px){
  a.af-lockup > i{position:absolute;width:1px;height:1px;padding:0;margin:-1px;
    overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}
  a.af-lockup{column-gap:8px}
  a.af-lockup .af-emblem{grid-row:auto;align-self:center}
  /* width and height, not the custom property: --af-emblem is set inline on
     the element and an inline custom property beats any stylesheet that tries
     to redefine it. The first cut of this block set --af-emblem and the mark
     stayed 34px at every width, which the measurement caught and the eye
     would not have. */
  .af-emblem--mast{width:26px;height:26px}
}
@media(min-width:561px) and (max-width:900px){
  a.af-lockup{column-gap:8px}
  .af-emblem--mast{width:26px;height:26px}
}

/* A <picture> is a wrapper and nothing else, so it should not be a box.
   `modern` wraps every self-hosted photograph to offer AVIF and WebP before
   the JPEG, which changes each image's parent; display:contents takes the
   wrapper out of layout entirely, so the <img> goes on participating in its
   flex or grid container exactly as it did. Checked before relying on it:
   `> img` appears zero times across all fifteen stylesheets, and so do
   img:first-child and img:nth-*, so nothing was reaching the image as a
   direct child in the first place. */
picture{display:contents}

/* ---- the explain family: /travel-points and /trust ----------------------- */
/* Pages whose job is to make an existing model legible rather than to sell
   anything. They carry no photography and no call to action, because a page
   answering "who am I dealing with" that ends in a button is answering a
   different question.

   These are af- rather than a prefix of their own. I built the first one with
   five tp- classes and no rules behind any of them, in a system whose design
   document is trying to get 24 prefixes down to 11; a second new prefix here
   would have made 26. */
.af-explain{max-width:74ch;margin:0 auto}
.af-ex{padding:28px 0;border-top:1px solid var(--c-border)}
.af-ex:first-child{border-top:0}
.af-ex h2{font-size:clamp(21px,2.4vw,27px);max-width:26ch;margin:0 0 12px}
.af-ex p{margin:0 0 14px;max-width:68ch;line-height:1.65}
.af-ex p:last-child{margin-bottom:0}
.af-ex-open{padding:var(--sp-4) 0 8px}
.af-ex-open h1{font-size:clamp(34px,5.4vw,62px);max-width:16ch;margin:0}

/* THE THREE LAYERS. Stacked in order of responsibility rather than numbered:
   the experience sits on the company, and the company contracts the ground.
   The eyebrow carries the layer's own name — experience, commercial,
   operations — which is the information a number would only stand in for.

   (This comment claimed the rows were numbered until the render showed they
   were not. A comment describing markup that does not exist is the same
   two-things-that-must-agree failure as everything else here, and it is worse
   in a stylesheet, because the next person trusts it instead of looking.) */
.af-layers{list-style:none;margin:var(--sp-4) 0 0;padding:0;display:grid;
  gap:1px;background:var(--c-border);border:1px solid var(--c-border)}
.af-layer{background:var(--c-bg);padding:22px 24px}
.af-layer h3{margin:0 0 8px;font-size:20px;display:flex;flex-wrap:wrap;
  align-items:center;gap:10px}
.af-layer p{margin:0;max-width:60ch;line-height:1.6}
.af-layer-n{margin:0 0 6px;font-family:var(--fj-mono);font-size:9.5px;
  letter-spacing:.22em;text-transform:uppercase;color:var(--c-accent)}

/* THE ACTS. A table, because "which of three parties does this, and why" is
   genuinely tabular and a list of ten would bury the pattern. The rows that
   declare carry a mark as well as a weight, so the distinction survives a
   monochrome print and a reader who cannot separate the two greys. */
.af-scroll{overflow-x:auto;margin-top:16px}
.af-acts{border-collapse:collapse;width:100%;font-size:14.5px}
.af-acts caption{text-align:left;caption-side:bottom;padding-top:12px}
.af-acts th,.af-acts td{text-align:left;vertical-align:top;padding:11px 14px 11px 0;
  border-top:1px solid var(--c-border)}
.af-acts thead th{border-top:0;font-family:var(--fj-mono);font-size:10px;
  letter-spacing:.18em;text-transform:uppercase;color:var(--c-muted);
  padding-bottom:8px}
.af-acts tbody th{font-family:var(--fj-mono);font-size:12px;letter-spacing:.1em;
  text-transform:uppercase;white-space:nowrap;font-weight:400}
.af-acts td i{font-style:normal;color:var(--c-muted)}
.af-acts .is-declare tbody th,.af-acts tr.is-declare th{color:var(--c-primary)}
.af-acts tr.is-declare th::before{content:"\2022 ";color:var(--c-accent)}

/* THE GROUND OPERATIONS. Named and reachable — that is the entire point of
   the section, so the name is the link where there is somewhere to send it. */
.af-ops{list-style:none;margin:var(--sp-4) 0 0;padding:0;display:grid;
  gap:24px;grid-template-columns:repeat(auto-fit,minmax(240px,1fr))}
.af-op h3{margin:0 0 4px;font-size:19px}
.af-op p{margin:0;line-height:1.6}
.af-op .af-note{margin:0 0 8px}

/* The close every discovery surface shares. See plate.onward().
   Sand ground so it reads as a different level from the content above it —
   the design system's z-axis is ground colour and rule, never a shadow. */
.af-onward{background:var(--c-sand);margin-top:var(--sp-5);padding:var(--sp-4) 0}
.af-onward>*{max-width:var(--frame);margin-left:auto;margin-right:auto;
  padding-left:44px;padding-right:44px}
/* The heading reuses .af-ex h2's clamp rather than choosing a new one.
   Written first as clamp(24px,3vw,38px), which raised this file's own
   distinct-size count from 29 to 30 and tripped the design ratchet — the
   site-wide total did not move, so it was a size that already existed
   somewhere, restated here. That is precisely the duplication the metric
   was added to catch, and it caught the commit that added the metric's
   author. Reuse, not a higher ceiling. */
.af-onward h2{font-size:clamp(21px,2.4vw,27px);max-width:20ch;margin:0 0 10px}
.af-onward p{margin:0 0 14px;max-width:60ch;line-height:1.6}
.af-onward-eye{font-family:var(--fj-mono);font-size:9.5px;letter-spacing:.22em;
  text-transform:uppercase;color:var(--c-accent);margin:0 0 8px}
.af-onward-do{display:flex;flex-wrap:wrap;gap:12px;margin:0}
@media(max-width:900px){.af-onward>*{padding-left:26px;padding-right:26px}}
@media(max-width:640px){.af-onward>*{padding-left:22px;padding-right:22px}}
