/* EuropeDoor — one stylesheet, one set of tokens.
   The rule that keeps this file honest: no page may ship its own <style>
   block. If something needs a new look, it needs a new token or a new
   component here, so the next page gets it too. */

:root {
  color-scheme: light dark;

  /* ── the palette: EUROPEAN FUTURE ─────────────────────────────
   *
   * See docs/instruction.md. The raw values are also in docs/palette.json,
   * which is the checkable copy: tools/checks.py recomputes the WCAG contrast
   * of every claimed pairing from those hexes, so a value nudged "slightly
   * warmer" in a redesign cannot quietly take a ratio with it.
   *
   * This is a migration of the previous system rather than a replacement of
   * it. Limestone stays as the ground, Atlantic green and terracotta stay
   * with narrow named roles, and gold leaves entirely. What is new is the
   * graphite foundation, the cobalt signature and the electric accent — and
   * the two worlds those make possible.
   *
   * TWO WORLDS, one set of components. <body data-world> selects which set of
   * semantic tokens the whole stylesheet resolves to. Everything below the
   * token block is written once and inherits.
   *
   *   DISCOVER      limestone ground, graphite ink, cobalt action.
   *   INTELLIGENCE  graphite ground, limestone ink, cobalt-lift action, and
   *                 cobalt-air as the accent. It was electric lime, and what
   *                 retired lime was not the light world it could never enter
   *                 but the dark one it was spreading across: the accent had
   *                 become the colour of every route, every dot and every lit
   *                 country, and a continent is not five per cent of a page.
   *
   * The ratio is the instruction, not the hex codes: 60 limestone / 25
   * graphite / 10 cobalt / 5 accent. A palette gives you a blue website with gold
   * buttons; a ratio gives you European editorial design over futuristic
   * infrastructure.
   */

  /* raw — never used directly by a component, only bound below */
  --graphite:     #101214;
  --graphite-2:   #1a1e22;
  /* A STEP THAT MEASURES 1.045 IS A ROUNDING ERROR WITH A TOKEN NAME.
     The light world's three surfaces step 1.09 and 1.12; the dark world's
     stepped 1.12 and then 1.045, so --paper-3 — tracks, wells, the deepest
     surface — was the card with a different name. Every progress track, every
     score bar and every hopbar on a dark card was drawn on its own colour.
     And the reason it was that small is the interesting half: graphite-3 was
     sized so that COBALT-LIFT would still clear AA on it, which is a claim
     about the link colour deciding the size of a surface. A surface sized by
     something that is not a surface. The step is real now and the link colour
     moved to keep its claim, which is the right way round. */
  --graphite-3:   #21262b;
  --limestone:    #f7f6f3;   /* never #fff: white is a screen, limestone is paper.
                                Kept from the previous system rather than replaced with
                                a warmer ivory — the ground was the part of the old
                                palette that was already right, and the owner's table
                                names it. */
  --limestone-2:  #eeece7;
  --limestone-3:  #e3e0d8;
  --cobalt:       #3157ff;   /* the signature. 4.93 on limestone, 4.52 on the card
                                ground, 4.04 on --paper-3 — so it is a DRAWN colour,
                                not a read one. See --signature below. */
  --cobalt-deep:  #2a4ad9;   /* everything cobalt that is text: 6.31 / 5.78 / 5.17 */
  --cobalt-lift:  #6685ff;   /* actions in the dark world. Chosen against the CARD, not
                                the ground — #5070FF clears 4.56 on #101214 and falls to
                                4.07 on #1a1e22, where links actually sit. A dark palette
                                has two backgrounds and the lighter one binds.
                                AND THE LIGHTEST OF THE THREE BINDS, which is the same
                                sentence one surface further on. #627fff measured 4.36
                                on the third surface once that surface became a real
                                step — so the value that was chosen against the card is
                                now chosen against the well: 4.62 / 5.08 / 5.69 across
                                all three grounds, and still clearly below cobalt-air. */
  --cobalt-air:   #8398ff;   /* the accent in the dark world's DISCOVER surfaces */
  /* THERE IS NO LIME, AND THE REASON IS A MEASUREMENT THE PALETTE NEVER
     MADE: WHERE IT WAS ACTUALLY BEING SPENT. #c8ff4d was declared as the
     dark world's accent — links, kickers, one button — and it ended up
     drawing GEOGRAPHY: seventeen journey routes, 894 homepage dots, 319
     destinations on /discover, 172 experiences, every lit country on every
     region glyph and every `here` dot on fifty country maps. An accent is a
     few per cent of a page; a continent is not a few per cent of anything.
     Acid green over a dark continent reads as a network diagram or a
     satellite overlay, which is the exact association the cartography split
     was written to escape, arriving through the accent instead of through
     the ground.
     It is replaced rather than rehomed, the way brass was. The dark world's
     accent is --cobalt-air, which this palette had already measured for
     precisely that ground in the DISCOVER-dark block: 7.06 on the graphite
     ground, 6.31 on the card, 6.04 on the deep surface, against the 4.5 a
     read colour needs. The cost is that a dark-preference reader can no
     longer tell the two worlds apart by hue — and the worlds were never a
     hue: INTELLIGENCE is dark in BOTH preferences, which is the whole of
     that distinction in the preference most readers are in. */
  /* NO --ultramarine. It was declared here for the life of this palette —
     "atmosphere only: gradients, map washes, never type" — and no rule ever
     spent it. Ultramarine is real in this product and it is not a CSS
     colour: it is the violet end of the PLATE ramp, hue 244° in
     render.PLATE_HUES, drawn in Python. A custom property nothing
     references is vocabulary that looks like a decision, and the register
     was recomputing a contrast claim about it against limestone-3 on every
     build. docs/palette.json > never. */

  /* Retained from the previous system, with narrow homes rather than as
     leftovers. Both are DISCOVER colours and neither appears in
     INTELLIGENCE, where the only accent is cobalt-air.

     --atlantic    heritage and provenance. The pages that talk about how
                   this project knows what it claims: /method, /sources,
                   /brand, /honest, /fund. It was the primary for a year and
                   it is the right colour for the parts of the product that
                   are about its own history and trustworthiness.
     --terracotta  the warm cultural accent: stories, events, experiences.
                   Inside DISCOVER, cobalt marks the structural and
                   geographic — countries, regions, destinations, journeys —
                   and terracotta marks the human and cultural. That is a
                   real editorial distinction, not decoration. */
  --atlantic:     #14483c;
  --provenance:   var(--atlantic);   /* the heritage rule on a provenance note */
  --atlantic-2:   #0e3630;
  --terracotta:   #a4491f;
  --terracotta-2: #8a3a17;   /* the deeper one, for text on --paper-3 where
                                #a4491f measures 4.48 and just misses */
  /* THE LIFTED STEPS, AND ONE OF THEM WAS BEING REFERENCED WITHOUT EXISTING.
     Both accents have to lift for a dark-preference reader — #14483c is
     1.81:1 on graphite — and the lifted values lived as raw hexes inside the
     dark blocks while `--provenance` in both of those blocks said
     `var(--atlantic-lift)`, a token this stylesheet has never declared. An
     unresolvable var() makes the whole declaration invalid at computed-value
     time and the property falls back to its INHERITED value, so the heritage
     rule down the side of 843 provenance panels painted in the text colour
     — limestone — in the dark preference and in the whole INTELLIGENCE
     world. Named here, once, and referenced everywhere they were typed. */
  --atlantic-lift:   #63b79c;
  --atlantic-lift-2: #77c7ad;
  --terracotta-lift:   #e08a5c;
  --terracotta-lift-2: #ea9a6c;

  /* There is no gold. Brass was the only gold token in the previous system,
     it was already forbidden on anything interactive, and European Future
     removes it: gold says luxury, premium, heritage, wealth, and this
     product has to say Europe, discovery, movement, intelligence, culture,
     future. The one place it survived — the "computed" source tier — is a
     neutral grey now, which is also more honest about what that tier is. */

  /* ── WORLD 01 — DISCOVER, light ────────────────────────────── */
  --paper:        var(--limestone);
  --paper-2:      var(--limestone-2);
  --paper-3:      var(--limestone-3);
  --ink:          var(--graphite);
  --ink-2:        #3a4048;
  --ink-3:        #565d66;   /* 5.05 on --paper-3; #5c636c measured 4.15 there and failed */
  --rule:         #d8d5cc;

  --sea:          var(--cobalt-deep);   /* links, focus rings, the interactive colour */
  --sea-dark:     #1f3aad;

  /* --door is the accent WHERE IT IS TEXT, so it is cobalt-deep and not the
     signature. The signature #3157FF was bound here first and the browser
     suite failed seven pages with it: 4.77 on ivory, but 4.36 on the card
     ground --paper-2 and 3.64 on stone, and a kicker lives on a card. This
     is the instruction's own finding arriving as a build failure — "cobalt
     reads on ivory and nowhere else" — which is what a check is for.

     So in the light world the accent and the action are the same blue, and
     the difference between them is typographic: a kicker is small-caps, a
     link is underlined, a button is filled. One signature used three ways is
     a firmer identity than two hues competing, and the second note in the
     ratio is graphite, not another colour. */
  --door:         var(--cobalt-deep);
  --door-dark:    #1f3aad;

  /* The signature at full strength, for things that are drawn rather than
     read. WCAG asks 3:1 of a graphic, not 4.5:1, so this is where #3157FF
     lives: the mark, the score bars, the dots on the map. */
  --signature:    var(--cobalt);

  /* Warnings are the one thing that must not be the brand colour, or an
     advisory reads as a link. A brick red, measured: 6.46 on ivory.
     AND IT WAS THE CULTURAL ACCENT INSTEAD. #a32a1e is hue 5, saturation
     69, lightness 38. Terracotta -- stories, events, experiences -- is hue
     19, saturation 68, lightness 38. Fourteen degrees apart at identical
     saturation and identical lightness, which measures 1.22:1 against each
     other, so a travel advisory and a story kicker are the same colour to
     the eye. The dark pair was worse: #f0a090 against terracotta-lift's
     #e08a5c measures 1.27, and both are salmon.
     A CONTRAST RATIO CANNOT SAY THAT TWO COLOURS ARE DIFFERENT COLOURS.
     It is a ratio of luminances, so two hues at the same lightness always
     measure 1.0 and every contrast check on this site said these were
     fine. docs/palette.json states this one as a HUE DISTANCE instead: 28
     degrees from terracotta and 30 from terracotta-lift, against the 25 the
     rule asks for. True red rather than brick, and the contrast went 6.46
     to 7.54 on limestone on the way past. */
  --warn:         #981f31;
  --warn-lift:    #ee8192;   /* the dark-preference step, named rather than typed twice */
  --warn-bg:      #f8ecee;

  /* THE CARTOGRAPHY — always INTELLIGENCE, wherever it is embedded.
     PAPER, INK, BLUE. The first version of these five was near-black water
     (#0a0e10) with a blue polygon on it, and the association it carried was
     GIS software: a dashboard, a satellite interface, a database selection
     state. Measured, the reason was that nothing was a step away from
     anything — land against water was 1.25:1, a coastline against its own
     land 1.53, and the highlighted country against its neighbours 1.45. A
     map whose layers are all within a third of a stop of each other is not
     a map, it is a silhouette on a dark tile.
     These are ink rather than black, and each layer is a real step:
     land:sea 1.50, coast:land 2.04, subject:land 2.02. The opening stays an
     opening — limestone wall against this water is 17.35:1, against the old
     water 17.94, so the signature loses a third of a stop and gains a
     cartography. They are declared in docs/palette.json with a role each
     and checked against this file, which for the life of the map they were
     not: five hexes governing every drawing on the site, outside the
     palette that is data. */
  /* THE ATLAS — the EDITORIAL cartography, on paper.
     The owner's direction: an EuropeDoor map must feel like an editorial
     European atlas, and black must stop being the default ground for every
     one of them. So the family splits on what the drawing IS. A country
     portrait, a destination's local view and a story's map are PICTURES —
     they are looked at — and a printed atlas is warm paper, pale water and
     an ink coastline. /map, the planner, search and the country reference
     map are INSTRUMENTS — they are operated — and those stay graphite,
     which is what the two worlds have always meant: not a theme, but
     whether the reader is browsing or working.
     Measured: land against water 1.39, coast against land 3.57, the subject
     against its neighbours 2.31. The aperture is then read by its CUT EDGE
     rather than by a tonal step from the wall, which is the machinery the
     dark preference already needed and already has. */
  /* THE ATLANTIC, IN FOUR STEPS. A flat pale fill made the water a surface
     the land was pasted onto; a real atlas has an ocean that gets deeper as
     it leaves the shore. These four are drawn as concentric strokes of the
     coastline itself, so the ramp is DISTANCE FROM LAND — the one thing a
     coastline can tell us — and never a claim about depth. This repository
     holds no bathymetry and nothing here implies a number of metres.
     Measured: the four steps are 1.53 to 1.86 apart, which is a ramp you
     can see and not a set of stripes; limestone against the deep is 10.4:1,
     so the aperture reads by tone again as well as by its cut edge. */
  /* AND THE RAMP DRIFTED TOWARD CYAN-GREEN AS IT LIGHTENED, WHICH IS THE
     ONE DIRECTION IT MUST NOT GO. Measured on the values it held:

         step             hue  sat  light
         deep             200   65   20     <- the most saturated colour here
         mid              195   51   32
         shallow          193   26   50
         coastal          185   21   63     <- teal, 17 degrees off the deep

     Two faults. The deep was 65% saturated, which made a flat teal covering
     the whole Atlantic the loudest thing in an atlas that says height is
     FELT and the typography dominates. And the hue walked toward green as
     the water lightened, so the shallow step — the one that meets the
     shore — was the furthest from both the deep water and the warm land it
     touches. Against a land family at 14-20% saturation that pairing is the
     muddiest one available: khaki against teal.

     One hue now, 205 to 202, which is the graphite's own family rather than
     a teal beside it. Saturation FALLS as the water lightens, 58 to 22, so
     the water quietens toward the shore instead of shouting at it. The
     lightness ramp is what carries the reading, exactly as it does on the
     land: steps of 1.63, 1.98 and 1.62 against 1.73, 1.86 and 1.53 before.
     Limestone measures 11.32 on the deep against 10.40, so the aperture
     reads by tone MORE than it did, and the land clears the water at 8.61
     against 7.91. Every number moved the right way. */
  --ocean-deep:    #153851;
  --ocean-mid:     #2c5977;
  --ocean-shallow: #5789a8;
  --ocean-coastal: #95adbb;
  --atlas-sea:     #153851;
  /* LAND IS ONE FAMILY, AND THE SUBJECT IS THE BRIGHTEST OF IT. The subject
     used to be a light blue, which worked against pale water and reads as
     SEA against an Atlantic one — Greece came out the same family of blue as
     the Aegean around it. An atlas separates the country it is about by
     making it the brightest land with the heaviest edge, not by turning it
     into another shade of water. Measured: subject against neighbour 1.33,
     subject against coastal water 2.14, and the outline 11:1 on its own
     fill. */
  /* AND THE BASE WAS MORE SATURATED THAN THE GROUND IT CARRIES. The land
     measured hue 42, sat 23; its own hypsometric bands run 14 to 20 and the
     page's limestone runs 20 at lightness 96. So on a flat plate — the 172
     destinations with no relief, every country, every region, every journey
     — the most saturated thing in the drawing was the paper, and 23% of
     yellow-orange at that lightness is sand rather than paper. THE LAND AND
     ITS TERRAIN ARE ONE FAMILY, AND THE BASE CANNOT BE LOUDER THAN THE
     GROUND IT CARRIES.
     One hue held at 42 across all three steps, saturation 30 / 17 / 12
     falling as the land recedes. Every relationship the register holds moved
     by less than 0.02 — subject to land 1.31 against 1.33, far to land 1.26
     against 1.28, land to water 8.68 against 8.61 — so this is the yellow
     cast coming out and nothing else moving. */
  --atlas-land:    #ddd9cf;
  /* NO --atlas-context. It was the same value as --atlas-land and nothing
     referenced it: context IS the same land, which is the rule the layer
     comment three thousand lines down already states and demonstrates by
     painting `.context path` with --atlas-land. A second name for one
     colour is two colours waiting to disagree. */
  /* The muted step of the same land, for context outside the subject and for
     a glyph drawn on limestone, where #ded8ca measures 1.20:1 against the
     page and is a silhouette you cannot quite see. It was a raw hex in two
     plate rules before the glyphs needed it; a colour used in two families
     is a token. */
  --atlas-far:     #c7c2b8;
  --atlas-here:    #f8f6f2;
  --atlas-coast:   #1d3a4d;
  --atlas-hereline:#1d3a4d;
  /* A FRONTIER AND A COASTLINE WERE THE SAME INK, AND THE HIERARCHY WAS TWO
     TENTHS OF A UNIT OF WIDTH. `docs/cartography.md` says "a frontier is the
     lightest because it is context, a coast is the edge of the subject's
     world" — and both were drawn in `--atlas-coast`, which measures 8.44 on
     the land. A political border at 8.44:1 is the strongest line on a plate
     whose subject is a valley, and on the 147 destinations and six journeys
     that carry relief it is drawn as its own pass, so the weights were the
     only thing separating them.
     A frontier is a claim about the ground; a coastline is the ground. 4.00
     on the land against the coast's 8.44, and it still clears 3:1 on the
     darkest and the palest hypsometric band, so it reads over relief as well
     as over flat paper. */
  --atlas-frontier:#4e6b7e;

  /* ── THE INSTRUMENT CARTOGRAPHY ──────────────────────────────────────
     THE PICTURES WERE GIVEN A CARTOGRAPHY AND THE INSTRUMENTS WERE NOT.
     `docs/cartography.md` splits every drawing on what it IS: a picture is
     paper, an instrument is graphite. The picture half got a four-step ocean,
     a land family, an ink coast and a lit subject, all measured. The
     instrument half kept five raw hexes invented before European Future —
     #0a1220, #253546, #1b2735, #4a6480, #3a6299 — which are NAVY, are in no
     token in `docs/palette.json`, and were recomputed by no check.

     Measured, land against water, on the two halves of the same atlas:

         the pictures   #ded8ca on #123f55    7.91
         /map           #253546 on #0a1220    1.50
         a country outside the frame          1.24

     1.24 is not a quiet country, it is a country that is not drawn. And the
     comment forty lines down this file already stated the 1.50 — the number was
     known and had never been acted on. This is the "GIS dashboard" reading
     the cartography split exists to escape, surviving on the one map the
     product calls its instrument.

     The fix is the hero's own sentence applied to the instrument: EUROPE IS
     WHAT THE LIGHT FALLS ON. The water is the dark ground and the land is
     lifted out of it, on a ladder that is ordered and measured rather than
     picked. And the coast becomes its own token, because a coastline is
     where land meets sea and a frontier is a line drawn on land — the exact
     separation the picture half made and the instrument half never did.

         land : sea       1.50 → 1.91     a mass rather than a hairline
         context : sea    1.24 → 1.44     a quiet country is still a country
         land : context   1.21 → 1.33     three tiers, ordered
         coast : sea         —    7.33    the boundary, which is what
         coast : land        —    3.84    SC 1.4.11 actually asks for
         subject : land   1.29 → 3.82     cobalt-air, the declared accent

     Neutral-cool graphite rather than navy: `--graphite` is #101214 and
     these are its family. `docs/palette.json` carries the register and
     `checks.py` recomputes every one of those separations from the hexes in
     THIS file, so the ladder cannot be nudged without the arithmetic saying
     so. */
  --map-sea:      #0b0e11;   /* the water: a step below the page's own ground */
  --map-context:  #2a2f36;   /* land outside the subject of this drawing */
  --map-land:     #3b424c;   /* the land mass, lifted out of the water */
  --map-coast:    #93a1ae;   /* the edge between them. 7.33 on the sea, 3.84 on the land */
  --map-border:   #616b76;   /* a frontier, which is a line drawn ON land and lighter than a coast */
  --map-hover:    #8398ff;   /* cobalt-air, the INTELLIGENCE accent: 3.82 on the land it replaces */
  /* AND THE SUBJECT OF A DRAWING IS NOT PAINTED IN THE ACCENT. Binding the
     selected state to cobalt-air was right for /map, where hover lifts one
     country under the cursor for as long as the cursor is there — five per
     cent of a screen, which is what an accent is. The SAME token filled the
     subject permanently on the fifty country reference maps, so Italy came
     out a quarter of the drawing in periwinkle: a continent drawn in the
     accent, which is the exact failure that removed electric lime, arriving
     through a different token one commit after the token changed.
     The picture half already answered it: "An atlas separates the country it
     is about by making it the brightest land with the finest heaviest edge,
     not by turning it into another shade of water" — and not by turning it
     into the accent either. Measured: the subject is 2.07 above its
     neighbours and 3.94 above the water, with a fine pale edge rather than a
     loud fill. Hover keeps the accent, because hover is momentary. */
  --map-here:     #66727f;   /* the subject: the brightest land. 2.07 on the land, 3.94 on the sea */
  --map-hereline: #ccd5dd;   /* its edge — the finest and the heaviest line on the drawing */
  /* AND THE INSTRUMENT'S SEA WAS ONE VALUE WHERE THE PICTURE'S IS FOUR.
     `docs/cartography.md`: "A flat pale fill made the water a surface the
     land was pasted onto. These four are CAST, not stroked — three soft
     shadows of the land silhouette, widest and deepest first, so the water
     lightens as it approaches a shore." True of the 824 plates since the
     ocean ramp was built, and the instrument had one flat near-black: the
     Baltic, the Adriatic, the Black Sea and the open Atlantic were the same
     value, so an inland sea read as a hole in the continent rather than as
     water.
     Three graphite steps, the same cast and the same argument — it is
     DISTANCE FROM LAND, the one thing a coastline can tell us, and never a
     claim about depth. Measured against the water: 1.09, 1.23, 1.45, and the
     land is 1.91, so the ramp is ordered and stays under the land it
     approaches. Cooler than the land at every step, because the thing being
     drawn is water. */
  --map-shore-3:  #101920;   /* the widest and faintest, far from any coast */
  --map-shore-2:  #18242e;
  --map-shore-1:  #22313e;   /* the narrowest and lightest, against the shore */


  /* type */
  --display: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, "Times New Roman", serif;
  --text: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  /* the whole type scale — eleven steps, no more */
  --t-xs:   0.75rem;
  --t-sm:   0.8125rem;
  --t-base: 0.9375rem;
  --t-md:   1.0625rem;
  --t-lg:   1.25rem;
  --t-xl:   1.5rem;
  --t-2xl:  1.875rem;
  --t-3xl:  2.375rem;
  --t-4xl:  3rem;
  --t-5xl:  3.75rem;
  --t-6xl:  4.75rem;

  /* space */
  --s1: 0.25rem; --s2: 0.5rem; --s3: 0.75rem; --s4: 1rem;
  --s5: 1.5rem;  --s6: 2rem;   --s7: 3rem;    --s8: 4.5rem; --s9: 7rem;

  --measure: 34rem;
  --page: 76rem;
  --radius: 3px;
  /* NO --shadow. The register called the vocabulary "a rest state and a
     lifted one" and there is no rest state: nothing on this site carries a
     shadow until it is hovered. Declared in all three world blocks, spent in
     none of them. The two `box-shadow` values this stylesheet really has are
     --shadow-lift and an inset 1.5px ring, which is a focus outline rather
     than a shadow — so the reason in docs/invariants.json was describing a
     vocabulary the stylesheet does not have. */
  --shadow-lift: 0 2px 4px rgba(20,22,30,.08), 0 18px 40px -18px rgba(20,22,30,.30);
}

/* Inside DISCOVER, the accent says what kind of thing you are reading.
 *
 *   cobalt      structural and geographic — the homepage, countries,
 *               regions, destinations, journeys. The default.
 *   terracotta  human and cultural — stories, events, experiences.
 *   atlantic    heritage and provenance — how this project knows what it
 *               claims: /method, /sources, /brand, /honest, /fund.
 *
 * Bound by the class the shell already sets, so no page builder had to
 * change for the first two. It is a distinction a reader will never name and
 * will feel: an essay about a festival should not be the same blue as a
 * boundary dataset.
 *
 * INTELLIGENCE overrides all of it below — in the dark world there is one
 * accent and nothing else, which is what makes crossing worlds legible. */
body.area-stories, body.area-events, body.area-experiences {
  --door:      var(--terracotta);
  --door-dark: var(--terracotta-2);
}
body[data-accent="heritage"] {
  --door:      var(--atlantic);
  --door-dark: var(--atlantic-2);
}

/* Both accents have to lift for a dark-preference reader, and the browser
   suite is what said so: #a4491f is 3.17:1 on graphite and #14483c is
   1.81:1, which is a heading nobody can see. The lifted values are the ones
   the previous system already used at night — this is the one place where
   the old palette's homework carried straight over. */
@media (prefers-color-scheme: dark) {
  body.area-stories, body.area-events, body.area-experiences {
    --door:      var(--terracotta-lift);
    --door-dark: var(--terracotta-lift-2);
  }
  body[data-accent="heritage"] {
    --door:      var(--atlantic-lift);
    --door-dark: var(--atlantic-lift-2);
  }
}

/* INTELLIGENCE outranks both, in every preference. `body.area-x` is
   specificity (0,1,1) and a bare [data-world] is (0,1,0), so without the
   extra element qualifier a cultural page that ever became INTELLIGENCE
   would keep terracotta on a graphite ground — 3.17:1, and invisible. No
   page is in that position today; the rule is here so none ever is. */
body[data-world="intelligence"] {
  --door:      var(--cobalt-air);
  --door-dark: #a3b4ff;
}

/* ── WORLD 02 — INTELLIGENCE ───────────────────────────────────
 *
 * The map, the planner, My Europe, search, journey construction,
 * personalisation, saved journeys, route and filter intelligence, and
 * EuropeDoor Guide.
 *
 * Dark in BOTH colour-scheme preferences, deliberately. The world is a
 * statement about where the reader is, not a lighting preference: if
 * INTELLIGENCE went light for a light-mode reader, the two worlds would
 * collapse into one and the whole idea would be a theme toggle with extra
 * steps.
 *
 * Applied to a whole page via <body data-world="intelligence">, and to a
 * single component inside a DISCOVER page — the map figure on a country page —
 * via the same attribute on that element. That is the door working as a design
 * language rather than as a glyph: a window into the machine, cut into an
 * editorial page. */
[data-world="intelligence"] {
  --paper:        var(--graphite);
  --paper-2:      var(--graphite-2);
  --paper-3:      var(--graphite-3);
  --ink:          var(--limestone);
  --ink-2:        #c3c8cc;
  --ink-3:        #949ba3;
  --rule:         #2a3138;

  --sea:          var(--cobalt-lift);
  --sea-dark:     var(--cobalt-air);
  --door:         var(--cobalt-air); /* the accent, ~5%. It was electric lime */
  --door-dark:    #a3b4ff;
  --signature:    var(--cobalt-lift);

  --warn:         var(--warn-lift);
  --warn-bg:      #271719;
  --provenance:   var(--atlantic-lift);

  --shadow-lift: 0 2px 4px rgba(0,0,0,.55), 0 18px 40px -18px rgba(0,0,0,.8);

  color-scheme: dark;
  background: var(--paper);
  color: var(--ink);
}

/* DISCOVER, for a reader who prefers dark.
 *
 * Not an inversion of the light world and not a copy of INTELLIGENCE. The
 * ground is the same graphite, because there is only one dark ground worth
 * having, and the accent is cobalt-air. It was written here first, for a
 * dark-preference editorial page, and measured here first — which is why it
 * was available the day the electric accent was retired. The two worlds are
 * now told apart by the fact that INTELLIGENCE is dark in BOTH preferences,
 * which is the whole of that distinction in the preference most readers are
 * in, rather than by a hue that only one of them could carry. */
@media (prefers-color-scheme: dark) {
  :root {
    --paper:        var(--graphite);
    --paper-2:      var(--graphite-2);
    --paper-3:      var(--graphite-3);
    --ink:          var(--limestone);
    --ink-2:        #c3c8cc;
    --ink-3:        #949ba3;
    --rule:         #2a3138;

    --sea:          var(--cobalt-lift);
    --sea-dark:     var(--cobalt-air);
    --door:         var(--cobalt-air);
    --door-dark:    #a3b4ff;
    --signature:    var(--cobalt-lift);

    --warn:         var(--warn-lift);
    --warn-bg:      #271719;
  --provenance:   var(--atlantic-lift);

    --shadow-lift: 0 2px 4px rgba(0,0,0,.55), 0 18px 40px -18px rgba(0,0,0,.8);
  }
}

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: var(--t-base)/1.65 var(--text);
  font-synthesis-weight: none;
}

/* `text-wrap: balance` ON EVERY HEADING, NOT ON SEVEN OF THEM. It was on
   the manifesto, the closing statement, the essay hed and four others —
   chosen one at a time, wherever somebody looked. Measured across 668
   headings on twenty-two pages: 30 run to more than one line at 390 and
   FIVE of those end on an orphan under a third of the widest line, the worst
   a 24-pixel last line under a 341-pixel one on /how-it-works. It is one
   declaration and it is the browser's own line-breaker; the seven
   per-element copies are gone. */
/* AND A WORD LONGER THAN THE COLUMN RAN OFF THE SCREEN. Europe writes
   `Jugendstilsenteret`, `Kunsthistorisches` and `Groeningemuseum`, and an h1
   is set at 76px: measured on the built pages, Groeningemuseum is 381 units
   of word in a 336-pixel box AT 390 — the width every gate here runs at —
   and it pushes the whole document 7px sideways; at 360 that is 37 and at
   320 it is 77. A headline running off the right edge of a phone is the
   defect this stylesheet already refuses of a map label sliced by the
   aperture and of a place name cut in a sticky bar, arriving on the one
   piece of type a reader reads first.

   `break-word` rather than `anywhere`: it breaks a word ONLY when the word
   cannot fit on a line of its own, so nothing that fits today moves by a
   pixel. Hyphenation is refused instead — `hyphens: auto` would hyphenate
   Norwegian and Dutch compounds against this document's `lang="en"`
   dictionary, which is a wrong break rather than no break. */
h1, h2, h3, h4 { font-family: var(--display); font-weight: 600; line-height: 1.12;
                 margin: 0; letter-spacing: -0.01em; text-wrap: balance;
                 overflow-wrap: break-word; }
h1 { font-size: clamp(var(--t-3xl), 5.2vw, var(--t-5xl)); }
h2 { font-size: clamp(var(--t-xl), 3vw, var(--t-2xl)); }
h3 { font-size: var(--t-lg); }
p { margin: 0 0 var(--s4); max-width: var(--measure); }

/* ── WHAT THE BROWSER DRAWS FOR YOU ──────────────────────────────────
 *
 * Two pieces of interface on every page of this site were painted by
 * Chromium's defaults rather than by the palette, and neither had ever been
 * looked at.
 *
 * SELECTION. Dragging across a paragraph produced the platform highlight —
 * on the graphite INTELLIGENCE world that is a pale blue block with the
 * page's own light text inside it, which is the one combination this
 * stylesheet spends a check on avoiding. `limestone on cobalt-deep` measures
 * 6.31:1 and is already in docs/palette.json, because it is what the primary
 * button uses; nothing new is claimed here. Bound to `--cobalt-deep` and
 * `--limestone` BY NAME rather than to `--signature` and `--ink`, because
 * the masthead rebinds both and a selection inside it would have come out
 * limestone on limestone.
 *
 * ACCENT-COLOR. The planner's seventeen interest checkboxes, the discover
 * filters and every other control the browser paints itself were rendering
 * in Chrome's own blue — a second, slightly different blue beside the one
 * band of signature colour this site has, on the two pages where a reader
 * does the most clicking. One property, and the checkbox becomes ours.
 *
 * AND IT WAS BOUND TO A LIGHT-WORLD TOKEN ON EVERY WORLD. `--cobalt-deep`
 * is 6.31:1 on limestone and **2.75:1 on the graphite ground, 2.46:1 on the
 * card** — under the 3:1 SC 1.4.11 asks of a user interface component, on
 * the five INTELLIGENCE pages, which are exactly the pages that hold the
 * checkboxes this property was added for. A checked box a reader cannot see
 * is the same class as the hero's invisible label: present, labelled,
 * keyboard-reachable, and not there.
 *
 * `--sea` rather than `--door`: the interactive colour rather than the
 * editorial accent. A checkbox is a control and stays one colour across the
 * site; `--door` carries the terracotta and atlantic that say what KIND of
 * thing is being READ, and a green tick on /fund is the accent doing a job
 * that belongs to the interactive token. cobalt-deep on paper, cobalt-lift
 * at 5.36 on graphite and 4.79 on the card.
 *
 * That also removes two special cases. `.checks input` and `.inlinecheck
 * input` each set their own `accent-color: var(--door)`, so the planner's
 * seventeen interest boxes were cobalt-air while every other control on the
 * same page was cobalt-deep — two blues, one form.
 */
::selection { background: var(--cobalt-deep); color: var(--limestone); }
/* ON `body`, NOT ON `:root`. A var() in a declaration resolves where the
   DECLARATION lives, and the world tokens are set on `body[data-world]`,
   which is a descendant of `:root` — so `:root { accent-color: var(--sea) }`
   resolved against the light world's value and then inherited that one
   number into every world. It reported cobalt-deep on the graphite planner,
   which is the 2.75:1 this rule exists to fix, while looking correct in the
   source. An inherited property carries the value it computed, not the
   expression that produced it. */
body { accent-color: var(--sea); }

/* PROSE LINKS WORE THE BROWSER'S DEFAULT UNDERLINE, WHICH SITS ON THE
   BASELINE. Nothing in this stylesheet declared `text-underline-offset` or
   `text-decoration-thickness`, so every link inside a sentence — the source
   credits under all 824 maps, the corrections link, "the full map", every
   cross-reference in every country's prose — was drawn with a rule through
   the descender of every g, j, p, q and y. It is the most visible
   typographic tell there is between a document and a web page, and it was
   on 1,072 pages because the default is what happens when nobody decides.
   One decision: the line drops clear of the baseline, and thickens on
   hover rather than changing colour, because a colour change on hover is a
   second statement about what the link IS. `from-font` where the face
   offers a position and 1px where it does not. */
a { color: inherit; }
a:where(:any-link) {
  text-underline-offset: 0.17em;
  text-decoration-thickness: 1px;
}
a:where(:any-link):hover { text-decoration-thickness: 2px; }
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible, summary:focus-visible {
  outline: 2px solid var(--sea); outline-offset: 3px; border-radius: 2px;
}
/* AND ON THE ONE BAND OF SIGNATURE COLOUR THE RING WAS THE SIGNATURE.
 *
 * --sea is cobalt-deep, the masthead is cobalt at 96%, and the outline sits
 * three pixels OUTSIDE the link — which on every other surface puts it on the
 * page and here puts it on the band. Measured on the shipped pixels:
 * #2a4ad9 on #2847d0 is 1.06:1. Keyboard focus was present, correctly placed,
 * correctly sized, and invisible, on the navigation of all 1,033 pages.
 *
 * It is the mark's own finding one element over: "on the signature itself a
 * cobalt leaf measures 1.28:1 and is simply not there", and the answer is the
 * same — the thing inverts rather than recolours. Limestone measures 6.70 on
 * the band. The four homepage doors already did this by hand, which is this
 * repository's other recurring sentence: a rule that exists is not a rule
 * that is inherited.
 *
 * NOT A --focus TOKEN, AND THAT WAS TRIED FIRST. `:root { --focus: var(--sea) }`
 * resolves --sea where the DECLARATION lives, so every world would inherit the
 * light world's cobalt — which is the accent-color bug this stylesheet already
 * has a paragraph about. An override on the surface is the only form that
 * cannot get that wrong. */
.masthead a:focus-visible, .masthead button:focus-visible {
  outline-color: var(--limestone);
}

.skip { position: absolute; left: -9999px; }
.skip:focus { left: var(--s4); top: var(--s4); z-index: 99; background: var(--paper); padding: var(--s2) var(--s4); border: 1px solid var(--ink); }

/* ── masthead ─────────────────────────────────────────────── */
/* THE MASTHEAD IS THE ONE BAND OF SIGNATURE COLOUR, AND IT IS OURS.
 *
 * It was paper on paper: a limestone bar over a limestone page, separated by
 * a hairline. Correct, quiet, and it spent none of the 10% the ratio gives
 * cobalt on the one element that appears on all 1,033 pages.
 *
 * The prompt for this was a booking site's navy band, and the measurement is
 * why it is NOT that navy. Their CTA blue is hue 212 at 100% saturation and
 * 45% lightness; this palette's cobalt is hue 229 at 100% and 60%. Ours is
 * the brighter blue already — what differed was COVERAGE, a solid band across
 * the top of every page against a pale one. So the band is the borrowed idea
 * and the colour stays ours, which also matters because /about publishes "Not
 * an OTA" and destination pages now link to one: wearing an OTA's livery on
 * the page that links to it blurs exactly the boundary the Stay layer exists
 * to keep visible.
 *
 * DONE BY REDEFINING THE TOKENS, NOT BY OVERRIDING THE RULES. The first
 * version added `.masthead .nav a`, `.masthead .mark-leaf` and three more
 * scoped selectors, and the dead-rule scan immediately reported four rules
 * that matched elements and changed none of them: the base `.nav a {color}`
 * and both `.mark-*` fills were superseded on every page, and
 * `.masthead .navsearch` (0,2,0) lost outright to `.navutil a` (0,2,1). That
 * is the fourth specificity collision this stylesheet has recorded, and it
 * was avoidable — the two-worlds system next to it already solves this by
 * resolving the SAME rules against different values. The masthead is a third
 * world, three lines long: the base rules keep working, nothing is
 * superseded, and there is nothing to lose a specificity fight.
 *
 * No new token and no new claim. `limestone on cobalt-deep` was already in
 * docs/palette.json at 6.31:1, because the primary button has always used it.
 *
 * 96% opaque rather than the paper bar's 88%: the ink-to-ground margin here
 * is 5.00 where graphite on limestone was 17.4, so there is far less room for
 * page content scrolling underneath to eat it.
 *
 * THE MARK'S TWO TONES INVERT RATHER THAN RECOLOUR. On paper the frame is ink
 * and the leaf is the signature; on the signature itself a cobalt leaf
 * measures 1.28:1 and is simply not there. Frame bright, leaf a step down —
 * which is also the mark's own logic, since it is a doorway and the opening
 * is the darker half. Both clear the 3:1 a drawn shape needs. Never one shape
 * at reduced opacity, which is what the mark's comment has said since it was
 * drawn. */
/* THE MASTHEAD IS STICKY AND NOTHING KNEW HOW TALL IT WAS.
 * `scroll-margin-top` computed to 0px on every id on the site, so every
 * in-page anchor scrolled its target to y=0 — which is where the bar is.
 *
 * On a wide screen the sections' own top margin absorbed it and the heading
 * landed 22px under the bar, touching it with no air, which is why nobody
 * saw this. ON A PHONE THE BAR IS TWO ROWS AND 89 PIXELS AND THAT MARGIN IS
 * 80, so the heading landed NINE PIXELS BEHIND IT — on the eight-item jump
 * nav that every one of the 319 destination pages carries, which is the one
 * navigation on this site whose whole job is moving a reader within a page.
 * Measured at both widths in Chromium before it was fixed, and again after.
 *
 * This is the --thumbbar failure at the other end of the page: two fixed
 * bars, and the offsets that clear them hard-coded 44px against a bar that
 * rendered at 63. So it takes the same repair — ONE TOKEN, a CEILING on the
 * bar and a FLOOR on everything that has to clear it, and a browser check
 * that scrolls to a real anchor and asserts the heading is below the bar
 * rather than behind it. The two rem values are the two measured heights
 * rounded up, and the sticky rail and the essay margin take their offsets
 * from the same token instead of the 5rem and 6rem they had guessed. */
:root { --mast: 3.75rem; }
@media (max-width: 44rem) { :root { --mast: 5.75rem; } }
[id] { scroll-margin-top: calc(var(--mast) + var(--s4)); }

.masthead { position: sticky; top: 0; z-index: 40;
            --ink:       var(--limestone);
            --ink-2:     color-mix(in srgb, var(--limestone) 85%, var(--cobalt-deep));
            --ink-3:     color-mix(in srgb, var(--limestone) 70%, var(--cobalt-deep));
            --signature: color-mix(in srgb, var(--limestone) 70%, var(--cobalt-deep));
            --door:      var(--limestone);
            --rule:      color-mix(in srgb, var(--limestone) 34%, transparent);
            background: color-mix(in srgb, var(--cobalt-deep) 96%, transparent);
            /* AND THE COLOUR ITSELF, WHICH THE FIRST TOKEN VERSION FORGOT.
             * Redefining --ink is not enough: the wordmark sets no colour of
             * its own and inherited graphite from <body>, so it measured
             * 2.75:1 on the cobalt band — present, legible-looking in a
             * thumbnail, and below AA. The two-worlds block one screen up
             * has always set `color: var(--ink)` for exactly this reason;
             * this is a third world and needed the same line. */
            color: var(--ink);
            backdrop-filter: saturate(1.4) blur(10px);
            border-bottom: 1px solid var(--rule); }
.masthead-in { max-width: var(--page); margin: 0 auto; padding: var(--s3) var(--s5); display: flex; align-items: center; gap: var(--s6); justify-content: space-between; }
.wordmark { display: inline-flex; align-items: center; gap: .42em; text-decoration: none; font-family: var(--display); font-size: var(--t-lg); letter-spacing: -0.02em; }
/* The mark. Two tones rather than one shape with opacity, so it holds on
   any ground including a photograph. It takes its colour from the palette,
   which is why it is inline SVG and not a file in an <img>. */
.mark { width: 1.05em; height: 1.05em; flex: none; }
.mark-frame { fill: var(--ink); }
.mark-leaf  { fill: var(--signature); }
.nav { display: flex; gap: var(--s5); flex-wrap: wrap; font-size: var(--t-sm); letter-spacing: .04em; text-transform: uppercase; }
.nav a { text-decoration: none; color: var(--ink-2); padding-bottom: 2px; border-bottom: 1.5px solid transparent; }
.nav a:hover { color: var(--ink); border-bottom-color: var(--door); }
.nav a[aria-current="page"] { color: var(--ink); border-bottom-color: var(--door); }

main { max-width: var(--page); margin: 0 auto; padding: var(--s6) var(--s5) var(--s9); }
main.wide { max-width: 92rem; }

/* ── breadcrumbs ──────────────────────────────────────────── */
.crumbs { font-size: var(--t-sm); color: var(--ink-3); margin-bottom: var(--s5); display: flex; flex-wrap: wrap; gap: var(--s2); align-items: baseline; }
.crumbs a { text-decoration: none; color: var(--ink-3); border-bottom: 1px solid var(--rule); }
.crumbs a:hover { color: var(--door); border-bottom-color: var(--door); }
.crumbs .sep { opacity: .5; }

/* ── page heads ───────────────────────────────────────────── */
.pagehead { margin-bottom: var(--s7); }
.pagehead .kicker { margin-bottom: var(--s2); }
/* No `color`: `.lede` already sets --ink-2 and every one of these is a
   `.lede`, so this restated the value it was about to inherit — the
   redundancy the dead-rule scan exists to find, and the one that took the
   count over its ceiling. */
.pagehead p.lede { font-size: var(--t-md); max-width: 44rem; margin-top: var(--s4); }
.kicker { font-size: var(--t-xs); letter-spacing: .14em; text-transform: uppercase; color: var(--door); margin: 0 0 var(--s2); font-weight: 600; }
/* AND THE ACCENT IS A MARK ON A PAGE HEAD, NOT ONLY A TINT ON ELEVEN PIXELS
   OF TYPE.

   Measured for the first time this session, over twelve pages spanning both
   worlds: the accent paints 0.3% of what a reader is shown against the 5 the
   ratio declares for it. Terracotta and atlantic are the entire
   art-directional difference between a magazine story and a country
   encyclopedia, and the only place either of them lands is a kicker at
   --t-xs. That is the design-direction audit's finding with a number under
   it, and it is not fixed by making the accent a ground — "generic tourism
   blue as a page background" is on the never list and a terracotta one would
   be the same mistake in a warmer hue.

   What it is fixed by is giving the accent a FORM. An eyebrow rule is the
   oldest device in editorial design and it costs nothing here: currentColor,
   so it is the family's own --door without a second declaration; a page head
   only, because `.row .kicker` is already --ink-3 for the reason three
   paragraphs up (an accent on every row is a texture, not an accent); and no
   new token, no new size, no new weight. */
/* FIVE CONTAINERS, AND THE LIST IS THE POINT. A page head's kicker lives in
   `.pagehead` on seventeen families, in `.arrivalsay` on the 319
   destinations, `.portraitsay` on the fifty countries, `.iherotext` on the
   five index heroes and `.essayhead` on the stories — and the same class
   appears 409 times inside `.card-body`, 369 times inside `.score` and three
   times inside a journey row, where it is a component's label rather than a
   page's subject. Naming the five is what keeps this one mark per page
   instead of the texture the paragraph above refuses. */
.pagehead .kicker::before,
.arrivalsay > .kicker::before,
.portraitsay > .kicker::before,
.iherotext > .kicker::before,
.essayhead > .kicker::before {
  content: ""; display: inline-block; vertical-align: .22em;
  width: 1.7rem; height: 2px; margin-right: .75em; background: currentColor;
}
/* AN ACCENT ON EVERY ROW OF A LIST IS NOT AN ACCENT, IT IS A TEXTURE.
   The kicker's job is to say what KIND of thing is being read — the accent
   system's whole argument, and the reason an essay is terracotta and a
   country is cobalt. One per page head answers that. Inside a row it
   answers nothing, because every row on the page is the same kind, and the
   ratio gives the accent five per cent of a screen:

       /journeys      17 rows, 17 chromatic lines
       /themes        13
       /countries      9
       /stories        9

   Seventeen cobalt uppercase lines down a page is the aperture-as-wallpaper
   finding in colour — a signal shared by every result carries no
   information, which is the same sentence as "never explain the constraint
   back", one system over. And it inverts the hierarchy it sits in: on
   /themes the kicker is the theme's ARGUMENT and the h3 is its name, so the
   brightest thing in the row was the subtitle.
   In a row it is ink: same size, same tracking, same weight, so it still
   reads as a label. The head keeps the accent, where it is true.

   AND THE RULE NAMED TWO CLASSES INSTEAD OF THE SITUATION, so it missed the
   page it mattered most on. Measured in Chromium across fifteen surfaces by
   reading the computed colour of every kicker and asking which sit outside
   a head: every index came back at zero and the HOMEPAGE came back at six —
   three journey rows carrying their country chain in cobalt uppercase above
   a serif name, and three story cards carrying their desk. `.jrow` and
   `.storylead` are not `.row` and not `.card`, so the repair written for
   exactly this reached neither, on the one page that opens the site. It is
   also the sharpest case of the inversion: the chain is the journey's own
   ROUTE, which is content rather than a label, and it was the brightest
   thing in the row.

   The situation is what the selector says now. A kicker inside a LINK is
   labelling one item in a set, where every sibling carries the same signal
   and the signal therefore carries nothing; a kicker outside one is
   labelling the page or the band, where it is the only one and it is true.
   That is one rule for every family that has been invented and every family
   that has not, which is what the two class names could never be. */
a .kicker { color: var(--ink-3); }
.lede { color: var(--ink-2); }

/* ── hero ─────────────────────────────────────────────────── */
.hero { padding: var(--s8) 0 var(--s7); border-bottom: 1px solid var(--rule); margin-bottom: var(--s7); }
.hero h1 { max-width: 18ch; }
.hero .lede { font-size: clamp(var(--t-md), 2vw, var(--t-lg)); max-width: 42rem; margin-top: var(--s5); }
.hero-actions { display: flex; gap: var(--s3); flex-wrap: wrap; margin-top: var(--s6); }

/* The primary action is the signature colour, not ink. Cobalt is 10% of the
 * ratio and this is where most of that 10% is spent: a graphite button is
 * correct and says nothing, and a palette whose signature never appears on
 * the one thing the reader is meant to press is a palette that exists only
 * in the documentation. Measured both ways — ivory on cobalt-deep is 6.09,
 * graphite on cobalt-lift is 5.36 — because the label inverts with the world
 * and only one of those two would have been checked by eye. */
.btn { display: inline-block; text-decoration: none; font-size: var(--t-base); padding: var(--s3) var(--s5); border-radius: var(--radius); border: 1px solid var(--sea); background: var(--sea); color: var(--paper); font-weight: 500; cursor: pointer; }
.btn:hover { background: var(--sea-dark); border-color: var(--sea-dark); }
.btn.ghost { background: transparent; color: var(--ink); border-color: var(--ink); }
.btn.ghost:hover { background: var(--paper-2); border-color: var(--sea); color: var(--sea); }

/* ── bands & grids ────────────────────────────────────────── */
.band { margin: var(--s8) 0; }
.band-head { margin-bottom: var(--s5); }
.band-head h2 { margin-bottom: var(--s2); }
.band .more { margin-top: var(--s5); font-size: var(--t-sm); }
.band .more a { text-decoration: none; border-bottom: 1px solid var(--door); color: var(--door); }

.grid { display: grid; gap: var(--s4); grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr)); }
.grid.cols-2 { grid-template-columns: repeat(auto-fill, minmax(22rem, 1fr)); }
.grid.cols-4 { grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr)); }
/* cols-4 did not produce four columns. auto-fill packs in as many 12rem
 * tracks as the container holds, which is five at 1280px — so the homepage's
 * eight ways-in tiles wrapped 5 + 3 and the second row read as an orphan.
 * The class is named for a count, so above the layout breakpoint it keeps
 * that count. Below it auto-fill still does the right thing, which is why
 * this is a floor on width rather than a rewrite. */
/* AND BETWEEN THE TWO BREAKPOINTS auto-fill GAVE THREE, so the only cols-4
 * grid on the site — the four doors on /how-it-works — rendered 3 + 1, one
 * door alone with two empty cells beside it, at 704 and at 834. Measured
 * across all 44 families at 390/704/834/1024/1280 for any grid whose last
 * row is emptier than it is full: this was the only one. A count that
 * divides the set is what a grid is for, so four items take two columns
 * here; at 390 auto-fill still gives one, and above 64rem they are four
 * across. Found on the TABLET contact sheet — invisible at both the widths
 * everything else is shot at. THE UPPER BOUND IS 64rem RATHER THAN A
 * FRACTION UNDER IT: at exactly 1024 both blocks match and the four-column
 * one is written after, so it wins. A 63.999rem would have been a seventh
 * breakpoint, and the register counts them. */
@media (min-width: 44rem) and (max-width: 64rem) {
  .grid.cols-4 { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 64rem) { .grid.cols-4 { grid-template-columns: repeat(4, 1fr); } }
/* AND cols-3 HAD NO RULE AT ALL, so it fell through to the default track and
 * packed FOUR at 1280. Nine macro regions came out 4 + 4 + 1 with the ninth
 * alone in a row and eight hundred pixels of nothing beside it — the stories
 * index's own fault, on /discover, where the nine are the one band that
 * earned its pictures. A class named for a count keeps that count above the
 * layout breakpoint; below it auto-fill is still right. */
@media (min-width: 64rem) { .grid.cols-3 { grid-template-columns: repeat(3, 1fr); } }

.card { display: block; text-decoration: none; background: var(--paper-2); border: 1px solid var(--rule); border-radius: var(--radius); overflow: hidden; transition: transform .16s ease, box-shadow .16s ease, border-color .16s ease; }
.card:hover { transform: translateY(-2px); box-shadow: var(--shadow-lift); border-color: var(--ink-3); }
.card-art { aspect-ratio: 16 / 9; overflow: hidden; background: var(--paper-3); }
.card.tall .card-art { aspect-ratio: 3 / 4; }
/* THE APERTURE, third renderer. Every generated landscape on EuropeDoor is
   seen through a doorway — SVG clipPath on the maps, a pixel mask on the
   social card, and here the box itself. Two horizontal radii of 50% meet at
   the centre of the top edge, so the pair is one continuous elliptical head
   with straight jambs below it: exactly the curve render.arch_path() draws.

   It is CSS and not the SVG clip because .plate has a 16/9 viewBox and its
   containers do not — .card-art is 16/9, .card.tall is 3/4 and the two lead
   mosaic tiles are 21/9 — and preserveAspectRatio="slice" crops the drawing
   to fill. An aperture cut inside that viewBox is cropped with it: sliced
   flat on the wide tiles and gone entirely on the tall card. A border-radius
   is measured against the painted box, so it fits whatever the box turns out
   to be. The corners fall back to --paper-3, which is what .card-art already
   paints, so the plate reads as die-cut out of the card rather than laid on
   top of it. */
.plate { width: 100%; height: 100%; display: block;
         border-radius: 50% 50% 0 0 / 34% 34% 0 0; }
.card-body { padding: var(--s4); }
.card :is(h2, h3) { font-size: var(--t-lg); margin-bottom: var(--s2); }
.card .blurb { font-size: var(--t-sm); color: var(--ink-2); margin: 0; }
.card .cardmeta { margin-top: var(--s3); font-size: var(--t-xs); color: var(--ink-3); letter-spacing: .04em; text-transform: uppercase; }

/* ── chips ────────────────────────────────────────────────── */
.chips { display: flex; flex-wrap: wrap; gap: var(--s2); margin: var(--s4) 0; }
.chip { font-size: var(--t-xs); text-decoration: none; padding: 3px var(--s3); border: 1px solid var(--rule); border-radius: 999px; color: var(--ink-2); background: var(--paper-2); white-space: nowrap; }
.chip:hover { border-color: var(--door); color: var(--door); }
/* A TAG WITH A COUNT ON IT IS A REGISTER ENTRY, NOT A CARD. /discover ended
   in four bands and one of them was a four-column grid of seventeen boxes,
   each carrying an icon, a figure and one word. Border, fill, radius and
   shadow all say "separate object placed here by a system"; spending the
   four of them on a tag name spends them on nothing, and the grid read as
   one texture — the /experiences finding, on the page whose whole subject is
   choosing. The register under it, the twelve months, was already chips.
   Tabular figures because the numbers are being compared. */
.countchip { display: inline-flex; align-items: baseline; gap: var(--s2); }
.countchip .chipn { color: var(--ink-3); font-variant-numeric: tabular-nums; }
.countchip:hover .chipn { color: inherit; }

/* ── facts ────────────────────────────────────────────────── */
.facts { display: grid; grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr)); gap: var(--s4) var(--s5); margin: var(--s6) 0; padding: var(--s5) 0; border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule); }
.fact dt { font-size: var(--t-xs); letter-spacing: .1em; text-transform: uppercase; color: var(--ink-3); margin-bottom: var(--s1); }
.fact dd { margin: 0; font-size: var(--t-base); }

/* ── columns of prose + rail ──────────────────────────────── */
.split { display: grid; grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr); gap: var(--s7); align-items: start; }
.rail { background: var(--paper-2); border: 1px solid var(--rule); border-radius: var(--radius); padding: var(--s5); font-size: var(--t-sm); position: sticky;
         top: calc(var(--mast) + var(--s5)); }
.rail h3 { font-size: var(--t-md); margin-bottom: var(--s3); }
.rail ul { margin: 0 0 var(--s4); padding-left: var(--s4); }
.rail li { margin-bottom: var(--s2); }
.rail p { max-width: none; }

/* ── lists that are really tables ─────────────────────────── */
.rows { border-top: 1px solid var(--rule); }
.row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--s4); align-items: baseline; padding: var(--s4) 0; border-bottom: 1px solid var(--rule); text-decoration: none; }
.row:hover { background: var(--paper-2); }
/* :is(h2, h3) BECAUSE THE LEVEL IS THE OUTLINE AND THE CLASS IS THE LOOK.
   A row's name is an h3 inside a band, where the band's own h2 is the level
   above it, and an h2 on the families where the LIST IS THE PAGE — the facet
   pages, the theme pages, the motion pages, the interest pages, /method,
   /discover and four indexes. 158 pages went h1 straight to h3, which is the
   fault the note and rail headings two thousand lines up already record:
   styling reasoning applied to heading levels. The styling is on the class,
   so the level is free to be correct. */
.row :is(h2, h3) { font-size: var(--t-md); margin: 0; }
/* `main h2 { margin-top: var(--s6); margin-bottom: var(--s4) }` is the BAND
   heading's rule, and it started matching every row and card title the moment
   those were promoted to h2 — 32 pixels above and 16 below, on 158 pages, for
   a change that was meant to move nothing a reader can see. The level is the
   outline; the class is the look, and the class has to say so out loud now
   that both levels reach the same elements. */
.card :is(h2, h3), .storylead :is(h2, h3), .storysm :is(h2, h3),
.storyleadwide :is(h2, h3), .journeyrow :is(h2, h3), .jrow :is(h2, h3),
.way :is(h2, h3) { margin: 0; }
/* A MEASURE IS FOR PROSE, AND A ROW'S SUBLINE IS A SEQUENCE.
   `p { max-width: var(--measure) }` is right on every paragraph on the site
   and wrong on this one: `.rowsub` is a <p> holding a middot-separated list
   of place names — Bruges · Siena · Tallinn · Sighișoara — and the measure
   broke it at 544 pixels inside a 948-pixel column. Four hundred pixels of
   nothing beside every row of /themes, /journeys, /europe-in and every other
   row index, and the list wrapped to two lines where one would have done.
   65 characters is the line length a reader can follow from end to start
   again; nobody reads a list of names that way, they scan it. The row's own
   grid is the constraint. */
.row .rowsub { font-size: var(--t-sm); color: var(--ink-2); margin: var(--s1) 0 0;
               max-width: none; }
.row .rowmeta { font-size: var(--t-xs); letter-spacing: .06em; text-transform: uppercase; color: var(--ink-3); white-space: nowrap; }

/* STATE IN THE FORM, NOT ONLY IN THE WORD. /how-it-works is three bands —
   built, designed, blocked — and every row carried its state as an uppercase
   label in the same weight and the same ink as the other two, so the only
   thing separating "Search: BUILT" from "Taking a payment: BLOCKED" was
   which heading you had scrolled past. On the one page whose whole argument
   is that most pre-launch products blur exactly those three.

   Three forms, and the form is what carries: a filled mark exists, a ring is
   drawn and not made, a ring with a bar through it is held shut. Hue is the
   second signal and never the only one — a reader who cannot separate two
   hues still separates a disc from a ring, and this stylesheet has no state
   palette to borrow, so inventing one for sixteen rows would be three tokens
   nothing else on the site uses. */
.statemark { display: inline-block; width: .62em; height: .62em; flex: none;
             margin-right: .55em; border-radius: 999px; position: relative;
             border: 1.5px solid currentColor; vertical-align: baseline; }
.sm-on   { background: var(--atlantic); border-color: var(--atlantic); }
.sm-part { background: linear-gradient(to right,
             currentColor 0 50%, transparent 50% 100%); }
.sm-draft { background: none; }
/* A SLASH THROUGH A SIX-PIXEL RING IS A BLOB. The first version drew the
   barred mark that way and it rendered as a diagonal smudge with two ends
   poking out of the circle — a form that has to be recognised at 6px cannot
   be a composition of two forms. A blocked thing is the one mark on the page
   that is not round: a filled square, which differs from the filled disc by
   SILHOUETTE and reads at any size. */
.sm-off { background: currentColor; border-radius: 0; }
/* NO FLEX HERE, AND THE FIRST VERSION OF THE STATE MARK ADDED IT. A flex
   container makes every child and every text run a flex ITEM, and a <br>
   between two items does nothing — so `2026-09-05<br><span>5 min</span>` on
   the stories index rendered as "2026-09-055 MIN", a date and a reading time
   run together with no separator, on every row of that page. The mark is an
   inline-block with its own right margin and flows in the line it belongs
   to; it never needed a flex container. */

/* THE OBSERVED SPREAD OF A SCORE DIMENSION. /method printed "0–97" beside
   all eight, which is a constant wearing the clothes of a measurement — and
   the wrong constant: nothing scores 0, because every dimension starts at a
   base of 34, and Food tops out at 96 where Authenticity tops out at 82.

   ONE AXIS FOR ALL EIGHT. The rule is the full 0–100 range, the block is
   where this dimension actually lands, and the tick is its median. A
   per-row axis would draw every dimension the same width, which is the
   constant again in a different medium. preserveAspectRatio="none" because
   the horizontal scale is the whole claim and there is no type inside the
   viewBox to be stretched with it. */
/* AN EMPTY STATE IS A PAGE, AND /my-europe's WAS A GREY BOX AND 250 PIXELS
   OF NOTHING. It is also the first thing every reader sees there — the list
   is empty until they save something — so it is the state that surface
   SHIPS in, not a fallback, and it was the only place on the site that
   explained what to do without offering a way to do it.

   Three ways in, and they are the site's own top-level surfaces rather than
   a curated pick: choosing three destinations to suggest would be a ranking
   this Atlas does not hold.

   NOT `.emptystate`, WHICH ALREADY EXISTS. That one is a 44ch rule-marked
   paragraph for a section with nothing in it, used by pages.py, and reusing
   the name gave this block a 420px max-width and a border it was not asked
   for — three cards squeezed into a third of the page while both rules
   looked correct on their own. Found by rendering it. */
.startstate { margin: var(--s6) 0 var(--s7); }
.startstate h2 { margin: 0 0 var(--s3); }
.startstate > p { color: var(--ink-2); margin: 0 0 var(--s6); max-width: 54ch; }
.emptyways { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
             gap: var(--s4); }
.emptyways a { display: grid; gap: var(--s1); text-decoration: none;
               padding: var(--s5); border: 1px solid var(--rule);
               border-radius: var(--radius); background: var(--paper-2); }
.emptyways b { font-family: var(--display); font-size: var(--t-lg);
               font-weight: 600; }
.emptyways span { color: var(--ink-3); font-size: var(--t-sm); }
.emptyways a:hover, .emptyways a:focus-visible { border-color: var(--door); }
.emptyways a:hover b, .emptyways a:focus-visible b { color: var(--door); }
@media (max-width: 44rem) { .emptyways { grid-template-columns: 1fr; } }

/* THE ASK PANEL SAID THE SAME THING TWICE AND STACKED BOTH IN A THIRD OF
   THE PAGE. A full-width textarea, then a narrow paragraph about what the
   planner reads, then the button, then a second narrow paragraph repeating
   that it runs in your browser and is not a model — with six hundred pixels
   of nothing beside each. Two notes about privacy, in one panel, either side
   of the control they describe.

   One note, beside the button it belongs to: the action is what the reader
   came for, and the explanation earns its place by sitting level with it
   rather than in front of it. */
.askfoot { display: grid; grid-template-columns: auto minmax(0, 1fr);
           gap: var(--s5); align-items: start; margin-top: var(--s5); }
.askfoot .hero-actions { margin: 0; }
@media (max-width: 52rem) { .askfoot { grid-template-columns: 1fr; } }

/* A GROUP HEADING INSIDE A LIST OF ROWS. Sixty-three destinations in one
   column, sorted by country with nothing marking where one country ended:
   an order that is real and invisible reads as alphabetical by accident.
   Not an <h3> — the rows already use one for the place name, and a heading
   level that jumps about inside one list is worse than none. */
.rowgroup + .rowgroup { margin-top: var(--s6); }
/* `max-width: none` because the base `p` rule caps every paragraph at the
   measure, and a rule that stops halfway across a list of full-width rows
   reads as a rendering fault rather than a divider. */
.rowgrouphead { display: flex; align-items: baseline; gap: var(--s3);
                max-width: none;
                margin: 0 0 var(--s2); padding-bottom: var(--s2);
                border-bottom: 1px solid var(--ink-3);
                font-size: var(--t-xs); letter-spacing: .12em;
                text-transform: uppercase; color: var(--ink-2); }
.rowgroupn { margin-left: auto; color: var(--ink-3);
             font-variant-numeric: tabular-nums; letter-spacing: 0; }

/* A COUNT AND ITS SHARE, IN A ROW'S META COLUMN. The bar is the same
   `hopbar` the journeys and /experiences use, so no third bar scale enters
   the system; it needs a width here because a right-aligned inline element
   has none of its own and rendered as a 30px stub. */
/* No `display`: `.hopbar` is already a block. Width and offset only. */
.rowmeta .hopbar { width: 5rem; margin: var(--s2) 0 0 auto; }

/* THE ROUTE THE PLANNER BUILT. Unframed, on the whole continent: /journeys
   frames each route because seventeen framed thumbnails is how you tell
   seventeen routes apart, and a planner shows one at a time, where WHERE in
   Europe it sits is part of what a reader is deciding. Capped, because the
   drawing is 1000x780 and at full column width it would be taller than the
   summary it belongs to. */
.planmap { margin: var(--s6) 0; }
/* THE SEA IS DRAWN, NOT INHERITED. `.constel` fills the land and leaves the
   water to whatever the page is, which is right in the DISCOVER world where
   the page is limestone. /plan is INTELLIGENCE, so the first render was pale
   land on near-black and the Mediterranean read as a hole rather than as a
   sea. The `.iheroart` treatment: the atlas palette's own water behind the
   atlas palette's own land, which is the split this site draws maps on — a
   picture is warm paper and pale water, an instrument is graphite. This is a
   picture inside an instrument, and it takes the picture's palette. It has
   no aperture, so painting a background is allowed here where it is not on
   an arched figure. */
.planmap .constel { display: block; width: 100%; max-width: 34rem;
                    margin-inline: auto; height: auto;
                    background: var(--atlas-sea); border-radius: var(--radius); }
.planmap .constel > use { fill: var(--atlas-land); }
/* A DOT WITH NO RADIUS IS NOT A DOT. `.constel-lit circle` sets only a fill;
   every family that draws one sets its own `r`, because a stop on a framed
   five-city route and a destination on a 200-dot interest tile are not the
   same mark. Without this the route drew and its stops did not, which is the
   invisible-thing failure: correct markup, nothing on the screen. */
.planmap .constel-lit circle { r: 9px; stroke: var(--atlas-here);
                               stroke-width: 2; }
.planmap .constel-route { stroke-width: 7; }
.planmap .constel-route.case { stroke-width: 14; }

/* ONE DRAWING FOR THE SIX. A month page's last band answers "where would
   you send me in October", and it was six abstract plates over six names.
   Six framed thumbnails would be six pictures of the same continent, so the
   set is lit on one extent beside the rows that name them — the /themes
   arrangement, on the family whose subject is time. */
.quietsend { display: grid; grid-template-columns: 20rem minmax(0, 1fr);
             gap: var(--s6); align-items: start; }
.quietart { margin: 0; }
.quietart .constel { width: 100%; height: auto; }
/* NOT `.constel-theme`, WHOSE MARKS ARE 22 UNITS. That radius is right for a
   themes row, drawn unframed at the whole continent; this drawing is FRAMED
   on six places, so the same number rendered as six blue discs the size of
   Switzerland. A radius in viewBox units is not a radius in pixels — the
   same arithmetic that took the map labels off narrow screens. */
.quietart .constel-lit circle { r: 10px; stroke: var(--atlas-here);
                                stroke-width: 2; }
@media (max-width: 60rem) {
  .quietsend { grid-template-columns: 1fr; }
  .quietart { max-width: 22rem; }
}

/* EVERY ROUTE THROUGH ONE PLACE. The /journeys opening scoped to a
   destination: one frame, all the routes, the place itself lit. Capped at a
   readable width rather than the column, because the drawing is 4:3 and a
   full-column one would be taller than the list it introduces. */
.facetart { margin: var(--s6) 0 var(--s7); }
.facetart .constel { display: block; width: 100%; max-width: 32rem;
                     margin-inline: auto; height: auto;
                     background: var(--atlas-sea); border-radius: var(--radius); }
.facetart .constel > use { fill: var(--atlas-land); }
.facetart .constel-lit circle { r: 9px; stroke: var(--atlas-here);
                                stroke-width: 2; }
.facetart figcaption { margin-top: var(--s3); font-size: var(--t-xs);
                       color: var(--ink-3); }

/* A COLLECTION HEADING SAT EXACTLY ON THE PANEL ABOVE IT. Measured on the
   populated My Europe: the "Start a collection" form ends at 596 and the
   "Everything else" heading begins at 596 — no gap at all, the heading's
   own rule running into the panel's border. `main h2` carries a top margin
   and `h3` does not, which is right everywhere a heading follows prose and
   wrong where it follows a bordered box. */
#mine h3 { margin-top: var(--s6); }

.rowdist { display: grid; gap: var(--s1); justify-items: end; }
/* 135 PIXELS FOR THE ONE PIECE OF EVIDENCE ON THE PAGE. /method is the
   formula and what the formula PRODUCES, and the second half was an
   8.5rem smudge right-aligned in a 1,168px row, drawn in the signature at
   34% — which measures 1.64:1 on limestone, under the 3:1 a graphical
   object needs, so the bar a reader was meant to compare across eight rows
   was barely a tint. Eight near-identical pale bands say "these are all the
   same"; the whole reason to draw them is that Value's median is 85 and
   Culture's is 52.
   The span is the signature outright now — a declared colour with a claim
   behind it in docs/palette.json, rather than an opacity nobody measured —
   and the median tick is the page's own ink, which reads ON the span at
   3.48:1 in light and 3.05:1 in dark. The end caps are the scale. */
.dist { width: 8.5rem; height: 14px; display: block; }
@media (min-width: 64rem) { .dist { width: min(19rem, 23vw); } }
.distaxis { fill: var(--rule); }
.distend  { fill: var(--ink-3); }
.distspan { fill: var(--signature); }
.distmed  { fill: var(--ink); }
.distnum { font-variant-numeric: tabular-nums; text-transform: none;
           letter-spacing: 0; color: var(--ink-3); }

/* ── notes & callouts ─────────────────────────────────────── */
/* A PANEL TWICE AS WIDE AS ITS OWN TYPE. Every paragraph on this site is
   capped at the measure — 34rem, about 65 characters, the line length a
   reader can follow from end to start again — and this panel had no width of
   its own, so on a 1,168px column it drew 1,168 pixels of fill behind 550
   pixels of text. Six hundred pixels of tinted ground saying nothing, on 843
   pages, with the rule that identifies it three columns away from the last
   word of every line. A fill is what says "this is one object"; a fill that
   runs half a column past the object is a band.
   Capped at the measure plus its own padding, so the panel is the shape of
   what is in it. Nothing about the type changes — this is the ground
   catching up with a rule the type has always had. */
.note { border-left: 3px solid var(--sea); background: var(--paper-2); padding: var(--s4) var(--s5); border-radius: 0 var(--radius) var(--radius) 0; margin: var(--s5) 0; font-size: var(--t-sm); max-width: calc(var(--measure) + 2 * var(--s5) + 3px); }
.note p:last-child { margin-bottom: 0; }
/* THE ADVISORY COLOUR WAS SPENT 263 TIMES ON THINGS THAT ARE NOT
   ADVISORIES AND 3 TIMES ON THINGS THAT ARE. Counted in the shipped HTML:

       255   "We do not hold opening hours, prices or a website for this"
         4   "This is a pre-launch draft, and it says so"
         3   "Check government travel advice before planning anything here"
         1   "What discoverability is not"
         1   "What 'unverified' means here"
         1   "The Fund holds no money, and will not until three things are true"

   Three of those are a travel advisory. The rest are provenance and policy —
   what this atlas holds, what it refuses to hold, and why — which is the
   HERITAGE accent's stated home: "how this project knows what it claims".
   A reader who has learned that the tinted panel means *read this carefully*
   meets it on all 255 place pages saying we do not list opening hours, and
   the one time it means a country under a travel advisory it says exactly
   what it said on Schönbrunn. That is the advisory-and-terracotta collision
   one level up: not two colours that look alike, one colour doing two jobs.

   A warning keeps the tinted ground, because a warning is meant to stop
   you. A provenance note is an ordinary note with the heritage rule down
   its edge — the same green as /method, /sources and /fund. */
/* AND THE PROVENANCE PASS REACHED 262 PANELS AND MISSED 465 THAT SAY THE
   SAME KIND OF THING. Counted in the shipped HTML after that commit, the
   notes still carrying the INTERACTIVE colour were:

       445   "EuropeDoor lists neither, yet — both are business listings
              rather than editorial entries, and all three things they need
              are blocked on the same thing as everything else commercial"
       130   "Food, events and practicalities are on the country page"
        17   "Why this page tells you its own tag is wide"
        12   "Build this into a route"
         6   what a facet page is, what the AI rule is, what this drawing is

   Every one of those except "Build this into a route" is a statement about
   what this atlas holds, refuses to hold, or has not built — which is the
   heritage accent's stated home. --sea is the INTERACTIVE colour: links,
   focus rings, the thing you can act on. It was edging a panel that says we
   will not list restaurants, on 445 destination pages.

   SO THE DEFAULT IS THE COMMON CASE, which is what a default is for. A note
   here is provenance unless it says otherwise; `.onward` is the one that
   hands a reader to another surface and takes the interactive colour, and
   `.warn` keeps the tinted ground because a warning is meant to stop you.
   `.sourced` is gone rather than kept as a synonym for the default: a second
   name for one thing is two things waiting to disagree, which is the same
   sentence this stylesheet just wrote about --atlas-context. */
.note { border-left-color: var(--provenance); }
.note.warn { border-left-color: var(--warn); background: var(--warn-bg); }
.note.onward { border-left-color: var(--sea); }
.note h3 { font-size: var(--t-md); margin-bottom: var(--s2); }

/* ── the itinerary/leg timeline ───────────────────────────── */
.legs { list-style: none; margin: var(--s6) 0; padding: 0; }
.leg { display: grid; grid-template-columns: 4.5rem minmax(0, 1fr); gap: var(--s4); padding: var(--s4) 0; border-top: 1px solid var(--rule); }
.leg:last-child { border-bottom: 1px solid var(--rule); }
.leg-when { font-family: var(--mono); font-size: var(--t-xs); color: var(--ink-3); letter-spacing: .02em; padding-top: 4px; }
.leg h3 { font-size: var(--t-md); margin-bottom: var(--s1); }
.leg h3 a { text-decoration: none; border-bottom: 1px solid var(--rule); }
.leg h3 a:hover { border-bottom-color: var(--door); }
.leg p { font-size: var(--t-sm); color: var(--ink-2); margin: 0; }
.leg .hop { font-size: var(--t-xs); color: var(--ink-3); margin-top: var(--s2); }

/* ── forms (the planner) ──────────────────────────────────── */
.form { display: grid; gap: var(--s5); background: var(--paper-2); border: 1px solid var(--rule); border-radius: var(--radius); padding: var(--s5); }
/* THE SEARCH BOX IS THE PAGE, AND IT WAS IN A CARD.
   /search is an instrument whose entire interface is one field; wrapping it
   in a bordered, filled, rounded panel says "separate object, placed here by
   a system" about the one thing the page exists to be. That is the Stay
   layer's own rule — no border box, no radius, no shadow, no fill — applied
   to the control instead of to a band.
   The field takes the weight the panel was carrying: a larger box, the label
   at instrument weight above it, and the help under it at a caption's
   measure. Nothing new is declared; --t-md is the lede size and the padding
   is a step already on the scale. */
#searchform.form { background: none; border: 0; border-radius: 0; padding: 0; }
#searchform .field > label { font-size: var(--t-sm); letter-spacing: .12em; }
#searchform .field input[type="text"] {
  font-size: var(--t-md); padding: var(--s4) var(--s4);
  border-color: color-mix(in srgb, var(--ink-3) 55%, transparent); }
#searchform .field input[type="text"]:focus { border-color: var(--sea); }
/* AND THE PLANNER IS THE SAME PAGE WITH TWO OF THEM. The ask box — the one
   control the lede points at, "say what you have and what you like, in a
   sentence" — sat in a grey card, and the fifteen-field form sat in a second
   identical grey card underneath it. Two equal panels say the two paths are
   equal; the page's own sentence says one leads and the other is "or the
   form below".
   Same treatment as the search box for the lead control, and the form keeps
   a surface — it IS a grouped set of controls, which is the case a panel was
   designed for — but as a rule and space rather than as a filled box. Four
   things deliberately absent, which is the Stay layer's rule and the reason
   its action band reads as part of the page. */
#askform.form { background: none; border: 0; border-radius: 0; padding: 0; }
#askform .field textarea {
  font-size: var(--t-md); padding: var(--s4);
  border-color: color-mix(in srgb, var(--ink-3) 55%, transparent); }
#askform .field textarea:focus { border-color: var(--sea); }
#planner.form { background: none; border: 0; border-radius: 0;
  border-top: 1px solid var(--rule); padding: var(--s6) 0 0; }
/* AND FOUR OF THE SEVEN BARS UNDER IT WERE A SPECK. The index extent is
   drawn as a share of its largest kind on an 80-pixel track, and the largest
   kind is 319 of 550 — so journeys, themes, stories and fund projects
   rendered at 5, 4, 3 and 4 per cent, which is between 2.4 and 4 pixels
   wide and three tall. A chart on which four of seven series cannot be seen
   is the wrong track, not the wrong data: the shares are true and the
   drawing has to be able to show them.
   Wider, and with the floor the journeys index already uses on its own
   segments — `min-width` on the fill, because a share that rounds to two
   pixels should still be a mark rather than nothing. 9 of 319 goes from 2.4
   pixels to 6.3. */
#results .rowmeta .hopbar { width: min(14rem, 30vw); }
#results .rowmeta .hopbar > span { min-width: 3px; }
/* :not(.inlinecheck) is load-bearing. The first attempt at this changed the
   group heading from a <label> to a <p class="fieldhead"> and left the rule
   as `.field > label` — but the checkbox's own wrapper IS a <label> and IS a
   direct child of .field, so it went on matching and the sentence went on
   rendering in capitals. The browser check written in the same commit is
   what said so; reading the CSS said it was fixed. */
.field > label:not(.inlinecheck), .field > .fieldhead, .fieldset > legend { display: block; font-size: var(--t-xs); letter-spacing: .1em; text-transform: uppercase; color: var(--ink-3); margin-bottom: var(--s2); font-weight: 600; }
/* AND `textarea`, WHICH WAS MISSING FROM THIS LIST. The only textarea
   outside the planner's ask box is My Europe's portable list, and it took
   the browser default of twenty columns: a 182x66 pixel window holding the
   whole saved list as JSON, on the one control whose entire purpose is
   "copy it, paste it into the same box on another device". `resize:
   vertical` because a reader with forty saved places needs to see them and
   nobody needs to drag it sideways. */
.field input[type="number"], .field input[type="text"], .field select,
.field textarea {
  width: 100%; font: inherit; padding: var(--s3); border: 1px solid var(--rule); border-radius: var(--radius);
  background: var(--paper); color: var(--ink);
}
/* A HELP LINE IS A CAPTION FOR THE CONTROL ABOVE IT AND IT WAS TOUCHING
   THE BORDER. Measured on /search: the input's box ends at y=454 and the
   paragraph explaining what the box understands starts at y=454 — zero
   pixels, so it reads as text that has fallen inside the field. The label
   above has eight. And the measure was the panel's: 1,118 pixels of box with
   a 544-pixel paragraph in it and 574 of empty card beside, which is the
   "six hundred pixels of white beside it" fault this stylesheet already
   records twice, at the size of a caption. */
.field > .small { margin-top: var(--s3); max-width: 62ch; }
.field textarea { resize: vertical; min-height: 6rem; font-family: var(--mono);
                  font-size: var(--t-sm); }
.fieldset { border: 0; padding: 0; margin: 0; }
.checks { display: flex; flex-wrap: wrap; gap: var(--s2); }
.checks label { font-size: var(--t-sm); border: 1px solid var(--rule); background: var(--paper); border-radius: 999px; padding: var(--s2) var(--s4); cursor: pointer; user-select: none; display: inline-flex; align-items: center; gap: var(--s2); }
.checks input { margin: 0; }
.checks label:has(input:checked) { border-color: var(--door); background: color-mix(in srgb, var(--door) 12%, var(--paper)); color: var(--ink); }
.form-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: var(--s4); }

.result-summary { display: flex; flex-wrap: wrap; gap: var(--s5); padding: var(--s5); border: 1px solid var(--rule); background: var(--paper-2); border-radius: var(--radius); margin-bottom: var(--s5); }
.result-summary div { min-width: 8rem; }
.result-summary dt { font-size: var(--t-xs); letter-spacing: .1em; text-transform: uppercase; color: var(--ink-3); }
.result-summary dd { margin: var(--s1) 0 0; font-size: var(--t-lg); font-family: var(--display); }

/* ── misc ─────────────────────────────────────────────────── */
.hr { border: 0; border-top: 1px solid var(--rule); margin: var(--s7) 0; }
.small { font-size: var(--t-sm); color: var(--ink-2); }
.mono { font-family: var(--mono); font-size: var(--t-sm); }
.stack > * + * { margin-top: var(--s4); }
.tag { display: inline-block; font-size: var(--t-xs); letter-spacing: .08em; text-transform: uppercase; padding: 2px var(--s2); border-radius: 2px; background: var(--paper-3); color: var(--ink-2); }
.tag.verified { background: color-mix(in srgb, var(--sea) 18%, var(--paper)); color: var(--sea-dark); }
.tag.advisory { background: var(--warn-bg); color: var(--warn); }

/* ── footer ───────────────────────────────────────────────── */
/* THE GAP BELOW THE LAST WORD WAS STATED TWICE, BY TWO ELEMENTS THAT DO
   NOT KNOW ABOUT EACH OTHER. `main` pads its bottom by --s9 and this rule
   added another --s9 above the band: 224 pixels of empty page between the
   last sentence and the footer rule, on all 1,072 pages. Measured on
   /themes, where the closing note ends at y=2862, main ends at 2990 and the
   footer starts at 3102.
   Neither declaration was wrong on its own, which is why it survived — the
   content wants air under it and a band wants separation from what it
   follows. But that is ONE relationship and it belongs to one of them. It
   is main's, because main is what the reader has been reading. */
.footer { border-top: 1px solid var(--rule); background: var(--paper-2); }
.footer-in { max-width: var(--page); margin: 0 auto; padding: var(--s7) var(--s5); }
.footer-lede { font-family: var(--display); font-size: var(--t-lg); max-width: 34rem; }
/* TWENTY LINKS IN ONE FLAT ROW, ON ALL 1,033 PAGES. Every one the same
   weight and the same size, wrapping into two lines of near-identical text,
   so the register of this site — a published method, a source register, an
   accessibility statement, a fund that holds nothing — read as boilerplate,
   and a reader looking for how the scores are computed scanned twenty items
   in no order. Four groups, laid out as columns, each link on its own line:
   a name in a column is found by reading down one list of five, not by
   scanning a paragraph of twenty. */
.footer-nav { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
              gap: var(--s6) var(--s5); margin: var(--s6) 0 var(--s7);
              font-size: var(--t-sm); align-items: start; }
.fgroup { display: grid; gap: var(--s2); align-content: start; }
.fghead { font-size: var(--t-xs); letter-spacing: .1em; text-transform: uppercase;
          color: var(--ink-3); margin: 0 0 var(--s1); }
.footer-nav a { text-decoration: none; color: var(--ink-2); }
.footer-nav a:hover, .footer-nav a:focus-visible { color: var(--door); }
@media (max-width: 52rem) {
  /* Two columns rather than one: four stacked lists is a very long footer on
     a phone, and the groups are short enough to sit side by side. */
  .footer-nav { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.footer-legal { font-size: var(--t-xs); color: var(--ink-3); max-width: 46rem; }

@media (max-width: 60rem) {
  .split { grid-template-columns: 1fr; }
  .rail { position: static; }
}
@media (max-width: 44rem) {
  /* THE MASTHEAD SPENT 168 PIXELS OF AN 844-PIXEL PHONE, which is a fifth of
     the viewport before a page says anything, and with breadcrumbs and the
     thumb bar it was 276 — a third of the first screen given to navigation,
     on all 1,033 pages. It stacked in three rows because `flex-direction:
     column` is the obvious phone answer and nobody measured what it cost.
     The destination family already compensates for this figure in its own
     padding, which is the wrong place: a page paying for the shell's
     decision is the shell's bug.

     Two rows. The wordmark and the two utilities share the first, because
     they are identity and they are short; the seven sections take the
     second as ONE line that scrolls sideways.

     NOTHING IS HIDDEN, and that is the constraint that decided this. The
     obvious cut is to drop the seven links on a phone, since the thumb bar
     carries five destinations already — but `display: none` takes them out
     of the tab order too, so a keyboard or screen-reader user on a narrow
     window loses Countries, Experiences, Journeys, Stories and Events
     entirely. A row that scrolls keeps every one of them reachable and
     focusable. The fade at the right edge is the affordance saying so. */
  .masthead-in {
    display: grid; grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: "brand util" "nav nav";
    align-items: center; gap: var(--s2) var(--s4);
    /* THE MASTHEAD SAT ON A 24px GUTTER AND EVERY WORD UNDER IT ON 16.
       `main` is padded --s4 at this breakpoint and `.masthead-in` was left
       on the desk's --s5, so the wordmark and the whole navigation were
       indented eight pixels further than the h1, the breadcrumb and every
       line of prose, on 1,033 pages at phone width. Nothing counts a
       gutter. It also happens to be the sixteen pixels the section row
       needs to fit on one line at 390. */
    padding-inline: var(--s4);
  }
  .masthead-in .wordmark { grid-area: brand; }
  .masthead-in .navutil  { grid-area: util; }
  .masthead-in .nav { grid-area: nav; flex-wrap: wrap; row-gap: var(--s2); }
  /* THE ROW THAT SCROLLED SIDEWAYS HID THREE SECTIONS AND CALLED THE FADE
     AN AFFORDANCE. Measured at 390: content 552px in a 366px box, so Plan,
     Stories and Events were entirely off-screen, and at 320 Journeys was
     too. The only signal was a 44px mask fade at the right edge. A link a
     reader cannot see is not reachable because it is focusable.

     The old comment framed the choice as "drop them and lose the tab order"
     against "scroll them and keep it", and it was right about five items
     and wrong to apply that to all seven. /discover, /plan and /my-europe
     are in the thumb bar, which appears at THIS EXACT breakpoint — so
     hiding those three here hides nothing from anybody, and showing them
     was a duplication that cost the other four their place on the screen.
     Explore and Discover are the same URL under two names, one bar apart.

     What is left wraps instead of scrolling: Countries, Experiences,
     Journeys, Stories and Events, all visible, all tappable, no gesture. */
  .masthead-in .nav a.inthumb, .masthead-in .navutil a.inthumb { display: none; }
  /* ONE ROW, AND THE ROW IS WHAT THE MEASUREMENT BOUGHT. Five sections at
     the desk tracking measured 332px of content plus four 16px gaps in a
     366px box, so they wrapped to two rows and the masthead grew from 93px
     to 120 — 21% of a 844px phone in navigation, which is the opposite of
     what this change is for. Tracking is the lever: .04em over ~40
     uppercase characters at 12px is 19px of pure letter-spacing, and a
     nav label is read as a shape rather than spelled out, so it loses
     nothing at .015em. With an 8px gap that is 332 -> 301 in a 342px box
     at 390 and one row.

     320 STILL TAKES TWO, and that is stated rather than hidden: 272px of
     usable width cannot hold five words at any honest size. The layout
     adapts; it does not pretend. */
  .nav { gap: var(--s2); font-size: var(--t-xs); letter-spacing: .015em; }
  .nav a { white-space: nowrap; }
  main { padding: var(--s5) var(--s4) var(--s8); }
  .leg { grid-template-columns: 1fr; gap: var(--s2); }
  /* The right-hand column of a row is a nowrap label at desk width. On a
     phone the longer ones — "204 km, a comfortable train leg, half a day at
     most" — pushed the page 47px wide. Stack, and let it wrap. */
  .row { grid-template-columns: 1fr; gap: var(--s2); }
  .row .rowmeta { white-space: normal; }
  /* A bar that right-aligns itself inside a right-aligned column is left
     hanging when the column stacks and goes left. Measured on /search's
     resting state at 390: the count sat at the left margin and its bar at
     the right, forty pixels of empty between two halves of one figure. */
  .rowmeta .hopbar { margin-left: 0; }
  .rowdist { justify-items: start; }
  .scorerow { grid-template-columns: 7rem 1fr 2.2rem; gap: var(--s2); }
}
/* MOTION: CONSIDERED, TRIED, AND REMOVED.
 *
 * The brief asks where motion would communicate movement, transition,
 * discovery, hierarchy or state. On the destination page the only candidate
 * was transition — the section nav jumps to an anchor several screens away,
 * and an instant jump leaves a reader unsure whether they moved down the
 * page or landed on a new one. Everything else considered was decoration:
 * reasons fading in, the map easing, cards lifting further.
 *
 * `html { scroll-behavior: smooth }` was added for that one case and is
 * gone again. It makes every programmatic scrollTo asynchronous, and the
 * browser suite failed within a minute: a check that scrolls to the bottom
 * and asserts the sticky bars do not cover the last link measured before
 * the scroll had landed. The layout was fine; the global setting was not.
 *
 * And the site already had an answer. planner.js opts in per call, with
 * scrollIntoView({behavior:"smooth"}), exactly where a transition needs
 * explaining. A global override was a blunter version of a decision this
 * codebase had already made properly.
 *
 * The sticky rail carries state on this page and animates nothing.
 */
@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
  .card:hover { transform: none; }
}

/* ── the Europe Experience Score ──────────────────────────── */
.score { margin: var(--s6) 0; padding: var(--s5); border: 1px solid var(--rule); border-radius: var(--radius); background: var(--paper-2); }
.scorerow { display: grid; grid-template-columns: 8.5rem 1fr 2.5rem; gap: var(--s3); align-items: center; margin-bottom: var(--s2); }
.scorelabel { font-size: var(--t-sm); color: var(--ink-2); }
.scorebar { height: 7px; background: var(--paper-3); border-radius: 999px;
            overflow: hidden; position: relative; }
/* THE MEDIAN TICK. Eight exact numbers with nothing to compare them to: a
   reader met "Authenticity 66" with no way to know that dimension never
   exceeds 82, or "Value 54" against an Atlas median of 85. Drawn as an
   overlay whose inner span is the same .wN utility the bar itself uses — a
   style attribute would force style-src open on all 1,033 pages — so the
   tick sits on that span's right edge and needs no second scale. */
/* AND `background: none` IS LOAD-BEARING. `.scorebar > span` paints the
   gradient fill, and the median overlay is also a direct span child of the
   bar — so the first version painted an inset:0 gradient over the whole
   track and EVERY dimension rendered at 100%. Bergen's Value 54 was the same
   length as its Nature 97, with the correct numbers beside them. Found by
   screenshotting the panel; the DOM measurement of the fill span was right
   the whole time, because the fill was never the thing that was wrong.

   AND `.scoremed` ON ITS OWN LOST THE SPECIFICITY FIGHT: (0,1,0) against
   `.scorebar > span` at (0,2,0), so the first repair changed nothing and the
   bars stayed full. Fourth time in this stylesheet that a rule has lost one
   of these and rendered as "the thing is simply wrong"; the escape is to
   match at the same level, not to declare louder. */
.scorebar > .scoremed { position: absolute; inset: 0; pointer-events: none;
                        background-image: none; }
.scoremed > span { display: block; height: 100%;
                   border-right: 1.5px solid color-mix(in srgb, var(--ink) 45%, transparent); }
.scorebar > span { display: block; height: 100%; background: linear-gradient(90deg, var(--sea), var(--signature)); }
.scorenum { font-family: var(--mono); font-size: var(--t-sm); text-align: right; color: var(--ink-2); }
.score .small { margin: var(--s4) 0 0; }

/* THE SHAPE OF A JOURNEY IS THE LENGTHS OF ITS LEGS, and the page had a
   section called "The shape of it" containing a table of facts. Every leg
   row was the same height whatever the distance, so the Carpathian Arc — a
   22 km hop and a 443 km haul in the same list — looked exactly like the
   White Villages, whose longest leg is 1.6x its shortest. The number was
   printed in words on all 104 legs and never once drawn.
   One scale within a journey, never between journeys: shared, every Alpine
   leg would be a stub beside an Arctic one and say nothing about either.
   Built from `.w0`-`.w100`, the same utility classes the Europe Experience
   Score uses, because a `style` attribute would force `style-src` open on
   all 1,033 pages. */
.hopbar { display: block; height: 3px; margin: var(--s2) 0 var(--s3);
          background: var(--paper-3); border-radius: 999px; overflow: hidden; }
.hopbar > span { display: block; height: 100%; background: var(--door); }
/* THE WHOLE ROUTE IN ONE LINE, on the journeys index. Same track, same ink,
   the legs laid end to end instead of stacked one per row — a variant of an
   element that already exists rather than a new one, which is the rule: no
   new primitive until repeated structure has actually emerged. The hairline
   between segments is what makes the legs countable; without it the bar is a
   length and says nothing about the rhythm. */
.hopbar.route { display: flex; gap: 1px; background: none; }
.hopbar.route > span { background: var(--door); border-radius: 999px;
                       min-width: 2px; }
.journeyrow .rowsub { margin-bottom: 0; }
/* THE QUERY, ON THE INDEX THAT LISTS TWELVE OF THEM. Set in the mono face
   the rest of the site uses for a computed value — a score, a coordinate, a
   SHA — because that is what this is: a sentence a machine produced from the
   dataset, not a description somebody wrote. Quieter than the strapline
   above it, because you read the name first and the query when you want to
   know what made the page. */
.motionrow .motionq { font-family: var(--mono); font-size: var(--t-sm);
                      color: var(--ink-2); margin: var(--s2) 0 0;
                      max-width: 62ch; }
.motionrow .rowmeta { text-align: right; font-variant-numeric: tabular-nums; }
.journeyrow .rowmeta { text-align: right; white-space: nowrap; }
/* HOW FAR THE THEME REACHES, WHICH IS THE ONLY FIGURE ON THIS INDEX THAT
   VARIES. Every theme holds eight places, so the count the cards used to
   print was a constant; the number of countries runs from three to eight and
   is the whole difference between a corner of the continent and an argument
   that crosses it. Right-aligned and tabular so the column reads as a range
   down the page rather than as thirteen unrelated numerals. */
/* LEFT, BECAUSE THE COLUMN IT WAS ALIGNED TO IS GONE. It was right-aligned
   against a right-hand count column; with the drawing on the left and one
   text block beside it, the count is a line of that block, and
   right-aligning a line of text to the end of a PROSE MEASURE puts it at a
   pixel nothing else on the page uses. The reason expired with the layout,
   which is the failure mode a rule with a reason in it exists to make
   visible. Tabular still: the column of counts reads as a range down the
   page. */
.themerow .rowmeta { font-variant-numeric: tabular-nums;
                     margin-top: var(--s3); }
/* The stop's number in the order — a real sequence, so a numeral says
   something true rather than decorating a list. */
.leg-no { display: block; font-family: var(--mono); font-size: var(--t-sm);
          color: var(--door); font-variant-numeric: tabular-nums; }
/* THREE OF ITS OWN ENTRIES, WHERE A HASH-DRAWN LANDSCAPE USED TO BE. The
   experience index carried eighteen generated plates standing for
   abstractions — "Adventure" opened on a church tower — and the family's
   real material is the writing. Set small and quiet: these are a taste of
   what is inside the tile, not a list, so they must not compete with the
   category's own name. */
.taste { margin: var(--s3) 0 0; display: grid; gap: 2px; }
.taste span { font-size: var(--t-xs); color: var(--ink-3); line-height: 1.5;
              overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* THE INVITATION IS THE DESIGN. A category page listed 48 experiences as a
   four-column table — country, city, name, summary, kind, band — everything
   at one size, so "Bosnian coffee, properly" and "The commuter ferry as a
   day out" were fighting a spreadsheet. This family's only material is the
   sentence somebody wrote about a thing you could go and do, and the whole
   design job is letting it be read.
   Two columns on a wide screen because forty-eight of them in one column is
   a scroll and not a menu; one on a phone. No card, no border, no plate:
   what separates two invitations is the air between them. */
.invites { list-style: none; margin: var(--s6) 0 0; padding: 0;
           display: grid; gap: var(--s6); }
.invite a { display: block; text-decoration: none; color: inherit; }
/* h2, NOT h3. The list lost its "How this list is built" band, and with it
   the only h2 on /experiences/nature — so the page ran h1 straight to h3 and
   the browser suite caught a skipped heading level on two of them. Each
   invitation IS a top-level item of this page, so h2 is what it was all
   along; the size is set here because a heading's level is its place in the
   document and not its size. */
.invite h2 { font-size: var(--t-md); line-height: 1.25;
             margin: 0 0 var(--s2); color: var(--ink); font-weight: 700; }
.invite a:hover h2, .invite a:focus-visible h2 { color: var(--door); }
.invite-sum { margin: 0; color: var(--ink-2); max-width: 46ch; }
/* THE PLACE IS THE KICKER, so it takes the kicker's ink rather than the
   footnote's. It was --ink-3 when it sat at the bottom of the entry as the
   last thing a reader reached; it is the first thing now, and the thing a
   reader scanning twenty-six countries is actually looking for. It is not
   a `.kicker` — that primitive carries the eyebrow rule and is one per page
   head, and forty-eight eyebrows in a column is the accent as texture,
   which this stylesheet has already recorded once on /journeys. */
.invite-where { margin: 0 0 var(--s1); font-size: var(--t-xs); letter-spacing: .08em;
                text-transform: uppercase; color: var(--ink-2); }
/* The selection rule, under the list it selected — a band with its own
   heading used to stand in front of it. */
.listrule { margin: var(--s7) 0 0; max-width: 60ch; font-size: var(--t-sm);
            color: var(--ink-3); }
/* Four links with a number each, where four empty cards used to be. */
.sublinkwrap { margin: var(--s5) 0 0; }
.sublinks { list-style: none; margin: 0; padding: 0;
            display: flex; flex-wrap: wrap; gap: var(--s3) var(--s5); }
.sublinks a { text-decoration: none; color: var(--door); font-weight: 600; }
.sublinks a:hover { text-decoration: underline; }
.sublinks a span { font-family: var(--mono); color: var(--ink-3); font-weight: 400; }
/* THE SHAPE OF A CATEGORY, drawn from its own sub-counts. Column, so the
   bar sits under the name it measures; a fixed track width so four bars are
   comparable, which is the entire point of drawing them at all. */
.sublinks.shares { display: grid; gap: var(--s4);
                   grid-template-columns: repeat(auto-fit, minmax(9rem, 12rem)); }
.sublinks.shares li { display: grid; gap: var(--s3); align-content: start; }
/* A BAR DIRECTLY UNDER A LINK READS AS AN UNDERLINE, and these links carry
   a real underline on hover. Space, and a track with visible ends, so it
   reads as a measure with a scale rather than as a decoration of the word
   above it. */
.sublinks.shares .hopbar { margin: 0; height: 5px; }
/* The overlap note is about the bars and must not touch them: stacked one
   per row on a phone it ran straight on from the last track and read as a
   caption on that one sub-category rather than on the set. */
.sublinks.shares + .small { margin-top: var(--s5); }
@media (min-width: 62rem) {
  .invites { grid-template-columns: repeat(2, minmax(0, 1fr));
             column-gap: var(--s8); }
}

/* ── the map ──────────────────────────────────────────────── */
/* The drawing is 1000 x 780, so at a full-width 1232px column it is 960px
 * tall and a 900px-high laptop shows Scandinavia and nothing else. Capping
 * the WIDTH from the viewport height, rather than capping the height, is what
 * keeps the whole continent on screen without letterboxing: an SVG with
 * width:100% and max-height centres itself inside a box it no longer fills,
 * and the empty sea either side of it is not free. 1.282 is MAP_W/MAP_H and
 * lives here as a number because CSS cannot read it from the SVG. */
/* 66vh BOUGHT A THIRD OF THE WIDTH FOR A GOAL IT DID NOT REACH.
   The rule above is right that letterboxing wastes the sea; the constant was
   wrong. Measured on a 1440-wide window with the map starting at y=380:

       window height   map width   map bottom   whole continent on screen?
             760          643          889              no
             900          762          981              no
            1000          846         1047              no
            1200         1015         1179              yes

   So at every ordinary height the map was BOTH too narrow AND still below
   the fold — paying 35% of the column for a promise it kept only on a very
   tall screen. And the cost was not the sea either side, it was empty PAGE
   either side, outside the panel, misaligned with a head that runs the full
   width. That reads as a broken layout rather than as a considered frame.

   Capping by the viewport height instead was tried and is worse: the wrap
   goes full width and the SVG letterboxes INSIDE it, so the empty sea moves
   from outside the panel to inside it and nothing is gained. The panel has
   to hug the drawing; only the budget was wrong.

   So the budget is 88vh rather than 66, still capped at the column so it can
   never overflow: 857 at a 760 window, 1015 at 900, 1354 at 1200, against
   643 / 762 / 1015 before. The whole continent is no longer promised above
   the fold, because that promise was only ever kept on a 1200px-tall screen
   and cost a third of the instrument to make. An instrument gets the width;
   the reader scrolls. */
/* THE INSTRUMENT'S OPENING IS A WELL, AND IT WAS A CHART FRAME.
   /map sat in a 1px `--rule` rectangle with sixteen pixels of sea matted
   round the drawing — a panel with a border, which is the association the
   cartography split exists to escape, on the page that IS the instrument.
   Every picture on this site is seen through an aperture and none of them
   has a border.
   THE ANSWER IS ALREADY WRITTEN DOWN, for the dark preference of the arch:
   two near-blacks are always about 1:1, so an opening cannot be read by the
   step from the wall and is read the way a real one is — by its CUT EDGE.
   The plates went 1.03 to 2.18 that way. This is the same relationship
   inverted: a dark page, a darker well, and a fine LIT line at the cut,
   measuring 2.15 on the water. An `outline` rather than an inset shadow,
   because the register counts shadows and two is the whole vocabulary.
   And the mat comes off: an instrument's water runs to the cut. */
.mapwrap {
  border: 0; border-radius: var(--radius);
  outline: 1px solid color-mix(in srgb, var(--limestone) 26%, transparent);
  outline-offset: -1px;
  background: var(--paper-2); padding: 0; margin: var(--s3) 0 var(--s5);
  overflow-x: auto; max-width: min(100%, calc(88vh * 1.282)); margin-inline: auto;
}
.europemap { width: 100%; height: auto; display: block; min-width: 34rem; }
.europemap .dot circle { fill: var(--sea); opacity: .78; transition: opacity .15s ease, r .15s ease; }
.europemap .dot:hover circle { fill: var(--door); opacity: 1; r: 7; }
.europemap .dot.advisory circle { fill: var(--warn); opacity: .5; }
.europemap .dot.off circle { opacity: .07; }
#mapcount { margin: var(--s3) 0 0; }

/* ── prose lists inside pages ─────────────────────────────── */
main ul.stack, main ol.stack { max-width: var(--measure); padding-left: var(--s5); }
main ul.stack li, main ol.stack li { margin-bottom: var(--s3); }
main h2 { margin-top: var(--s6); margin-bottom: var(--s4); }

/* ── the search link in the masthead ──────────────────────────────── */
.navsearch { color: var(--door); }
.navsearch span { font-size: 1.1em; line-height: 1; }

/* ── the hero map ─────────────────────────────────────────────────── */
.hero { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s7); align-items: center; }
/* The hero map is an INTELLIGENCE figure on a DISCOVER page, which is the
 * same rule every other embedded map here follows: the world says what the
 * reader is looking at, not which page it happens to sit on. It is also the
 * whole visual rhythm of the homepage — one dark, dense, cinematic frame at
 * the top, and progressively quieter bands under it.
 *
 * It rendered as a solid black silhouette the first time, because the land
 * paths are styled only under .europemap and .countrymap and an unstyled
 * <path> fills black. Nothing failed; the map simply arrived as a blot. */
/* A RULE OLDER THAN ITS ONLY USER, AND IT HID THAT USER'S ONLY PICTURE ON
 * EVERY PHONE. `display: none` with a `display: block` at 64rem was written
 * for the homepage hero map — an instrument with a filter row that really
 * did need a desk — and that map was removed. The one element left carrying
 * this class is /discover's continent, the single drawing on the page whose
 * job is to open one, and it has not existed below 1024px since. Nothing
 * counts a drawing that is not drawn: the first-screen instrument reported
 * /discover's first figure at 1686 and was reading the year band eight
 * screens down. Same sentence as `fill: none` on the same page, two commits
 * over — a rule older than its only user. */
.heromap { display: block; text-decoration: none; }
.heromap svg { width: 100%; height: auto; display: block;
               background: var(--map-sea); border-radius: var(--radius); }
.heromap .context path { fill: var(--map-context); }
.heromap .countries path { fill: var(--map-land); stroke: var(--map-coast); stroke-width: 1; }
.herodot { fill: var(--signature); opacity: .8; transition: opacity .2s ease; }
.herodot.advisory { fill: var(--map-border); opacity: .7; }
.heromap:hover .herodot { opacity: .85; }
.heromap-cap { display: block; margin-top: var(--s3); font-size: var(--t-xs); letter-spacing: .1em; text-transform: uppercase; color: var(--ink-3); }
.heromap:hover .heromap-cap { color: var(--door); }
@media (min-width: 64rem) {
  .hero { grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr); }
}

/* ── the sentence box on the planner ──────────────────────────────── */
.form.ask { margin-bottom: var(--s6); border-color: var(--door); }
/* ONLY WHAT DIFFERS. `.field textarea` carries the width, the padding, the
   border, the ground and the ink for every textarea on the site now, so
   everything this rule used to repeat was a declaration changing nothing —
   the dead-rule scan said so the moment the shared rule existed. What is
   left is the two things that are genuinely this box's own: it takes a
   sentence rather than a blob of JSON, so it is set in the reading face at
   reading size, and it starts at two lines rather than six. */
.form.ask textarea {
  font: inherit; font-size: var(--t-md); min-height: 3.4rem;
}
/* AND 3.4rem IS TWO LINES, WHICH IS TWO OF THE FOUR THE PLACEHOLDER TAKES
   ON A PHONE. Measured at 390: the box is 88px and the placeholder needs
   116, so "mountains and food" was cut off below the fold of a box a reader
   is being asked to type a sentence into — on the one control that IS this
   page. The example is what teaches somebody what the parser reads, so the
   sentence stays and the box grows: four lines at the width where it wraps
   to four. */
@media (max-width: 44rem) {
  .form.ask textarea { min-height: 7.6rem; }
}

/* ── a journey drawn over the map ─────────────────────────────────── */
.routeline { fill: none; stroke: var(--door); stroke-width: 2.5; stroke-linejoin: round; stroke-dasharray: 7 5; opacity: .9; }
.routedot { fill: var(--door); stroke: var(--paper); stroke-width: 2; }

/* ── masthead utilities: search and My Europe sit apart from the nav ── */
.navutil { display: flex; gap: var(--s4); align-items: center; font-size: var(--t-sm); letter-spacing: .04em; text-transform: uppercase; }
.navutil a { text-decoration: none; color: var(--ink-2); border-bottom: 1.5px solid transparent; padding-bottom: 2px; }
.navutil a:hover { color: var(--door); border-bottom-color: var(--door); }
.navsearch { color: var(--door); }
.navsearch span { font-size: 1.1em; line-height: 1; }
@media (max-width: 60rem) { .masthead-in { flex-wrap: wrap; } }

/* ── the small map on a destination page ──────────────────────────── */
.minimap {
  margin: var(--s5) 0; padding: var(--s4); border: 1px solid var(--rule);
  border-radius: var(--radius); background: var(--paper); color: var(--ink);
}
.minimap svg { width: 100%; height: auto; display: block; }
/* EVERY MINIMAP IS ARCHED — 824 of 824 — so the un-arched fills below never
   applied to anything. They were written when a map was a light panel on a
   light page and kept when it became a dark opening; the `.arched` variants
   have carried every one of these values since. The dead-rule scan lists
   them, and the honest resolution is to delete the fill rather than raise
   the ceiling: styling that cannot apply is dead code that looks like a
   decision. The geometry (size, weight, family) stays here, because that IS
   what applies. */
.minidot circle { opacity: .55; }
.minidot:hover circle { fill: var(--door); opacity: 1; }
.minidot.here circle { opacity: 1; }
.minilabel { font-family: var(--text); font-size: calc(11px / var(--z)); }
.minilabel.here { font-weight: 600; }
.minimap figcaption { font-size: var(--t-xs); color: var(--ink-3); margin-top: var(--s3); }

/* ── the day-by-day inside a planned leg ──────────────────────────── */
.daylist { list-style: none; margin: var(--s3) 0 0; padding: 0; font-size: var(--t-sm); }
.daylist li { padding: var(--s2) 0; border-top: 1px dotted var(--rule); color: var(--ink-2); }
.daylist li strong { color: var(--ink); font-weight: 600; }
.leg .alt { margin-top: var(--s3); color: var(--ink-3); }
.leg .alt a { color: var(--ink-2); }

/* Notes and rails carry section-level headings, so they are <h2>. They are
   small because they are asides, not because they are subordinate — styling
   reasoning applied to heading levels produced h1 → h3 skips on every page
   with a note above the first real section. */
.note h2.mini, .rail h2.mini { font-size: var(--t-md); margin: 0 0 var(--s2); line-height: 1.25; }
.rail h2.mini + p { margin-top: 0; }
.rail h2.mini:not(:first-child) { margin-top: var(--s4); }

/* ── the map popup ────────────────────────────────────────────────── */
.mappopup { position: relative; max-width: 30rem; margin: var(--s4) 0; padding: var(--s5); border: 1px solid var(--ink-3); border-radius: var(--radius); background: var(--paper-2); box-shadow: var(--shadow-lift); }
.mappopup h2.mini { margin-bottom: var(--s2); }
.mappopup p { max-width: none; }
.mappopup p:last-child { margin-bottom: 0; }
.mappopup-close { position: absolute; top: var(--s2); right: var(--s3); border: 0; background: none; font-size: var(--t-lg); line-height: 1; cursor: pointer; color: var(--ink-3); }
.mappopup-close:hover { color: var(--door); }
.europemap .placedot circle { fill: var(--door); opacity: .5; }
.europemap .placedot:hover circle { opacity: 1; }

/* ── the land ────────────────────────────────────────────────────────
 *
 * Real geography, from Natural Earth via scripts/map/. The design problem
 * this solves is that a map with a basemap in it wants to look like every
 * other map with a basemap in it, and a tile-service pastiche — motorway
 * yellow, park green, a scale bar — would say EuropeDoor bought a map. This
 * one is drawn the way the rest of the site is drawn: paper, one rule
 * weight, colour reserved for the thing you can act on.
 *
 * So the sea is the page. Land is a shade above it. A country we hold is a
 * shade above that, and the only saturated colour on the whole drawing is
 * the country under the pointer and the destinations on it. Nothing else
 * competes.
 *
 * --z is the current zoom, written by map.js on every viewBox change.
 * Everything with a fixed on-screen size divides by it: an SVG stroke and an
 * SVG radius are in user units, so without this a 1px border is 8px at 8x
 * and Europe becomes a coloured blob with the coastline eating the coast.
 */
/* The sea is its own token, not the page ground. The first version used
 * --paper-2 for the wrapper and --paper for land, which is a 4% lightness
 * step: on a good monitor it was a subtle map, and on a laptop at an angle it
 * was a blank rectangle with dots on it. A map has to survive being looked at
 * badly. The sea is now a desaturated Atlantic — cool against the warm
 * limestone of the land, which is also the one place on the site where the
 * primary green is allowed to be a surface rather than an action, because
 * here it is not offering anything. */
.europemap, .minimap { --z: 1; }
.mapwrap { background: var(--map-sea); }
/* THE DATA CUT WAS DRAWN AS A FRONTIER ON THE ONE MAP A READER OPERATES.
   data/geo/ stops at 52°E and 33°N because that is where this product stops
   writing about places, and under this conic the eastern cut runs from x=747
   at 70°N to x=1024 at 40°N — a clean straight diagonal through Russia.
   The hero has faded along it since the hero was drawn; /map showed it raw,
   and once the land was lifted out of the water it became the most visible
   line in the drawing.
   The stops carry an opacity and no colour, because the SVG default for
   stop-color is #000 and this stylesheet has already shipped a layer that
   said graphite and painted black. The colour is the water's. */
.mapcut { pointer-events: none; }
/* THE SHORE BAND, CAST RATHER THAN STROKED — the picture family's own
   technique, applied to the instrument that never got it. A filter needs no
   second copy of Europe's geometry and cannot lose a cascade fight, which is
   why the plates use one: their first version was three `<use>` of the land
   group and painted nothing at all, because a <use> clone IS matched by the
   selectors that style the original where the original sits.
   The shadow is of the group's UNION, so an internal frontier casts none —
   exactly the coast, and never a border. */
.europemap #countries, .europemap #detail,
.minimap.arched:not(.atlas) .countries {
  filter: drop-shadow(0 0 2px var(--map-shore-1))
          drop-shadow(0 0 7px var(--map-shore-2))
          drop-shadow(0 0 16px var(--map-shore-3)); }
#mapedge stop, #mapfoot stop { stop-color: var(--map-sea); }
/* THE SAME CUT, ON THE PICTURE HALF. Every plate framed on the continent
   drew the 52°E diagonal and the 33°N arc raw — /beyond-the-obvious, the
   thirteen themes, the twelve motions, the nine stories, the seventeen
   interests. The colour is the water's, because that is what the land is
   dissolving into, and the stops carry an opacity only: the SVG default for
   stop-color is #000 and this stylesheet has already shipped a layer that
   said graphite and painted black. */
.datacut { pointer-events: none; }
/* A <stop> THAT NO RULE REACHES IS BLACK, AND THE FIRST VERSION OF THIS
   SELECTOR REACHED NONE OF THEM. The gradients were emitted in a <defs>
   beside the group rather than inside it, so `.datacut stop` matched
   nothing and 596 picture plates faded to the SVG default — a black smear
   arriving at the eastern edge of the parchment, which is precisely the
   rendering fault the fade exists to remove, arrived at from the other
   side. Invisible on most pages because the ramp sits at 52°E and most
   plates are framed far west of it at zero opacity; plain on the twenty
   framed on the whole continent. The defs moved inside the group.

   The `.atlas` qualifier came off with it. Every plate that emits a data
   cut is a picture plate — the group is placed at the `cities` slot and the
   instrument family does not draw one — so the class was restating what the
   markup already guarantees, and an instrument variant written for a case
   that does not exist is the dead rule the scan refuses. /map fades to
   `--map-sea` through `.mapcut`, which is a different mechanism because it
   is a different drawing. */
.datacut stop { stop-color: var(--atlas-sea); }
.europemap .context path { fill: var(--map-context); stroke: none; }
.europemap .countries path {
  fill: var(--map-land); stroke: var(--map-border);
  stroke-width: calc(1px / var(--z)); stroke-linejoin: round;
}
.europemap.noborders .countries path { stroke: none; }
.europemap .cshape { cursor: pointer; }
.europemap .cshape path { transition: fill .15s ease; }
.europemap .cshape:hover path { fill: var(--map-hover); stroke: var(--sea); }
.europemap .cshape:focus-visible path { stroke: var(--sea); stroke-width: calc(2.5px / var(--z)); }
.europemap .cshape.on path { fill: var(--map-hover); stroke: var(--sea); stroke-width: calc(1.6px / var(--z)); }
/* Monaco is 2 km² and Vatican City is 0.44 km². Natural Earth has no polygon
 * for either at 1:50m, so they are a ringed point rather than a shape we
 * invented — the ring is what says "this is a marker, not a country the size
 * of a village". */
.europemap .nogeo circle {
  fill: var(--map-land); stroke: var(--ink-3);
  stroke-width: calc(1.4px / var(--z)); r: calc(5px / var(--z));
}
.europemap .nogeo a:hover circle { stroke: var(--sea); fill: var(--map-hover); }

.europemap .dot circle { r: calc(4.2px / var(--z)); }
.europemap .dot:hover circle { r: calc(7px / var(--z)); }
.europemap .placedot circle { r: calc(2.6px / var(--z)); }
.routeline { stroke-width: calc(2.5px / var(--z)); stroke-dasharray: calc(7px / var(--z)) calc(5px / var(--z)); }
.europemap .routedot { r: calc(7px / var(--z)); }

/* Regions are a grouping, not a boundary — we hold which destinations belong
 * to a region and we do not hold region geometry. The hairlines say
 * "these belong together" without drawing a frontier we cannot source.
 * See docs/data-licenses/eurostat-gisco-nuts.md. */
.europemap .rtie { stroke: var(--sea); stroke-width: calc(.75px / var(--z)); opacity: .3; }
.europemap .rlabel text {
  fill: var(--ink-2); font-size: calc(11px / var(--z)); font-weight: 600;
  text-anchor: middle; paint-order: stroke; stroke: var(--map-land);
  stroke-width: calc(3px / var(--z)); stroke-linejoin: round;
}
.europemap .rlabel:hover text { fill: var(--sea); }

/* The map goes above the controls, which sounds obvious and was not what
 * shipped first. The first layout put the geography toggles, thirteen
 * interest filters and two selects between the headline and the drawing, so
 * on a 1280x1000 laptop the page called "the map" opened with no map on it.
 * Controls now live in a closed <details> underneath, and the map starts
 * about 380px down where it can be seen without scrolling.
 *
 * The side column is the other half of that fix: clicking a country used to
 * fill a panel below the map, which on a tall drawing is off-screen — so the
 * click appeared to do nothing at all. */
/* minmax(0, 1fr) at BOTH widths, and min-width: 0 on the column. A grid item
 * defaults to min-width: auto, which means it refuses to shrink below its
 * content — and the content here is an SVG with min-width: 34rem, whose
 * horizontal scrolling .mapwrap was quietly handling until the day it became
 * a grid item. Turning the map page into two columns therefore pushed 204px
 * off the right edge of a 390px phone, and the static check that guards
 * against exactly this could not see it: it reads the HTML, and this is a
 * layout that only exists once a browser has run the grid algorithm. The
 * browser suite is what found it. */
.mapstage { display: grid; gap: var(--s5); align-items: start; grid-template-columns: minmax(0, 1fr); margin-top: var(--s4); }
.mapmain { min-width: 0; }
/* The second column exists only while there is something in it. A permanent
 * 21rem sidebar holding a legend took a fifth of the drawing away from the
 * map on every visit to pay for a panel most visits never open — and with
 * scripting off it could never open at all, so the map would have been narrow
 * for nothing. map.js sets .withpanel. */
@media (min-width: 62rem) {
  .mapstage.withpanel { grid-template-columns: minmax(0, 1fr) 21rem; }
}
.mapside:empty, .mapside { min-width: 0; }
.mapside > * { margin-top: 0; }
.maphint { border: 1px solid var(--rule); border-radius: var(--radius); padding: var(--s4); background: var(--paper-2); margin: var(--s4) 0; }
.maphint p:last-child { margin-bottom: 0; }
.legend { list-style: none; padding: 0; margin: var(--s3) 0; font-size: var(--t-sm); }
.legend li { display: flex; gap: var(--s3); align-items: baseline; margin-bottom: var(--s2); }
.legend .sw { flex: 0 0 auto; width: 1rem; height: 1rem; border-radius: 3px; border: 1px solid var(--map-coast); }
.legend .sw.land { background: var(--map-land); }
.legend .sw.ctx { background: var(--map-context); }
.legend .sw.dest { background: var(--sea); border-radius: 50%; border-color: var(--sea); width: .75rem; height: .75rem; }
.legend .sw.ring { background: var(--map-land); border-radius: 50%; border-color: var(--ink-3); border-width: 2px; width: .8rem; height: .8rem; }
/* Land under the small maps. The country map is the middle rung of
 * Europe -> country -> region -> destination, and before there was geometry
 * it did not exist: a country page could list its regions and could not show
 * you where any of them were. */
.countrymap svg { background: var(--map-sea); border-radius: var(--radius); }
.countrymap .context path { fill: var(--map-context); }
/* The two rules that were here — `.countrymap .countries path` and the same
   with `.here` — set a fill and a stroke and never won anywhere. Both are
   (0,3,1) against `.minimap.arched .countries path`, which is further down
   the file, so the country map's own styling was entirely superseded and the
   `.here` highlight meant Austria was drawn on Austria's page in exactly the
   same grey as Germany, on all fifty. The live version is beside the macro
   fill, at a specificity that wins. Deleted rather than left in place:
   styling that cannot apply is dead code that looks like a decision. */
.countrymap .rtie { stroke: var(--sea); stroke-width: 1; opacity: .35; }
/* The fill and the halo that were here never applied either — same fight,
   `.minimap.arched .rlabel text` wins it — so a region name was styled for a
   light panel and drawn on a dark opening. What is left is the geometry,
   which is what this selector actually decides. */
.countrymap .rlabel text {
  font-size: 15px; font-weight: 700; text-anchor: middle;
  paint-order: stroke; stroke-linejoin: round;
}
/* A MACRO REGION IS A SET OF WHOLE COUNTRIES, AND THEY HAVE TO READ AS ONE.
   The first render drew the names in the dark ink of a light page on a
   near-black ground: the class passed to the placement rule was
   `rlabel-text`, which does not exist, so nothing styled them at all. The
   size is the same declaration the region names use rather than a
   seventeenth. The FILL is further down, next to the rule that was beating
   it. */
.macromap .mmlabel {
  fill: color-mix(in srgb, var(--limestone) 92%, transparent);
  font-family: var(--display); font-size: 15px; font-weight: 700;
  paint-order: stroke; stroke: var(--map-sea); stroke-width: 3; stroke-linejoin: round;
}
.countrymap .rlabel:hover text { fill: var(--sea); }

.rowside { display: flex; flex-direction: column; align-items: flex-end; gap: var(--s2); flex: none; }
.btn.tiny { font-size: var(--t-xs); padding: var(--s1) var(--s3); }

.maptools { margin: var(--s6) 0; border-top: 1px solid var(--rule); padding-top: var(--s4); }
.maptools > summary { cursor: pointer; font-weight: 600; }
.maptools .mini { margin: var(--s5) 0 var(--s2); }

.mapzoom { display: flex; align-items: center; gap: var(--s2); margin: 0; flex-wrap: wrap; }
.zbtn {
  min-width: 2.5rem; min-height: 2.5rem; border: 1px solid var(--rule);
  border-radius: var(--radius); background: var(--paper); color: var(--ink);
  font-size: var(--t-md); line-height: 1; cursor: pointer;
}
.zbtn.wide { min-width: auto; padding: 0 var(--s3); font-size: var(--t-sm); }
.zbtn:hover { border-color: var(--sea); color: var(--sea); }
.maplayers fieldset { border: 0; padding: 0; margin: var(--s4) 0 0; }
.maplayers legend { padding: 0; margin-bottom: var(--s2); }
.maplayers label { display: inline-flex; align-items: center; gap: var(--s2); margin-right: var(--s4); }

.countrypanel {
  position: relative; max-width: 34rem; margin: var(--s4) 0; padding: var(--s5);
  border: 1px solid var(--ink-3); border-radius: var(--radius);
  background: var(--paper-2); box-shadow: var(--shadow-lift);
}
.countrypanel p { max-width: none; }
.regionlist { list-style: none; padding: 0; margin: var(--s4) 0; }
.regionlist li { padding: var(--s2) 0; border-top: 1px solid var(--rule); }
.regionlist .rlink { font-weight: 600; }

/* ── saved rows with a collection picker ──────────────────────────── */
.row.saved { grid-template-columns: minmax(0,1fr) auto; }
.row.saved select { font: inherit; font-size: var(--t-sm); padding: 2px var(--s2); border: 1px solid var(--rule); border-radius: var(--radius); background: var(--paper); color: var(--ink); }
.linkish { border: 0; background: none; padding: 0 0 0 var(--s2); color: var(--ink-3); cursor: pointer; font: inherit; font-size: var(--t-xs); text-decoration: underline; }
.linkish:hover { color: var(--warn); }
.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* THE hidden ATTRIBUTE DRAWS NOTHING ON AN SVG GROUP, and for a while a
   rule here — `[hidden] { display: none !important; }` — was what hid the
   map's layers. The UA sheet's own version of that rule is namespaced to
   HTML, so the places layer had shipped visible while marked hidden, and one
   line of CSS looked like the fix.

   It was half of one. It left every layer switch on /map one stylesheet
   request away from being inert, and it made the switches depend on a
   cascade: Chromium 131 does not apply that rule to those groups while
   Chromium 141 does, so `places` was hidden on this machine and drawn on the
   CI runner, and two browser checks disagreed with two others for three
   months over which browser was looking.

   The groups carry SVG's own `display` presentation attribute now, which
   needs no stylesheet and no cascade, so this rule stopped changing anything
   — and the dead-rule scan said so within one run, at 24 against a ceiling of
   23. Deleted rather than counted: a rule that matches elements and changes
   none of them is dead code that looks like a decision. `[hidden]` on the
   HTML panels is the UA sheet's job and always was; neither .countrypanel
   nor .mappopup sets a display of its own for it to have to beat. */

/* ── a story's byline ─────────────────────────────────────────────── */
.byline { color: var(--ink-3); margin-top: var(--s3); }
.byline time { font-variant-numeric: tabular-nums; }

/* ── the question on the homepage ─────────────────────────────────── */
.askhome { display: grid; gap: var(--s3); margin-top: var(--s6); max-width: 40rem; }
.askhome label { font-size: var(--t-xs); letter-spacing: .1em; text-transform: uppercase; color: var(--ink-3); font-weight: 600; }
.askhome input { width: 100%; font: inherit; font-size: var(--t-md); padding: var(--s3) var(--s4); border: 1px solid var(--ink-3); border-radius: var(--radius); background: var(--paper); color: var(--ink); }
.askhome input:focus { border-color: var(--door); }
.askhome button { justify-self: start; }
.askhome-note { margin-top: var(--s2); }
.heromap-filters { margin-top: var(--s4); }
@media (min-width: 40rem) { .askhome { grid-template-columns: 1fr auto; align-items: end; } .askhome label { grid-column: 1 / -1; margin-bottom: calc(var(--s2) * -1); } }

/* A checkbox that sits inside a field rather than in a chip row.
 *
 * THE RULE THAT WAS WRITTEN TWICE AND NEVER APPLIED. The field label above a
 * control is uppercase; the text beside a checkbox is a sentence and must
 * not be — "ONLY PLACES WITH A HIGH DISCOVERABILITY SCORE" set in caps over
 * two lines is a label nobody reads. Somebody noticed that, wrote the fix,
 * and wrote it a second time three hundred lines further down, and the
 * page kept rendering in caps both times: the markup was
 * `<div class="field"><label>…</label><label class="inlinecheck">…`, so
 * `.field > label` (0,1,1) beat `.inlinecheck` (0,1,0) and the reset lost
 * the cascade to a rule that was not even about it.
 *
 * The markup is the fix, not a specificity war: the group heading was never
 * a control label — it was an explicit `for=` label on an input that already
 * had a wrapping one, so every one of these checkboxes had two labels and an
 * accessible name of both concatenated. It is a <p class="fieldhead"> now,
 * `.field > label` does not apply, and one .inlinecheck block does the rest.
 * A browser check asserts the computed text-transform, because computed
 * style is the only thing that could ever have caught this. */
.inlinecheck { display: flex; align-items: baseline; gap: var(--s2); font-size: var(--t-sm);
  text-transform: none; letter-spacing: normal; color: var(--ink-2); font-weight: 400; }
.inlinecheck input { margin: 0; }

/* ── layout utilities, and why they are not inline styles ─────────
 *
 * These eleven classes and the .w0-.w100 scale below replace every
 * style="..." attribute the generator used to emit. That was not a tidiness
 * exercise: a style ATTRIBUTE cannot be allowed by a CSP hash the way an
 * inline <style> block can — Chromium's own message says hashes do not
 * apply to style attributes — so one style="margin-top:..." anywhere forced
 * style-src to carry 'unsafe-inline', which allows every injected style
 * including the ones an attacker writes. The browser checks caught it the
 * first time the policy shipped, on 987 pages at once.
 *
 * The width scale is 101 rules and about 2 KB. Score bars are the only
 * genuinely dynamic dimension on the site and they already round to a whole
 * percent, so the scale is complete rather than approximate.
 */
.mt7 { margin-top: var(--s7); }
.mt6 { margin-top: var(--s6); }
.mt0 { margin-top: 0; }
.mt5 { margin-top: var(--s5); }
.mt-tight { margin-top: .4rem; }
.mb0 { margin-bottom: 0; }
.db  { display: block; }
.nodec { text-decoration: none; }
.measure { max-width: var(--measure); margin: var(--s7) 0; }

/* An absence, stated. Not a smaller, greyer version of a list — the same
   measure and the same ink as the thing that would have been there, because
   a gap this site chose is content, not a rendering failure. The strong lead
   is what is missing; the rest is why, and what would fill it. */
.emptystate { max-width: 44ch; margin: var(--s5) 0; color: var(--ink-2);
              border-left: 2px solid var(--rule); padding-left: var(--s4); }
.emptystate strong { display: block; color: var(--ink); font-weight: 600;
                     margin-bottom: var(--s2); }

/* ── the essay ────────────────────────────────────────────────────────
 *
 * THE ONE FAMILY WHERE TYPE CAN CARRY THE WHOLE TRANSFORMATION, at no
 * imagery cost — and the family the design audit measured as the clearest
 * failure of a shared chassis: a magazine story and a country encyclopedia
 * placed an identically-sized h1 at an identical vertical position, and the
 * entire art-directional difference between them was an 11px kicker
 * changing hue.
 *
 * So a story is set, not laid out. Three moves, all of them typographic:
 *
 *   the body is SERIF. --display is already in the stack for every heading
 *   on the site, so this costs zero webfonts and zero bytes, and it is the
 *   single loudest signal a reader gets that they have moved from an atlas
 *   entry to a piece of writing. Nothing else on europedoor.com sets a
 *   paragraph in it.
 *
 *   the measure GROWS with the face. 34rem is right for 15px sans; the same
 *   column at 19px serif holds about 52 characters and reads as a poem. 38
 *   puts it back to roughly 68.
 *
 *   the head has AIR where the chips were. The tag row sat directly under
 *   the byline, four grey pills between the standfirst and the story, and
 *   it is the least urgent thing on the page — a search link. It is at the
 *   foot now, with the save action, where a reader who has finished is.
 */
 * ONE new font size, and four line-heights unchanged. The first draft of
 * this block cost two sizes and four line-heights, and the invariant
 * register caught all six: css.font_sizes 15 -> 17, css.line_heights 4 -> 8.
 * Five of the six were vanity — 1.62 instead of 1.5 for the serif, 1.03
 * instead of 1.12 on the hed, a deck clamp one decimal off one that already
 * existed. They were snapped to values the site already had and nothing
 * about the page changed that a reader could name.
 *
 * The sixth was said to be real and was not. "The hed's own clamp" was
 * declared here as the one difference that survived — and it was
 * `clamp(var(--t-3xl), 5.2vw, var(--t-5xl))`, which is the base `h1` rule
 * copied out character for character. A declaration that restates what the
 * element already inherits is the redundancy the dead-rule scan exists to
 * find, and this one was carrying the whole argument for the family.
 *
 * So the finding it was written against stood: eleven of twelve families
 * place an identically sized h1 at an identical height, and the family
 * whose entire claim is that it is EDITORIAL rather than reference was one
 * of the eleven.
 *
 * A HED IS NOT A NAME. The overture takes the hero's clamp because its
 * subject is one word — Vienna, Chamonix — and the name is the event. A
 * story title is a SENTENCE, 27 to 48 characters across the nine, and a
 * sentence set at a name's size and a name's 14ch measure is four lines of
 * shouting. It takes the same ceiling and a lower floor, over a measure
 * half as wide again: big enough that the page opens like a magazine and
 * loose enough that "What a pilgrimage is for when you do not believe"
 * reads as one thought.
 *
 * ONLY THE CEILING MOVES, and that is the invariant register's doing. The
 * first version set a 6.2vw middle term and a 1.06 leading, which reads
 * better and is two values this stylesheet does not have — `css.font_sizes`
 * and `css.line_heights` both went red, on a rule whose own comment claimed
 * it introduced nothing. The register was right and the comment was wrong.
 * What is left uses the site's own numbers throughout: the same 5.2vw rate
 * every h1 rises at, the same 1.12 leading every heading has, and a ceiling
 * that is already a token and already spent by the hero and the overture.
 */
.essay { --measure: 38rem; }
.essayhead { margin: var(--s6) 0 var(--s7); }
.essayhead h1 {
  font-size: clamp(var(--t-3xl), 5.2vw, var(--t-6xl));
  line-height: 1.12; letter-spacing: -.022em; max-width: 21ch;
  margin: var(--s4) 0 0;
}
/* AND THE DECK IS A PARAGRAPH, NOT A COLUMN. At 30ch under a hed set to
   21ch the standfirst ran to six lines against the hed's two, so the
   shape of the opening was a wide title over a narrow grey ribbon. A deck
   is read at a glance; the measure is the one prose already uses here. */
.essayhead .deck {
  font-family: var(--display); font-size: clamp(var(--t-lg), 2.4vw, var(--t-2xl));
  line-height: 1.25; color: var(--ink-2); max-width: 40ch;
  margin-top: var(--s5);
}
.essayhead .byline {
  margin-top: var(--s6); padding-top: var(--s3);
  border-top: 1px solid var(--rule); max-width: 30ch;
  font-size: var(--t-xs); letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-3);
}
.essaybody { max-width: var(--measure); margin: var(--s7) 0 var(--s8); }
.essaybody p {
  font-family: var(--display); font-size: var(--t-lg);
  line-height: 1.5; color: var(--ink); margin: 0 0 var(--s5); max-width: none;
}
/* The opening paragraph, one step up. Not a drop cap: a versal is the first
   thing a template reaches for and the last thing that means anything, and
   it fails outright on "Białowieża has never been logged" where the letter
   carries a diacritic. A size change says the same thing and survives every
   language this atlas writes in. */
.essaybody p:first-child { font-size: var(--t-xl); }
.essayfoot { margin-top: var(--s7); padding-top: var(--s5);
             border-top: 1px solid var(--rule); }
.essayfoot .chips { margin-bottom: var(--s5); }

/* The margin. A 38rem measure inside a 76rem page leaves a column, and an
   essay that ignores it is a narrow strip of text adrift on paper — which
   is what this page was. The places the story is about go there: the same
   list the map at the top is drawn from, beside the paragraphs that name
   them rather than under the save button at the very bottom, where it was.
   Sticky, because the whole point is that it stays while the reader
   travels down the piece. */
.essaywrap { display: grid; gap: var(--s8);
             grid-template-columns: minmax(0, var(--measure)) minmax(0, 1fr);
             align-items: start; }
.essaywrap.nomargin { display: block; }
.essaymargin { position: sticky; top: calc(var(--mast) + var(--s6)); margin: var(--s7) 0;
               padding-top: var(--s3); border-top: 2px solid var(--ink); }
.mp-label { font-size: var(--t-xs); letter-spacing: .08em; text-transform: uppercase;
            color: var(--ink-3); margin: 0 0 var(--s4); font-family: var(--text);
            font-weight: 600; }
.marginplace { display: block; text-decoration: none; padding: var(--s3) 0;
               border-bottom: 1px solid var(--rule); }
.marginplace:hover .mp-name { color: var(--door); }
.mp-name { display: block; font-family: var(--display); font-size: var(--t-md);
           color: var(--ink); line-height: 1.25; }
.mp-country { display: block; font-size: var(--t-xs); color: var(--ink-3);
              letter-spacing: .04em; margin-top: 2px; }
.mp-note { font-size: var(--t-xs); color: var(--ink-3); margin-top: var(--s4);
           max-width: none; }
@media (max-width: 60rem) {
  .essaywrap { grid-template-columns: 1fr; gap: var(--s6); }
  .essaymargin { position: static; }
}
.mw44 { max-width: 44rem; }
.mw34 { margin: var(--s4) 0; max-width: 34rem; }
.frame { max-width: 100%; border: 1px solid var(--rule); border-radius: var(--radius);
         aspect-ratio: 21/9; overflow: hidden; }
.w0 { width: 0%; }
.w1 { width: 1%; }
.w2 { width: 2%; }
.w3 { width: 3%; }
.w4 { width: 4%; }
.w5 { width: 5%; }
.w6 { width: 6%; }
.w7 { width: 7%; }
.w8 { width: 8%; }
.w9 { width: 9%; }
.w10 { width: 10%; }
.w11 { width: 11%; }
.w12 { width: 12%; }
.w13 { width: 13%; }
.w14 { width: 14%; }
.w15 { width: 15%; }
.w16 { width: 16%; }
.w17 { width: 17%; }
.w18 { width: 18%; }
.w19 { width: 19%; }
.w20 { width: 20%; }
.w21 { width: 21%; }
.w22 { width: 22%; }
.w23 { width: 23%; }
.w24 { width: 24%; }
.w25 { width: 25%; }
.w26 { width: 26%; }
.w27 { width: 27%; }
.w28 { width: 28%; }
.w29 { width: 29%; }
.w30 { width: 30%; }
.w31 { width: 31%; }
.w32 { width: 32%; }
.w33 { width: 33%; }
.w34 { width: 34%; }
.w35 { width: 35%; }
.w36 { width: 36%; }
.w37 { width: 37%; }
.w38 { width: 38%; }
.w39 { width: 39%; }
.w40 { width: 40%; }
.w41 { width: 41%; }
.w42 { width: 42%; }
.w43 { width: 43%; }
.w44 { width: 44%; }
.w45 { width: 45%; }
.w46 { width: 46%; }
.w47 { width: 47%; }
.w48 { width: 48%; }
.w49 { width: 49%; }
.w50 { width: 50%; }
.w51 { width: 51%; }
.w52 { width: 52%; }
.w53 { width: 53%; }
.w54 { width: 54%; }
.w55 { width: 55%; }
.w56 { width: 56%; }
.w57 { width: 57%; }
.w58 { width: 58%; }
.w59 { width: 59%; }
.w60 { width: 60%; }
.w61 { width: 61%; }
.w62 { width: 62%; }
.w63 { width: 63%; }
.w64 { width: 64%; }
.w65 { width: 65%; }
.w66 { width: 66%; }
.w67 { width: 67%; }
.w68 { width: 68%; }
.w69 { width: 69%; }
.w70 { width: 70%; }
.w71 { width: 71%; }
.w72 { width: 72%; }
.w73 { width: 73%; }
.w74 { width: 74%; }
.w75 { width: 75%; }
.w76 { width: 76%; }
.w77 { width: 77%; }
.w78 { width: 78%; }
.w79 { width: 79%; }
.w80 { width: 80%; }
.w81 { width: 81%; }
.w82 { width: 82%; }
.w83 { width: 83%; }
.w84 { width: 84%; }
.w85 { width: 85%; }
.w86 { width: 86%; }
.w87 { width: 87%; }
.w88 { width: 88%; }
.w89 { width: 89%; }
.w90 { width: 90%; }
.w91 { width: 91%; }
.w92 { width: 92%; }
.w93 { width: 93%; }
.w94 { width: 94%; }
.w95 { width: 95%; }
.w96 { width: 96%; }
.w97 { width: 97%; }
.w98 { width: 98%; }
.w99 { width: 99%; }
.w100 { width: 100%; }

/* ── the thumb bar ────────────────────────────────────────────
 *
 * From the UI specification: Home, Explore, Map, Plan, Me, on a phone only.
 * Above 44rem it is display:none and the masthead does the whole job — two
 * navigations visible at once is two things to keep in step, and the
 * masthead is the one with room for all seven primary items.
 *
 * The 44px minimum is not decoration. WCAG 2.2 AA added a 24x24 target-size
 * floor and 44 is the comfortable version of it; the browser suite measures
 * every one of these five and fails below 44.
 *
 * env(safe-area-inset-bottom) keeps the labels off the home indicator on a
 * notched phone, where they otherwise sit underneath it and cannot be read
 * or tapped.
 */
/* One token for the bar's height, because three other rules have to agree
 * with it: the sticky action sits directly on top of it, and both the footer
 * and the destination footer have to clear them. The first version hard-coded
 * 44px in those offsets while the bar actually rendered at 63, so the action
 * sat on top of the navigation — caught by measuring both boxes in Chromium,
 * which is the only way that kind of mistake ever shows up. */
:root { --thumbbar: 3.5rem; }
.bottomnav { display: none; }
@media (max-width: 44rem) {
  .bottomnav {
    display: grid; grid-template-columns: repeat(5, 1fr);
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 45;
    height: calc(var(--thumbbar) + env(safe-area-inset-bottom, 0));
    background: color-mix(in srgb, var(--paper) 94%, transparent);
    backdrop-filter: saturate(1.4) blur(10px);
    border-top: 1px solid var(--rule);
    padding-bottom: env(safe-area-inset-bottom, 0);
  }
  .bottomnav a {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 2px; height: 100%; min-height: 44px;
    text-decoration: none; color: var(--ink-2); font-size: var(--t-xs);
  }
  .bottomnav svg { width: 22px; height: 22px; }
  .bottomnav a[aria-current="page"] { color: var(--door); }
  /* The bar sits over the page, so the footer needs room to clear it or the
   * last link on every page is permanently under the thumb bar. */
  .footer { padding-bottom: calc(var(--s7) + var(--thumbbar) + env(safe-area-inset-bottom, 0)); }
}

/* ── the destination page's own contents ──────────────────────
 *
 * A row of anchors on a desktop; a horizontally scrolling strip on a phone,
 * as the UI specification asks. It scrolls rather than wraps because a
 * wrapped tab bar changes height as the label set changes per page, and the
 * hero below it then moves by a different amount on every destination.
 *
 * scroll-padding-top on the root is what makes these anchors land correctly:
 * without it the sticky masthead covers the heading you just jumped to, and
 * the page looks like it went to the wrong place.
 */
/* THE SAME HIDDEN NAVIGATION AS THE MASTHEAD, IN A SECOND COMPONENT, ON
   319 PAGES. This row is a destination's own contents and it scrolled
   sideways on a phone: measured at 390, 567px of links in a 358px box, so
   Events, Travel tips, Stay and Onward were off-screen — half the sections
   of the richest family in the product, behind a swipe with no affordance
   at all (not even the masthead's mask fade).

   It wraps. A contents list is page content rather than persistent chrome,
   so a second line costs nothing: the masthead had to fight for one row
   because it is on every screen of every page, and this is not. */
.sectionnav {
  display: flex; gap: var(--s4); margin: var(--s5) 0;
  padding-bottom: var(--s2); border-bottom: 1px solid var(--rule);
  flex-wrap: wrap; row-gap: var(--s1);
}
.sectionnav a {
  white-space: nowrap; text-decoration: none; color: var(--ink-2);
  font-size: var(--t-sm); padding: var(--s2) 0; min-height: 24px;
  border-bottom: 2px solid transparent;
}
.sectionnav a:hover { color: var(--ink); border-bottom-color: var(--door); }

/* ── the persistent action on a destination ───────────────────
 *
 * "Add to my journey", not "Book now". There is nothing to book, and a
 * booking button that cannot book is the single most dishonest control a
 * travel site can ship.
 *
 * It sits above the thumb bar rather than replacing it — both are fixed, so
 * the offset has to account for the bar's height and the safe area, or the
 * two overlap on a notched phone.
 */
.stickycta { display: none; }
@media (max-width: 44rem) {
  .stickycta {
    display: flex; align-items: center; gap: var(--s3);
    position: fixed; left: 0; right: 0; z-index: 44;
    bottom: calc(var(--thumbbar) + env(safe-area-inset-bottom, 0));
    padding: var(--s3) var(--s4);
    background: color-mix(in srgb, var(--paper) 94%, transparent);
    backdrop-filter: saturate(1.4) blur(10px);
    border-top: 1px solid var(--rule);
  }
  /* NO `.stickycta-where`. It held the destination's name in a flexed span
     with an ellipsis, and on a 390-pixel phone two buttons leave it about a
     hundred pixels: measured on all 319 destinations, 146 of them rendered a
     cut word. The name is in the buttons' own accessible labels now, which
     is where it does some work. */
  .stickycta .btn { min-height: 44px; display: inline-flex; align-items: center;
                    justify-content: center; }
  /* The primary takes the room the name was wasting; the secondary is sized
     by its own word. Both keep the 44px target. */
  .stickycta .btn:not(.ghost) { flex: 1; }
  /* Room for both bars, or the last section of every destination page is
   * permanently underneath them. */
  body.area-countries .footer {
    padding-bottom: calc(var(--s7) + var(--thumbbar) + 5rem + env(safe-area-inset-bottom, 0));
  }
}

/* The sticky masthead would otherwise cover any heading an in-page link
 * jumps to. This is the one line that makes every anchor on the site land
 * where the reader expects. */
html { scroll-padding-top: 5rem; }

/* ── the map, as a list ───────────────────────────────────────
 * The accessible alternative to a picture that cannot be read out. Two or
 * three columns where there is room; one where there is not, because a
 * multi-column list on a phone is a list you scroll past sideways.
 */
.maplist { margin: var(--s6) 0; padding: var(--s5); border: 1px solid var(--rule);
           border-radius: var(--radius); background: var(--paper-2); }
.maplist summary { cursor: pointer; font-weight: 600; min-height: 24px; }
.maplist h3 { margin: var(--s5) 0 var(--s2); font-size: var(--t-md); }
.maplist .cols { columns: 1; }
@media (min-width: 44rem) { .maplist .cols { columns: 2; column-gap: var(--s6); } }
@media (min-width: 64rem) { .maplist .cols { columns: 3; } }
.maplist .cols li { break-inside: avoid; margin-bottom: var(--s2); }

/* ── the staged wait ──────────────────────────────────────────
 * Not decoration: each line is ticked when the work it names has actually
 * finished, and a failure marks the step it stopped on. role="status" means
 * a screen reader hears the change without the focus being stolen.
 *
 * .staged carries TWO shapes, and for the life of this page it carried one
 * of them at the wrong moment. The five-step list belongs to a build. The
 * boot — reading the Atlas index before the reader has asked for anything —
 * is a single sentence with no list at all, because four of those five steps
 * are waiting on the reader and a progress report that cannot finish is
 * worse than none. See the comment on booting() in planner.js.
 */
.staged .stages { list-style: none; padding: 0; margin: var(--s3) 0 0; }
.staged .stages li { display: flex; gap: var(--s3); align-items: baseline;
                     margin-bottom: var(--s2); font-size: var(--t-sm); }
.staged .todo { color: var(--ink-3); }
.staged .now { color: var(--ink); font-weight: 600; }
.staged .done { color: var(--ink-2); }
.staged .failed { color: var(--warn); font-weight: 600; }

/* ── the four doors ───────────────────────────────────────────
 * A sequence, not a menu. Each card is a link now (they were inert divs
 * describing five pillars), so the section that explains the product is
 * also the fastest way into it. The arrow only appears on hover and focus:
 * four permanent arrows in a row is four arrows competing.
 */
.doors .door { position: relative; }
.doors .door .kicker { color: var(--sea); }
.doorgo { margin: var(--s3) 0 0; color: var(--door); font-size: var(--t-lg);
          opacity: 0; transform: translateX(-4px); transition: opacity .18s ease, transform .18s ease; }
.doors .door:hover .doorgo, .doors .door:focus-visible .doorgo { opacity: 1; transform: none; }
@media (max-width: 60rem) { .doorgo { opacity: 1; transform: none; } }

/* ── the manifesto ────────────────────────────────────────────
 * Set as verse rather than as a paragraph: one line per line, generous
 * leading, the display face, and a measure narrow enough that no line ever
 * wraps on a desktop. A manifesto that wraps stops being a manifesto.
 */
.manifesto { font-family: var(--display); font-size: clamp(var(--t-lg), 2.4vw, var(--t-2xl));
             line-height: 1.5; max-width: 44rem; }
.manifesto p { margin: 0 0 var(--s3); max-width: none; text-wrap: balance; }
.manifesto-close { margin-top: var(--s6); color: var(--sea); font-weight: 600; }

/* ── the trust architecture ───────────────────────────────────
 * Four sources, and the point is that they LOOK different. A badge with a
 * colour, a left rule in that colour, and the honest count underneath each
 * one. Community is deliberately the muted one: nothing on this site
 * carries it yet, and the tier should look empty because it is.
 */
.tiers { display: grid; gap: var(--s4); margin: var(--s5) 0; }
@media (min-width: 52rem) { .tiers { grid-template-columns: 1fr 1fr; } }
.tier { border: 1px solid var(--rule); border-left-width: 4px; border-radius: var(--radius);
        padding: var(--s4) var(--s5); background: var(--paper-2); }
.tier h3 { margin: 0 0 var(--s3); }
.tierbadge { display: inline-block; font-family: var(--text); font-size: var(--t-xs);
             letter-spacing: .12em; text-transform: uppercase; font-weight: 700;
             padding: 3px 9px; border-radius: 999px; }
.tier-verified  { border-left-color: var(--sea); }
.tier-verified  .tierbadge { background: var(--sea); color: var(--paper); }
.tier-editorial { border-left-color: var(--door); }
.tier-editorial .tierbadge { background: var(--door); color: var(--paper); }
.tier-computed  { border-left-color: var(--ink-3); }
.tier-computed  .tierbadge { background: var(--ink-3); color: var(--paper); }
.tier-community { border-left-color: var(--ink-3); }
.tier-community .tierbadge { background: transparent; color: var(--ink-3);
                             box-shadow: inset 0 0 0 1.5px var(--ink-3); }

/* ── photographs ──────────────────────────────────────────────
 * There are none yet; this is what serves one when there is. The focal
 * point is a per-image custom property because a 21:9 crop of a portrait
 * photograph, centred, takes the sky and loses the subject — which is the
 * single commonest way a responsive hero goes wrong.
 */
/* NINE FOCAL ANCHORS AS CLASSES, BECAUSE THE ALTERNATIVE WAS AN INLINE
   STYLE. picture() used to emit style="--fx:..;--fy:.." and checks.py refuses
   a style attribute anywhere — one of them forces style-src open on every
   page. The register was empty, so the path was never rendered and the
   contradiction sat there waiting for photograph number one. */
.photo { display: block; width: 100%; height: 100%; object-fit: cover;
         object-position: 50% 50%; }
.photo.f-lt { object-position:   0%   0%; }
.photo.f-ct { object-position:  50%   0%; }
.photo.f-rt { object-position: 100%   0%; }
.photo.f-lc { object-position:   0%  50%; }
.photo.f-cc { object-position:  50%  50%; }
.photo.f-rc { object-position: 100%  50%; }
.photo.f-lb { object-position:   0% 100%; }
.photo.f-cb { object-position:  50% 100%; }
.photo.f-rb { object-position: 100% 100%; }
/* No `display: block`: <picture> is display:inline by default and the UA
   sheet does not need overriding here, because its only child is the <img>
   that carries the layout. The dead-rule scan reported both of these the
   first time a photograph actually rendered — two declarations written for a
   code path the empty register never ran. */
picture { position: relative; height: 100%; }
/* The credit is always present in the markup and shown on hover or focus.
   Always-visible credits on 988 pages is a design nobody ships; credits that
   exist only in a data file are a licence risk. This is the compromise, and
   it is readable to a screen reader either way. */
/* THE SCRIM IS 72% AND THE ARITHMETIC IS WHY, not taste. A credit sits on
   a photograph nobody has seen yet, so its contrast cannot be read off two
   tokens — it is whatever the picture underneath happens to be. The scrim is
   what makes the ratio a property of the DESIGN rather than of the picture,
   which is the sentence the homepage hero already uses, and 55% did not
   deliver it: the worst case is a white frame, 55% black over white
   composites to rgb(115,115,115), and white on that measures 4.74:1 — over
   the AA floor by a twentieth, on a value nobody had computed. At 72% the
   composite is rgb(71,71,71) and the floor is 9.2:1 whatever the photograph
   does. checks.py recomputes both numbers from this declaration. */
.credit { position: absolute; right: 0; bottom: 0; margin: 0;
          font-size: var(--t-xs); letter-spacing: .03em;
          padding: 3px 8px; color: #fff; background: rgba(0,0,0,.72);
          opacity: 0; transition: opacity .16s ease; }
picture:hover .credit, picture:focus-within .credit { opacity: 1; }
@media (max-width: 44rem) { .credit { opacity: 1; } }

/* ── editing an itinerary ─────────────────────────────────────
 * Buttons, not drag handles. Drag-and-drop is unusable with a keyboard,
 * unusable with a screen reader and miserable on a phone, and this is a
 * list of at most fourteen things — "move earlier" is a better interaction
 * that merely looks less impressive.
 *
 * They sit at 44px on a phone because they are the controls a reader uses
 * most and the WCAG 2.2 target-size floor is not a ceiling.
 */
.legedit { display: flex; flex-wrap: wrap; gap: var(--s3); margin: var(--s3) 0 0;
           font-size: var(--t-sm); }
.legedit .linkish { min-height: 32px; padding: 2px 0; }
.legedit .linkish[disabled] { opacity: .35; cursor: default; text-decoration: none; }
.legedit .drop { color: var(--warn); }
@media (max-width: 44rem) {
  .legedit { gap: var(--s4); }
  .legedit .linkish { min-height: 44px; }
}

/* ── adding a stop ────────────────────────────────────────────
 * A search box and a list of buttons rather than a combobox widget: the
 * whole Atlas is already in memory, so this is a filter over an array, and
 * a real <button> per result is keyboard-operable and screen-reader
 * readable without a single ARIA role having to be got right.
 *
 * The distance sits on every row because on a route the question is always
 * "what does this stop actually cost me", and a list that hides it invites
 * a 900 km detour that looks like a small edit.
 */
.addstop { margin: var(--s4) 0 0; padding: var(--s4); border: 1px solid var(--rule);
           border-radius: var(--radius); background: var(--paper-2); max-width: 34rem; }
.addstop label { display: block; font-weight: 600; margin-bottom: var(--s2); }
.addq { width: 100%; padding: var(--s3); border: 1px solid var(--rule);
        border-radius: var(--radius); background: var(--paper); color: var(--ink);
        font: inherit; min-height: 44px; }
.addq:focus { border-color: var(--sea); }
/* The focus ring is offset 3px and was landing on the help text below it. */
.addstop .small { margin-top: var(--s3); }
.addhits { list-style: none; margin: var(--s3) 0 0; padding: 0; }
.addhits li { margin: 0; }
.addhit { display: flex; justify-content: space-between; align-items: baseline; gap: var(--s4);
          width: 100%; text-align: left; min-height: 44px; padding: var(--s2) var(--s3);
          border: 0; border-radius: var(--radius); background: none; color: inherit;
          font: inherit; cursor: pointer; }
.addhit:hover { background: var(--paper-3); }
.addhit .small { color: var(--ink-3); }

/* ── Discover Mode ────────────────────────────────────────────
 * Interest chips are real toggle buttons carrying aria-pressed, not styled
 * checkboxes — a screen reader should hear "Mountains, pressed", which is
 * what the control actually is.
 */
.picks { gap: var(--s2); }
.chip.pick { cursor: pointer; font: inherit; font-size: var(--t-sm);
             border: 1px solid var(--rule); background: var(--paper); color: var(--ink-2);
             min-height: 36px; }
.chip.pick:hover { border-color: var(--sea); color: var(--ink); }
.chip.pick[aria-pressed="true"] { background: var(--sea); border-color: var(--sea);
                                  color: var(--paper); }
@media (max-width: 44rem) { .chip.pick { min-height: 44px; } }

/* The "why this" line. It is the point of the whole feature, so it is set
 * apart rather than folded into the body copy — a recommendation you cannot
 * interrogate is an advertisement. */
.whythis { margin: var(--s3) 0 0; font-size: var(--t-sm); color: var(--ink-2);
           border-left: 3px solid var(--sea-dark); padding-left: var(--s3); max-width: none; }
.whyall { margin: 0 0 var(--s4); font-size: var(--t-sm); color: var(--ink-2);
          border-left: 3px solid var(--sea); padding-left: var(--s3); max-width: none; }
.whyall span, .whythis span { font-weight: 700; color: var(--sea);
                              letter-spacing: .04em; text-transform: uppercase;
                              font-size: var(--t-xs); margin-right: var(--s2); }

/* ── the homepage progression ─────────────────────────────────
 * The specification asks the homepage to move the reader through open,
 * discover, wonder, understand, plan, go — rather than be a grid of thirty
 * cards. A progression nobody can see is just an ordering, so the steps are
 * named, and one band changes ground so the rhythm is felt rather than
 * merely intended.
 */
.stage { font-size: var(--t-xs); letter-spacing: .18em; text-transform: uppercase;
         font-weight: 700; color: var(--sea); margin: 0 0 var(--s2); }
.stage::before { content: ""; display: inline-block; width: 1.6rem; height: 1px;
                 background: var(--sea); vertical-align: middle;
                 margin-right: var(--s3); }

/* The wonder band. Quieter ground and a wider measure, because the point of
   it is to slow the reader down for a moment rather than to be scanned. */
.band.tone-quiet { background: var(--paper-2); border-top: 1px solid var(--rule);
                   border-bottom: 1px solid var(--rule);
                   margin-left: calc(50% - 50vw); margin-right: calc(50% - 50vw);
                   padding: var(--s8) calc(50vw - 50%); }
/* THE STRIP IS THE END OF THE BAND. `.wayin` breaks out of this band's
   own padding to run edge to edge, so the 72px below it plus the more
   link's 24px left a hundred and twelve pixels of empty grey with one
   small link floating in it — measured at 1440 under a strip that had just
   been tightened from 352px to 245. Closing a band with a hole undoes the
   band above it. */
.band.tone-quiet:has(.wayin) { padding-bottom: var(--s5); }
.wayin + .more { margin-top: var(--s4); }
.band.tone-quiet .band-head h2 { max-width: 20ch; }
.band.tone-quiet .lede { max-width: 46rem; }

/* ── What if? ─────────────────────────────────────────────────
 * The panel sits after the itinerary, not before it: it is a question about
 * a plan you already have, and putting it above would make it a filter.
 * The preview is a note rather than a modal, because nothing here needs to
 * be dismissed before the reader can look at their route again — the whole
 * point is comparing the two.
 */
.whatif { margin: var(--s7) 0 0; padding: var(--s5); border: 1px solid var(--rule);
          border-radius: var(--radius); background: var(--paper-2); }
.whatif h2.mini { margin-top: 0; }
.whatif .chips { margin: var(--s4) 0; }
.whatif-preview { background: var(--paper); border-left: 4px solid var(--sea); }
.whatif-preview .stack { margin: var(--s3) 0; }
.whatif-preview .stack li { margin-bottom: var(--s2); }

/* ── travel profile ───────────────────────────────────────────
 * Deliberately built from the same .scorebar as the Europe Experience
 * Score, because it is the same kind of thing: a derived number with its
 * method published, not a measurement. Using a different, more
 * personal-looking visual would be the first step towards it reading as a
 * personality test.
 */
.dna .dnarows { margin: var(--s4) 0; }
.dnarow { display: grid; grid-template-columns: 11rem 1fr 2.5rem auto;
          gap: var(--s3); align-items: center; margin-bottom: var(--s2); }
.dnalabel { font-size: var(--t-sm); color: var(--ink-2); }
.dnanudge { display: flex; gap: var(--s2); }
.dnanudge .linkish { min-width: 28px; min-height: 28px; text-decoration: none;
                     font-size: var(--t-md); text-align: center; }
@media (max-width: 44rem) {
  .dnarow { grid-template-columns: 1fr auto; grid-template-areas: "l n" "b b" "u u"; }
  .dnalabel { grid-area: l; }
  .dnarow .scorenum { grid-area: n; }
  .dnarow .scorebar { grid-area: b; }
  .dnanudge { grid-area: u; }
  .dnanudge .linkish { min-width: 44px; min-height: 44px; }
}

/* ── the full-bleed hero ──────────────────────────────────────────────
 *
 * The homepage led with structure and it was cold: a headline, a form, a
 * technical note, a row of counts and a 90 KB data map, above eight
 * identical generated tiles. Correct, disciplined, and it made the reader
 * meet the data model before they wanted to go anywhere.
 *
 * This is the entrance instead. Full-bleed, the masthead over it, the type
 * and the discovery field inside the picture. It is built to hold a
 * licensed photograph and to be shippable before one arrives — .shot is
 * added only when the register actually holds the file, and the ground
 * below is what a reader sees until then.
 *
 * The fallback is deliberately NOT a generated plate. Rendering the plate
 * system at 1200x500 was tried and measured: flat, monochrome, a dead slab
 * across the bottom third. It carries a 160x100 card and cannot carry this.
 */
/* EUROPE IN THE OPENING — an engraved line, not a dark mass.
   It sits to the right at its own proportion and the opening runs on past
   it: the projection is 1.28:1 and a hero is nearly 2:1, so slicing it would
   cut the Arctic off the top and the Mediterranean off the bottom, which are
   the two edges that make the shape recognisable. The empty space to the
   left is where the type goes — the composition, not something to fill. */
/* AND IT FADES WHERE THE DATA STOPS, rather than ending on a straight line.
   data/geo/ is cut at 52°E, and under the conic that cut runs from x=747 at
   70°N to x=1024 at 40°N — a clean diagonal straight through Russia. On an
   instrument that is a frontier of the dataset and the page says so; at the
   scale of a screen it reads as a rendering fault. The drawing fades out
   over its eastern quarter and its bottom eighth, so the continent emerges
   from the dark instead of being sliced by it, which is also true: that is
   where what we hold ends. */
/* NO `pointer-events: none` HERE ANY MORE. It was right while the drawing
   was decorative and sat under the type; with fifty countries as links it is
   what stopped every one of them from being clickable. A child set back to
   `auto` under a `none` ancestor is hit-testable in HTML and was not here:
   the click landed on the <svg> itself and stopped. Keyboard activation
   worked the whole time, which is exactly the kind of half-working that
   ships. The type is a later sibling and still paints over the map, so
   nothing under it becomes unselectable. */
.heroeurope { position: absolute; inset: 0 0 0 auto; width: min(78%, 62rem);
              z-index: 0;
              display: grid; align-items: center;
              /* THE FADE HID HALF THE CONTINENT ONCE THE MAP BECAME THE
                 PICTURE. It was tuned when this was a 15% silhouette that only
                 had to suggest a shape, and it started at 40% of the drawing's
                 width — which is central Europe. Measured on the built page:
                 Italy, the Adriatic, Greece and the whole Balkan peninsula sat
                 inside the fade and read as sea, on the hero of a European
                 travel product. The bottom fade began at 78%, which is about
                 40 degrees north: Sicily and Crete went with them.

                 It exists for one thing only and now covers only that thing.
                 data/geo/ is cut at 52°E, and under the conic that cut runs
                 from x=747 at 70°N to x=1024 at 40°N — a clean diagonal
                 through Russia that reads as a rendering fault rather than as
                 the edge of what we hold. The fade starts where the cut does
                 and not before; the bottom one softens North Africa where the
                 band's own edge would otherwise slice it. */
              /* AND THE BOTTOM FADE MOVED INTO THE DRAWING. It was here, in
                 percentages of this box, to hide the atlas's 33°N cut through
                 North Africa — a fade in the element's coordinates covering an
                 edge in the projection's, which agree only at one aspect
                 ratio and stopped agreeing the moment the hero learned to
                 stack on a phone. It is a gradient along the 33rd parallel
                 now, inside the SVG, applied to the atlas groups only: the
                 ground below it is real land and stays. See heroeurope(). */ }
.heroeurope svg { width: 100%; height: 100%; }

/* AND ON A NARROW SCREEN THE SIDE-BY-SIDE COMPOSITION HAS NOWHERE TO PUT THE
   CONTINENT. The desktop hero is type on the left and Europe on the right,
   which needs two columns; a phone has one. Held at 78% of a 390px screen the
   drawing was 304 units wide for a whole continent — Denmark, the Low
   Countries and the Adriatic stopped resolving into coastline at all — and
   because the box was the full height of a 709px hero with a 1.28:1 picture
   letterboxed inside it, two thirds of the opening was empty water above a map
   nobody could read, with the headline crossing the Mediterranean.

   So below 52rem the two columns become two rows: the drawing takes the whole
   width at its own proportion and the type sits under it. The map goes from
   304 to 390 units wide and from a quarter of the opening to half of it, and
   nothing is cropped — this is the same fitted drawing, given the width it
   needed. Found by rendering the page at 390 after the desktop hero was
   finished; every measurement of the hero had been taken at 1440. */
@media (max-width: 60rem) {
  .heroeurope { position: static; z-index: auto; width: 100%; height: auto;
                align-items: start; }
  /* AND A FULL-WIDTH CONTINENT IS TOO MUCH HERO ON A TABLET. At 820 the
     drawing is 772 units wide and 602 tall, and with the type under it the
     opening ran to 1,044 — a reader on a 900-tall window met the map and
     nothing else, which is the opposite of the fault this fixed on a phone.
     The height is capped instead: below the cap the width binds and nothing
     changes, above it the drawing letterboxes inside its own box. */
  /* THE ASPECT MUST BE THE VIEWBOX'S, and for one commit it was the old
     one. 1000/780 was right until the hero got its own wider window; against
     a 1120/800 viewBox it letterboxed by twelve pixels top and bottom, and an
     SVG clips to its VIEWPORT rather than to its viewBox — so those two bands
     showed the geometry outside the frame, ending in a straight horizontal
     line above and below the continent. checks.py asserts the two agree. */
  .heroeurope svg { height: auto; aspect-ratio: 1120 / 800; max-height: 54vh; }
  /* FULL-BLEED, AND THE LAST TENTH FADES. Stacked, the drawing's own frame
     edges are inside the hero rather than under the arch, and with the ground
     painted in graphite an edge is black meeting blue on a straight line. The
     left and right go under the aperture by taking the hero's own padding
     back; the bottom cannot, because the type is under it, so the picture
     fades out over its last tenth. This is not the fade that was removed —
     that one hid a DATA cut at 33°N and now lives in the drawing as a radial
     gradient along that parallel. This one ends the PICTURE, and a picture
     that ends is allowed to say so. */
  .heroeurope { width: 100vw; margin-left: calc(50% - 50vw);
                -webkit-mask-image:
                  linear-gradient(to bottom, #000 90%, transparent 100%);
                mask-image:
                  linear-gradient(to bottom, #000 90%, transparent 100%); }
  /* AND THE DRAWING STARTS AT THE TOP OF THE OPENING. Stacked, the hero's
     own top padding put 90 pixels of sea above the picture, so the frame's
     top edge sat in open water — and with the ground beyond the atlas
     painted in graphite that edge is black meeting blue on a straight
     horizontal line across the Arctic. Run to the top and the arch cuts it,
     which is what the arch is for. */
  .herofull { min-height: 0; row-gap: var(--s6); padding-top: 0; }
}
/* FILLED, NOT DRAWN AS A WIRE. The first version stroked every country path
   and the hero became a political map: fifty internal borders competing with
   a headline at the scale of a screen. "Europe as an engraved line" is the
   rule for the INSTRUMENT, where a reader is looking for a frontier. A hero
   is looking for a continent — one silhouette, whose only outline is where
   land meets sea, because adjacent countries share an edge and the fills
   merge into a single mass. */
/* WARM LAND ON COLD WATER, which is the whole of this cartography in two
   lines and is what the hero did not have. Parchment at 78% over the Atlantic
   ground reads as land at a glance and still lets the ground through, so the
   continent sits IN the sea rather than on top of it. Context — the countries
   this atlas does not write about — is the same paper, quieter, exactly as it
   is on a plate: the difference that is real is how much we know about them,
   not what the ground is made of. */
/* ONE PATH PER GROUP, FILLED AND STROKED IN THE SAME OPAQUE COLOUR, WITH THE
   TRANSPARENCY ON THE GROUP. The stroke closes the hairline seams between two
   independently simplified neighbours; the group opacity composites the whole
   group once, so two coincident strokes at a shared frontier cannot double
   into a bright line. Doing it the other way round — translucent paint,
   per-country paths — drew fifty white borders and made a political map of
   the hero. See heroeurope(). */
/* THE GROUND EUROPE STANDS ON, AND IT IS NOT MADE OF THE SAME MATERIAL.
   The first version drew Asia and Africa from the same parchment as the
   atlas at a sixth of its presence — quieter Europe, in other words, and it
   read as a haze the eye kept trying to resolve into countries. They are
   graphite instead: the same near-black the INTELLIGENCE world is grounded
   in, solid, so the land past the atlas is not faint paper but unlit ground.

   That is the whole picture in one line. **Europe is what the light falls
   on.** The two fades along the 52°E and 33°N cuts now run from lit
   parchment into shadow rather than from one paper to a fainter one, and the
   hero stops being a map with a quiet margin and becomes a door with a lit
   room through it. No relief, no border, no title, nothing to click: the
   difference a reader is meant to feel is not "these countries are less
   important", it is "this is where the light stops".

   `--graphite` rather than `#000`, for the same reason limestone is never
   `#fff`: black is a screen and graphite is a shadow. */
/* AND IT IS NOT STROKED, which the layer in front of it has to be.
   `#heroland` closes the hairline seams between two independently simplified
   neighbours with a stroke in its own fill colour. This layer has no seams to
   close — it is one source, one simplification, and the rings are whole
   landmasses rather than countries — and stroking it drew a rectangle: the
   rings are clipped to the frame, so a stroke runs along all four edges of
   the window and outlines it. On the parchment version that read as a pale
   panel behind the continent, which is the single most conspicuous thing a
   background layer can do; in graphite it would draw the frame itself. Found
   at 390px, where the drawing is a third of the size and there is nothing
   else to look at. */
.heroeurope .lyr-beyond path { fill: var(--graphite); stroke: none; }
/* THE SHORE BAND, AND IT IS THE PALETTE'S OWN SHALLOW STEP. The ocean ramp
   has four: deep, mid, shallow, coastal. The hero's ground is 55% of the way
   from deep to mid and the water had nothing else in it, so half the picture
   was one flat field — a 20% crop of the middle read as a competent atlas of
   north-west Europe and as nothing else.

   `--ocean-shallow`, tight rather than soft. The first version mixed a
   lighter blue by hand and blurred it at nine units, and Iceland came out
   with a LIT AURA round it: a soft halo reads as an object glowing, and a
   narrow band reads as water getting shallower. Same idea, four units apart.

   Constant width, because it is a drawing convention and not a claim about
   depth — nothing in this repository holds a sounding, and real bathymetry
   is not a constant band. */
.heroeurope .lyr-coastal-water use {
  fill: color-mix(in srgb, var(--ocean-shallow) 76%, transparent); }
/* THE COAST, HEAVIER THAN A FRONTIER. Only the outer half of this stroke is
   ever seen: the land's own fill covers the rest, and a neighbour's fill
   covers it along every internal border. So one declaration gives the
   coastline its own weight without a second geometry and without the
   frontiers gaining any. */
.heroeurope .herocoast use {
  fill: none; stroke-width: 2.4; stroke-linejoin: round;
  stroke: color-mix(in srgb, var(--graphite) 52%, var(--atlas-land)); }
/* THE DUSK IS THE SAME GRAPHITE AS THE GROUND, which is what makes the
   continent dissolve INTO it rather than under a grey veil. The stops carry
   their own opacity; only the colour is set here, in one place, so the ground
   and the light that turns the atlas into it can never drift apart. */
/* THE GRAIN, AT THE STRENGTH OF A PAPER STOCK AND NOT OF A TEXTURE. Over
   everything the atlas drew and under the dusk, multiplying so it darkens the
   light half of the picture and leaves the dark half alone — which is what a
   printed grain does and what an added-light one would not. */
.heroeurope .herograin { opacity: .085; mix-blend-mode: multiply;
                         pointer-events: none; }
/* AND THIS SELECTOR MATCHED NOTHING FOR THE LIFE OF THE DRAWN HERO. The
   stops are inside <defs>, not inside `.herodusk` — the group only
   REFERENCES the gradients — so `.heroeurope .herodusk stop` never selected
   one, and every stop fell back to the SVG default for stop-color, which is
   #000. The layer this file calls two rectangles of graphite ramped to pure
   black: measured (0, 0, 0) against the (16, 18, 20) of the ground beside
   it, and it is the one thing `--graphite` rather than #000` was written
   down to prevent.

   The dead-rule scan could not see it either: that scan finds rules which
   MATCH elements and change none of them, and a rule matching no element at
   all is a different fault. The browser suite now reads the resolved
   stop-color instead, which is the only honest instrument for this. */
.heroeurope #heroedge stop, .heroeurope #herofootg stop {
  stop-color: var(--graphite); }
/* EVERY LAYER OVER THE LAND IS TRANSPARENT TO A POINTER. The terrain, the
   rivers, the frontiers and the dusk are all drawn above the countries and
   all of them have fills, so each one swallowed the click in turn. They are
   set to `none` individually rather than by putting `none` on the whole
   drawing and `auto` back on the anchors: an anchor set back to `auto` under
   a `none` ancestor did not receive the click here, and a rule that has to be
   overridden is a rule that will be got wrong again. */
.heroeurope .lyr-beyond, .heroeurope .lyr-terrain, .heroeurope .lyr-rivers,
.heroeurope .lyr-country-bounds, .heroeurope .heroctxg,
.heroeurope .herodusk { pointer-events: none; }
/* PAINTED BY INHERITANCE, NEVER BY ID, and that is what makes the boundary
   pass free. The frontiers are a `<use>` of the same path above the terrain,
   and a `<use>` clone is matched by any selector that matches the original —
   `#heroland { fill }` would come back filled inside the copy. Nothing
   selects the path: fill and stroke sit on the group and are inherited, so
   the clone inherits from its own parent instead. See heroeurope(). */
.heroeurope .herolandg, .heroeurope .heroctxg {
  fill: var(--atlas-land); stroke: var(--atlas-land);
  stroke-width: 1.4; stroke-linejoin: round; }
/* NO `opacity` ON `.herolandg`, AND THAT IS THE RULE RATHER THAN AN OMISSION.
   Below 1 the two coincident strokes at a shared frontier composite brighter
   than either and the map grows fifty white borders — which is why the land
   used to be one merged path, and why it can be fifty separate countries,
   and therefore fifty links, only while the group is opaque. At 1 two
   identical opaque strokes are one stroke. It also reads better: 0.78 let the
   sea through and made the parchment grey against the lifted blue, so the eye
   could not tell coast from country from mountain. Declaring `opacity: 1`
   here is a rule that changes nothing and the dead-rule scan says so, so the
   promise is kept by checks.py instead. */
/* A COUNTRY IS A DOOR, AND IT HAS TO LOOK LIKE ONE WHEN YOU REACH IT. The
   fill lifts toward limestone under a hover or a keyboard focus; the terrain
   is drawn over it at half opacity, so the change reads through the bands
   rather than being hidden by them. The focus ring is the site's own. */
.heroeurope a path { transition: fill .18s ease; }
@media (prefers-reduced-motion: reduce) {
  .heroeurope a path { transition: none; }
}
.heroeurope a:hover path, .heroeurope a:focus-visible path {
  fill: color-mix(in srgb, var(--limestone) 62%, var(--atlas-land)); }
.heroeurope a:focus-visible { outline: 3px solid var(--signature);
                              outline-offset: 2px; }
.heroeurope .heroctxg { opacity: .30; }
/* FIFTY COUNTRIES, FELT RATHER THAN READ. Not a political map — at reading
   distance nobody should think "I see borders", and everybody should know
   this is a map of Europe rather than a relief sculpture of it. One ink,
   thin, a quarter of the way from the parchment to the page's own ink, and
   the same stroke draws the COAST: a frontier has parchment on both sides
   and the coastline has the sea on one, so the coast comes out the sharpest
   line in the drawing without a second weight being declared anywhere. */
.heroeurope .lyr-country-bounds {
  fill: none; stroke-width: 1.05; stroke-linejoin: round;
  stroke: color-mix(in srgb, var(--graphite) 30%, var(--atlas-land)); }
/* WATER IS ITS OWN MATERIAL, and it is the sea's colour rather than a
   darker land. Thinner than the coastline: a river drawn as heavily as a
   coast turns a country into a leaf. */
.heroeurope .lyr-rivers .riv {
  fill: none; stroke-width: .8; stroke-linecap: round;
  stroke: color-mix(in srgb, var(--ocean-deep) 44%, var(--atlas-land)); }
.heroeurope .lyr-rivers .riv.major { stroke-width: 1.15; }

.heroeurope .lyr-rivers .lake {
  stroke: none;
  fill: color-mix(in srgb, var(--ocean-deep) 72%, var(--atlas-land)); }
/* THE SAME ABSOLUTE SCALE, AND FOR ONE COMMIT IT WAS NOT. These are
   cartography.HYPSOMETRIC's own colours — 600 m warm ochre, 1,200 m pale
   brown, 2,000 m light stone — and the top one was #efe9dd here against
   #e6e0d6 everywhere else, a brighter snow chosen by eye on a dark hero and
   nowhere written down. One colour meaning two heights is precisely what the
   single-strength rule forbids, and it arrived by the route the rule was
   written for: an adjustment that looked right on the surface being worked
   on. checks.py reads all three off HYPSOMETRIC now.

   The ALPHA is the hero's own treatment and is a different question from the
   colour. A plate mixes each band 65% of the way from the land tone to its
   hypsometric colour on an opaque parchment ground; the hero draws the pure
   colour over land that is itself at 78%, so the alpha is what keeps the
   Alps from reading as a second continent laid over the first. The scale is
   absolute in both; only the ground under it differs.

   The 200 m band is not here at all — it is 0.013 of luminance from the land
   tone on a plate and nothing whatever at continental scale. */
/* No `stroke: none` here: nothing strokes a band, and the dead-rule scan
   counts a declaration that changes nothing as dead however defensive it is. */
.heroeurope .lyr-terrain .tband { fill-rule: evenodd; }
/* AND THE ALPS ARE DISCOVERABLE RATHER THAN THE HEADLINE. The alphas were
   74/82/92, chosen when relief was the only thing in the picture besides the
   coastline; with frontiers, rivers and lakes in it the eye went terrain,
   terrain, terrain, and only then Europe. Continent first, geography second,
   terrain third. */
.heroeurope .lyr-terrain .t600  { fill: color-mix(in srgb, #cbc2b4 52%, transparent); }
.heroeurope .lyr-terrain .t1200 { fill: color-mix(in srgb, #c2b5a3 62%, transparent); }
.heroeurope .lyr-terrain .t2000 { fill: color-mix(in srgb, #b7a994 74%, transparent); }
/* THE RANGES, AS AN EDGE RATHER THAN AS A NAME. The brief asked for the
   Alps, the Pyrenees, the Carpathians, the Scandes and the Caucasus to be
   recognisable, with no labels. They are already in the drawing — those five
   are exactly where the 1,200 and 2,000 metre bands are — but as a tonal
   wash they read as haze at this size. A hairline on the two upper band
   boundaries turns the wash into a ridge: same data, same layer, no new
   geometry and nothing to read. It is thinner and paler than a frontier,
   because a range is a fact about the ground and a border is a claim about
   it. */
.heroeurope .lyr-terrain .t1200,
.heroeurope .lyr-terrain .t2000 {
  stroke-width: .55; stroke-linejoin: round;
  stroke: color-mix(in srgb, var(--graphite) 22%, transparent); }

/* The way in, moved out of the opening. A form inside the doorway made the
   first screen a search box with a picture behind it; the invitation is the
   continent, and the control belongs directly under it where it reads as the
   answer to "how". */
/* THERE WAS NO SPACE AT ALL BETWEEN THE CONTINENT AND THE FORM. The hero
   carries margin-bottom: --s7 and this pulled --s7 back off it, so the net
   gap was zero: the arch's flat bottom edge, dark, then a small grey label
   on white, then an input. Rendered, that is the strongest image on the
   site handing straight to a text field with no breath — and the band reads
   as a form somebody bolted under a picture rather than the first question
   on the other side of the door. */
.askband { margin: 0 0 var(--s8); }
.askband .hero-intents { margin-top: var(--s4); }

/* COUNTRY = IDENTITY. The name beside the shape, and the shape in a tall
   door. The homepage's opening is wide because a continent is wide; a
   country is a figure and stands in a figure's doorway. The aperture's
   proportion follows its subject — a signature that is the same shape at
   every size is a stamp. */
.pagehead.portraithead { display: grid; gap: var(--s6); align-items: center; }
/* THE PLATE: the figure and its foot. The locator and the key started
   INSIDE the figure and inherited the whole atlas skin through the cascade —
   `.minimap.arched.atlas .context path` is (0,4,1) and beat every rule the
   inset had, so Europe was drawn in the plate's own land and coast colours
   at 80 pixels and read as a scribble. A sibling, not a child. */
.plate { width: min-content; }
.portrait { margin: 0; }
/* One page in the atlas carries a caption under its door — Russia, whose
   outline is closed by the 52°E edge of data/geo/ rather than by a border.
   `min-content` above is what keeps that sentence wrapping to the door's own
   width instead of setting the width of the figure and shoving the door
   sideways. */
.portrait figcaption { margin-top: var(--s3); }
/* HUNG AT ONE HEIGHT, LIKE PICTURES IN A ROOM — the width is the country's.
   Sized by WIDTH the doors came out at wildly different heights, because a
   door's proportion follows its country: Austria's was 258px tall and
   Portugal's 512, on two pages a reader meets one after the other, and the
   head jumped by a quarter of a screen between them. Sizing by height keeps
   the doorway the same and lets the shape inside it differ, which is the
   whole claim this figure makes. Measured after: every portrait 416px, width
   258 (Portugal) to 541 (Austria). */
.portrait svg { display: block; width: auto; height: 26rem; }
/* THE MARK FOR A COUNTRY THE SOURCE CANNOT DRAW. A ring and a core, the
   same two-part mark /map uses for the six countries with no polygon at
   1:50m, at the size a portrait is looked at rather than the size a
   continent is scanned at. The ring is the claim ("it is here"); the core
   is the point the ring is about. */
.minimap.portrait.setting .cmark circle {
  fill: none; stroke: var(--signature); stroke-width: 2.6; }
.minimap.portrait.setting .cmark .core {
  fill: var(--signature); stroke: none; }

/* The portrait's own fill rule lived here and is DELETED, not kept: the
   atlas skin sets the subject's fill and stroke at the same specificity and
   further down the file, so this one matched fifty elements and changed
   none of them. The dead-rule scan found it in the same run that shipped
   the skin, which is what that scan is for. */
/* THE SHAPE IS THE ANSWER AND ON A PHONE IT WAS THE SIXTH THING. Measured
   at 390 on /europe/austria: masthead 89, breadcrumb 52, kicker 20, name 54,
   tagline 81, extent 40, six interest chips 99 — and the door itself began
   at 591, two hundred pixels below the fold, on the one family whose whole
   claim is that a reader keeps the shape, the name and one sentence. The
   extent and the chips are the RECORD, which is what every band below the
   fold is for; they are not what a country page opens on. Moving them
   after the portrait is a DOM change rather than a rule, because they sat
   inside `.portraitsay` and a grid can only place what it can address.
   Measured after: the portrait begins at 395. */

/* AND THE TWO COLUMNS START AT 64rem, NOT 52. `auto` gives the door the width
   its country needs and the type whatever is left, which is right at 1280 and
   catastrophic just above the old breakpoint. Measured on Austria, whose door
   is the widest at 541px:

       704   type 672px   one column
       834   type 174px   the tagline sets on five lines of three words
       900   type 240px   four lines
      1024   type 364px
      1180   type 520px   the composition it was designed as

   A 60px name, a display-serif tagline, an extent line and six chips in 174
   pixels beside a 540-pixel drawing is not a two-column head; it is a column
   of nothing with a picture next to it. 834 is also the width the glyph's own
   missing middle size was found at, for the same reason: two widths cannot
   find a rule that is wrong only between them.

   The door needs 541 and the type needs about 22rem to set that name, so the
   pair needs roughly 1,005 pixels of column. 64rem is the next breakpoint this
   stylesheet already has, and below it the head takes the phone's order —
   which is the composition the destination head switches to at 62rem. No new
   breakpoint value enters the register. */
@media (min-width: 52rem) and (max-width: 64rem) {
  /* Centred, as on a phone: between the two the door is at full height and
     stands alone under the name. On `.plate` rather than `.portrait` —
     `.plate` is `width: min-content`, so centring the figure inside it moves
     nothing and the door stays against the left edge with two hundred pixels
     of paper beside it. The box that has slack is the one to centre. */
  .portraithead .plate { margin-inline: auto; }
}
@media (min-width: 64rem) {
  /* `auto` rather than a fixed track: the door takes the width its country
     needs and the type takes the rest, so a narrow country does not leave a
     column of nothing beside it. The placement is EXPLICIT because the
     phone order now differs from the desk one: in document order the meta
     block follows the door, and on a desk it belongs under the name in
     column one. Stated rather than left to auto-flow, which would put it
     in column two under the picture. The two `1fr` rows ABOVE and BELOW the
     pair are what keeps the old composition: a door is 416px and the name
     and its extent are about 300, so with two auto rows the leftover was
     split BETWEEN them and the page rendered 350 pixels of white between
     the tagline and the chips. The slack belongs outside the group, not
     inside it. */
  .pagehead.portraithead { grid-template-columns: minmax(0, 1fr) auto;
                           grid-template-rows: 1fr auto auto 1fr;
                           column-gap: var(--s8); row-gap: var(--s5);
                           align-items: start; }
  .pagehead.portraithead .portraitsay  { grid-column: 1; grid-row: 2; }
  .pagehead.portraithead .headmeta { grid-column: 1; grid-row: 3; }
  .pagehead.portraithead .plate { grid-column: 2; grid-row: 1 / -1;
                                  align-self: center; justify-self: end; }
}
@media (max-width: 52rem) {
  /* On a phone the portrait goes under the name and loses a third of its
     height: at 26rem the widest door is 541px, wider than the screen, and
     the character line has to arrive before the picture in any case. */
  .portrait { margin-inline: auto; }
  /* AND 15rem IS TOO TALL BELOW 360. The door is sized by HEIGHT so that
     fifty countries hang at one height, and its frame is held to a constant
     1.299 — measured on all fifty built portraits, every one of them a
     508x391 viewBox — so 15rem is always 312 pixels wide. At 320 the column
     is 288 and the page scrolled sideways by 8 on every country page, which
     is the narrowest phone still in use and the one this stylesheet already
     records the wordmark being sliced at.

     The cap is arithmetic rather than a guess: the column is the viewport
     less two phone gutters, and the divisor is the frame's own proportion,
     which `checks.py` asserts against the built plates so it cannot go
     stale silently. At 390 it resolves to 276 and the min keeps 240, so no
     ordinary phone moves. */
  .portrait svg { height: min(15rem, calc((100vw - 2 * var(--s4)) / 1.299)); }
}

/* ── a fund project: a thing, in a country ────────────────────────
   THE WHOLE FUND FAMILY HAD NO GEOGRAPHY. Measured by tools/opening.js, a
   project page was 0% picture on the first screen — no figure anywhere on
   the page, at any width — on the one register whose argument is that every
   entry is a real thing in a real place with a named local partner. It was
   invisible because the family list the instruments read had never carried a
   fund project page, a month page or a sub-category page at all.

   A GLYPH, NOT A WINDOW, which is the reach head's own rule twenty lines up
   and the reason two index openings came off. A country page's aperture is
   the country as SUBJECT; here the subject is the project and the country is
   where it is. Twelve doorways under the fifty on /countries would be the
   signature as wallpaper.

   PAINTED, NOT INHERITED, and the first two versions were both wrong in
   ways this stylesheet already records. `.countryglyph` has exactly one
   paint rule anywhere and it is scoped to `.card-map`, so outside a card
   this drawing takes the SVG default, which is black — the `<stop>` no rule
   reached. Then `--atlas-here` measured rgb(248,246,242) on a limestone page
   of rgb(247,246,243) and Ireland rendered as a 1.1px outline of nothing:
   that is `path.here` on the country portraits exactly, a fill written on
   the premise that there is water underneath it, used where there is not.
   `--cobalt` is what `.regionglyph .countries path` already lights a country
   with on this ground, and it needs no sea to be read.

   Hung at one height like the portraits, for the same reason: sized by
   width, Romania and Norway would set the head at two different heights on
   two pages a reader meets one after the other. */
.pagehead.fundhead { display: grid; gap: var(--s6); align-items: center; }
.fundart { margin: 0; }
.fundart .countryglyph { display: block; width: auto; height: 14rem; }
.fundart .countryglyph .countries path { fill: var(--cobalt); }
.fundart figcaption { margin-top: var(--s3); max-width: 28ch;
                      font-size: var(--t-xs); color: var(--ink-3); }
@media (min-width: 64rem) {
  /* `auto` for the drawing, as on the country head: a narrow country takes
     the width its shape needs and the name takes the rest. 64rem rather
     than 52 for the reason measured there — between the two breakpoints a
     project name is a 60px serif line in whatever is left beside a picture,
     and four of these names run to five words. */
  .pagehead.fundhead { grid-template-columns: minmax(0, 1fr) auto;
                       column-gap: var(--s8); align-items: center; }
}
@media (max-width: 52rem) {
  /* 13rem rather than the portrait's proportional cut. A door is 26rem and
     comes down to 15; this is already a small mark and taking a third off it
     put Ireland at 100 pixels on a 390 screen, which is the size a country
     stops being a shape and becomes a blob. The glyph's own frame is held to
     the canvas proportion, so what a phone actually constrains is the WIDTH,
     and at 13rem that is 267 of the 358 available. */
  .fundart .countryglyph { height: 13rem; }
}

/* THE HERO IS THE DOORWAY, AND EUROPE IS WHAT IS THROUGH IT.
   Stripped of its mark and set beside eight other families, this page was a
   navy gradient, a headline, a search box and four chips — a travel product
   and nothing more specific. Six of the other eight carried the aperture and
   were unmistakable. The one page that has to say what this is said least.
   So the opening is cut here at the largest size the arch appears anywhere:
   full width, the head struck shallow across it the way a mason strikes a
   wide one, and limestone in the corners above the curve. Light wall, dark
   opening — the same sentence as every map on the site, at the scale of a
   screen. */
.herofull {
  position: relative; isolation: isolate; overflow: hidden;
  margin-left: calc(50% - 50vw); margin-right: calc(50% - 50vw);
  /* THE HERO'S CONTAINER SWUNG 0.435 TO 3.720 AND NOBODY HAD MEASURED IT.
     An 8.6x range of aspect, which with the declared source range left 7%
     of a photograph's frame guaranteed visible — a slot that destroys
     whatever is put in it rather than a slot that can be art-directed.

     The wide end is this line: at 2000x640, min(84vh, 46rem) is 538 and the
     block is 2000 wide. 43vw is a floor on the height that only engages
     when the window is wider than it is tall, so the aspect cannot pass
     100/43 = 2.33 at any size.

     It is not only about a photograph nobody has licensed yet. RENDERED at
     1920 the arch stopped reading as a doorway: the head's curve goes so
     shallow it is an ellipse, the continent floats in the middle of it, and
     a third of the picture is empty sea either side of a headline. The cap
     is what a printed atlas does with a spread — the frame has proportions
     of its own and does not simply take the paper's. */
  min-height: max(min(84vh, 46rem), 43vw);
  display: grid; align-items: end;
  padding: calc(var(--s8) * 2) calc(50vw - 50%) var(--s8);
  margin-bottom: var(--s7);
  border-radius: 50% 50% 0 0 / 34% 34% 0 0;
  /* Depth without a photograph, and the bed a photograph sits on. Two
   * radial pools over deep ink — atmosphere rather than a flat panel — in
   * the palette's own colours, no new token. */
  /* Two pools instead of three, and both moved out of the continent's way.
     They were painting depth for a hero that had no picture in it; there is
     a picture in it now, and a green pool under the Atlantic was competing
     with the thing it was standing in for. */
  /* THE HERO IS THE ATLANTIC, AND EUROPE IS IN IT.
   *
   * This was graphite with two radial pools, which is a bed for a photograph
   * and was the right thing while the map was a faint silhouette standing in
   * for one. It is not right for a map: land and sea were the same near-black
   * and the Mediterranean, the Baltic and the Black Sea did not exist, so
   * Italy sat in a void and the southern half of the continent dissolved.
   *
   * The ground is now the atlas's own water — the same four-step Atlantic
   * ramp every plate on this site is drawn on, deep at the edges and lifting
   * toward the middle where the continent sits. Nothing else on the page
   * changed: the type is still limestone, and limestone on `--ocean-deep`
   * measures far above AA, which near-black also did. What changes is that
   * the hero now belongs to the same cartography as the 824 plates behind
   * it, instead of being the one map on the site drawn in a palette of its
   * own. */
  /* AND THE POOLS WERE LIT UNDER THE EMPTIEST QUARTER OF THE DRAWING.
   *
   * Two of the three sat at 72% 62% and 62% 0% — under European Russia,
   * which is the one part of this map that is a pale wash fading into the
   * dark because data/geo/ stops at 52°E. A bright pool of water directly
   * beneath a fading landmass does not read as depth: it reads as a light
   * beam across the north-east, straight-edged because the fade runs along
   * the 52°E cut, and it was the brightest thing in the picture. On a phone,
   * where the sea either side is squeezed away, it was the ONLY thing in the
   * picture. Rendering it against a flat ground found it in one look; no
   * count could, because the pools measure the same wherever they are put.
   *
   * The lift belongs where the continent is, which is also where it is true:
   * shelf seas are lighter than the deep ocean. One pool low and centre-left,
   * over the North Atlantic approaches and the western basin, and a very
   * quiet Atlantic-green wash upper-left so the empty half of the hero is
   * water rather than a panel. Limestone measures 8.4:1 on the lightest
   * point of the first and 8.6:1 where the two stack, against 10.4:1 on the
   * bare ground — AAA throughout, and the browser suite re-measures it. */
  /* AND THE WATER HAD TO BECOME OBVIOUSLY WATER. `--ocean-deep` alone is
   * #123f55, which against limestone type is a magnificent 10.4:1 and against
   * warm parchment land is very nearly black: the hero read as a relief
   * sculpture lit on a dark ground rather than as land in a sea. Every plate
   * on this site sits on the same four-step Atlantic ramp and uses more of
   * it; the hero was pinned to the deepest step because it was built when the
   * map was a faint silhouette that needed the contrast.
   *
   * The ground is 55% of the way up that ramp now — #1e5469, a desaturated
   * European blue rather than a navy — and the parchment above it is
   * luminous instead of grey. Limestone measures 7.7:1 on it bare and 6.9:1
   * on the lightest point of the pool over it, against 10.4 and 8.4 before:
   * AAA for the headline, AA with room for the standfirst, and the browser
   * suite re-measures both. It is a mix of two tokens rather than a new one:
   * this is the same ocean, further up its own ramp. */
  background:
    radial-gradient(58% 54% at 46% 74%, color-mix(in srgb, var(--ocean-mid) 40%, transparent), transparent 70%),
    radial-gradient(70% 60% at 10% 30%, color-mix(in srgb, var(--atlantic) 18%, transparent), transparent 66%),
    color-mix(in srgb, var(--ocean-mid) 55%, var(--ocean-deep));
  color: var(--limestone);
}
/* THE REVEAL — THE ONE PART OF THE SIGNATURE THE HERO DID NOT HAVE.
 *
 * An `outline` rather than an inset `box-shadow`: both follow the radius and
 * neither affects layout, and the invariant register counts shadows. Two is
 * the whole vocabulary — a rest state and a lifted one — and a cut edge is
 * not a third of those.
 *
 * Every plate on this site cuts its aperture three ways and then draws two
 * more things at the cut: `.archrim`, the wall's face laid over the drawing's
 * edge, and `.archrim.inner`, a fine ink line just inside it. That second
 * line is what an opening in masonry actually shows and where the eye reads
 * depth from — and the hero, which cuts the arch at the largest size it
 * appears anywhere, had a rounded rectangle and nothing at the cut.
 *
 * Same relationship, same colour, same weight as the 824 plates: graphite at
 * 38%, one pixel, inside the curve. It fades out down the sides because the
 * bottom of this arch is not a cut — it is where the picture meets the page,
 * and a line across it would read as a border on the section below. */
.herofull::before {
  content: ""; position: absolute; inset: 0; z-index: 1;
  border-radius: inherit; pointer-events: none;
  outline: 1px solid color-mix(in srgb, var(--graphite) 38%, transparent);
  outline-offset: -1px;
  -webkit-mask-image: linear-gradient(to bottom, #000 46%, transparent 84%);
  mask-image: linear-gradient(to bottom, #000 46%, transparent 84%); }

/* AND THE SCRIM WAS GATED ON A PHOTOGRAPH THE REGISTER DOES NOT HOLD.
 *
 * "A scrim is not decoration here: it is what makes the contrast ratio a
 * property of the design rather than of the picture" — written on the rule
 * below, which is `.herofull.shot::after` and therefore applies only once a
 * photograph is licensed. The drawn hero, which is what every reader has
 * actually seen, had no scrim at all: its type sat straight on the drawing,
 * so the ratio was a property of the picture, which is the thing the
 * sentence one line up says a scrim exists to prevent.
 *
 * The declared figures above are real and were measured against the WATER —
 * 7.7:1 bare, 6.9:1 on the lightest pool. The continent is drawn over that
 * water now, and the standfirst's right-hand end runs across the lit
 * parchment of Iberia: sampled off the rendered pixels with the type
 * hidden, limestone at 88% over that ground measures 3.39:1, under the 4.5
 * AA floor, on the most-seen page on the site. Every contrast check here
 * reads a declared token against a declared background, and not one of them
 * had ever asked what colour is actually under the words.
 *
 * A bottom-left wash rather than a full scrim, because the picture is the
 * product: the type occupies one corner and the continent must stay
 * luminous everywhere else. Five stops rather than two — a two-stop ramp
 * has a crease at each end and the eye draws a line along it, which this
 * hero has already been fixed for twice. */
/* z-index 0 rather than -1, and the type lifted to 1: `.heroeurope` is at 0
 * and a wash under the drawing is a wash under the thing it has to darken.
 * The first version was at -1, the sea behind the type went from 30,84,105
 * to 22,44,53 and the lit parchment of Iberia — the only place the ratio
 * actually failed — did not move by one value. */
.herofull::after {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  border-radius: inherit;
  background: radial-gradient(86% 78% at 2% 104%,
    color-mix(in srgb, var(--graphite) 76%, transparent) 0%,
    color-mix(in srgb, var(--graphite) 72%, transparent) 26%,
    color-mix(in srgb, var(--graphite) 56%, transparent) 50%,
    color-mix(in srgb, var(--graphite) 28%, transparent) 70%,
    color-mix(in srgb, var(--graphite) 8%, transparent) 86%,
    transparent 100%);
}

/* The photograph, when one is licensed, and the scrim that keeps the type
 * readable over it. A scrim is not decoration here: it is what makes the
 * contrast ratio a property of the design rather than of the picture. */
.herofull picture { position: absolute; inset: 0; z-index: -2; }
.herofull .photo { width: 100%; height: 100%; object-fit: cover; }
.herofull.shot::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--graphite) 62%, transparent) 0%,
    color-mix(in srgb, var(--graphite) 34%, transparent) 42%,
    color-mix(in srgb, var(--graphite) 86%, transparent) 100%);
}
.herofull .credit { position: absolute; right: var(--s4); bottom: var(--s3); z-index: 1;
                    font-size: var(--t-xs); color: color-mix(in srgb, var(--limestone) 70%, transparent); }
.herobody { max-width: 46rem; position: relative; z-index: 1; }
.herofull .kicker { color: color-mix(in srgb, var(--limestone) 78%, transparent); }
/* The kicker repeats the headline verbatim, so it is decoration to a
 * screen reader and is hidden from one rather than read twice. */
.herofull .kicker { visibility: hidden; }
/* The colour that was here is inherited from .herofull and the dead-rule
   scan said so; what this selector really decides is the measure. */
.herofull h1 { max-width: 14ch;
               font-size: clamp(var(--t-4xl), 7vw, var(--t-6xl)); }
.herofull .lede { color: color-mix(in srgb, var(--limestone) 88%, transparent);
                  max-width: 34rem; }

/* The discovery field belongs to the picture, not to a form below it. */
.askhero { display: grid; gap: var(--s3); margin-top: var(--s6); max-width: 44rem; }
/* INK, NOT LIMESTONE — the form left the doorway and its label did not.
   This was written for a control INSIDE the hero, where limestone at 76% is
   right on graphite. The form now sits on the light band under the opening,
   and the label went with it: limestone on limestone, 1.00:1, invisible.
   The browser suite caught it and nothing else could have — the label is
   present, labelled, keyboard-reachable and completely unreadable, which is
   the same failure this stylesheet already records in the other direction
   for the hero masthead. */
/* AND THE BAND CAME OFF THE HERO, so the label stopped being a micro-caption
   over a photograph and became the first thing anybody is asked. A shouty
   uppercase label reads as a form; this is meant to read as a question. */
/* THE QUESTION IS THE PROPOSITION, so it is set at the size an opening
   line is set at. --t-lg was the size of a form label, which is what this
   looked like. The clamp is the one five other headings already use — a
   sixteenth font-size has been refused in this file twice. */
.askhero label { font-family: var(--display); font-size: clamp(var(--t-lg), 2.4vw, var(--t-2xl));
  line-height: 1.25; text-wrap: balance;
                 color: var(--ink-2); }
.askhero input { width: 100%; font: inherit; font-size: var(--t-md);
                 padding: var(--s4); border: 1px solid transparent; border-radius: var(--radius);
                 background: var(--paper); color: var(--ink); }
.askhero input:focus-visible { border-color: var(--signature); }
/* The chips were limestone on a graphite scrim because they sat inside the
   hero. On the light band that treatment is a grey pill, which is the shape
   this stylesheet uses for something INERT — and these are the fastest way
   into the product. */
.askband .chip, .herofull .chip { background: transparent;
                  border-color: var(--ink-3); color: var(--ink-2);
                  font-size: var(--t-sm); padding: var(--s1) var(--s4); }
.askband .chip:hover, .herofull .chip:hover { background: var(--paper-2);
                  border-color: var(--door); color: var(--door); }
@media (min-width: 40rem) { .askhero { grid-template-columns: 1fr auto; align-items: end; }
  .askhero label { grid-column: 1 / -1; margin-bottom: calc(var(--s2) * -1); } }

/* The masthead sits over the hero on the homepage only, and returns to its
 * own ground the moment the reader scrolls past it. One shell, one
 * masthead — this is a body attribute the single stylesheet reacts to,
 * exactly like data-world, not a second header. */
/* main carries top padding on every page, and the hero has to start at y=0
 * or the overlaid masthead sits in a strip of limestone with
 * limestone-coloured links on it — measured, not guessed: the links were
 * the right colour the whole time and simply had a light ground under them.
 *
 * The first fix was `margin-top: -32px` on the hero, which is the desktop
 * padding hard-coded. main's padding is smaller on a phone, so the hero
 * started at y=-8 there and the top of the picture was cropped. Zeroing the
 * padding is exact at every width; a negative margin matching one
 * breakpoint's value is a magic number waiting for the next breakpoint. */
body[data-hero] main { padding-top: 0; }
/* A SCRIM, not transparency, and it stays sticky.
 *
 * The first version made the masthead transparent and absolute. The browser
 * suite failed it at 1.00:1 and was right in a way the pixel probe was not:
 * the hero is a SIBLING of the masthead, not an ancestor, so nothing in the
 * box model gives those links a dark ground. Sampling painted pixels said
 * 16.5:1 and that was true only for as long as the hero happened to be
 * behind them and happened to be dark. The photograph is not chosen yet and
 * may have a bright sky, in which case white-on-transparent is unreadable —
 * which is precisely why every photographic hero puts a scrim under its
 * navigation.
 *
 * `absolute` also silently cost the homepage its sticky navigation: it
 * scrolled away and never came back, on the one page in the product where a
 * reader is most likely to want to leave for somewhere else. Sticky, with a
 * scrim, is both fixes at once. */
/* AND THEN THE HERO STOPPED BEING A PHOTOGRAPH FILLING THE FRAME.
 * The scrim above was right for that: type over an image needs a ground.
 * The hero is now a doorway CUT IN THE WALL, and the wall is limestone —
 * so a graphite band across the top painted over the two corners that make
 * it read as an opening, and the whole thing became a dark banner with a
 * curved bottom edge. The masthead stands on the wall, in the wall's own
 * colours, which is also every other page's masthead and one less thing.
 * Sticky is kept, for the reason above it. */
body[data-hero] .masthead { position: sticky; }
/* Every link in the masthead, by its real class. The first version styled
 * `.masthead a` and a `.brand` that does not exist in this markup, so the
 * navigation rendered ink-on-graphite and was invisible — the header was
 * there, keyboard-reachable and completely unreadable. Only looking at the
 * rendered page found it. */
/* The limestone link colours that were here went with the scrim: on the
 * wall they were invisible-on-light, which is the same failure the comment
 * above records in the other direction. */

/* ── the discovery mosaic ─────────────────────────────────────────────
 * Eight equal tiles read as a database index — every way into the
 * continent given identical weight, which is the one thing an editor
 * never does. This is a LAYOUT, not a new primitive: the children are the
 * same .card used on 842 pages, and an asymmetric grid appears nowhere
 * else in the codebase, so there is no repeated structure to extract.
 */
.grid.mosaic { grid-template-columns: 1fr; }
@media (min-width: 44rem) { .grid.mosaic { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 64rem) {
  /* Six tracks, not four. The first version spanned the lead tile across two
   * ROWS as well as two columns, and a .card is art-then-body: the art took
   * the whole doubled row and pushed the label out of the tile, so Mountains
   * rendered as a picture with no name. It also left 8 items in a 4-track
   * grid as 2.5 rows, with dead cells under the last two.
   *
   * Column spans only. Two wide tiles at the top, six even ones under them:
   * 3+3, then 2+2+2, then 2+2+2. Eighteen cells, eight tiles, no gaps and
   * no card whose body has nowhere to go. */
  .grid.mosaic { grid-template-columns: repeat(6, 1fr); }
  .grid.mosaic > :nth-child(1),
  .grid.mosaic > :nth-child(2) { grid-column: span 3; }
  .grid.mosaic > :nth-child(n+3) { grid-column: span 2; }
  .grid.mosaic > :nth-child(1) .plate,
  .grid.mosaic > :nth-child(2) .plate { aspect-ratio: 21 / 9; }
}
.homefoot { margin-top: var(--s8); }


/* ── DESTINATION: the art direction of one content family ─────────────
 *
 * The audit found eleven of twelve page families opening with an
 * identically-sized h1 at an identical vertical position, after an
 * identical kicker and lede. A destination and an archaeological site and a
 * 34-day journey all began the same way. This is the first family given a
 * composition of its own.
 *
 * These class names are NOT new primitives. .overture, .whygo, .reasons and
 * .placeband each appear on exactly one family, and a primitive here needs
 * the same pattern in three places. They are this family's art direction,
 * and they compose the existing .pagehead, .kicker, .chip, .facts and
 * .minimap rather than replacing any of them — which is why the primitive
 * reach floors still hold.
 *
 * WHAT IS DOMINANT, AND WHY. A destination's only universal editorial asset
 * is one authored sentence and three or four highlights: every one of the
 * 319 has them, while 194 have no places at all and 98 have neither places
 * nor experiences. So the sentence is the hero and the highlights are the
 * argument. Population, coordinates, the region link and the scores moved
 * BELOW both — a reader who has not been told why to care about a place has
 * no use for its population.
 */
/* Mobile-first, and the values are not decoration: measured at 390x844, the
 * shell spends 168px on a wrapped masthead and 53 on breadcrumbs, so 41% of
 * the first screen is gone before the place is named. What this family can
 * control is the space it spends itself, so it spends less of it on a phone
 * and the argument starts higher. Written mobile-first and widened at an
 * EXISTING breakpoint, so the breakpoint ceiling does not move. */
.overture { padding-top: var(--s4); }
/* THE NAME IS THE EVENT, AND IT WAS SET AT AN INDEX LABEL'S SIZE.
 *
 * The three head roles were built to differ in measure, position and air,
 * and a seventeenth type size was refused twice on the grounds that a role
 * needing a new scale value is a decoration. That refusal is right and is
 * kept: this introduces no token and no value. What was never measured is
 * that `h1 { clamp(--t-3xl, 5.2vw, --t-5xl) }` has a fluid term worth 20px
 * at 390, so THE FLOOR WINS ON EVERY PAGE ON THE SITE AT PHONE WIDTH —
 * `tools/opening.js` reports 21 of 27 families at exactly 38px. The scale is
 * fluid on a desktop and fixed on a phone, so at the width most readers
 * arrive at, an overture and an index were the same size to the pixel and
 * the whole distinction rested on measure and air.
 *
 * An overture is ONE THING and its name is the event; `docs/image-philosophy.md`
 * and the first-class audit both say the same thing from different ends —
 * with no photograph in the register, desire is carried entirely by type,
 * and the type was set at the size every other family uses.
 *
 * THE VALUE IS THE HERO'S OWN, verbatim from `.herofull h1` further up this
 * file: 48px at a phone, 76px at the widest. Nothing new is declared, the
 * scale stays at sixteen sizes, and the one page that already decided what
 * "this is the event" looks like now lends it to the eight families whose
 * subject is a single named thing. */
.overture h1 { max-width: 14ch;
               font-size: clamp(var(--t-4xl), 7vw, var(--t-6xl)); }
/* The authored sentence, at display scale. It was a .lede — 17px grey body
 * copy — which is how the best-written thing on the page ended up looking
 * like a caption. 66 to 144 characters across the atlas, median 103, so the
 * measure is set for two lines at the top of that range and never more. */
/* TWO SIZES, ONE TREATMENT — because the copy is not one length.
 *
 * Measured across the families that carry a statement: a country's tagline
 * runs 51–121 characters, a region's summary 45–149, a destination's 66–144.
 * A theme's summary runs 239–344, every one of the thirteen, and set at
 * 26ch of display serif that is eight lines of headline before the reader
 * reaches anything they can act on — a paragraph wearing a statement's
 * clothes.
 *
 * The fix is NOT to drop those families to the grey lede: the statement is
 * how this atlas speaks and a theme is not a lesser page. It is a second
 * size, picked by the generator from the length of the string, at a wider
 * measure. Same face, same colour, same role; one step down and more room,
 * which is what a longer sentence needs and what a shorter one must not
 * have. */
.statement.long {
  font-size: clamp(var(--t-lg), 2.4vw, var(--t-2xl));
  line-height: 1.25;
  max-width: 44ch;
}
.statement {
  font-family: var(--display);
  font-size: clamp(var(--t-xl), 3.1vw, var(--t-3xl));
  line-height: 1.12;
  letter-spacing: -0.012em;
  max-width: 26ch;
  margin: var(--s4) 0 0;
  color: var(--ink);
  text-wrap: balance;
}
/* The only factual line above the argument, and only two facts: what kind
 * of thing it is, and whether it is an afternoon or a week. Both change
 * whether you would go at all. */
.orient {
  margin: var(--s4) 0 0; font-size: var(--t-xs); letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-3); font-weight: 600;
}
.overture .chips { margin-top: var(--s4); }

/* THE ARGUMENT. Numbered, because three reasons presented as a bulleted
 * list read as a specification and the same three numbered read as an
 * editor's case. */
/* ARRIVAL: THE NAME, WHAT IT IS, AND WHAT YOU WOULD DO — all in one look.
   A destination page put the three reasons a person would go BELOW the head,
   in a three-column band with a rule over it, and put the view of where the
   place is below THAT, at y=840 on a 900-tall screen. So the first screen of
   a page whose whole promise is "you are here" carried a name, a sentence,
   four chips and 620 pixels of empty limestone beside them.
   The reasons are the best writing on the page and they answer the question
   a reader arrives with, so they move into the head, beside the name. The
   view comes up under both, full width. Same components, same primitives,
   one different arrangement: the head is not a headline any more, it is a
   first look round. */
.pagehead.overture.arrivalhead { display: grid; gap: var(--s6); }
.arrivalhead .whygo { border-top: none; margin-top: 0; padding-top: 0; }
/* AND THE VIEW IS PART OF THE OPENING, NOT THE BAND AFTER IT.
 *
 * This head's own comment already says it — "the head is not a headline any
 * more, it is a first look round" — and the one thing a first look round is
 * actually made of was outside it. Measured at 390 the destination page
 * reached its first drawing at 950 and `tools/opening.js` reported 0% of
 * the first screen as picture, on the largest family in the product and the
 * one the first-class audit calls the richest page and the most punished by
 * having no photograph.
 *
 * The ORDER is not the fault and is not changed: name, what the place is,
 * why go — desire before orientation, which is the brief's own sequence.
 * What changes is that "where it is" stops being a separate band four
 * hundred pixels down and joins the two it sits between. On a phone the
 * column reads name → sentence → the view → why go; on a desk the two
 * columns are exactly what they were and the view comes up full width under
 * both, which is where it already was. The desktop composition does not
 * move at all; the phone gets the drawing it never had.
 *
 * The map is what this product spends where the benchmark spends a
 * photograph, and it was below the fold on 319 pages. */
.arrivalhead .placeband { margin: 0; }
/* AND THE EXTENT AND THE TAGS COME AFTER THE VIEW, ON THE THREE FAMILIES
 * WHOSE OPENING IS A PICTURE.
 *
 * The country page went first and the measurement was the same everywhere:
 * "5 destinations across 3 regions · capital Vienna" and six interest chips
 * are 139 pixels of RECORD sitting between the sentence a reader came for
 * and the drawing that answers where it is. The record is what every band
 * below the fold is for. Measured at 390: destination 574 -> the picture is
 * the fourth element rather than the sixth, region 585 -> the same.
 *
 * `.headmeta` on all three, because the third time a repair is made by hand
 * it should stop being made by hand, so the country head's own meta
 * block was renamed to this in the same breath: one class, three families. */
.headmeta { display: grid; gap: var(--s3); justify-items: start; }
.headmeta .orient { margin: 0; }
.headmeta .chips { margin: 0; }
/* A REGION'S OPENING IS ITS MAP, and it was a band after the head. Same
 * move, same order: the name, what the region is, where it is, then the
 * record. The map is 2.6:1 and cannot stand beside the type the way a
 * country's portrait does, so this head stays one column at every width and
 * the order is the document's. */
.reghead .minimap { margin: var(--s5) 0 0; }
/* The region head is flow rather than grid, so the space above the record
   is a margin here where the arrival head gets it from the grid's row-gap.
   Without it the extent line starts on the pixel the map's caption ends. */
.reghead .headmeta { margin-top: var(--s5); }
/* AND A PLACE IS THE FOURTH FAMILY, on the same reasoning and with the same
   two rules. Its drawing measures 1168x467 at 1280 — 2.5:1, the region's own
   figure — so it stays one column at every width and the order is the
   document's. `.card-art` is here because the register will one day hold a
   photograph of a place, and the slot has to be spaced like the drawing it
   replaces rather than discovering that on the day it is filled: a code path
   nothing exercises is a code path nothing checks. */
.placehead .minimap,
.placehead .card-art { margin: var(--s5) 0 0; }
.placehead .headmeta { margin-top: var(--s5); }
@media (min-width: 62rem) {
  .pagehead.overture.arrivalhead {
    grid-template-columns: minmax(0, 1fr) minmax(0, 24rem);
    column-gap: var(--s8); align-items: start; }
  .arrivalhead .arrivalsay { grid-column: 1; grid-row: 1; }
  .arrivalhead .whygo      { grid-column: 2; grid-row: 1; }
  .arrivalhead .placeband  { grid-column: 1 / -1; grid-row: 2; }
  .arrivalhead .headmeta   { grid-column: 1 / -1; grid-row: 3; }
  /* One column in the head, not three: the reasons are a list read down
     beside the name, and the three-across arrangement below the fold was
     the shape of a band, not of an answer. */
  .arrivalhead .reasons { grid-template-columns: 1fr; gap: var(--s5); }
  .arrivalhead .reasons li { grid-template-columns: 2.6rem 1fr;
                             align-items: baseline; gap: var(--s3); }
  .arrivalhead .whygo { padding-top: var(--s2); }
}

.whygo { margin-top: var(--s6); border-top: 1px solid var(--rule); padding-top: var(--s5); }
.whygo h2 { font-size: var(--t-xs); letter-spacing: .12em; text-transform: uppercase;
            color: var(--ink-3); font-weight: 600; margin: 0 0 var(--s5); }
.reasons { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s4); }
.reasons li { display: grid; grid-template-columns: 2.6rem 1fr; align-items: baseline;
              gap: var(--s3); }
.reasons .rn { font-family: var(--mono); font-size: var(--t-sm); color: var(--door);
               font-variant-numeric: tabular-nums; }
/* An existing type step and an existing line-height. The first version
 * invented a clamp and a 1.42 for this, which is how a type scale grows by
 * two steps for one section nobody would have missed them on. */
.reasons .rt { font-size: var(--t-md); line-height: 1.5;
               max-width: 46ch; color: var(--ink); }
@media (min-width: 62rem) {
  .overture { padding-top: var(--s7); }
  .whygo { margin-top: var(--s8); padding-top: var(--s6); }
  /* auto-fit, not a fixed 3. 272 destinations carry three highlights and 47
   * carry four; a hard three-column grid wrapped the fourth onto a row of
   * its own, and Venice and Longyearbyen both shipped with an orphan. */
  .reasons { grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); gap: var(--s6); }
  .reasons li { grid-template-columns: 1fr; gap: var(--s2); }
}

/* WHERE IT IS, AND WHAT KIND OF COUNTRY IT IS — one figure, so the
 * illustration has a reason to exist beside a real coordinate instead of
 * sitting at the top as a decorative box. The caption says which of the two
 * it is; a generated horizon shown silently next to real geography invites
 * a reader to take it for a photograph. */
.placeband { margin: var(--s8) 0 0; display: grid; gap: var(--s4); }
.placeband-art .plate, .placeband-art picture { display: block; width: 100%;
  height: 100%; object-fit: cover; border-radius: var(--radius); }
.placeband-art { aspect-ratio: 16 / 9; overflow: hidden; border-radius: var(--radius); }
.placeband-map .minimap { margin: 0; }
.placeband figcaption { grid-column: 1 / -1; font-size: var(--t-xs); color: var(--ink-3);
                        line-height: 1.5; }
@media (min-width: 62rem) {
  .placeband { grid-template-columns: 5fr 7fr; align-items: stretch; }
  .placeband-art { aspect-ratio: auto; }
  /* No photograph, no illustration: the map takes the full width and its
   * labels become legible, which is the whole reason the plate went. */
  .placeband.maponly { grid-template-columns: 1fr; }
}


/* ── EXPERIENCE: the second family to get a composition ───────────────
 *
 * The audit found this the most under-designed family relative to its
 * promise: /experiences/food/markets rendered as six left-aligned titles
 * with right-aligned metadata and no images, cards or bands at all — a
 * database table with a heading, on a page whose job is to make somebody
 * want to do something.
 *
 * WHAT THE PAGE ACTUALLY HAS: six well-written sentences about six specific
 * things, in six different countries. The spread across Europe is the offer
 * — and it was 11px grey text, right-aligned, at the end of each row. So the
 * country leads now, and the page reads down its left edge like the contents
 * of a magazine feature.
 *
 * NO IMAGERY, DELIBERATELY. The obvious move was a plate per row, and it is
 * the exact trap the brief names: a plate here would be an illustration of
 * the CITY, already shown on the city's own page, placed because the system
 * has one rather than because this page needs it. The typography and the
 * geography carry it instead.
 *
 * `row exprow`, never a replacement — the `row` primitive keeps its reach.
 */
/* A SUB-CATEGORY HAS NO AUTHORED SENTENCE, so it is not given one.
 *
 * The first version set the parent category's blurb as the hero on every
 * sub-page, which put "Markets, tables, cellars, vineyards and the dishes
 * that belong to one valley" in 40px type at the top of the Markets page —
 * authored, true, and about the wrong thing. Forty sub-categories have a
 * name, a keyword list and no characterisation; that is a content gap, and
 * the design's job is to leave it visible rather than paper it with a
 * sentence about something else.
 *
 * So a CATEGORY page leads with its blurb, and a SUB page leads with the
 * countries it reaches — which is the honest answer to "what is on this
 * page", and on this family it is also the offer. */
.countryspread {
  margin: var(--s5) 0 0; font-size: var(--t-xs); letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-3); line-height: 1.5;
}
.countryspread.lead {
  margin-top: var(--s4); font-size: var(--t-sm); letter-spacing: .08em;
  color: var(--ink-2); max-width: 46rem; line-height: 1.5;
}
.countryspread.lead span:not(.sep) { color: var(--ink); font-weight: 500; }
.countryspread .sep { color: var(--rule); }

.explist { margin-top: var(--s7); }
.exprow { align-items: start; }
.exp-where { margin: 0; display: grid; gap: 2px; align-content: start; }
.exp-country {
  font-size: var(--t-xs); letter-spacing: .1em; text-transform: uppercase;
  font-weight: 600; color: var(--door);
}
.exp-city { font-size: var(--t-sm); color: var(--ink-2); }
.exp-what h3 { margin: 0; }
.exp-what .rowsub { margin-top: var(--s2); max-width: 52ch; }
.exp-kind {
  font-size: var(--t-xs); letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-3); display: grid; gap: 2px; justify-items: start;
}
.exp-band { color: var(--ink-3); letter-spacing: .04em; }
.rulenote { margin-top: var(--s6); color: var(--ink-3); }

/* A phone reads it as one column: where, then what, then which kind. The
 * desktop three-track layout is a composition and does not survive being
 * stacked, so it is only asked for once there is width for it. */
@media (min-width: 52rem) {
  .exprow { display: grid; grid-template-columns: 13rem 1fr 9rem; gap: var(--s5); }
  .exp-kind { justify-items: end; text-align: right; }
}


/* ── JOURNEY: the third family, and the one about movement ────────────
 *
 * The audit found the widest gap in the product here: 34 days and eight
 * countries rendered as an article with one picture at the top, an
 * eleven-item fact table above the route, and the sequence itself as a
 * bulleted list four screens down. The content is a line across a continent
 * and the page was a document.
 *
 * THE CONNECTOR IS NOW DRAWN. Each stop is a node on a continuous rule, and
 * the distance to it sits ON that rule, above the town it leads to — because
 * a hop is the movement between two places, not a footnote belonging to the
 * second one. Nothing here is invented: the nodes are the legs, the line is
 * the order, and the kilometres are the same haversine the planner uses.
 *
 * The first and last nodes are filled. A journey has a beginning and an end,
 * and that is the one thing a list of thirteen identical bullets cannot say.
 */
/* A north-south journey has a tall, narrow bounding box, and the route map's
 * viewBox IS that box — so at width:100% "Arctic to Mediterranean" rendered
 * about two thousand pixels deep with its labels colliding. The frame is
 * capped and the drawing letterboxes inside it, which is what
 * preserveAspectRatio already does. */
.routewrap { margin: var(--s7) 0 0; }
/* SIZED BY HEIGHT, NOT WIDTH. The viewBox is the route's own bounding box,
 * so a north-south journey is 2.35x taller than it is wide: at width:100%
 * "Arctic to Mediterranean" rendered about 2,700px deep with Lofoten
 * printed straight through Abisko. Setting the height and letting the width
 * follow the ratio gives a portrait map for a portrait route and a wide one
 * for the Hanseatic Arc, without either being told which it is. */
.routewrap .routemap svg {
  display: block; margin-inline: auto;
  height: min(70vh, 640px); width: auto; max-width: 100%;
  background: var(--map-sea); border-radius: var(--radius);
}
.routewrap .routemap .context path { fill: var(--map-context); }
.routewrap .routemap .countries path { fill: var(--map-land); stroke: var(--map-coast); stroke-width: 1; }

.legs.route { margin-top: var(--s6); }
.route .leg {
  grid-template-columns: 6rem minmax(0, 1fr);
  gap: var(--s4);
  border-top: none;
  padding: 0 0 var(--s6);
}
.route .leg:last-child { border-bottom: none; padding-bottom: 0; }
.route .leg-when { display: grid; gap: 2px; align-content: start; padding-top: 0; }
.route .leg-day { color: var(--ink-2); }
.route .leg-nights { color: var(--ink-3); }

/* The rail: one border, one node per stop. */
.route .leg-what {
  position: relative;
  border-left: 1px solid var(--rule);
  padding-left: var(--s5);
  padding-bottom: var(--s2);
}
.route .leg:last-child .leg-what { padding-bottom: 0; }
.route .leg-what::before {
  content: ""; position: absolute; left: -5px; top: 6px;
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--paper); border: 2px solid var(--door);
}
.route .leg:first-child .leg-what::before,
.route .leg:last-child .leg-what::before { background: var(--door); }

/* The distance sits on the rule, above the stop it leads to. */
.route .hop {
  margin: 0 0 var(--s3); font-size: var(--t-xs); color: var(--ink-3);
  letter-spacing: .04em;
}
.route .leg:first-child .hop { display: none; }
.route .leg h3 { margin: 0; }
.route .leg-country {
  display: block; font-size: var(--t-xs); letter-spacing: .1em;
  text-transform: uppercase; color: var(--door); font-weight: 600; margin-top: 2px;
}
.route .why { margin-top: var(--s2); max-width: 52ch; }

@media (min-width: 52rem) {
  .route .leg { grid-template-columns: 8rem minmax(0, 1fr); }
}


/* ── DESTINATION, the lower two-thirds ────────────────────────────────
 *
 * The first pass at this family designed the opening and left everything
 * below the placeband exactly as the audit found it: a score-bar dashboard
 * as the first thing after the argument, section ledes explaining OUR DATA
 * POLICY rather than the place ("2 recorded so far. We hold what each one
 * is and deliberately not its opening hours"), and the best practical
 * writing on the page — how many nights, when to come, how to get there —
 * shut in a grey rail on the right.
 *
 * An invitation followed three screens later by a database is not a
 * first-grade page. It is a first-grade opening bolted to the old one.
 *
 * So: the practical answers come into the flow, directly under the map,
 * because "how long, when, how do I get there" is what a reader asks
 * immediately after deciding they might go. The rail keeps the ACTIONS,
 * which is what a rail is for. And the metadata and the scores go last,
 * under a heading that says what they are.
 */
.practical {
  margin-top: var(--s6);
  padding-top: var(--s5);
  border-top: 1px solid var(--rule);
  display: grid;
  gap: var(--s6);
}
.practical h2 { margin: 0 0 var(--s2); }
.practical p { margin: 0; color: var(--ink-2); max-width: 34ch; }
.practical ul { margin: 0; padding-left: var(--s4); color: var(--ink-2);
                font-size: var(--t-base); }
.practical li { margin-bottom: var(--s2); }
/* auto-fit, not repeat(3): the destination page has three of these and the
   country page has four — Getting around, When to come, Worth knowing, At
   the table — and a fixed three left the fourth alone on a second row
   looking like a mistake. auto-fit gives three columns to three items and
   four to four, at the same track width, so both pages read as one band. */
@media (min-width: 52rem) {
  .practical { grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
               gap: var(--s7); margin-top: var(--s8); padding-top: var(--s6); }
}
/* The country's own argument, one step up, in the flow under its map. Not
   .lede: a lede sits in the pagehead and is grey. This is body copy that
   happens to be first. */
.measure.lead p { font-size: var(--t-md); color: var(--ink); }

/* The honesty notes stay — they are a rule here — but they stop being the
 * section's introduction. A disclosure about what we deliberately do not
 * hold is worth reading after the list, not instead of it. */
.sourcenote {
  margin-top: var(--s4);
  font-size: var(--t-xs);
  color: var(--ink-3);
  max-width: 60ch;
}

.placeband.maponly { grid-template-columns: 1fr; }
@media (min-width: 62rem) { .placeband.maponly { grid-template-columns: 1fr; } }


/* ── THE ARCH: EuropeDoor's signature aperture ────────────────────────
 *
 * The mark is a door with a semicircular head. The arch is also the one
 * architectural form the whole continent shares — Roman, Romanesque,
 * Gothic, Moorish, and every railway station built to look like all four.
 *
 * So the door stops being a logo shape and becomes the APERTURE. Geography
 * is seen through a doorway: the locator on a destination, the route on a
 * journey, the country on its own page. It is the same opening at every
 * scale, on every family, and it is the thing that should be recognisable
 * with the wordmark cropped off.
 *
 * The curve is cut in the SVG, not approximated with border-radius, because
 * a segmental arch struck from a real radius is a different shape from two
 * quarter-ellipses and the difference is exactly what makes it read as
 * architecture rather than as a rounded corner.
 */
/* The aperture is CUT OUT OF THE PAGE. The ground inside the arch is the
 * sea; outside it there is nothing — the page itself shows through. A dark
 * rectangle with a slightly darker arch on it is a panel; a doorway with
 * the page visible around it is an opening, and that is the whole idea. */
.archground { fill: var(--map-sea); }

/* A LIGHT WALL WITH A DARK OPENING.
 *
 * The figure used to carry data-world="intelligence", which resolves --paper
 * to graphite — so the aperture was a dark arch on a dark panel and the door
 * did not read at all. The --map-* tokens are global, not world-scoped, so
 * the map keeps its own dark palette without the figure claiming the dark
 * world; the frame loses its panel and its border, and the arch is cut
 * straight into the page. Light wall, dark opening. That is a door.
 *
 * It also fixes the caption, which was light-on-light the moment the panel
 * went: it now takes the page's own ink because the page's own world applies.
 */
.minimap.arched {
  background: none; border: 0; padding: 0; border-radius: 0;
  color: inherit;
}
.minimap.arched svg { background: none; border-radius: 0; }
.routewrap .routemap.arched svg { background: none; }
.minimap.arched figcaption { color: var(--ink-3); }
/* The homepage hero is the same aperture. It is an <a>, not a <figure>, so
   it takes the same treatment by name rather than by inheritance. */
.heromap.arched svg { background: none; border-radius: 0; }
/* THE REGISTER SAYS THE LAND IS A MASS AND THIS DREW A HAIRLINE.
 *
 * docs/palette.json declares --map-land against --map-sea at 1.8 with the
 * reason written out: "the land is a mass, not a hairline. Europe is what
 * the light falls on — the hero's reading, applied to the instrument." And
 * checks.py recomputes that ratio from the hexes in this file, so it has
 * been green since the day it was written.
 *
 * `fill: none` here made it false of the only page that uses this class.
 * /discover drew all fifty countries as unfilled outlines at 1px on
 * #0b0e11, with 319 dots over them — a wireframe continent on black, which
 * is the exact thing the brief names as what this product must never feel
 * like. The rule is older than the class's current user: it was written for
 * a homepage hero map that was removed, where an outline WAS the look.
 *
 * A separation measured between two tokens says nothing about whether
 * either is ever painted. That is the same gap as a contrast claim read
 * off a declaration rather than off the pixels a reader gets, and the
 * browser suite now asks the second question.
 *
 * /map fills its land with --map-land and strokes it with the coast, and
 * /discover links straight to /map: two instruments drawing one continent
 * two ways was the inconsistency underneath the defect. */
.heromap.arched .countries path { fill: var(--map-land); stroke: var(--map-coast);
                                  stroke-width: 1; vector-effect: non-scaling-stroke; }
.heromap.arched .context path { fill: var(--map-context);
                                stroke: color-mix(in srgb, var(--map-coast) 45%, transparent);
                                stroke-width: 0.7; vector-effect: non-scaling-stroke; }
.heromap.arched .herodot { fill: var(--cobalt-air); opacity: .95;
                           transition: opacity .2s ease, r .2s ease, fill .2s ease; }
.heromap.arched .herodot.advisory { fill: var(--map-border); opacity: .6; }

/* WHAT A NARROWED EUROPE LOOKS LIKE.
 *
 * A place that drops out GOES QUIET, it does not go away: the shape of the
 * continent is the point of the drawing, and a map that deleted its unlit
 * places would be a different Europe for every query. So the unlit dot takes
 * the LAND's own tone rather than a dimmed accent — it stops being a place
 * and becomes ground, which is exactly what "does not fit what you asked
 * for" means here.
 *
 * `r` is a presentation attribute and CSS may set it, which is how
 * `.constel-dense` already sizes its marks. Two-tenths of a second, on the
 * one property that carries the meaning, and the global reduced-motion rule
 * takes it off. */
.heromap.arched .herodot.unlit { fill: var(--map-land); opacity: .5; r: 3; }
.heromap.arched .herodot.lit { fill: var(--cobalt-air); opacity: 1; r: 5.5; }

/* THE SCALE BAR, WHICH THE PROJECTION EARNED.
 *
 * Under the equirectangular projection this replaced, ground distance per
 * drawn unit ran from 7.52 km at 33°N to 2.85 km at 71.5°N — 164% across one
 * frame — so a single bar would not have been an approximation but a lie.
 * The conformal conic holds 6.05–6.57 km/unit across the whole of Europe, so
 * a bar can be drawn, and geo.scale_bar() omits it on any frame where one
 * number is not true to within 2% of itself.
 *
 * Drawn like an instrument, not like a label: a hairline in the map's own
 * limestone, sitting in the negative space the conic's fan leaves at the
 * bottom of the aperture. */
.scalebar path { fill: none; stroke: color-mix(in srgb, var(--limestone) 62%, transparent);
                 stroke-width: 1.1; vector-effect: non-scaling-stroke; }
/* 11px, not 10: the map labels are already 11 and a scale bar is not worth
   a seventeenth font size on a site that holds sixteen. The invariant
   register caught it in the same commit that introduced it. */
.scalebar text { font-family: var(--text); font-size: 11px; letter-spacing: .06em;
                 fill: color-mix(in srgb, var(--limestone) 62%, transparent); }

/* The aperture draws in its OWN ink, not the page's.
 *
 * Dropping data-world="intelligence" fixed the wall and cost the drawing its
 * colours: the route resolved --door to cobalt-deep and went navy-on-black.
 * The inside of the arch is a dark ground whatever world the page is in, so
 * it names its colours explicitly. Lime is the electric accent and is
 * dark-ground-only by policy — 16:1 on graphite, 1.05:1 on ivory — and a
 * doorway cut into the page is exactly the dark ground that policy is for. */
/* The lime route rule lived here and is DELETED, not kept: every map that
   draws a route is an atlas plate now, so this matched elements on every one
   of them and changed none. Lime is the instrument's signal; a journey's line
   on parchment is cobalt. */
/* opacity:1 is the load-bearing half. The base rule is
   `.minidot circle { fill: var(--ink-3); opacity: .55 }` — a fill and a
   dimmer. Overriding only the fill left both dimmers in place, so the
   colour-mix's 55% was multiplied by the element opacity's 55% and the
   dots inside the aperture painted at 30% of limestone: 2.55:1 on the
   opening's ground, under the 3:1 a non-text graphic needs. Nothing
   could see it — a dot is drawn with `fill` on an SVG <rect>, which is
   neither a CSS colour nor a CSS background — until the browser suite
   started reading the fills and compositing the alpha itself. 5.86:1
   now. */
.minimap.arched .minidot circle { opacity: 1;
  fill: color-mix(in srgb, var(--limestone) 55%, transparent); }
.minimap.arched .minidot.here circle { fill: var(--cobalt-air); }
/* AND WHICH END IT STARTS AT. A route line drawn in order is the same
   drawing as the same route travelled backwards, so the last stop is hollow
   and the first is not, and the caption says which is which — an unexplained
   second mark is a puzzle, not a signal. Only an ordered set gets it:
   `line=True` is already the one thing separating a route from a theme.
   `:not(.hit)` because each dot carries a transparent 24px touch target as
   well, and painting THAT is how three grey blobs the size of a region
   appeared on every country map with every check green.
   Placed after the `.here` rule rather than before it: same specificity,
   (0,4,1) both, so the later one wins and the earlier one is dead. */
/* (deleted: superseded by the atlas rule below, which is where every
   route map now lives) */
.minimap.arched .minilabel { fill: color-mix(in srgb, var(--limestone) 72%, transparent); }
.minimap.arched .minilabel.here { fill: var(--limestone); font-weight: 600; }
/* The region names, which the aperture left behind. .countrymap .rlabel
   still resolved --ink-2 against the drawing's INTELLIGENCE world and haloed
   it in --map-hover, a mid-navy, four pixels wide: at 15px bold that reads
   as a smudge rather than a label, and the halo was lighter than the ground
   it was meant to knock out of. The halo is the ground colour now — it
   exists to cut the coastline out from behind the type and to do nothing
   else — and the fill is stated, like every other mark inside the arch. */
.minimap.arched .rlabel text { fill: color-mix(in srgb, var(--limestone) 88%, transparent);
  stroke: var(--map-sea); stroke-width: 3; }
.minimap.arched .rlabel:hover text { fill: var(--cobalt-air); }

/* THREE ROLES, AND EVERY PAGE HAS ONE.
   Measured across twenty-two rendered families: twenty-one placed a 60px h1
   at y=164 or y=212, and the only one that differed had a hero. The
   difference between families was one 11px kicker changing hue.

   The roles are not decoration, they are what the reader is doing:

     overture    one thing. The name is the event; it gets a narrow measure
                 and air above it, and the page opens rather than starts.
     index       a set. What matters is HOW BIG, so the extent sits BESIDE
                 the name on a wide screen instead of under it, and the set
                 itself starts a line and a half sooner.
     instrument  a tool. The title is a label at section weight, on the same
                 line as its kicker, because the page IS the thing.

   None of the three introduces a type size. A seventeenth was refused in
   commit 38 and is refused here: the difference between a magazine story and
   an encyclopedia entry is measure, position and air, and if it needs a new
   scale value it is not a role, it is a decoration. */
.pagehead.index .lede { max-width: 46ch; }
@media (min-width: 62rem) {
  /* START, not end. `align-items: end` bottom-aligned the h1 with the lede,
     so on /beyond-the-obvious — whose lede runs to five lines — the kicker
     sat at the top and its own title dropped sixty pixels away from it. The
     extent sits beside the name; it does not push it down. */
  .pagehead.index { display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
                    column-gap: var(--s7); align-items: start; }
  .pagehead.index .kicker { grid-column: 1; }
  .pagehead.index h1 { grid-column: 1; }
  /* ON THE TITLE'S ROW, NOT SPANNING BOTH. A grid item that spans two rows
     distributes its height across them, so a five-line lede made row one —
     the kicker's row — tall, and /beyond-the-obvious put its title 59px
     below its own kicker. The extent belongs beside the NAME, which is row
     two, and the kicker keeps row one to itself. */
  .pagehead.index .lede { grid-column: 2; grid-row: 2; margin: 0;
                          align-self: start; }
}
/* REACH, DRAWN, ON THE ONE FAMILY WHOSE OFFER IS THE SPREAD.
 *
 * The first-class audit calls the experience category page the
 * worst-composed page in the product: no visual element anywhere on it, at
 * any width, at any scroll position — a database printout. Two rebuilds
 * since then gave it the invitation as its unit and the sub-category shares
 * as a bar, and both were right; what it still had no answer for is that
 * its own stated offer, the spread across Europe, was a figure in a grey
 * line.
 *
 * `docs/signature-moments.md` refuses geography here and the refusal is
 * KEPT rather than overruled: forty-eight dots would say "food is
 * everywhere", which is true and is not an insight. Countries are not dots.
 * Luxury lights two of fifty and Adventure forty-four, and that comparison
 * only exists while all eight are drawn at the same extent — which is why
 * this glyph is unframed and the nine macro regions are framed.
 *
 * A GLYPH, NOT A WINDOW. Six index families already open on the same arch
 * in the same position and two of them were removed for it: an index
 * opening that is a continent with a different number of marks on it is not
 * a different opening. So no `.iheroart`, no aperture, no sea panel — the
 * /themes treatment, which is a mark on the page's own paper.
 *
 * AND THE DATA CUT FADES INTO THE PAGE, NOT INTO THE ATLANTIC. `.datacut
 * stop` is `--atlas-sea`, which is right where the land dissolves into
 * water on a picture plate and paints a navy wash across a bare glyph on
 * limestone. `.minemap` already answers this the same way. Third family to
 * need it; the rule is the ground the drawing is standing on.
 *
 * IT IS PLACED AFTER THE INDEX ROLE'S OWN MEDIA QUERY, because it varies
 * it. The first version sat above that query at equal specificity, so the
 * base rule won on every count and the lede rendered on top of the map. */
.reachhead .reach { display: grid; gap: var(--s2); justify-items: start; }
/* THE FILL IS GONE AND THE SIZE IS WHAT THIS RULE IS FOR. `.constel` already
   sets `fill: var(--atlas-far)` further down the file, so this restated it at
   higher specificity and changed nothing — the dead-rule scan found it the
   run after /fund joined this head, which is the ceiling doing its job. A
   declaration that restates what the element already gets is the commonest
   thing that scan finds, and the answer is to delete it rather than to raise
   the number. */
.reachhead .reach .constel { width: 100%; max-width: 22rem; }
.reachhead .datacut stop { stop-color: var(--paper); }
.reachhead .reachnote { font-size: var(--t-xs); color: var(--ink-3);
                        max-width: 36ch; margin: 0; }
@media (min-width: 62rem) {
  /* FOUR ROWS, NAMED, AND THE SLACK GOES IN THE LAST ONE. A cell spanning
     every row distributes its height across all of them, so the drawing
     being taller than the type opened a 250-pixel hole between the name and
     the extent — the same mechanic that once dropped /beyond-the-obvious's
     title sixty pixels below its own kicker, two rules up this file. */
  .pagehead.index.reachhead { grid-template-columns: minmax(0, 1.05fr) minmax(0, 22rem);
                              grid-template-rows: auto auto auto 1fr;
                              align-items: start; }
  /* The lede and the extent come back under the name in column one, because
     column two is now carrying the picture. A variant that adds an element
     has to say where the ones it displaces go, or they land on top of it.
     AND IT MUST OUT-SPECIFY WHAT IT DISPLACES: `.pagehead.index .lede` is
     three classes and `.reachhead .lede` is two, so the first version lost
     and the lede rendered on top of the map. That is the collision this
     stylesheet has now recorded four times. */
  .pagehead.index.reachhead .lede   { grid-column: 1; grid-row: 3; max-width: 52ch; }
  .pagehead.index.reachhead .orient { grid-column: 1; grid-row: 4; align-self: start; }
  .pagehead.index.reachhead .reach  { grid-column: 2; grid-row: 1 / -1;
                                      align-self: start; }
}

/* THE REVEAL — the cut edge of the aperture.
   "Light wall, dark opening" is the whole reading of the door, and it only
   held in one colour-scheme preference. Measured on every arched map, as the
   contrast between the page its corners reveal and the ground inside it:

       light preference   17.94:1
       dark preference     1.03:1

   In the dark preference the wall is graphite and the opening is graphite:
   no step, no door, on every page that draws one. Darkening the opening
   cannot fix it — two near-blacks are always about 1:1, and pure black
   against the graphite ground measures 1.11.

   So the door is read the other way a real one is, by its edge: a doorway is
   visible because the wall's surface stops. The line is drawn from the same
   arch_path() the clip uses, outside the clip group so it is not cut away by
   itself, and it is stated in both preferences rather than only the one that
   needs it — a signature that changes shape with a system setting is two
   signatures. */
.minimap.arched .archedge,
.heromap .archedge { fill: none; stroke: var(--rule); stroke-width: 1; }
@media (prefers-color-scheme: dark) {
  /* Where it is the only thing saying "opening", it carries more weight. */
  .minimap.arched .archedge,
  .heromap .archedge { stroke: color-mix(in srgb, var(--limestone) 26%, transparent); }
}

/* THE ATLAS SKIN. Specificity is deliberate and generous: every rule it
   beats is `.minimap.arched …` at (0,3,1), so these are (0,4,1) and win
   without argument. Checked in the browser, not counted here — three
   collisions in this stylesheet have already rendered as the thing simply
   not being there. */
/* THE STYLESHEET PAINTS LAYERS, NOT ELEMENTS. `cartography.ORDER` names
   fourteen layers and this file carries a rule for each one it can draw, so
   a reader of either file finds the same names and `checks.py` can assert
   that a declared layer is actually painted. A layer added to the renderer
   with no rule here used to render as nothing, which is the failure this
   codebase has made three times on specificity alone. */
.minimap.arched.atlas .lyr-ocean rect { fill: var(--atlas-sea); }
.minimap.arched.atlas .lyr-land .countries path { fill: var(--atlas-land);
                                        stroke: var(--atlas-coast);
                                        stroke-width: 0.9; }
/* CONTEXT IS THE SAME LAND. `context` means "a country this atlas does not
   write about", which is a fact about the dataset and not about the ground:
   giving it its own tone drew a warm wedge across Cyprus, Azerbaijan and
   Türkiye that a reader can only read as a geographic claim. Same fill, a
   lighter coastline, because the difference that IS real is how much we
   know about it. */
.minimap.arched.atlas .lyr-land .context path { fill: var(--atlas-land);
                                      stroke: color-mix(in srgb, var(--atlas-coast) 45%, transparent); }
/* THE THREE VOICES OF CONTEXT. A plate of France used to be France and
   twenty polygons of equal weight, every one of them asking to be read. The
   hierarchy is now: the subject, then its neighbours in warm parchment, then
   the rest of Europe getting quieter with distance. Nothing is hidden — a
   far country keeps its shape, its coastline and its <title>, because a
   reader looking at France still needs to see that Spain is underneath it.
   The band is measured, not listed: distance from the subject on this
   drawing, in the drawing's own units.
   MIXED TOWARD A WARM GREY, NOT TOWARD THE SEA. The first version mixed the
   quieter bands toward the water, which desaturates and darkens them into
   exactly the blue they have to be distinguished from: Germany and Spain
   went quiet by becoming sea. Parchment stays parchment and simply recedes. */
.minimap.arched.atlas .lyr-land .countries path.mid,
.minimap.arched.atlas .lyr-land .context path.mid {
  /* DERIVED FROM THE FAMILY RATHER THAN TYPED. This was the raw hex
     #d6d0c1, chosen by eye to sit between the land and the far band — and
     when the land family moved it stayed exactly where it was, so the middle
     voice of the context hierarchy was the one step that no longer belonged
     to its own ramp. A mix of the two steps it sits between cannot drift
     from them. */
  fill: color-mix(in srgb, var(--atlas-land) 55%, var(--atlas-far));
  stroke: color-mix(in srgb, var(--atlas-coast) 62%, transparent); }
.minimap.arched.atlas .lyr-land .countries path.far,
.minimap.arched.atlas .lyr-land .context path.far {
  fill: var(--atlas-far); stroke: color-mix(in srgb, var(--atlas-coast) 38%, transparent); }

/* THE WASH HAD NOTHING UNDER IT BUT THE SEA, AND SO THE SUBJECT OF EVERY
   COUNTRY PORTRAIT WAS DRAWN COLD.

   The rule that stood here was a translucent white — "a wash, not a fill, so
   whatever is under it survives" — written on the premise that the land tone
   is underneath and that terrain would be one day. On a portrait there is no
   land underneath: `lyr-ocean` is a rect of --atlas-sea across the whole
   frame, the countries are painted on top of it, and the subject is painted
   ONCE, with this fill. So 62% white sat on #153851 and Austria measured
   #ccd9e1 on the shipped page while its neighbours measured #d3cfc5 — a cold
   blue-grey country in a warm parchment frame, on all fifty portraits, which
   reads as water rather than as the subject. Sampled from the rendered page,
   not guessed: at thumbnail size it looks like a slightly lighter country.

   Opaque --atlas-here, which is the token that exists for exactly this and
   had no user. 1.31 against the neighbours' --atlas-land, which is the
   editorial atlas convention — the subject is the paper and the neighbours
   are a half-tone — with --atlas-hereline carrying the frontier at 11.03.
   Both are separations in docs/palette.json now, so the step cannot be
   tuned away. No portrait draws terrain (fifty plates, none with the class),
   so there is nothing under the subject for a wash to preserve; the day one
   does, the translucency comes back for that family and not for this one. */
.minimap.arched.atlas .lyr-land .countries path.here {
  fill: var(--atlas-here);
  stroke: var(--atlas-hereline); }
/* THE TERRAIN, AND THE CEILING ON IT.

   Four hypsometric bands from a public-domain elevation model, painted
   lowest first and mixed toward the land tone rather than laid over it with
   an opacity — stacked translucency compounds, and the fifth band would be a
   different colour depending on what happened to be under it.

   ONE PALETTE, AND IT IS THE CEILING THE EXPERIMENT APPROVED. Chamonix was
   rendered at four strengths and looked at (docs/terrain-prototype.md): the
   strongest was rejected because at that tonal distance the ochre stops
   being a ground and becomes a ramp, and the map reads as a visualisation of
   height instead of a picture of a valley. These are 65% of the way from the
   land tone to each band's hypsometric colour.

   A SECOND, FAINTER STRENGTH WAS BUILT FIRST AND MEASURED OUT. At 40% the
   only band Bergen has is 0.043 of luminance from the land tone — a layer
   that ships twenty-three kilobytes and cannot be seen — and two strengths
   make #d8ceb4 mean 600 m on one page and something else on another, which
   is not a hypsometric scale. A flat place is already quieter, automatically:
   it reaches only the quiet end of an absolute scale. Bergen's ground crosses
   one boundary and gets one step; Chamonix's crosses four.

   The 200 m step is 0.013 of luminance from the land tone and is meant to
   be: it separates a plain from a foothill and is not a step to be read.
   Whether a plate gets any of this at all is a MEASUREMENT — the spread and
   the crest of the ground within 25 km — never a list of mountainous places
   typed by somebody. */
.minimap.arched.atlas .lyr-terrain .tband { fill-rule: evenodd; }
.minimap.arched.atlas .lyr-terrain .t200  { fill: #d7d2c8; }
.minimap.arched.atlas .lyr-terrain .t600  { fill: #d1cabd; }
.minimap.arched.atlas .lyr-terrain .t1200 { fill: #cbc2b2; }
.minimap.arched.atlas .lyr-terrain .t2000 { fill: #c4baa9; }

/* THE FRONTIER, RE-DRAWN ABOVE THE RELIEF. A boundary on this map is a
   stroke on the land path, so the bands paint over it: the prototype's first
   render carried a clear France/Switzerland/Italy frontier without terrain
   and none with it. `cartography.ORDER` has always put `country-bounds`
   above `terrain`; this is the layer being unfolded from `land`, and it is
   emitted only on the plates that draw relief, because without terrain a
   second pass is 5 KB of duplicate geometry for no visible change.

   non-scaling-stroke, or the frontier is a worm: the land is drawn inside a
   scale(10) and 0.9 units becomes nine. The duplicate reproduced that
   failure the moment it was written without this line, one screen away from
   the rule that already documents it. */
.minimap.arched.atlas .lyr-country-bounds .bnd {
  fill: none; stroke: var(--atlas-frontier); stroke-width: 0.9;
  vector-effect: non-scaling-stroke; }
.minimap.arched.atlas .lyr-country-bounds .bnd.here {
  stroke: var(--atlas-hereline); stroke-width: 1.1; }

/* A PEAK, AND ITS MEASURED HEIGHT. Not relief and never called relief: a
   hillshade needs an elevation model this atlas does not have. A triangle
   and a number is how a physical atlas has always said "mountains here", and
   the height is somebody else's measurement rather than a surface we fitted. */
.minimap.arched.atlas .lyr-summits .peak { fill: var(--atlas-coast);
  stroke: var(--atlas-here); stroke-width: 1.1; paint-order: stroke; }
.minimap.arched.atlas .lyr-labels .peakname {
  font-family: var(--display); font-size: calc(var(--t-xs) / var(--z)); font-style: italic;
  fill: var(--atlas-coast); paint-order: stroke;
  stroke: var(--atlas-here); stroke-width: 2.2; }

/* A SEA IS AN AREA, SO IT IS WIDELY TRACKED AND CARRIES NO MARK. In the
   water colour rather than in ink, because it names the water it sits in.
   The rule is here before the data — the layer is written and the fetch is
   one command — since a rule written after the fact is argued about in a
   hurry. */
.minimap.arched.atlas .lyr-labels .sname {
  font-family: var(--display); font-size: calc(var(--t-xs) / var(--z)); font-style: italic;
  letter-spacing: .32em; text-transform: uppercase;
  fill: color-mix(in srgb, var(--atlas-land) 78%, transparent); }

/* A RIVER'S NAME, AND IT IS THE LAST PHYSICAL FAMILY TO GET ONE. Summits,
   ranges and seas have had labels since the plates were built; rivers were
   drawn with a <title> and nothing on the page, so Vienna carried six
   anonymous blue lines while the dataset names all 153 of them.
   In the WATER's ink rather than the land's, italic like every physical
   name, and tracked less than a sea because a river is a line and a sea is
   an area. It takes the same `--z` compensation as its siblings — written
   here rather than appended at the end of the file, because the scar this
   family exists beside is precisely a label family that was added somewhere
   else and inherited none of the rules. */
.minimap.arched.atlas .lyr-labels .rname {
  font-family: var(--display); font-size: calc(var(--t-xs) / var(--z)); font-style: italic;
  letter-spacing: .06em;
  fill: color-mix(in srgb, var(--atlas-land) 82%, transparent);
  paint-order: stroke;
  stroke: color-mix(in srgb, var(--ocean-deep) 55%, transparent); stroke-width: 2.0; }

/* A PHYSICAL FEATURE IS SET APART FROM A POLITICAL ONE, which an atlas has
   always done with an italic serif. ALPS and PYRENEES are geography; a
   frontier is a decision. */
.minimap.arched.atlas .lyr-labels .fname {
  font-family: var(--display); font-size: calc(var(--t-xs) / var(--z)); font-style: italic;
  letter-spacing: .1em;
  fill: color-mix(in srgb, var(--atlas-hereline) 72%, transparent);
  paint-order: stroke; stroke: var(--atlas-here); stroke-width: 2.2; }

/* A REGION IS A GROUPING, AND SPACED UPPERCASE IS HOW AN ATLAS SAYS SO —
   the same treatment a sea gets, because both name an area rather than a
   point, and neither has a boundary drawn round it here. Quieter than a
   place name and placed after them: a grouping is the thing a reader can
   most afford to lose. */
.minimap.arched.atlas .lyr-labels .rname {
  font-size: var(--t-xs); letter-spacing: .18em; font-weight: 600;
  fill: color-mix(in srgb, var(--atlas-hereline) 62%, transparent);
  paint-order: stroke; stroke: var(--atlas-here); stroke-width: 2.4; }

/* THE COUNTRY'S OWN NAME, set across the plate the way an atlas sets one:
   tracked, quiet, and under the place names rather than over them. It is
   also what makes the plate survive the recognition test — strip the
   wordmark and the page title and the drawing still says where it is.
   `--t-lg` rather than a literal: a font-size in an SVG is in user units, so
   this renders at about 21px on a 416px plate, and no seventeenth size
   enters the stylesheet for it. */
.minimap.arched.atlas .lyr-labels .cname,
.heroeurope .lyr-labels .cname {
  font-family: var(--display); font-size: var(--t-lg);
  letter-spacing: .22em; text-transform: uppercase;
  fill: color-mix(in srgb, var(--atlas-hereline) 46%, transparent); }
/* THE SAME TYPOGRAPHY, ONE LEVEL UP, AND ITS OWN INK. The plate sets a
   country's name in the highlight colour because there is one country on it
   and it is the subject; the hero has fifty and none of them is. Graphite at
   a third, which is a shade quieter than the frontiers — a name should be
   found when looked for and never met before the shape it names. */
.heroeurope .lyr-labels .cname {
  fill: color-mix(in srgb, var(--graphite) 48%, transparent); }
/* AND THEY COME OFF ON A PHONE. `--t-lg` is a length in the viewBox, and the
   viewBox is 1,120 units wide: at 390 that is a third of a pixel per unit, so
   a twenty-unit name renders at seven pixels — not small type, type that does
   not resolve into glyphs. It is the same measurement that took the map
   labels off narrow screens on eight hundred pages. The countries keep their
   shape, their link and their accessible name. */
@media (max-width: 44rem) {
  .heroeurope .lyr-labels { display: none; }
}

/* The place names on an atlas plate: ink on a paper halo, so a name stays
   legible where it crosses a coastline. The capital is set in small caps
   the way every printed atlas distinguishes it — `capital` is authored data
   per country, not a ranking derived from anything. */
.minimap.arched.atlas .lyr-labels .pname { fill: var(--graphite); font-size: 11px;
                               font-family: var(--text); paint-order: stroke;
                               stroke: var(--atlas-land); stroke-width: 2.6; }
.minimap.arched.atlas .lyr-labels .pname.cap { font-weight: 700; letter-spacing: .06em; }
/* A CITY IS FILLED, EVERYTHING ELSE IS A RING, AND A CAPITAL IS A STAR.
   Every place used to be the same dot: a database printed on a map. A ring
   is how a printed atlas has always drawn a place that is worth going to and
   is not a city, which is most of what this atlas is about. */
.minimap.arched.atlas .lyr-destinations .pmark.open {
  fill: var(--atlas-here); stroke: var(--atlas-hereline); stroke-width: 1.4; }
.minimap.arched.atlas .lyr-destinations .pmark.cap { fill: var(--cobalt-deep);
  stroke: var(--atlas-land); stroke-width: 1.4; paint-order: stroke; }
/* ON A PHONE THE PLATE IS 240px TALL, so 18 units renders at 6.8px — not
   small type, type that does not resolve into glyphs. The names are dropped
   and the marks are kept, which is the rule every other map here follows:
   every one of these places is named in full in the regions band below. */
@media (max-width: 62rem) { .minimap.arched.atlas .lyr-labels .pname { display: none; } }

/* THE LIMESTONE RIM. A printed plate has a reveal you can see the thickness
   of; the arch had a single hairline. Two lines, the outer in the page's own
   limestone and the inner in ink, is what an architectural opening actually
   shows: the wall's edge, then the shadow of the cut. */
.minimap.arched.atlas .archedge { stroke-width: 1.1; }
/* THE APERTURE IS A WINDOW CUT IN LIMESTONE, not an SVG in a rounded
   rectangle. The wall's face is laid over the drawing's edge and a fine ink
   line sits just inside it, which is what an opening in masonry actually
   shows and where the eye reads depth from. Both are struck from the same
   `arch_path()` as the clip and the reveal: three curves that disagree by a
   pixel is three signatures. */
.minimap.arched.atlas .archrim { fill: none; stroke: var(--limestone);
                                 stroke-width: 6; }
.minimap.arched.atlas .archrim.inner { stroke: var(--graphite);
                                       stroke-width: 1; opacity: .38; }

/* THE PLATE FOOT: where in Europe this is, and what the marks mean. Both are
   on the benchmark and both are cheap; a key is how a map stops needing a
   caption to explain its own dots. */
.platefoot { display: flex; align-items: flex-start; gap: var(--s5);
             margin-top: var(--s4); }
.locator { margin: 0; flex: 0 0 auto; }
.locator svg { display: block; width: 7rem; height: auto; }
.locator .countries path { fill: var(--limestone-3); stroke: none; }
.locator .context path { fill: var(--limestone-3); stroke: none; }
.locator .countries path.here { fill: var(--door); stroke: none; }
.locator .locring { fill: none; stroke: var(--door); stroke-width: 1.2; }
.locator figcaption { font-size: var(--t-xs); color: var(--ink-3);
                      letter-spacing: .08em; text-transform: uppercase;
                      margin-top: var(--s2); }
.platekey { list-style: none; margin: 0; padding: 0; display: grid;
            gap: var(--s2); font-size: var(--t-xs); color: var(--ink-3); }
.platekey li { display: flex; align-items: center; gap: var(--s3); }
.platekey li::before { content: ""; flex: 0 0 auto; width: 10px; height: 10px;
                       border-radius: 999px; }
/* THE KEY SHOWS THE MARKS THE PLATE ACTUALLY DRAWS. It carried a blue dot
   for a capital after the capital became a star, and no city at all after
   the city became a filled dot — a key that disagrees with its map is worse
   than none, because a reader believes it. */
.platekey .k-cap::before { background: var(--door);
  clip-path: polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%,
                     21% 91%, 32% 57%, 2% 35%, 39% 35%);
  border-radius: 0; width: 12px; height: 12px; }
.platekey .k-city::before { background: var(--atlas-hereline); }
.platekey .k-dest::before { background: var(--atlas-here);
                            border: 1.4px solid var(--atlas-hereline); }
.platekey .k-here::before { background: #ffffff;
                            border: 1.1px solid var(--atlas-hereline);
                            border-radius: 2px; }

/* A ROUTE ON PAPER needs NO RULE HERE, and the attempt to write one was
   dead code: `.routeline` is already cobalt-deep at 90% opacity — `--door`
   is the same token — so an atlas-scoped rule restating it matched every
   route and changed none of them. The lime override that used to sit here
   is gone with it, because every map that draws a route is an atlas plate
   now. */

/* RIVERS AND LAKES. The rules are here before the data, because the layer
   is written and the file is one fetch away — and a rule written after the
   fact is a rule argued about in a hurry. In the water colour, by rank, and
   ALWAYS thinner than the coastline's 0.9: a river drawn as heavily as a
   coast turns a country into a leaf. */
.minimap.arched.atlas .lyr-rivers .riv { fill: none; stroke: var(--ocean-shallow);
  vector-effect: non-scaling-stroke; stroke-linecap: round; }
.minimap.arched.atlas .lyr-rivers .riv.major { stroke-width: 0.8; }
.minimap.arched.atlas .lyr-rivers .riv.minor { stroke-width: 0.5;
  stroke: color-mix(in srgb, var(--ocean-shallow) 70%, transparent); }
.minimap.arched.atlas .lyr-rivers .lake { fill: var(--ocean-shallow);
  stroke: var(--ocean-mid); stroke-width: 0.5;
  vector-effect: non-scaling-stroke; }

/* THE FOUR LAYERS THIS ATLAS CANNOT DRAW carry no rule, on purpose: a rule
   for a layer that emits nothing is dead code that looks like a decision.
   Their appearance is decided in `cartography.DECIDED` and asserted there.
   terrain · hillshade · region-bounds

/* THE COAST TREATMENT. A printed atlas puts a soft band in the water along
   a coast, and it is what makes a coastline read as an edge rather than as
   a line between two flat fields. Drawn by stroking the land silhouette
   thickly UNDER the land fill, so the band falls entirely in the water.
   It is a convention, not bathymetry: this atlas holds no depth data and
   this says nothing about how deep anything is. */
/* The stylesheet paints LAYERS now, not ad-hoc classes: `lyr-coastal-water`
   is a declared layer of the stack and this is its rule, so a reader of
   either file finds the same fourteen names. */
/* THE COASTAL RAMP, CAST RATHER THAN STROKED. Three soft shadows of the
   land silhouette, widest and deepest first, so the water lightens as it
   approaches a shore. It is DISTANCE FROM LAND — the one thing a coastline
   can tell us — and never a claim about depth; this atlas holds no
   bathymetry and nothing here implies a number of metres.
   THE FIRST VERSION WAS THREE `<use>` OF THE LAND GROUP AND PAINTED NOTHING.
   A `<use>` shadow tree is still matched by the selectors that style the
   original paths, so every clone kept the land's own paper fill and 0.9px
   coast stroke, and the wide stroke set on the `<use>` never reached it. It
   measured correctly on the `<use>` element itself and was not on the page —
   the fourth time in this stylesheet that a treatment rendered as the thing
   simply not being there. A filter needs no second copy of Europe's
   geometry and cannot lose a cascade fight. */
.minimap.arched.atlas .lyr-land {
  filter: drop-shadow(0 0 3px var(--ocean-coastal))
          drop-shadow(0 0 9px var(--ocean-shallow))
          drop-shadow(0 0 20px var(--ocean-mid)); }

/* THE BOUNDARY HIERARCHY. One weight for everything made a frontier and a
   coast the same statement, and put the country this plate is about at the
   same weight as the country next to it. Three: a frontier is the lightest
   because it is context, a coast is the edge of the subject's world, and
   the subject's own outline is the heaviest thing on the plate. */
.minimap.arched.atlas .lyr-land .context path { stroke-width: 0.7; }
.minimap.arched.atlas .lyr-land .countries path { stroke-width: 0.9; }
/* AN ELEGANT HIGHLIGHT, NOT A THICK TECHNICAL OUTLINE. 1.1 against the
   coastline's 0.9 and the frontier's 0.7: the subject is the heaviest line
   on the plate by a hair, which is all a hierarchy needs. A soft luminous
   edge — a wide light halo inside the boundary — needs a stroke-only second
   pass over the same geometry, the same refactor terrain will force. */
.minimap.arched.atlas .lyr-land .countries path.here { stroke-width: 1.1; }

/* The places, as marks rather than as navigation. Ink with a paper ring, so
   one reads against the country's own blue and against the water alike. */
.minimap.arched.atlas .lyr-destinations .pmark { fill: var(--atlas-hereline);
                               stroke: var(--atlas-land); stroke-width: 1.6; }

/* THE MARKS AND NAMES ON A PAPER MAP. Lime on graphite is the INSTRUMENT's
   signal — it is the colour /map lights a result in — and on paper it is a
   highlighter. Here the place you are reading about is cobalt, its
   neighbours are ink, and the names are ink with a paper halo, which is how
   a printed atlas keeps a name legible over a coastline. */
/* A dot's ground is its own paper ring, for the same reason a name's is:
   the ring is what separates it from a coastline or a deep sea behind it. */
.minimap.arched.atlas .lyr-destinations .minidot circle { fill: var(--atlas-hereline);
  stroke: var(--atlas-land); stroke-width: 1.6; paint-order: stroke; }
.minimap.arched.atlas .lyr-destinations .minidot.here circle { fill: var(--cobalt-deep);
  stroke: var(--atlas-land); stroke-width: 2; paint-order: stroke; }
/* AND THE LAST STOP STAYS HOLLOW. This rule lost to the one above the moment
   route maps became atlas plates — (0,5,1) against (0,4,1) — so every journey
   silently lost the one mark that gives its line a direction, and a route
   drawn in order became the same picture as the same route travelled
   backwards. Found by the dead-rule scan in the run that shipped the
   migration. */
.minimap.arched.atlas .lyr-destinations .minidot.last circle:not(.hit) {
  fill: var(--atlas-here); stroke: var(--cobalt-deep); stroke-width: 2.4; }
.minimap.arched.atlas .lyr-destinations .minidot .hit,
.minimap.arched.atlas .lyr-destinations .minidot.here .hit { fill: transparent; stroke: none; }
.minimap.arched.atlas .lyr-labels .minilabel { fill: var(--graphite);
                                   paint-order: stroke;
                                   stroke: var(--atlas-land); stroke-width: 2.6; }
/* THE SIGNATURE IS A COLOUR THAT GETS DRAWN, NOT READ — the rule that
   already separated `--signature` from `--door` on the rest of the site,
   met again here. Cobalt-deep names the place you are reading about and
   measures 4.27:1 on this water, under AA. So the DOT is cobalt and the
   NAME is ink in bold: the accent is drawn, the text is read. */
   The `fill` is NOT restated here: the base `.minilabel` rule above already
   sets ink, and a declaration that restates what the element already has is
   a dead rule — the scan counts it, and it counted this one. */
.minimap.arched.atlas .lyr-labels .minilabel.here { font-weight: 700; }
.minimap.arched.atlas .lyr-labels .rlabel text { fill: var(--graphite); paint-order: stroke;
                                     stroke: var(--atlas-land); stroke-width: 3; }
/* The scale bar in coast measured 2.57:1 on the water it is drawn over — a
   rule you cannot read is not a scale. A slate that clears AA on both the
   water and the land, because a bar crosses from one to the other. */
.minimap.arched.atlas .lyr-labels .scalebar line,
.minimap.arched.atlas .lyr-labels .scalebar path { stroke: var(--atlas-land); }
/* The bar crosses water and land alike, so it is paper with an ink
   halo: the reverse of a name, and readable on both. */
.minimap.arched.atlas .lyr-labels .scalebar text { fill: var(--atlas-land);
  paint-order: stroke; stroke: var(--ocean-deep); stroke-width: 2.4; }

/* On paper the reveal is the whole reading of the opening, so it is ink
   rather than a hairline rule. */
.minimap.arched.atlas .archedge { stroke: var(--graphite); stroke-width: 1.2;
                                  opacity: .55; }

/* THE TAP TARGET IS NOT THE DOT.
   A .minidot is r=5.5 in a 1000-unit viewBox, which renders at 3.9px across
   on a 358px phone; WCAG 2.2 AA puts the floor at 24. Enlarging the drawn
   dot would destroy the map, so each link carries a transparent circle
   behind its dot, sized at build time to half the distance to the nearest
   other dot — the largest target that can never steal a neighbour's tap. */
/* AND IT HAS TO OUT-SPECIFY THE FILLS ABOVE, WHICH IT DID NOT.
   `.minidot .hit` is (0,2,0) and `.minimap.arched .minidot circle` is
   (0,3,1), so the touch targets painted at 55% limestone: three grey blobs
   the size of a region across every country map, and lime saucers on the
   month and quiet maps. Fifty-one static checks, seven hundred and
   seventy-seven browser checks, 1,334 section assertions and every
   invariant were green — a contact sheet of twelve families found it in one
   look, which is the whole argument for rendering the thing. */
.minimap .minidot .hit,
.minimap.arched .minidot .hit,
.minimap.arched .minidot.here .hit { fill: transparent; stroke: none; }
/* A context dot is not a link and must not offer to be one. */
.minidot.context { pointer-events: none; }

/* A LABEL THAT DOES NOT RESOLVE INTO GLYPHS IS WORSE THAN NO LABEL.
   `.minilabel` is 11 units in a 1000-unit viewBox and the browser scales the
   viewBox to its container, so the size a reader actually gets is
   11 x (width / 1000). Measured across four map families:

       viewport   390   480   704   900  1024  1280
       label px   3.9   4.9   7.4   9.4  10.7  12.8

   Under about 860px it is below 9px — every phone and most tablets were
   shown names nobody can read, on eight hundred pages, for the life of the
   embedded map. Nothing measured it because every check asked whether the
   label was PLACED, not whether it was legible.

   There is no non-scaling-text in SVG, so this has to be two rules and the
   build has to say which applies: a map with six names or fewer enlarges
   them on a phone, one with more cannot — forty-one names at two and a half
   times the size is a smear — and drops them, keeping every dot, every
   <title>, and the list of names that sits under the figure on every page
   that draws one.

   The enlargement REUSES `calc(11px / var(--z))`, which was already in this
   file compensating for the zoom on /map. The first version wrote
   `font-size: 26px` and the invariant register caught it in the same commit
   as a seventeenth type size — correctly. A compensation constant for a
   coordinate system is not a typographic choice, and the mechanism for it
   already existed; --z is the scale a map is drawn at, and .42 is what takes
   an 11px label to 9.4px at 390. */
@media (max-width: 44rem) {
  .minimap:not(.dense) { --z: .42; }
  /* A LABEL IS A LABEL. The physical names were written after this rule and
     inherited none of it: `.peakname`, `.fname` and `.sname` were sized in
     a fixed px inside a scaled viewBox, which is the defect the paragraph
     above exists for, and they rendered at 5.1px at 390 — Monte Rosa's
     height was 6 pixels tall and printed through Chamonix's own name. They
     take the same --z compensation now and they disappear on a dense map
     for the same reason a place name does. */
  .minimap.dense .minilabel,
  .minimap.dense .peakname,
  .minimap.dense .fname,
  .minimap.dense .sname,
  .minimap.dense .rname { display: none; }
  /* And the names that only fit at the small size. Enlarging the type broke
     the collision rule that placed it: 434 overlapping pairs on 275 of the
     815 pages that draw a labelled map, "Hallstatt" through "Berchtesgaden"
     by 49px. The build re-tests every box at this scale and marks the ones
     that lose; they keep their place on a wide screen. */
  .minimap .wide-only { display: none; }
}

/* AN INSTRUMENT'S TITLE IS A LABEL, BECAUSE THE PAGE IS THE TOOL.
   Measured across twenty-two rendered families: twenty-one place a 60px h1
   at y=164 or y=212, and the only one that differs is the homepage, which
   has a hero. On the five INTELLIGENCE pages that is actively wrong — the
   head pushed the instrument to y=436 on /plan, y=449 on /map and y=460 on
   /search, so half the first screen of a tool was a magazine headline and
   three lines of prose. /discover was worse: its controls started at 1989,
   two screens down.

   The h1 reuses the h2 declaration VERBATIM rather than introducing a
   thirteenth size. That is not a saving, it is the argument: an instrument's
   title carries the weight of a section heading, not of an overture. The
   kicker sits on the same line, because a tool does not need two.

   The lede keeps only the invitation. Provenance — which datasets, which
   projection, what the map is not — belongs below the instrument, and on
   /map it already lived there. */
.pagehead.instrument { display: grid; gap: var(--s2); padding-bottom: var(--s3); }
.pagehead.instrument h1 { font-size: clamp(var(--t-xl), 3vw, var(--t-2xl)); max-width: 46ch; }
.pagehead.instrument .kicker { margin: 0; }
.pagehead.instrument .lede { font-size: var(--t-md); max-width: 52ch; margin: 0; }

/* WHAT THE INSTRUMENT READS, IN THE COLUMN THAT WAS EMPTY. See
   pages.head_extent: a title is rarely a third of the page wide, so the
   `auto 1fr` row left about six hundred pixels of nothing beside it on all
   five INTELLIGENCE pages. Tabular, so three counts on one line read as a
   set; the label stays quiet so the number is the thing that carries. */
.headextent { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s5);
              margin: 0; font-size: var(--t-sm); color: var(--ink-3);
              font-variant-numeric: tabular-nums; }
.headextent b { font-weight: 600; color: var(--ink-2); }

@media (min-width: 52rem) {
  /* The kicker and the title on one line, baseline-aligned: two stacked
     labels above a tool is one label too many. */
  /* THE TITLE HAS A FLOOR AND THE EXTENT IS THE COLUMN THAT GIVES. A grid
     hands an `auto` track its max-content width before an `fr` track takes
     what is left, and the extent's max-content is the whole of "319
     destinations scored · 50 countries · 17 routes already built". Measured
     at 834, the width the tablet sheet is shot at: /plan's h1 had 127 pixels
     and set 43 characters on FIVE lines, /map two, /discover two — and all
     of them are one line at 704, where the head is still one column, and one
     line again by 1024. A head that is worse in the middle of its own range
     than at either end is a track sizing fault rather than a design.

     `.headextent` is a wrapping flex list, which is exactly the thing that
     should give: it wraps to two lines and the title keeps one. */
  .pagehead.instrument { grid-template-columns: auto minmax(20rem, 1fr) auto;
                         align-items: baseline; column-gap: var(--s4); }
  .pagehead.instrument .lede { grid-column: 1 / -1; }
  .headextent { justify-content: flex-end; text-align: right; }
}

/* THE EUROPEAN YEAR, AS ITS OWN SHAPE.
   The events family's signature moment. It replaced twelve identical chips
   on the index and a previous/whole-year/next row on each month page: every
   one the same width and the same weight whether the month held 3 fixtures
   or 28, which is a table of contents for a year and a year is the one thing
   that is not a list.

   TWO ELEMENTS, ON PURPOSE. The bars are an SVG with
   preserveAspectRatio="none" — twelve columns SHOULD fill whatever width
   they are given — and that transform scales everything in the viewBox,
   text included. With the month names inside it they rendered at 49% of
   their own width on a 390px screen: squashed type, on a phone, on thirteen
   pages, and font-size in CSS does not save it because the transform is
   applied after. The names are an HTML list instead, on a grid of the same
   twelve columns. The list is also the navigation: a 2px bar was never a
   reliable target, and the SVG is aria-hidden because it now says nothing
   the list does not.

   No aperture. The door is how this atlas draws GEOGRAPHY; a year is not a
   place, and twelve little arches would be the signature as wallpaper. */
.yearband { margin: var(--s5) 0 var(--s6); }
.yearband .ybars { display: block; width: 100%; height: 104px; }
/* THE CAPTION NAMES A LINE AND THE LINE MEASURED 1.16:1.
 *
 * "Above the line is what is on. Below it is how many countries are in
 * their quieter shoulder" — and the axis that sentence depends on was
 * --rule, sampled off the rendered pixels at rgb(231,230,223) against a
 * page of rgb(247,246,243). A reader had to infer the line from where the
 * bars stop. This is the ONE axis on the site and it is load-bearing prose,
 * not a hairline between two list rows, which is what --rule is for.
 *
 * And the two series measured 1.46 and 1.74 against the page and 1.19
 * against EACH OTHER. They are 150 degrees apart in hue, so a
 * colour-sighted reader tells them apart instantly — which is exactly the
 * trap docs/palette.json records in its `distinct` block: a contrast ratio
 * is a ratio of luminances, two hues at one lightness always measure about
 * 1.0, and every check here passed. Position carries them too (one is
 * above the axis and one below), so they do not need 3:1 from each other —
 * but a chart whose bars are 1.46 from the paper is a chart you have to
 * look for. */
/* 1.5, NOT 1. A one-pixel line in a viewBox scaled by
   preserveAspectRatio="none" does not land on a device pixel, so the
   painted pixel is a blend: --ink-2 at 1px sampled 2.59:1 against the page
   where the token itself is far darker. The ratio a reader gets is the
   ratio of the pixel, not of the declaration — which is the whole reason
   the contrast work on this site moved to sampling. */
.yearband .ybase { stroke: var(--ink-2); stroke-width: 1.5; }
.yearband .ybar { fill: color-mix(in srgb, var(--terracotta) 52%, transparent); }
.yearband .yshoulder { fill: color-mix(in srgb, var(--sea) 44%, transparent); }
/* The month you are on is the only one that gets the full accent. */
.yearband .ybar.on { fill: var(--terracotta); }
.yearband .yshoulder.on { fill: color-mix(in srgb, var(--sea) 78%, transparent); }

.yearband .ykeys { list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(12, 1fr); }
.yearband .ykey a { display: flex; flex-direction: column; align-items: center;
  gap: 1px; padding: var(--s2) 0; text-decoration: none; color: var(--ink-3); }
.yearband .ymon { font-size: var(--t-xs); letter-spacing: .08em; text-transform: uppercase; }
.yearband .ynum { font-size: var(--t-xs); font-variant-numeric: tabular-nums; }
.yearband .ykey a:hover .ymon,
.yearband .ykey a:hover .ynum,
.yearband .ykey a:focus-visible .ymon,
.yearband .ykey a:focus-visible .ynum { color: var(--door-dark); }
.yearband .ykey.on a { color: var(--door-dark); font-weight: 700; }
.yearband .whyall { margin-top: var(--s3); }
@media (max-width: 44rem) {
  /* 44rem, which is the phone breakpoint this stylesheet already has seven
     of — the first version of this block invented a seventh at 640px, in
     px, and the invariant register caught it in the same commit. Six
     breakpoints is six layouts somebody has to test in Chromium.

     Twelve columns at 390px are 32px each and at 360px they are 27.3.
     "SEP" at 12px with the letter-spacing gone is 27.2 wide, which is a
     tenth of a pixel of margin and therefore no margin at all — the next
     font fallback overflows it. Mixed case is 22.5, and reads better small
     besides, so the caps go on the phone and the count keeps its own line
     under the name. 12px is the smallest size in the scale and a
     thirteenth would be a new one.

     The measured limit, stated rather than waved at: at 320px the columns
     are 23.9 and "May" renders at 24.8, so one label in twelve is 0.9px
     wider than its own cell. It is centred in a centred cell whose
     neighbours have between 1 and 9px of slack, the labels do not touch,
     and the page does not scroll — measured at 320, 360 and 390. It is a
     tolerance, not a clean fit, and the next size down does not exist. */
  .yearband .ybars { height: 84px; }
  .yearband .ymon { letter-spacing: 0; text-transform: none; }
}

/* EUROPE AS AN ENGRAVED LINE, not a dark mass.
 *
 * Filled land at this zoom is almost the whole frame, so #1f252a on #0a0e10
 * rendered as black on black and the doorway opened onto nothing. Drawing
 * the frontier instead of filling the country turns the aperture into an
 * engraving — and on a European travel product the borders are not
 * decoration, they are the information: this is the valley where France,
 * Switzerland and Italy meet, and you can see that.
 */
/* non-scaling-stroke, or the frontier is a worm. The land is drawn inside a
 * scale(span) transform, so a 1.1-unit stroke rendered at five pixels and
 * the border of France looked like a river in flood. */
/* THE LAND IS PAINTED, AND FOR THE LIFE OF THE EMBEDDED MAP IT WAS NOT.
   `fill: none` made every minimap a set of frontier lines on one dark
   field, so Portugal's Atlantic edge meant nothing, Greece's islands were
   blue fragments rather than islands, and a country was a polygon floating
   in a UI tile. /map — the site's own big instrument — has painted its land
   since it was built, so the family was already inconsistent with the one
   drawing everybody sees.
   Four layers, which is what makes a map a map: the water is the ground of
   the opening, the land sits on it, the coast is the edge between them, and
   the subject is the only thing in the accent. */
/* LAND IS NEVER THE COLOUR OF WATER, on an instrument either. These maps
   drew land with `fill: none`, so the sea showed through it and a country
   was a near-black shape on a near-black ground separated only by a
   hairline. That is the failure the editorial rule names — black land on
   black water — and an instrument is exempt from the PAPER, not from being
   legible. `--map-land` against `--map-sea` is a measured 1.50 step. */
.minimap.arched .countries path { fill: var(--map-land);
                                  vector-effect: non-scaling-stroke; }
.minimap.arched .context path { fill: var(--map-context);
                                vector-effect: non-scaling-stroke; }
.minimap.arched .countries path { stroke: var(--map-coast); stroke-width: 1.1; }
/* AND THE MACRO MEMBERS ARE FILLED, WHICH TAKES BEATING THE RULE ABOVE.
   `.macromap .countries path.here` and `.minimap.arched .countries path` are
   both (0,3,1), and the second is further down the file, so a map of the
   Nordics rendered with the Nordics indistinguishable from the rest of
   Europe — while the caption said "filled". That is the second specificity
   collision in three commits that rendered as "the thing simply is not
   there": the touch targets painted because they LOST one, and this lost one
   too. Neither is visible in any count. */
/* THE MACRO SELECTOR IS GONE FROM THIS PAIR: a macro map is an editorial
   illustration and wears the atlas skin now, so its members are filled by
   `.lyr-land .countries path.here` and this half matched fifty elements and
   changed none of them. The country reference map is an INSTRUMENT and keeps
   its half. */
/* AND THE COUNTRY ON ITS OWN PAGE, which lost the same fight on all fifty.
   `.countrymap .countries path.here` sets a fill and a stroke and never wins
   anywhere: found by deleting every rule in this stylesheet in turn and
   seeing whether anything moved. Austria was drawn on Austria's page in
   exactly the same grey as Germany. */
.minimap.arched.countrymap .countries path.here {
  fill: var(--map-here); stroke: var(--map-hereline); stroke-width: 1.3;
  vector-effect: non-scaling-stroke; }
.minimap.arched .context path { stroke: color-mix(in srgb, var(--map-coast) 45%, transparent); stroke-width: 0.7; }


/* ── the Stay layer ───────────────────────────────────────────────────
   ACCOMMODATION AS AN EDITORIAL ANSWER, NOT AN OTA CARD GRID.

   The commercial purpose of this section is a referral, and the fastest way
   to build it is the way that destroys it: four cards with a photograph, a
   price and a star rating, which is somebody else's inventory pasted under
   our masthead and reads as an advertisement inserted into an article. This
   atlas holds no rooms, no prices, no availability and no ratings — see
   tools/lib/stay.py — so it has nothing to put in a card and says instead
   the two things it does know: where the ground puts you, and when the beds
   go.

   So the whole grammar here is rules and space. NO BORDER BOX, NO RADIUS, NO
   SHADOW, NO FILL: those four are what say "separate object, placed here by
   a system" and they are exactly the reading to avoid. The heavy rule over
   each reading is a broadsheet device — it groups without enclosing — and
   the action sits below a hairline rather than inside a panel, because a
   panel around a commercial link is a banner. */
.stayreads { display: grid; gap: var(--s6); }
.stayread { border-top: 2px solid var(--ink); padding-top: var(--s3); }
.stayread h3 { font-size: var(--t-md); margin: 0 0 var(--s3); line-height: 1.25; }
.stayread p { margin: 0 0 var(--s3); color: var(--ink-2); max-width: 46ch; }
.stayread p:last-child { margin-bottom: 0; }
/* Two readings, two columns, at the breakpoint the practical band already
   uses — a seventh breakpoint for a second instance of the same idea is a
   breakpoint that drifts from the first one. */
@media (min-width: 52rem) {
  .stayreads { grid-template-columns: 1fr 1fr; gap: var(--s7); }
}
/* THE ACTION AND THE BOUNDARY SIT SIDE BY SIDE, AND THE FIRST VERSION DID
   NOT. Button, who-holds-what and disclosure all stacked in the left half of
   a 1,168px band with six hundred pixels of white beside them — the same
   fault as the 280px card alone in its row on the old stories index, and
   found the same way, by rendering the page rather than reading it. The
   button takes the left track and the two sentences that say who you are
   about to deal with take the right, so the row is full and the boundary is
   beside the thing that crosses it rather than under it. */
/* THE COUNTRY PAGE'S HAND-OFF TAKES THIS GRAMMAR RATHER THAN INVENTING ONE.
   Fifty country pages had no action at all — not a button, not a save, and
   only the planner link every page carries in its masthead. What they needed
   is what the Stay layer already composes: a rule, the action beside the
   sentence that says what it will do. The first attempt used `.note.onward`
   and `c_note_tones` failed it at 62 pages, correctly — that panel wears the
   interactive colour and is deliberately rare. */
.staygo, .handoff { margin-top: var(--s7); padding-top: var(--s5);
          border-top: 1px solid var(--rule);
          display: grid; gap: var(--s5); align-items: start; }
.staygo p, .handoff p { margin: 0 0 var(--s3); }
.staygo p:last-child, .handoff p:last-child { margin-bottom: 0; }
@media (min-width: 52rem) {
  .staygo, .handoff { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--s7); }
}
/* The provider row. A comparison, not a menu: it renders only where two or
   more providers are enabled, because one provider is a button. */
.staylabel { display: block; text-transform: uppercase; letter-spacing: .06em;
             font-size: var(--t-xs); color: var(--ink-3); margin-bottom: var(--s1); }
/* WHO HOLDS WHAT, DIRECTLY UNDER THE BUTTON THAT LEAVES. The boundary
   between the discovery layer and the fulfilment layer is the one thing a
   reader must not have to infer, and inferring it from a logo is how people
   come to believe we took their money. */
.staywho { color: var(--ink-2); max-width: 54ch; }
.stayalso { color: var(--ink-3); }
/* The disclosure is quiet and it is never absent. Both states carry one:
   "we earn nothing from this" answers the reader's question as squarely as
   "we may earn a commission", and a disclosure that appears only once we are
   paid is an advertisement with a conscience. */
.staydisc { color: var(--ink-3); max-width: 54ch; }
.stay > .sourcenote { margin-top: var(--s6); }

/* ── the theme constellation ──────────────────────────────────────────
   THE ARGUMENT DRAWN, BECAUSE THE ROWS WERE A WALL OF GREY TEXT.

   This index went from a card grid to rows, which traded one default for
   another: a contact sheet of twelve families showed it as one of two cells
   that were simply text. What separates thirteen themes is REACH, and reach
   is a shape — Renaissance Europe is a knot over Italy and France, Thermal
   Europe is a line from Iceland to the Caucasus.

   NO APERTURE ON IT. Thirteen doors on one page is the signature as
   wallpaper, which the events band already refused for the same reason. This
   is a glyph, not a window: no arch, no ground, no frame, no label. */
.constel-defs { position: absolute; width: 0; height: 0; overflow: hidden; }
/* ONE ROW SHAPE FOR THE THREE INDEXES THAT DRAW THEIR SET. `.themeside` was
   a right-hand column holding the drawing and a count, and it is gone: with
   the drawing on the left and one text block beside it there is no second
   column to hold, and the count is a line of the text like the others.
   Deleted rather than kept as a synonym, which is what was done to
   `.sourced` for the same reason. */
/* No `display: block`: the dead-rule scan caught it as a declaration that
   matches and changes nothing, because an SVG in this position is already
   laid out as a block. */
/* 132 PIXELS FOR THE WHOLE OF EUROPE, ON THE PAGE WHOSE ARGUMENT IS THE
   SHAPE. /themes closes with "a knot is an argument about one corner of
   Europe, a scatter is one about the whole of it" — and at 132 pixels the
   continent is 132 pixels, so eight lit points across Italy are three pixels
   apart and a knot and a scatter are the same smudge. The row is 1,168 wide
   and 180 tall and the text beside it wraps at 62 characters; the drawing
   was sized to what was left rather than to what it has to say.
   204 on a row wide enough to hold it, and the 132 stays below 64rem, where
   the row stacks and the glyph is a thumbnail again. Nothing else moves: the
   row's height is set by the prose, not by this. */
.constel { width: 132px; height: auto; }
/* 204 PIXELS FOR THE WHOLE OF EUROPE, BESIDE FIVE HUNDRED PIXELS OF
   NOTHING. The row is 1,168 wide; the kicker, the name and the eight places
   end at 488, and the drawing sat in a 204-pixel box hard against the right
   edge with half a column of paper between them. That is the fault this
   repository has now named four times — the old stories index, the Stay
   action band, the ask panel, /themes itself when it was a card grid — and
   it was still here, on the page whose closing sentence is "a knot is an
   argument about one corner of Europe, a scatter is one about the whole of
   it". At 204 pixels the eight dots of Renaissance Europe are four pixels
   apart and a knot and a scatter are one smudge.
   The row's proportions are stated instead of inferred: the type column at
   the measure, which is what the type was using anyway, and the drawing
   takes what is left up to 22rem. Same glyph, same shared extent —
   thirteen themes are only comparable if every one is drawn on the same
   Europe — and what grows is the only quantity this drawing carries, the
   DISTANCE between the eight marks. Renaissance Europe's knot over Italy
   and France measures 69 pixels across where it measured 40.
   The space between the type and the drawing stays, and it is not the dead
   space this row was diagnosed with: every index on this site right-aligns
   its meta column, and a rule that spans both is what makes the two ends one
   row. What was wrong was the drawing's SIZE, not its position. */
/* A MARK SIZED FOR EIGHT STOPS, DRAWN TWO HUNDRED TIMES. The interests
   index reuses the theme row's constellation, and a theme holds eight
   places while History & ruins holds two hundred — at r=17 that is a solid
   field of blue with the coastline lost underneath it, which says "a lot"
   and nothing about shape. The whole argument of that page is that the
   seventeen shapes differ, so above sixty points the mark comes down and
   the fill opens up. */
.constel-dense .constel-lit circle { r: 11px; fill-opacity: .7; }
/* THE INTERESTS ROW IS NOT THE THEME ROW, and borrowing its proportions
   left six hundred pixels of nothing in the middle. A theme's left column
   holds eight place names and fills a whole measure; an interest's holds a
   name and one short line, so the same grid put the drawing hard against
   the right edge with a void between. Narrower drawing, closer, and the
   name at the size the row is worth — seventeen rows, one screen apart, and
   the name is what a reader is choosing between. */
.interestrow :is(h2, h3) { font-size: var(--t-lg); }

/* ── MY EUROPE DRAWS THE LIST IT IS ABOUT ──────────────────────────────
 * The empty case is the state this page SHIPS in — the list is empty until
 * a reader saves something — and it was a heading, a paragraph and three
 * bordered boxes on graphite, with no picture, on the one page whose
 * subject is a set of places. The continent is drawn with nothing on it,
 * and every saved place lights a mark: the honest shape of the emptiness
 * rather than an apology for it.
 *
 * NO APERTURE. This is a glyph of what you hold, not a window onto
 * somewhere — the same reason the theme rows and the year band refuse one,
 * and the reason this page is not a twenty-third arch in the same position. */
.minemap { margin: var(--s6) 0 var(--s7); }
/* 46rem WAS THE WHOLE SCREEN. Rendered at 1280 it was a 736-pixel continent
   filling the fold with nothing on it, which is a picture of the emptiness
   shouting about it. 30rem is a glyph you read in one look. */
.minemap .constel { display: block; width: min(30rem, 100%); }
/* AND IT CAME OUT PARCHMENT ON GRAPHITE. `.constel` defaults to
   --atlas-land, which is the PICTURE half of the cartography split, and
   this page is data-world="intelligence" — an instrument. Pictures are
   paper, instruments are graphite, and the split is on what the drawing IS
   rather than on where it sits. Same land tone /map and /discover use. */
.minemap .constel { fill: var(--map-land); }
.minemap .constel-lit circle { fill: var(--cobalt-air); }
/* AND THE DATA-CUT FADE CAME OUT --atlas-sea, WHICH IS THE PICTURE OCEAN.
   Every other drawing that carries this fade sits ON a sea panel, so the
   stop is the ground it is blending into. This glyph sits straight on the
   page, in the dark world, so #153851 painted a visible navy rectangle
   across the whole viewBox — the cartography split broken one commit after
   /discover was fixed for the same thing, in the code that fixed it.
   The ground here is the page, so the stop is the page's own paper. */
.minemap .datacut stop { stop-color: var(--paper); }
.minemap figcaption { margin-top: var(--s4); color: var(--ink-3);
                      font-size: var(--t-sm); line-height: 1.5; max-width: 46rem; }
#minecap { color: var(--ink-2); }
.rowart { margin: 0; }
.rowart .constel { width: min(15rem, 100%); }
/* THE GLYPH HAS THREE SIZES AND ONLY TWO WERE WRITTEN.
 *
 * There is a desk rule at 64rem and a phone rule at 44rem, and BETWEEN THEM
 * neither applied: at 834 both row families fell back to the drawing's
 * intrinsic size, a continent 132 pixels wide. That is the "204 pixels for
 * the whole of Europe" measurement this stylesheet records one family over,
 * at two thirds of it, on the width a reader is most likely to be comparing
 * things at.
 *
 * A PLAIN RULE AT THE SAME SPECIFICITY, IMMEDIATELY BEFORE THE DESK BLOCK,
 * rather than a seventh breakpoint. Two attempts failed first and both are
 * this file's recorded traps: a base rule LATER in the file beat the 64rem
 * block and took the desk glyph from 288 back to 240 without touching the
 * desk rule; and writing it as `.interestrow .rowart .constel` made it
 * (0,3,0), which beats the desk block's (0,2,0) at every width. So it is
 * (0,2,0), it is above the block that overrides it, and the invariant
 * register's breakpoint ceiling is what said to look for this at all —
 * every breakpoint is a layout somebody has to test in Chromium. */

@media (min-width: 64rem) {
  .themerow .constel-theme.constel-dense .constel-lit circle { r: 11px; }
  /* THREE HUNDRED AND THIRTY-SIX PIXELS OF NOTHING IN THE MIDDLE OF EVERY
     ROW. Measured at 1280: the text column is 544, the drawing is 288 and
     right-aligned inside a 608-pixel column, so a third of the row is a
     void between the eight place names and the shape they make. The whole
     argument of this index is that you compare the list with the shape, and
     they were as far apart as the page allows.

     The measure is for PROSE and this column holds a middot list of place
     names, which is a sequence you scan — the same distinction this
     stylesheet already records for `.rowsub`. So the text takes the room
     and the drawing sits next to it. */
  .themerow { grid-template-columns: 18rem minmax(0, 1fr); }
  /* Same void, different cause: an interest row's text is a name and one
     short line, so no amount of column gives it to. The drawing takes the
     column instead — 22rem rather than 15 — which also makes two hundred
     marks legible where 15rem made them a texture. */
  /* FIVE HUNDRED AND SEVENTY-ONE PIXELS, WHICH IS HALF THE ROW. The note
     above says "the drawing takes the column instead" and only half of it
     was applied: the column became 18rem and the drawing stayed pinned to
     the far edge of it, so a 288-pixel continent sat at x=880 with the text
     ink stopping at 350. Measured across every two-column row family at
     1280, as the distance from the last painted glyph in the text block to
     the left edge of the next column:

         /themes      17%   /countries   36%
         /stories     28%   /journeys    38%
         /europe-in   48%   /interests   49%

     What separates the top of that list from the bottom is the LENGTH OF
     THE TEXT, not the family: /themes carries a middot list of eight place
     names and fills its column; an interest row is a name and one short
     line, and a motion row is a name, a line and a query. No column gives
     that text more to say, so the answer is not a wider text column — it is
     to stop spending the whole row on one.

     Three columns: what it is, the shape it makes, how many. The drawing
     comes in beside the text where the two can be read together, and the
     count keeps the right edge, which is the two-edge convention /countries
     already reads as a table. The drawing does NOT grow: its width is
     capped by the page's own length, recorded below, and this costs no row
     height at all. */
  .interestrow, .motionrow { align-items: center; gap: var(--s6); }
  .rowart .constel { width: 18rem; }
  /* AND THE MARK HAD TO COME DOWN AS THE DRAWING WENT UP. A radius in
     viewBox units is a radius in pixels at exactly one width: 22 units is
     4.5 pixels at 204 and 10.5 at 480, so the first attempt at this drew
     eight blue discs the size of Switzerland and the continent under them
     disappeared. Same arithmetic as the map labels that do not resolve into
     glyphs on a phone, in the other direction. 17 units at 288 is the
     9-pixel dot the row has always had; what grows is the DISTANCE between
     them, which is the only thing this drawing is for.
     AND THE WIDTH IS CAPPED BY THE PAGE'S OWN LENGTH RATHER THAN BY TASTE.
     The viewBox is 1000x780, so every pixel of width costs 0.78 of row
     height thirteen times over: at 22rem the rows were 351 tall and the page
     ran to 5,739 pixels, which is within fifty of the old stories index this
     repository threw away for being 5,792. 18rem holds the rows at 282. */
  .themerow .constel-theme .constel-lit circle { r: 17px; }
}
/* The continent is context and must not compete with the eight lit points:
   a flat wash at the weight of a rule, no stroke, no sea. */
/* EVERY PATH, BECAUSE THE CLASS I REACHED FOR IS NOT THE CLASS geo.landmass
   EMITS. It writes `context` and `atlas`; `.constel .countries path` matched
   nothing and the continent painted at its inherited fill — a near-black blob
   with the eight lit points invisible inside it. Fifth selector miss in this
   stylesheet that rendered as "the thing is simply not there", and the same
   lesson each time: target what the generator writes, not what you assume it
   writes. */
/* AND A SELECTOR CANNOT REACH INSIDE A <use>. The continent is cloned from
   <defs> into thirteen rows, and cloned content lives in a shadow tree that
   `.constel path` cannot match — so it painted at the SVG default, black, with
   the eight lit points invisible inside a blob. That is the <use> trap this
   repository already lost a day to on the hero, and the escape is the same
   one: PAINT BY INHERITANCE. `fill` on the container is inherited through the
   clone; the lit dots are real DOM and override it. */
/* THE GLYPHS ARE CARTOGRAPHY AND WERE NEVER GIVEN THE CARTOGRAPHY.
   The palette splits maps on what the drawing IS — a picture is warm paper,
   pale water and an ink coast; an instrument is graphite — and the split was
   applied to the plates and to nothing else. The glyph family, which is
   every theme row, every journey row, every story, every homepage tile,
   every region on the atlas index and all five index openings, kept a grey
   wash and the dark world's accent: an acid-green continent on near-black,
   which reads as a network diagram or a satellite overlay. It is the
   largest map family on the site by page count and it was the only one
   still outside the standard.
   Land is the atlas's own muted parchment, marks are the signature — the
   colour this palette reserves for things DRAWN rather than read — and a
   mark carries a hairline of the ground so it separates from whatever it
   sits on. Nothing here is a new colour. */
.constel { fill: var(--atlas-far); stroke: none; }
.constel-lit circle { fill: var(--cobalt); }
.themerow:hover .constel-lit circle { fill: var(--cobalt-deep); }
/* And the row is baseline-aligned, which is right for two columns of type and
   strands a 100px drawing at the top of its cell with the text beginning
   below it. The glyph is a figure, not a line of text. */
/* `.themerow .rowmeta` is already declared with the count column, 2,800
   lines up. Restating it here was a fully redundant rule and the dead-rule
   scan said so within one run — which is the argument for that scan: two
   commits to the same family, months apart in reading order, and neither
   knew about the other. */
.themerow, .storyrow { align-items: center; }
/* .storyrow is here rather than in a rule of its own because it is the SAME
   fault: a row is baseline-aligned, which is right for two columns of type
   and strands a drawing at the top of its cell with the text starting below
   it. Third family to hit it; one declaration, not three. */
/* A DRAWING HIDDEN ON A PHONE, AND THE EXCEPTION LIST GREW FOUR TIMES
   BEFORE THE RULE WAS MEASURED.

   `.constel:not(.regionglyph) { display: none }` below 44rem was written
   when a constellation was a 132-pixel mark inside a desk row: at that size
   eight stops are four pixels apart and there is nothing to see. Every
   family that afterwards gave its drawing the WIDTH on a phone had to be
   excepted by name — the journey rows, a story's places, /themes, then
   /interests and /europe-in when their wrapper was renamed — and each time
   the drawing had been invisible for at least one build.

   Measured in Chromium at 390 by reading the computed display of every
   `svg.constel` on ten surfaces:

       /europe/austria           3 region minis    HIDDEN
       /discover/adriatic-…      9 country glyphs  HIDDEN
       /journeys                17 route drawings  HIDDEN

   That is 129 region cards across the fifty country pages, 50 country cards
   across the nine macro pages and every row on the journeys index — about
   196 drawings, each leaving a 279-pixel panel of flat sea with nothing in
   it, because `.card-map` paints the same ocean the drawing sits on. A hole
   that is the colour of the picture is a hole nothing counts.

   And with those three shown, NOTHING IS LEFT HIDDEN: every constellation on
   the site is either in a card that is full width on a phone or in a row
   that stacks to full width. A rule with no remaining user is dead code that
   looks like a decision, so it is deleted rather than given a fifth
   exception. The page length does not move by a pixel — the panels were
   already there and already that size.

   `display: block` stays, on every one of them: an `<svg>` is inline by
   default and sits on a text baseline, which is the third fault this
   stylesheet records about drawings in rows. Stated ONCE here — three
   families were each restating it (`.quietart`, `.rowart`, `.iheroart`) and
   the dead-rule scan reported all three within a run of this moving, which
   is what that scan is for: a declaration that matches its element and
   changes nothing reads as a decision and is not one. */
.constel { display: block; }

@media (max-width: 44rem) {
  /* min(22rem) rather than 100%: this block runs to 44rem, so a full-width
     glyph is 568 pixels tall at 600 and the themes page ran to 9,684 — the
     same fault the interests rows were fixed for, on the family they were
     copied from. At 390 the cap is the width anyway. */
  .rowart .constel { width: min(22rem, 100%); }
  .themerow .constel-theme .constel-lit circle { r: 14px; }
  .themerow .constel-theme.constel-dense .constel-lit circle { r: 7px; }
}

/* ── two axes over one set, drawn as what separates them ──────────────
   /experiences was eighteen identical bordered boxes in two grids of four.
   Border, fill, radius and shadow each say "separate object", and spending
   all four on every tile of an eighteen-tile page spends them on nothing —
   the grid read as one texture, and the thing that actually tells these rows
   apart, that Family holds 131 entries and Luxury holds 5, was four
   characters of kicker type.

   The count is the subject, so the count is drawn, with the bar and the
   figure on the same line at the end of the row. No box: a rule between rows
   is what separates them, exactly as on /themes and /journeys. */
.barrow .barside { display: flex; flex-direction: column; align-items: flex-end;
                   gap: 0; flex: none; width: 168px; }
.barrow .barside .hopbar { width: 100%; margin: var(--s1) 0 0; }
.barrow { align-items: center; }

/* ── the atlas index shows where each region IS ───────────────────────
   /countries was the page about Europe with no Europe on it: nine headings
   and fifty rows of name, tagline and a count. Measured on the built page the
   median horizontal band used 44% of the column — and the point is not the
   empty space, it is that a reader picking a region of Europe was never shown
   one.

   COUNTRIES ARE LIT HERE, NOT DESTINATIONS. The homepage tiles and /themes
   already light dots on this silhouette; a third family drawing the same
   picture is the signature becoming wallpaper. This family's subject is
   countries and a macro region is the one grouping with real polygons behind
   it, so whole countries are what is filled.

   The head and the drawing sit side by side because the drawing is the
   band's subject rather than an illustration after it — the same reason the
   Stay layer puts its action and its boundary on one line. */
.bandtop { display: flex; align-items: center; gap: var(--s6);
           margin-bottom: var(--s5); }
.bandtop .band-head { flex: 1 1 auto; min-width: 0; margin-bottom: 0; }
.bandart { flex: 0 0 auto; width: 400px; margin: 0; }
.bandart .constel { width: 100%; }
/* The members are real DOM rather than cloned content, so a selector reaches
   them — it is the SHARED continent underneath that lives in a shadow tree
   and is painted by inheritance. Both halves of the <use> trap in four
   lines. */
.regionglyph .countries path { fill: var(--cobalt); }
.macroband:hover .regionglyph .countries path { fill: var(--cobalt-deep); }
@media (max-width: 60rem) {
  /* Below the two-column measure the drawing goes under the head at its own
     proportion rather than being squeezed beside it — the same rule the hero
     takes at 52rem, for the same reason. */
  .bandtop { display: block; }
  .bandart { width: min(100%, 380px); margin-top: var(--s4); }
}

/* ── the homepage tiles draw their own destinations ───────────────────
   ELEVEN ABSTRACT PLATES IN A COLUMN IS PLACEHOLDER ART DOING A PICTURE'S
   JOB. The hero is strong and everything under it was a search form, eight
   purple gradients, three more, a grey note and three hundred pixels of
   nothing — a SaaS body under an atlas hero. The plate system was already
   measured as unable to carry a hero; it cannot carry this either.

   What replaced them is not a better painting, it is the data: every
   destination carrying that tag, lit on one shared silhouette. The count on
   the tile is the number of dots on it. */
/* A TILE IS A PICTURE, SO IT IS THE ATLAS'S GROUND AND NOT THE PAGE'S.
   `--paper-2` is a card surface, which means the tile was limestone on a
   DISCOVER page and graphite on /discover — the same drawing on two grounds,
   and on the dark one an acid continent floating in near-black. The
   cartography is world-independent by design: paper, ink, blue, wherever it
   is embedded. */
/* A region's marks are bigger than a 200-destination tile's, because a
   region holds two or three and the tile holds two hundred. */
/* Only the radius: the stroke is already `--atlas-here` at 1.6 from the rule
   above, and restating it is a declaration that changes nothing — which the
   dead-rule scan says out loud. */
.card-map .regionmini .constel-lit circle { r: 11px; }
/* A MAP CARD IS NOT 16:9, AND THE LETTERBOX WAS INVISIBLE BECAUSE IT WAS THE
   SAME SEA. `.card-art` is 16/9 because that is a PHOTOGRAPH's proportion;
   every drawing that has ever been put inside one is 1.282, because
   `constellation()` fits every frame to 1000x780 — 129 region minis, 44
   country glyphs and the nine region tiles, all of them. So the drawing fits
   to the box's HEIGHT and a third of its width is padding: on /discover at
   1280 the tile is 345 wide and the continent is 231 of it, and at 834 the
   whole of Europe renders 138 pixels wide, two-thirds of the 204 that
   `/themes` already measured as too small. It survived because `.card-map`
   paints `--atlas-sea` and so does the drawing's own ocean, so the wasted
   third reads as more sea rather than as a frame that does not fit. The
   padding keeps the drawing off the card edge and is inside the ratio. */
.card-map { background: var(--atlas-sea); display: flex; align-items: center;
            justify-content: center; padding: var(--s4);
            aspect-ratio: 1000 / 780; }
.card-map .constel { fill: var(--atlas-land); }
.card-map .regionglyph .countries path { fill: var(--atlas-here);
  stroke: var(--atlas-coast); stroke-width: 1.1; vector-effect: non-scaling-stroke; }
.card:hover .card-map .regionglyph .countries path { fill: #fff; }
.card-map .constel-lit circle { fill: var(--cobalt);
  stroke: var(--atlas-here); stroke-width: 1.6; }
/* FIT, DO NOT CROP. `height: auto` on a 4:3 drawing inside a 16:9 box makes
   the drawing taller than the box, and `overflow: hidden` takes the
   difference off the top and bottom — so a country framed on its own extent
   had its north cut off by the card. The letterbox costs nothing here
   because the box is already painted in the sea the drawing sits on. */
.card-map .constel { width: 100%; height: 100%; }
/* A COUNTRY CARD'S PICTURE IS THE COUNTRY. Five Nordic cards drew five
   pictures of Europe differing only in which shape was white — see
   country_glyph(). No context here at all: the macro map at the top of the
   same page answers "where in Europe" once, at full size, and a card that
   repeats it is the signature as wallpaper in a second drawing. */
.card-map .countryglyph .countries path { fill: var(--atlas-here);
  stroke: var(--atlas-coast); stroke-width: 1.1; vector-effect: non-scaling-stroke; }
.card:hover .card-map .countryglyph .countries path { fill: #fff; }
/* Bigger tiles, more dots, smaller marks: a 200-destination tile at 380px
   with the theme index's radius would be a solid field. Set here rather than
   per circle, so 894 dots cost four characters each rather than eleven. */
.card-map .constel-lit circle { r: 7px; }
.constel-theme .constel-lit circle { r: 22px; }
/* A CASING, THEN A CORE — see `pages.route_line`. No single colour clears
   3:1 on both cream land and the deep Atlantic; the arithmetic has no
   solution, so the answer is the one every printed map uses. The casing is
   the land's own light tone, wider, opaque, and it is what separates the
   line from the ground and from the sixteen routes crossing it. */
.constel-route { fill: none; stroke: var(--cobalt); stroke-width: 5;
                 stroke-linejoin: round; stroke-linecap: round; }
.constel-route.case { stroke: var(--atlas-here); stroke-width: 10; opacity: .92; }


/* EVERY TYPE VALUE IN THIS SECTION IS ONE THE SITE ALREADY HAD. The first
 * version introduced four — a 2xl-to-3xl clamp, a bare 2xl, and two leadings
 * — and the invariant register refused all four. It is right to: the design
 * audit's central finding is that eleven of twelve families place an
 * identically sized h1 in an identical position, and no size that already
 * existed could fix that. A door is not that argument. A role that needs a
 * new scale value is a decoration, and this one reads at the sizes the site
 * already publishes.
 */

/* NOTE: this section is `.way`, not `.door`, and the first version was
 * `.door`. The Brand Bible's four doors on /how-it-works already own
 * `.doors`, `.door` and `.doorgo` — and that component reveals its arrow
 * on hover, so `.doorgo { opacity: 0 }` matched the homepage's four
 * "Explore →" links and every one of them rendered present, labelled,
 * cobalt, keyboard-reachable and INVISIBLE. Measured rather than guessed:
 * the element was 133x33 at the right end of its row with opacity 0.
 * Third component in this stylesheet to lose a fight it did not know it
 * was in, and the first to lose it by NAME rather than by specificity.
 */

/* ── THE HOMEPAGE ENTRANCE ────────────────────────────────────────────
 *
 * Eight tiles became four doors, and a door carries no map.
 *
 * The tile version drew every destination with a tag lit on the shared
 * silhouette, and the reasoning was sound: the count on the tile was the
 * number of dots on it. Rendered, it said something else — eight beige
 * Europes with blue dots above three more on the journeys, eleven maps
 * before a reader has experienced anything. Repetition turned the signature
 * into background noise, and the labels were too small to read besides. It
 * is this stylesheet's own rule about the aperture arriving through another
 * door: a signature applied to everything is wallpaper.
 *
 * So the section is TYPE-LED with a photograph slot, and the three sections
 * under the hero each carry a DIFFERENT visual language — a door has none, a
 * journey has its route, a story has its own scatter of places. Four
 * adjacent cards wearing the same picture of Europe is the thing this
 * replaced.
 *
 * FOUR THINGS DELIBERATELY ABSENT, as on the Stay layer: no border box, no
 * radius, no shadow, no fill. Those four are what say "separate object,
 * placed here by a system", and this is meant to read as a page rather than
 * as a component library. A rule and space do the separating. */
/* ── the ways in are a full-bleed strip, and each panel is a photograph ──
   THE BAND WAS FOUR BLOCKS OF TYPE IN THE PAGE COLUMN. The doors were
   written for a photograph and the register is empty, so the one part of
   this page whose job is to make somebody want to go somewhere was doing it
   entirely in words. Two answers were tried and rejected before this one: a
   lead door with its own constellation, which put a fifth picture of Europe
   on a page that opens on one; and a single photograph across the whole
   band, which cannot be art-directed, because the four names sit over it and
   a summit captions Mountains while contradicting the other three.

   One photograph per door, in one strip that runs edge to edge like the
   hero. Every picture then answers the words on top of it, the band still
   reads as one dominant element, and the four slots can fill ONE AT A TIME:
   the strip is composed at every step, where a single band image is all or
   nothing.

   The panel's ground is the atlas's own water, so the empty state is a
   composition rather than a hole, and the tokens are rebound INSIDE the
   panel — the same move the cobalt masthead makes — so every .way rule
   below works unchanged on a dark ground instead of being rewritten. On
   #123f55 limestone measures 10.40:1 and its 78% tint 7.02:1. */
.wayin {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0;
  margin: var(--s6) calc(50% - 50vw) 0;
}
.way {
  position: relative; isolation: isolate; overflow: hidden;
  display: flex; align-items: flex-end;
  /* SIZED FOR WHAT IS IN IT, WHICH TODAY IS TYPE. 58vh is a photograph's
     height, and the register holds none — so measured on the built page each
     panel was about 300 pixels of flat teal above 230 pixels of words, four
     across, on the homepage's second screen. A slot waiting for a picture is
     honest; three hundred pixels of it is a hole.
     The full height comes back with the photograph, on `.shot`, so the strip
     composes at every step rather than only when all four are licensed —
     which is the argument for four slots rather than one band image. */
  /* NO FLOOR WITHOUT A PICTURE. This said "SIZED FOR WHAT IS IN IT, WHICH
     TODAY IS TYPE" over a fixed 22rem, which is not that — it halved the
     hole rather than closing it. RENDERED and measured: at 1440 each panel
     is 352px holding about 180px of words, so 170 pixels of flat teal sit
     above every heading; at 834 the 2x2 reserves 24rem a panel and the band
     is more than half empty; at 390 the strip runs 1,184px of which roughly
     six hundred are colour. A slot waiting for a picture is honest and this
     was four of them, on the homepage's second screen, reading as images
     that failed to load.

     The floor lives on `.shot` alone now, at every breakpoint. With no
     photograph a door is exactly its type in an edge-to-edge dark strip —
     which is a decision, and the only dark band in the body, and therefore
     the dominant element the section needs. */
  padding: var(--s7) var(--s5) var(--s6);
  background: var(--atlas-sea);
  text-decoration: none;
  --ink:   var(--limestone);
  --ink-2: color-mix(in srgb, var(--limestone) 90%, transparent);
  --ink-3: color-mix(in srgb, var(--limestone) 78%, transparent);
  --rule:  color-mix(in srgb, var(--limestone) 34%, transparent);
  --door:  var(--limestone);
  color: var(--ink);
}
/* A HAIRLINE, NOT A GAP. A gutter between four photographs makes four cards
   of them; a hairline makes one strip cut into four. A BORDER rather than an
   inset shadow: the register counts box-shadow declarations and two is the
   whole vocabulary, a rest state and a lifted one. A divider is not a
   shadow, and writing it as one spends a slot that means something else. */
/* "THE STRIP IS COMPOSED AT EVERY STEP RATHER THAN ONLY WHEN ALL FOUR ARE
   LICENSED" WAS THE ARGUMENT FOR FOUR SLOTS, AND IT WAS NOT TRUE.
   Rendered with one, two and three stand-in photographs injected into the
   live page — `img-src 'self' data:` allows a data URI, so the state can be
   seen without touching the register — the strip jumped from 304px to 522
   the moment the FIRST picture landed, and the three unfilled doors became
   522px tall with two hundred pixels of flat teal above their type. That is
   the hole this band was just emptied of, reintroduced by one acquisition,
   and worse than before because an empty door now sits beside a filled one.

   So the tall height is conditional on the strip being COMPLETE, not on any
   door being filled: `:not(:has(.way:not(.shot)))` is "no door lacks a
   photograph". Until then a filled door shows its picture at the type's own
   height, which is 1.31 at 1280 and 1.79 at 390 — both inside the container
   range data/image-purposes.json declares, so the crop contract holds at
   every step too. */
.wayin:not(:has(.way:not(.shot))) .way { min-height: min(58vh, 33rem); }
.way + .way { border-left: 1px solid color-mix(in srgb, var(--limestone) 20%, transparent); }
/* FOUR DOORS, FOUR DIFFERENT HEADING HEIGHTS. The panels are bottom-aligned
   so the count and the arrow line up, and the four taglines wrap to one line
   or two — so measured on the built page the headings sat at y=140, 117, 174
   and 174. Four across, on the homepage's second screen, and the one thing a
   reader compares is the four names.
   Subgrid, so the four ROWS are shared: name, tagline, where, meta. Each
   panel still sizes to its own content and the strip still bottom-aligns;
   what changes is that the four rows are one set of rows rather than four.
   Chromium has had subgrid since 117 and CI runs 131. */
.waytext { position: relative; z-index: 2; width: 100%; }
@media (min-width: 64rem) {
  /* The 1fr row is the slot. `min-height` on the PANEL cannot survive
     subgrid — a grid item spanning rows is as tall as those rows, and the
     rows are sized by their content — so the floor moves to the container
     and a flexible first row absorbs the slack. That matters for a code
     path nothing exercises yet: `.way.shot` asks for 58vh the day a
     photograph lands, and without this the picture would have been a
     281-pixel band. */
  .wayin { grid-template-rows: auto auto auto auto auto; }
  /* COMPLETE, not "any". The floor lives on the container because subgrid
     cannot take a min-height on the item — and it has to ask the same
     question the item's rule asks, or the strip grows to photograph height
     on the first acquisition and the three unfilled doors become holes
     again. Rendered with one, two and three stand-ins: 304px to 522 the
     moment one picture landed. */
  .wayin:not(:has(.way:not(.shot))) { min-height: min(58vh, 33rem);
                          grid-template-rows: 1fr auto auto auto auto; }
  .way { grid-row: 1 / -1; display: grid; grid-template-rows: subgrid;
         min-height: 0; }
  .waytext { display: grid; grid-row: 2 / -1; grid-template-rows: subgrid; }
}
.way picture { position: absolute; inset: 0; z-index: 0; display: block; }
.way .photo {
  width: 100%; height: 100%; object-fit: cover;
  transition: transform .5s cubic-bezier(.2, .6, .2, 1);
}
/* THE SCRIM IS ONLY ON THE PHOTOGRAPH. Over the flat water it would darken a
   ground that already carries the type at 10:1 and add nothing.

   FIVE STOPS, NOT TWO, and it is the same finding the hero's fades already
   record: a two-stop ramp has a crease at each end and the eye draws a line
   along it — a Mach band, reported three times as "a hard edge" while
   widening the ramp only moved the crease. It has to reach full strength
   BEFORE the type starts, because the guarantee here is that limestone
   clears AA over any photograph a person picks, not over the ones that
   happen to be dark at the bottom. */
.way.shot::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(to top,
    rgba(9, 22, 30, .94) 0%,
    rgba(9, 22, 30, .92) 26%,
    rgba(9, 22, 30, .74) 44%,
    rgba(9, 22, 30, .40) 64%,
    rgba(9, 22, 30, .16) 82%,
    rgba(9, 22, 30, .06) 100%);
  transition: opacity .4s ease;
}
/* A PANEL IS A LINK AND HAS TO SAY SO. `.way:hover h3 { color: var(--door) }`
   is inherited from the type-led version and --door is limestone in here, so
   the heading changed to the colour it already was: a hover state that
   changes nothing, which is the dead-rule failure this stylesheet has now
   recorded four times. The picture moves instead, which is what a
   photographic panel has to offer, and the scrim eases off it. */
.way:hover .photo, .way:focus-visible .photo { transform: scale(1.045); }
.way:hover.shot::after, .way:focus-visible.shot::after { opacity: .88; }
.way:focus-visible { outline: 2px solid var(--limestone); outline-offset: -4px; }
@media (prefers-reduced-motion: reduce) {
  .way .photo, .way.shot::after { transition: none; }
  .way:hover .photo, .way:focus-visible .photo { transform: none; }
}
@media (max-width: 60rem) {
  .wayin { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .wayin:not(:has(.way:not(.shot))) .way { min-height: min(44vh, 24rem); }
}
/* ONE COLUMN AT 52rem, NOT 40, BECAUSE A SECOND RULE HAD ALREADY TAKEN IT.
   A `.wayin { grid-template-columns: 1fr; gap: var(--s6) }` sat three
   hundred lines further down in the 52rem block and won on order — so
   between 640 and 832 the strip was one column with 32px gutters between
   the panels, which is four cards of what this band exists not to be. The
   rule twelve lines above says it in words: "A HAIRLINE, NOT A GAP. A
   gutter between four photographs makes four cards of them; a hairline
   makes one strip cut into four." Third time a duplicate declaration a
   screen away from its twin has quietly reversed a decision here. */
@media (max-width: 52rem) {
  .wayin { grid-template-columns: 1fr; }
  .way { padding: var(--s6) var(--s4); }
  /* 17rem IS A PHONE'S HEIGHT AND THIS BLOCK RUNS TO 832. A single-column
     door at 760 wide and 272 tall is 2.794:1 — a letterbox — and the
     container check caught it the run after the strip moved to one column
     here. That matters for a photograph nobody has licensed yet and the
     check is why it is not a surprise later: with the source range this
     purpose declares, a 2.794 box leaves 10% of a frame guaranteed
     visible, under the 12% floor. 52vw takes over the moment the panel is
     wider than it is tall. */
  .wayin:not(:has(.way:not(.shot))) .way { min-height: max(17rem, 52vw); }
  /* The divider stacks with the panels: horizontal, or the strip comes
     apart into four blocks that happen to share a colour. */
  .way + .way { border-left: 0;
                border-top: 1px solid color-mix(in srgb, var(--limestone) 20%, transparent); }
}
/* A PANEL HEADING, NOT A PAGE HEADLINE. The doors were half-page blocks and
   carried a 40px serif; four panels of a strip are 320 pixels wide at 1280
   and "Coast & islands" broke across three lines in one. `--t-xl` is already
   in the scale: a size that exists only for this band is a decoration. */
.way h3 {
  /* UP ONE STEP, BECAUSE THE TYPE IS NOW THE WHOLE DOOR. --t-xl was set
     when the name was a caption over a photograph; with no picture it is
     the thing itself.

     AND IT IS A CLAMP THAT ALREADY EXISTS RATHER THAN A NEW DECLARATION.
     `var(--t-2xl)` bare was the sixteenth distinct font-size in this file
     and `line-height: 1.1` the fifth leading, and the invariant register
     caught both in one run — a ceiling is a ceiling. The clamp is the same
     one five other headings use: 30px on a desktop where a door is a
     quarter of the strip, 24 on a phone where it is the whole width, which
     is the behaviour that was wanted anyway. */
  font-size: clamp(var(--t-xl), 3vw, var(--t-2xl));
  line-height: 1.12; margin: 0 0 var(--s3); letter-spacing: -0.02em;
}
/* And it goes back DOWN when the strip is complete: over a picture the name
   is a caption again, and 30px of serif over somebody's landscape is the
   thing every stock-photo travel site does.

   ON THE STRIP, NOT THE DOOR. Per-door it produced two heading sizes side
   by side the moment one photograph landed — measured in the partial render
   above: "Mountains" at --t-xl beside "Coast & islands" at --t-2xl, with
   nothing to explain the difference but which slot happened to be filled
   first. Four names a reader compares have one size. */
.wayin:not(:has(.way:not(.shot))) .way h3 { font-size: var(--t-xl); letter-spacing: 0; }
/* The editorial line is the promise and the only italic on the page: it is
 * the sentence a reader answers, where everything under it is fact. */
.wayline { font-family: var(--display); font-style: italic; font-size: var(--t-lg);
            color: var(--ink-2); margin: 0 0 var(--s3); }
/* THE PLACES, NOT A SECOND TAGLINE. `.waywhere` held "From the Alps to the
   Caucasus" under "A morning above the clouds" — two lines orienting where
   nothing was orienting once — and the rule it replaces is the same rule
   with a different name: a list of four names is scanned, not read, so it
   is set at the caption size and the middots carry the rhythm. */
.wayplaces { color: var(--ink-3); margin: 0 0 var(--s4); max-width: 34ch;
             font-size: var(--t-sm); line-height: 1.5; }
.waymeta {
  display: flex; justify-content: space-between; align-items: baseline; gap: var(--s4);
  font-size: var(--t-sm); letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-3); border-top: 1px solid var(--rule); padding-top: var(--s3); margin: 0;
}
.waygo { color: var(--door); font-weight: 600; white-space: nowrap; }
.way:hover h3, .way:focus-visible h3 { color: var(--door); }
/* And when a photograph is licensed it becomes the dominant element, which
 * is the whole point of the slot: cartography orients and photography
 * persuades. `.shot` is added only when the register actually holds the
 * file — the same test the hero makes, for the same reason. */
.way picture { display: block; aspect-ratio: 3 / 2; overflow: hidden; margin-bottom: var(--s4); }
.way .photo { width: 100%; height: 100%; object-fit: cover; }

/* A journey is a row, because a journey is chosen on where it goes in order.
 * The route drawing sits BESIDE the text as the explanation rather than
 * standing in for it — three small cards each carrying a continent-wide
 * squiggle was three more maps in the eleven. */
.jrows { border-top: 1px solid var(--rule); }
/* 13rem drew the whole continent 132 pixels wide, which is the tiny-label
 * failure this page was rebuilt to remove, one section down. A route is
 * the explanation of a journey and it has to be readable to explain
 * anything: at 22rem the Arctic-to-Mediterranean line is a line. */
.jrow {
  display: grid; grid-template-columns: minmax(0, 1fr) 22rem; gap: var(--s6);
  align-items: center; padding: var(--s6) 0; border-bottom: 1px solid var(--rule);
  text-decoration: none; color: inherit;
}
.jrow h3 { font-size: clamp(var(--t-xl), 3vw, var(--t-2xl)); line-height: 1.12;
           margin: 0 0 var(--s2); }
.jrowsub { color: var(--ink-3); margin: 0 0 var(--s4); max-width: 52ch; }
.jrowmeta {
  display: flex; justify-content: space-between; align-items: baseline; gap: var(--s4);
  font-size: var(--t-sm); letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-3); margin: 0;
}
/* .constel is a 132px glyph — the size it is on a theme row, where it is a
 * mark rather than a drawing. A journey's route has to be READ, so this
 * one takes the column it was given. Same SVG, same viewBox, different
 * job: the failure one section up was eight of these at glyph size being
 * asked to carry a category. */
.jrowart { align-self: center; }
.jrowart .constel { width: 100%; }
.jrowart .constel-lit circle { r: 9px; }
.jrowart .constel-route { stroke-width: 7; }
.jrow:hover h3, .jrow:focus-visible h3 { color: var(--door); }
/* A LEAD JOURNEY AT SIZE WAS TRIED AND MEASURED OUT. Three identical rows
   give the band three medium elements and no dominant one, which is the
   shape every section under the hero has — so the first of the three, which
   crosses seven countries from 69°N to 38°N, was given a 34rem column.
   Rendered, that is the fault it was meant to fix: a route is FRAMED on its
   own extent, and the Arctic-to-Mediterranean extent is tall, so a wider
   column makes a 490-pixel picture beside 140 pixels of type and three
   hundred pixels of empty row under it. The thing that makes each of these
   legible — its own frame — is what stops one of them being the wide
   element. Three rows, kept. */

/* Stories: one lead with its own places drawn at size, two beside it. */
.storyband { display: grid; grid-template-columns: 1.4fr 1fr; gap: var(--s6);
             align-items: start;
             border-top: 1px solid var(--rule); padding-top: var(--s6); }
.storylead, .storysm { display: block; text-decoration: none; }
.storylead :is(h2, h3) { font-size: clamp(var(--t-xl), 3vw, var(--t-2xl)); line-height: 1.12;
                margin: 0 0 var(--s3); }
/* The lead story's own places, at the size the journeys' routes are:
 * this is the section's one dominant visual, and at 132px it was a
 * grey smudge losing to two headlines beside it. */
.storyart { margin-bottom: var(--s4); }
/* 26rem was too far the other way: a pale continent with six dots on it,
 * mostly empty, losing to two headlines beside it. 18rem is a MARK — the
 * shape of where the piece is set — which is the job, and the journeys'
 * routes stay the largest drawing on the page because a route is the one
 * thing here that has to be read rather than recognised. */
/* THE LEAD STORY'S PICTURE IS THE LEAD'S. 18rem was set when this drawing
   was one of nine in a column; it is one of one now, at the top of a band,
   and held to 288px in a 440px column it left a third of the row empty
   beside it — the same measured gap that rebuilt this index. */
/* AND THE LEAD'S PICTURE WAS STACKED ABOVE ITS TYPE IN A COLUMN TWICE ITS
   WIDTH. Measured on the built homepage: `.storyart` is 663 pixels wide, the
   drawing inside it caps at 26rem — 416 — and the headline starts 332 pixels
   below the top of the band. So the lead story, the one piece this section
   exists to lead with, began a third of a screen lower than the two
   secondary headlines beside it, with 247 pixels of empty paper to the right
   of its own picture.
   The stories index already answered this one row over: `.storyleadwide` is
   a drawing and its type SIDE BY SIDE. Same composition here, at the width
   this column has — which also puts all three headlines on one line, which
   is what makes a lead read as a lead. */
.storyart .constel { width: 100%; max-width: 26rem; }
@media (min-width: 64rem) {
  .storylead { display: grid; grid-template-columns: 15rem minmax(0, 1fr);
               gap: 0 var(--s5); align-items: start;
               /* min-content, or the four type rows share the drawing's
                  height between them and the standfirst floats away from
                  the headline it belongs to. */
               grid-auto-rows: min-content; }
  .storyart { margin-bottom: 0; grid-row: 1 / span 4; }
  .storyart .constel { max-width: none; }
}
.storyart .constel-lit circle { r: 14px; }
.storysm { padding-bottom: var(--s5); border-bottom: 1px solid var(--rule); margin-bottom: var(--s5); }
.storysm:last-child { border-bottom: 0; margin-bottom: 0; padding-bottom: 0; }
.storysm :is(h2, h3) { margin: 0 0 var(--s2); }
.storylead:hover :is(h2, h3), .storysm:hover :is(h2, h3),
.storylead:focus-visible :is(h2, h3), .storysm:focus-visible :is(h2, h3) { color: var(--door); }

/* THE CLOSING STATEMENT REPLACED A ROW OF COUNTS. "319 destinations · 17
 * journeys · 50 countries" is the database introducing itself, and it was
 * the last thing on the page — so the reader left having been told the size
 * of the index rather than what it is for. The counts are still here, in the
 * quiet line, because they are true and checkable; they are no longer the
 * argument. */
/* AND THE SECOND VERSION WAS 44rem OF CENTRED PROSE OVER A DEAD GAP. Rendered
 * at 1280 the band was a headline, two centred lines, a button, then a long
 * centred technical note and about 150 pixels of nothing before the footer —
 * the last thing a reader sees on the page that matters most, and the one
 * shape on the homepage that says "the content ran out". Centring is what a
 * page does when it has not decided where the emphasis is.
 *
 * The statement is a statement now: left, at display size, on the measure
 * prose actually wants. The counts come out of the quiet line and become the
 * one thing on the page that is allowed to be a number — the atlas's EXTENT,
 * beside the argument rather than under it, so the band has two things in it
 * and no gap to fill. They are still derived and still checkable. */
.closing { margin: var(--s9) 0 0; border-top: 1px solid var(--rule);
           padding-top: var(--s7); }
.closein { display: grid; grid-template-columns: minmax(0, 1fr) auto;
           gap: var(--s7) var(--s8); align-items: start; }
.closesay { max-width: 32rem; }
.closing h2 {
              margin: 0 0 var(--s4); }
.closebody { color: var(--ink-2); font-size: var(--t-md); margin: 0 0 var(--s5); }
.closego { margin: 0; }

/* The extent, as a definition list, because that is what it is: three terms
 * and the size of each. Tabular figures so the three numbers line up on their
 * own right edge — they are read as a column, not as three sentences. */
.closeextent { display: grid; gap: var(--s4); margin: 0; text-align: right; }
.closeextent div { display: grid; gap: var(--s1); }
.closeextent dt { font-size: var(--t-xs); letter-spacing: .09em;
                  text-transform: uppercase; color: var(--ink-3); }
/* No new type size: the extent takes the same clamp the section heads take,
 * because a number that is the size of a heading IS a heading here. A
 * seventeenth size was refused. */
/* No `color` here: the number already inherits --ink, and restating it is a
   declaration that changes nothing — the dead-rule scan says so. */
.closeextent dd { margin: 0; font-family: var(--display);
                  font-size: clamp(var(--t-xl), 3vw, var(--t-2xl));
                  font-variant-numeric: tabular-nums; }

.colophon { color: var(--ink-3); font-size: var(--t-sm);
            margin: var(--s7) 0 0; max-width: 44rem; }

@media (max-width: 52rem) {
  /* The extent goes under the statement and lies down: three tall serif
   * numerals stacked in a right-hand column is a shape a phone has no room
   * for, and the numbers read perfectly well in a row. */
  .closein { grid-template-columns: 1fr; gap: var(--s6); }
  .closeextent { grid-template-columns: repeat(3, minmax(0, 1fr));
                 text-align: left; }
}

@media (max-width: 52rem) {
  /* One column, and the journey route goes UNDER its text rather than into a
   * 13rem column that is no longer 13rem wide. */
  .jrow, .storyband { grid-template-columns: 1fr; gap: var(--s4); }
  .jrowart { max-width: 20rem; }
}

/* THE PLANNER BAND WAS POSITION TWO ON THE PAGE AND IT READ AS A FORM.
 *
 * Under the hero sat a shouty uppercase label, an input, a button, four grey
 * pills and FIVE LINES of projection prose — standard parallels, central
 * meridian, the elevation survey. Technically true and the second thing a
 * reader met, which is the "it led with structure" failure the hero was
 * rebuilt to fix, reappearing one band down. The credit moved to the foot of
 * the page, where a credit belongs and where every check that requires it is
 * equally satisfied.
 *
 * And the chips were `--paper-2` pills with a `--rule` border, which is the
 * shape this stylesheet uses for something INERT. These are the fastest way
 * into the product. They keep the primitive and take the ink they deserve. */
.closing .sourcenote { margin-top: var(--s5); }

/* ── THE INDEXES THAT SHOWED EVERYTHING EXCEPT THE THING ──────────────
 *
 * /journeys was seventeen rows of kicker, name, dot-separated stops, a thin
 * bar and three right-aligned facts. Every one identical, no drawing
 * anywhere — and the HOMEPAGE was showing three journeys better than the
 * journeys index showed seventeen, because the homepage drew their routes.
 *
 * A journey is chosen on where it goes. That is the one question this atlas
 * can answer in a picture and the one the index refused to. No two of the
 * seventeen lines are alike — Arctic to Mediterranean is the length of
 * Europe, the Iberian Circle is a loop in one corner — so this is the family
 * where a drawing per row DIFFERENTIATES rather than repeats, which is the
 * test the homepage's four doors failed and this one passes. */
/* The name was --t-lg, a hair above the stops list under it, so seventeen
 * journeys had seventeen quiet headings. An index's rows are its subject. */
.journeyrows .row { grid-template-columns: minmax(0, 1fr) 15rem; align-items: center; }
.journeyrow :is(h2, h3) { font-size: clamp(var(--t-xl), 3vw, var(--t-2xl));
                 line-height: 1.12; margin: 0 0 var(--s2); }
.journeyrow:hover :is(h2, h3), .journeyrow:focus-visible :is(h2, h3) { color: var(--door); }
.jart .constel { width: 100%; }
.jart .constel-lit circle { r: 11px; }
.jart .constel-route { stroke-width: 8; }
/* No `color`: `.row .rowmeta` already sets --ink-3 and every .jfacts is
   one, so restating it is a declaration that changes nothing. It only became
   visible when the dead-rule scan started visiting a motion page. */
.journeyrow .jfacts {
  text-align: left; margin: var(--s3) 0 0; font-size: var(--t-sm);
  letter-spacing: .06em; text-transform: uppercase;
}

/* And /stories was nine of the same row with a 132px grey Europe at the end
 * of each: too small to name a place, and eight of them saying nothing eight
 * times. The lead is the newest piece — a date, not a judgement — with its
 * own places drawn at size; the other eight are the row without the glyph. */
/* No border-top: the opening above already closes with one, and two rules
 * with a gap between them reads as a mistake rather than as a division. */
/* No `color: inherit`: `a { color: inherit }` is the global rule at the top
 * of this file, and restating it here is the redundancy the dead-rule scan
 * exists to find — a declaration that matches its element and changes
 * nothing, which reads as a decision and is not one. */
/* AND NO `display: grid` EITHER, for the same reason one paragraph up:
   `.storylead` already sets it and this element carries both classes, so
   the declaration matched its element and changed nothing. The dead-rule
   scan found it the run after /stories lost its opening drawing, which is
   when the two hover rules it had been carrying started winning and this
   one took their place at the ceiling. */
.storyleadwide {
  grid-template-columns: 22rem minmax(0, 1fr); gap: var(--s6);
  align-items: center; text-decoration: none;
  padding: var(--s6) 0;
}
.storyleadwide :is(h2, h3) {
  font-size: clamp(var(--t-xl), 3vw, var(--t-2xl));
  line-height: 1.12; margin: 0 0 var(--s3);
}
.storyleadwide .rowsub { max-width: 52ch; }
.storyleadwide:hover :is(h2, h3), .storyleadwide:focus-visible :is(h2, h3) { color: var(--door); }

@media (max-width: 52rem) {
  .journeyrows .row, .storyleadwide { grid-template-columns: 1fr; gap: var(--s4); }
  /* `min(22rem, 100%)`, the same cap every other stacked row drawing takes.
     It was a flat 18rem, chosen while these seventeen routes were hidden
     below 44rem entirely — so the number was only ever about the band
     between 44 and 52rem, and at 390 it left 88 pixels of paper down the
     right of every row. The cap is still there for the top of the band,
     where a full-width glyph is 546 pixels tall seventeen times over. */
  .jart .constel { width: min(22rem, 100%); }
}

/* ── THE EDITORIAL INDEX OPENING ──────────────────────────────────────
 *
 * Five indexes opened identically — kicker, 60px serif h1, lede in the
 * right-hand column, a rule, then rows — and then ran straight into a
 * two-column list with a right-aligned meta column. That is the
 * design-direction finding at its plainest: a shared template is not a
 * shared experience, and these five were the clearest case of it on the
 * site.
 *
 * An index opens on its own subject now, at size, BESIDE the type rather
 * than under it. The art is a photograph where the register holds one and
 * the family's own drawing where it does not — every route at once for the
 * journeys, the year as a chart for the events. Never a generated plate:
 * that is the measurement that took eleven of them off the homepage.
 *
 * THE PHOTOGRAPH SLOT IS THE POINT OF THE LAYOUT AND IT IS EMPTY TODAY. The
 * composition is designed for the no-image state rather than degraded into
 * it: the drawing holds the right-hand column at the same proportion a
 * photograph will, so the day a row lands in data/images.json nothing on the
 * page moves except what is inside that frame. */
/* A VARIANT OF `pagehead`, sharing its role and its measure. */
/* No `display: grid`: `.pagehead.index` already sets it and every `.ihero`
   is one — a variant of that head rather than a head of its own, which is
   the whole reason it is called a variant. */
.pagehead.ihero {
  gap: var(--s7); align-items: center;
  padding: var(--s6) 0 var(--s7); border-bottom: 1px solid var(--rule); max-width: none;
}
.pagehead.ihero.wide { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
                       grid-template-rows: 1fr auto auto 1fr;
                       row-gap: var(--s6); }
/* EXPLICIT, BECAUSE THE PHONE ORDER NOW DIFFERS FROM THE DESK ONE. In
   document order the actions and the note follow the picture, so a phone
   meets the drawing before them; on a desk they belong under the lede in
   column one, and auto-flow would put them in column two under the arch.
   The two `1fr` rows above and below the pair are what keeps the group
   centred against a figure taller than it — with two auto rows the leftover
   height is split BETWEEN them, which on the country head rendered 350
   pixels of white between the tagline and the chips. The slack belongs
   outside the group. */
.pagehead.ihero.wide > .iherotext { grid-column: 1; grid-row: 2; }
.pagehead.ihero.wide > .iherofoot { grid-column: 1; grid-row: 3; }
.pagehead.ihero.wide > .iheroart  { grid-column: 2; grid-row: 1 / -1;
                                    align-self: center; }
/* The foot is its own stack, so the space above it is the grid's row-gap
   and the space inside it is this gap — one of each, rather than a margin
   on the chips that the row-gap then adds to. */
.iherofoot { display: grid; gap: var(--s4); justify-items: start; }
/* A SEPARATOR BELONGS TO THE RELATIONSHIP, NOT THE ELEMENT, and this one had
 * been written as a property of the head. Every index hero draws a rule under
 * itself to part the opening from the set below it — right on twenty-one
 * families and wrong on the 404, whose drawing IS the body: the rendered page
 * showed a hairline across the column and then two hundred and seventy pixels
 * of nothing above the footer, on the one page a reader arrives at having
 * already failed to find something. Nothing counts an empty band, and no
 * check reads a divider for what is on the other side of it. */
main > .pagehead:last-child { border-bottom: 0; margin-bottom: 0; padding-bottom: 0; }
.iherotext h1 {
  font-size: clamp(var(--t-3xl), 5.2vw, var(--t-5xl));
  line-height: 1.12; margin: 0 0 var(--s4);
}
/* No `color`: `.lede` already resolves to --ink-2, and the scan reads a
 * restatement of an inherited value as a rule that changes nothing. */
.iherotext .lede { font-size: var(--t-md); max-width: 42ch; margin: 0; }
.iherofoot .chips { margin: 0; }
.iherofoot .small { margin: 0; color: var(--ink-3); }

/* The aperture, once per page rather than once per card. An index opening is
 * exactly where the signature belongs — it is the one large drawing on the
 * page — and it is the same curve, from the same numbers, as the 824 plates
 * and the hero. */
.iheroart {
  margin: 0; aspect-ratio: 4 / 3; overflow: hidden;
  border-radius: 50% 50% 0 0 / 34% 34% 0 0;
  /* The opening is a map, so its ground is the Atlantic rather than the
     dark world's paper. Graphite made five index openings a black tile with
     a grey continent and acid marks on it; the plates two clicks away have
     been cream land on this water since the cartography split. One
     cartography, whatever the drawing is embedded in. */
  background: var(--atlas-sea);
}
.iheroart .constel, .iheroart svg { width: 100%; height: 100%; }
.iheroart.shot picture, .iheroart.shot img {
  display: block; width: 100%; height: 100%; object-fit: cover;
}
/* Every route at once: seventeen real sequences on one continent, which is a
 * picture no other travel product can draw. The land is quiet and the lines
 * carry it, because the subject is the reach of the set rather than Europe. */
/* THE OPENING IS THE ATLAS AT INDEX SIZE. It was a dark ground with a grey
 * continent on it and whatever the dark world's accent happened to be lit on
 * top, which is how 172 experience dots came out cobalt on graphite —
 * present, correct, counted and invisible — and then acid green when that
 * was fixed. Neither was a decision about a MAP. Cream land on the
 * Atlantic, marks in the signature, and every mark carries a hairline of
 * the land so it separates from the water it may be sitting beside. */
.iheroart .constel { fill: var(--atlas-land); }
.iheroart .constel-lit circle { fill: var(--cobalt); r: 9px;
  stroke: var(--atlas-here); stroke-width: 1.8; }
/* r:9, not the theme row's 22. A glyph's radius is fitted to a 132px mark
 * and this frame is five times that, so the same class rendered dots the
 * size of small countries. Eleven units is not eleven pixels, again. */
/* THE ATLAS INDEX OPENS ON THE FIFTY, AND ITS FILL IS COBALT ON GRAPHITE.
 * `.regionglyph .countries path` is --door, which is right on a limestone
 * band and is the `.minidot .hit` failure on the opening's dark ground:
 * present, correct, counted and not there. Third time a colour has been
 * carried onto this ground unchanged.
 *
 * NOT LIME EITHER, AND THAT IS THE ARGUMENT RATHER THAN A SAVING. The other
 * three openings light a SUBSET — seventeen routes, 172 experiences, the
 * places nine essays are set in — and lime is what says "these ones". Here
 * the subset is every country there is, so lighting it states nothing and
 * draws one flat blob. Paper countries with the shadow between them say the
 * thing the page is for: the continent is fifty separate places, and the
 * frontier is the picture. It is the hero's reading at index size, light
 * falling on Europe.
 *
 * non-scaling-stroke because this is a 1,000-unit viewBox scaled into a
 * 600-pixel box, and a scaled hairline is the frontier-is-a-worm failure
 * already shipped once here, one screen from the rule that documents it. */
.iheroart .regionglyph .countries path {
  fill: var(--atlas-here);
  stroke: var(--atlas-coast); stroke-width: 1.1;
  vector-effect: non-scaling-stroke;
}
.allroutes { fill: var(--atlas-land); }
.allroutes .constel-route { stroke-width: 4; }
.allroutes .constel-route.case { stroke-width: 9; }

@media (max-width: 60rem) {
  .pagehead.ihero.wide { grid-template-columns: 1fr;
                         grid-template-rows: none; gap: var(--s5); }
  /* One column, and the three blocks take document order: name, picture,
     then what to do about it. `initial` rather than restating a row number —
     a placement written for two columns is not a placement at all here. */
  .pagehead.ihero.wide > .iherotext,
  .pagehead.ihero.wide > .iherofoot,
  .pagehead.ihero.wide > .iheroart { grid-column: auto; grid-row: auto; }
  .iheroart { aspect-ratio: 3 / 2; }
}

/* The planner's method, under the tool rather than beside it. Four hundred
 * words of scoring weights level with the fields a reader is filling in is
 * two things competing for one attention, and it squeezed the form into two
 * thirds of the page to make room. Under the tool is also where the question
 * is asked: run it, look at the answer, then ask why it chose that. */
.planmethod { border-top: 1px solid var(--rule); margin-top: var(--s7); padding-top: var(--s6); }
.methodcols { columns: 2; column-gap: var(--s7); }
.methodcols > * { break-inside: avoid; }
.methodcols h2 { margin-top: 0; }
@media (max-width: 60rem) { .methodcols { columns: 1; } }

/* The planner's fields take the whole measure. `.split` was 1.7fr / 1fr with
 * the method in the second column; the method moved under the tool and the
 * grid kept both columns, so the form ran in two thirds of the page with a
 * third of nothing beside it — removing something can leave the same hole
 * that adding the wrong thing does. */
.planform > div { max-width: none; }

/* ── PRINT ─────────────────────────────────────────────────────────
 *
 * A DESTINATION PAGE IS THE ONE THING ON THIS SITE PEOPLE PRINT, and there
 * was no print rule anywhere in 4,700 lines. Printed as it stood, a
 * destination page put a cobalt masthead across the top of every sheet, ran
 * the INTELLIGENCE world's near-black ground through the printer on any
 * embedded map, and carried a thumb bar, a sticky action, a search box and
 * a twenty-link footer onto paper where not one of them can be pressed.
 *
 * Three rules only, because a print stylesheet that redesigns the page is a
 * second design nobody looks at:
 *
 *   1. Nothing that can only be TAPPED is printed. Navigation, the sticky
 *      action, the save buttons, the forms, the skip link, the footer's
 *      link columns. The footer's legal line stays: on paper it is the only
 *      thing that says who published this.
 *   2. Ink on paper. The two worlds are a screen idea — a dark ground is
 *      the reader's own toner — so INTELLIGENCE prints as DISCOVER does,
 *      and the map families take the atlas palette they already have for a
 *      printed picture.
 *   3. Nothing breaks across a page in the middle of a claim. A row, a
 *      fact, a score bar and a figure with its caption each stay whole.
 *
 * `print-color-adjust: exact` on the drawings only: a coastline whose land
 * and sea both come out white is not a map, and it is the one place on the
 * page where the colour IS the information.
 */
@page { margin: 16mm; }

@media print {
  :root { --mast: 0rem; }
  .masthead, .bottomnav, .stickycta, .skip, .footer-nav,
  form, .askhero, .chips, .zbtn, .mapzoom,
  [data-save], .sectionnav { display: none !important; }
  body, body[data-world="intelligence"] {
    background: #fff; color: #111; }
  /* NOT `padding: 0`. The first version zeroed it and the page printed
     flush to both edges of the sheet — the measure went with it and the
     destination map ran off the right-hand side, arch and all. The sheet's
     own margin is the gutter, so it is declared once on @page and the shell
     keeps a small one of its own. */
  main { padding: 0 4mm; }
  .footer { border-top: 1px solid #ccc; background: none; padding-top: 1em; }
  a { color: inherit; text-decoration: none; }
  .row, .fact, .scorerow, figure, .note, .card, li { break-inside: avoid; }
  h1, h2, h3 { break-after: avoid; }
  .minimap, .plate, .constel, .yearband, .europemap {
    print-color-adjust: exact; -webkit-print-color-adjust: exact; }
}

/* ── the country band ───────────────────────────────────────────────── */
/* FIFTY COUNTRY PAGES WERE THE LARGEST FAMILY ON THIS SITE WITH NO
   PHOTOGRAPH ANYWHERE — seven thousand pixels each, and the page a reader
   most often reaches from a search for "Austria travel". The band is what a
   whole country feels like, above its name.

   IT IS AN ADDITION AND NOT A REPLACEMENT. The country portrait stays
   exactly where it is: it is this family's signature moment and it is the
   thing that says which country this page is about. And with no photograph
   there is no band at all — no empty frame, no plate — which is the
   homepage's own finding, that a slot waiting for a picture is honest and
   three hundred pixels of one is a hole.

   THE ASPECT IS DECLARED AT EVERY WIDTH AND NOTHING BESIDE IT DECIDES ITS
   HEIGHT, which is what makes this crop checkable by arithmetic where
   `.placeband-art` is not: that one is as tall as the map next to it, so its
   crop is decided by a neighbour and can be neither art-directed nor
   checked. 16/9 below 62rem and 21/9 above it gives a guaranteed 52% of a
   source frame, against the homepage hero's 15%. */
.pageband {
  margin: 0 0 var(--s6);
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--paper-2);
}
@media (min-width: 62rem) { .pageband { aspect-ratio: 21 / 9; } }
.pageband picture,
.pageband img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* A RULE THAT CHANGES TWO PROPERTIES OF AN ELEMENT GOVERNED BY SIX IS NOT A
   DIFFERENT DESIGN, IT IS A BROKEN ONE — and this one shipped on the first
   real acquisition. It said "the credit belongs under the picture and never
   over it" and set `margin-top` and `color` to say so. `.credit` is
   `position: absolute` at the foot of the picture, on a near-black scrim, at
   `opacity: 0` until hover; `.pageband figcaption` is (0,1,1) against
   `.credit`'s (0,1,0), so the COLOUR was overridden and the position, the
   scrim and the opacity were not. The result was the site's caption ink —
   `--ink-2`, a dark grey for a light page — laid on 55% black: **2.01:1**,
   on the two links Pexels' terms require, on every family that opens on a
   photograph. `margin-top` on an absolutely positioned element does nothing
   at all, so the rule achieved exactly one thing and that thing was the
   defect.

   AND "UNDER THE PICTURE" WAS NOT AVAILABLE TO IT. `picture()` emits the
   figcaption INSIDE the `<picture>`, and `.pageband` is an `aspect-ratio`
   box with `overflow: hidden` — so a static caption in there is clipped
   away entirely rather than moved below. The rule was describing a layout
   the markup cannot produce. One credit design, everywhere, is what this
   stylesheet is for; the exception is deleted rather than completed. */
