/*
 * The slippy map's structure, shared by every module that draws one.
 *
 * THE TEMPLATE'S, BECAUSE TWO MODULES NEED IT. This began inside
 * module_trade.css, which was the only stylesheet with a map in it. The cooler
 * log module plots a trip on the same engine, and what two modules need is the
 * template's -- the same rule that put assets/js/category_tree.js and
 * includes/avatar_builder.php here.
 *
 * THE SEAM IS "WHAT THE ENGINE REQUIRES" VERSUS "WHAT A MODULE WANTS IT TO LOOK
 * LIKE". assets/js/trade_map.js hardcodes the class names below -- it writes
 * .trade-map__tile elements and .trade-marker buttons whatever module asked for
 * the map -- so these rules have to exist wherever it runs, and they are the
 * only ones that do. A module's own pins, cards, keys and legends stay in that
 * module's stylesheet, because nothing outside it needs them.
 *
 * THE CLASS PREFIX IS STILL trade-, DELIBERATELY. Renaming it would touch the
 * engine, both modules' stylesheets and both modules' markup at once, for a
 * cosmetic gain, and the engine's class names are an implementation detail
 * nobody types by hand. It is a scar from where the code came from, not a
 * statement about who owns it now.
 *
 * NOTHING HERE POSITIONS A TILE OR A PIN. Those move constantly and are written
 * as rules into a stylesheet the engine creates at run time; see the note at
 * the top of assets/js/trade_map.js about why a style attribute cannot be used.
 * A walker moves every frame and is placed through its element's style object
 * instead, which the policy allows -- see placeWalkerElement().
 */

:root {
    --colour-trade-map-surface: var(--colour-page-background);
    --colour-trade-map-text: var(--colour-card-text);

    /* THE OUTLINES SIT ON TOP OF THE MAP'S OWN INK, so this is deliberately
       weaker than any border token: it has to be legible against pale ground
       without drowning the place names it crosses. */
    --colour-trade-map-boundary: rgba(90, 78, 66, 0.45);

    /* How tall a map is by default. A module that wants a different height sets
       this on its own container rather than overriding .trade-map. */
    --trade-map-height: min(70vh, 44rem);
    --trade-marker-size: 2.25rem;

    /* A WALKER'S BOX BEFORE THE MAP HAS SIZED IT FOR THE ZOOM -- after that the
       script's --trade-walker-size wins; see describeWalkerBoxPixels() in
       assets/js/trade_map.js. Also the size of your own face in a map's key. */
    --trade-figure-size: 1.6rem;
}

/* ---------------------------------------------------------------------------
 * The map itself
 * ------------------------------------------------------------------------ */

.trade-map {
    position: relative;
    height: var(--trade-map-height);
    border: 1px solid var(--colour-border);
    border-radius: var(--radius-medium);
    background-color: var(--colour-trade-map-surface);
    color: var(--colour-trade-map-text);

    /* TILES ARE ABSOLUTELY POSITIONED AND WOULD OTHERWISE SPILL. The rounded
       corner is only visible because this clips them. */
    overflow: hidden;

    /* The map handles its own dragging, so the browser must not also try to
       pan the page when a drag starts on it. */
    touch-action: none;
}

.trade-map__viewport {
    position: absolute;
    inset: 0;
    cursor: grab;
}

.trade-map__viewport.is-dragging {
    cursor: grabbing;
}

/* NOTHING ON THE MAP OR THE GLOBE TRANSITIONS WHERE MOTION IS REDUCED -- not even for the hundredth of a millisecond the
   site's own rule leaves every element; see site_styles.css. That rule shortens every transition rather than taking it
   away, and every property is transitioned, since that is what a transition's property is until something says
   otherwise. A transition of any length holds an element at its old value until the next frame the browser animates,
   and the script writes a canvas's picture and its box together: the picture showed at once and the box a frame or
   more later. Zooming out, the new ground was drawn into the old ground's box for those frames, its coast inland of the
   sea's, and between the two coasts was the wall behind the map -- a black band along every shore.

   The script moves these every frame and waits on none of them to finish. */
@media (prefers-reduced-motion: reduce) {
    .trade-map__viewport,
    .trade-map__viewport *,
    .globe,
    .globe * {
        transition-duration: 0s !important;
    }
}

/* THE FOCUS WHILE THE VIEW MOVES: the edges darkened round a clear middle, which the script places, sizes and
   darkens through the custom properties and the opacity -- see focusFrame() in assets/js/trade_map.js. A gradient
   and not a blur, and never in the way of a pointer. */
.trade-map__focus {
    position: absolute;
    inset: 0;
    pointer-events: none;
    opacity: 0;
    background: radial-gradient(
        circle at var(--trade-map-focus-x, 50%) var(--trade-map-focus-y, 50%),
        rgba(6, 10, 8, 0) 0,
        rgba(6, 10, 8, 0) var(--trade-map-focus-clear, 100vmax),
        rgba(6, 10, 8, 0.85) calc(var(--trade-map-focus-clear, 100vmax) + var(--trade-map-focus-fade, 120px))
    );
}

/* AND ITS BLUR, on the high graphics step: what is behind blurred, and the same clear middle cut out of it by a mask.
   Faded in and out by its opacity; the blur itself is never changed, since a new radius is a new blur to work out. */
.trade-map__focus-blur {
    position: absolute;
    inset: 0;
    pointer-events: none;
    opacity: 0;
    -webkit-backdrop-filter: blur(5px);
    backdrop-filter: blur(5px);
    -webkit-mask-image: radial-gradient(
        circle at var(--trade-map-focus-x, 50%) var(--trade-map-focus-y, 50%),
        transparent 0,
        transparent var(--trade-map-focus-clear, 100vmax),
        #000 calc(var(--trade-map-focus-clear, 100vmax) + var(--trade-map-focus-fade, 120px))
    );
    mask-image: radial-gradient(
        circle at var(--trade-map-focus-x, 50%) var(--trade-map-focus-y, 50%),
        transparent 0,
        transparent var(--trade-map-focus-clear, 100vmax),
        #000 calc(var(--trade-map-focus-clear, 100vmax) + var(--trade-map-focus-fade, 120px))
    );
}

/* THE FOG OF WAR: a sheet over the ground in its own coarse pixels, stretched smooth so the edge of what a member
   has explored is soft rather than stepped -- see writeFogCanvas() in assets/js/trade_map.js. */
.trade-map__fog-canvas {
    position: absolute;
    left: 0;
    top: 0;
    pointer-events: none;
    image-rendering: auto;
}

/* THE GROUND NOT YET READY, COVERED, with a hole cut in the cover for the ground that is: a rectangle as a zoom out's
   ground comes in, a band from a line across the middle as a zoom in's does. The script writes the hole's four edges --
   see revealFrame() in assets/js/trade_map.js.

   A HARD EDGE AND NOTHING ELSE. It was a circle with a soft edge, which read as a dark ring round a blur; the hole is the
   shape the ground is actually built in, and the cover is flat wall. EVEN-ODD, so the second rectangle in the one
   polygon cuts the first rather than adding to it. Never in the way of a pointer. */
.trade-map__reveal {
    position: absolute;
    inset: 0;
    pointer-events: none;
    background-color: var(--colour-trade-map-surface);
    clip-path: polygon(
        evenodd,
        0 0, 100% 0, 100% 100%, 0 100%, 0 0,
        var(--trade-map-reveal-left, 0px) var(--trade-map-reveal-top, 0px),
        var(--trade-map-reveal-right, 0px) var(--trade-map-reveal-top, 0px),
        var(--trade-map-reveal-right, 0px) var(--trade-map-reveal-bottom, 0px),
        var(--trade-map-reveal-left, 0px) var(--trade-map-reveal-bottom, 0px),
        var(--trade-map-reveal-left, 0px) var(--trade-map-reveal-top, 0px)
    );
}

/* AND OVER A SHARPENING ZOOM, THE NEW GROUND IN COARSE BLOCKS on the cover, and cut by its hole as the cover is -- see
   sharpenStep() in assets/js/trade_map.js. Placed by the script over the ground it is of, and stretched with its blocks
   kept square-edged: a block is meant to be seen as one. */
.trade-map__reveal-grid {
    position: absolute;
    left: var(--trade-map-grid-left, 0px);
    top: var(--trade-map-grid-top, 0px);
    width: var(--trade-map-grid-width, 0px);
    height: var(--trade-map-grid-height, 0px);
    image-rendering: pixelated;
}

/* ---------------------------------------------------------------------------
 * The scene the map is held in
 *
 * WHERE THE PAGE ASKS FOR ONE, the map is a picture: the world on a canvas in a gilt frame, hung on a plastered wall
 * under a picture light with a brass plaque beneath, or lying on a table. The script paints the frame, the lamp, the
 * plaster and the wood as pixel art and places every piece, and says which scene with a class on the
 * viewport -- see "The scene the map is held in" in assets/js/trade_map.js.
 *
 * THE GILT, THE BRASS AND THE WOOD ARE MATERIALS, NOT THEME COLOURS, so they are the same in the dark as in the light.
 * The wall is the reader's: their Map surface colour, lit from above in their accent.
 * ------------------------------------------------------------------------ */

.trade-map__wall {
    position: absolute;
    inset: 0;
    pointer-events: none;
    display: none;
}

.trade-map__viewport--scene-gallery .trade-map__wall,
.trade-map__viewport--scene-table .trade-map__wall {
    display: block;
}

/* THE WALL, AND THE COVER OVER GROUND NOT YET READY, ARE ONE SURFACE: both fill the viewport from the same corner, so the
   cover is seamless with the wall past the world and a zoom's ground opens out of the wall itself. */
.trade-map__viewport--scene-gallery .trade-map__wall,
.trade-map__viewport--scene-gallery .trade-map__reveal {
    background-color: var(--colour-trade-map-surface);
    background-image:
        radial-gradient(
            90% 70% at 50% 0%,
            color-mix(in srgb, var(--colour-accent, #c08a2a) 20%, transparent) 0%,
            transparent 72%
        ),
        radial-gradient(
            140% 120% at 50% 40%,
            transparent 48%,
            color-mix(in srgb, #000000 30%, transparent) 100%
        ),
        var(--trade-map-wall-texture, none);
    background-size: 100% 100%, 100% 100%, 64px 64px;
    background-repeat: no-repeat, no-repeat, repeat;
    image-rendering: pixelated;
}

/* THE ROOM ROUND THE TABLE: dark past the far edge of the wood, with a little of
   the reader's own surface colour in it. The cover over ground not yet ready stays the canvas's colour -- see
   .trade-map__reveal -- since what is not painted yet on a picture is bare canvas. */
.trade-map__viewport--scene-table .trade-map__wall {
    background:
        radial-gradient(
            120% 90% at 50% 100%,
            color-mix(in srgb, var(--colour-trade-map-surface) 14%, #2a1c12) 0%,
            #150e09 62%,
            #0a0705 100%
        );
}

/* AND THE AIR OVER THE FAR END OF THE TABLE IS THE ROOM'S DARK, not the surface colour a laid-back map fades to. More
   specific than the rule that fades it, which comes later in this sheet. */
.trade-map__viewport.trade-map__viewport--scene-table.is-tilted::after {
    background-color: rgba(12, 8, 5, 0.5);
}

/* PRESERVE-3D, BOTH LAYERS: the table lies below the ground and the frame's sides run down to it, and a flat layer
   between them and the stage would press them back into the ground's plane. */
.trade-map__gallery {
    position: absolute;
    inset: 0;
    pointer-events: none;
    transform-style: preserve-3d;
}

.trade-map__gallery-canvas,
.trade-map__frame-part,
.trade-map__frame-side,
.trade-map__gallery-light,
.trade-map__gallery-lamp,
.trade-map__gallery-plaque,
.trade-map__table,
.trade-map__table-shadow {
    position: absolute;
    left: 0;
    top: 0;
}

.trade-map__table {
    background-repeat: repeat;
    image-rendering: pixelated;
}

/* THE PICTURE'S SHADOW ON THE WOOD, hard-edged with one step of penumbra, as the frame's shadow on the wall is. */
.trade-map__table-shadow {
    background-color: rgba(0, 0, 0, 0.42);
    box-shadow: 0 0 0 4px rgba(0, 0, 0, 0.18);
}

/* THE FRAME'S SIDES, standing down from its outer edge to the table. Each turns about the edge it hangs from, and a side
   turned away from the reader is not drawn at all. */
.trade-map__frame-side {
    background-repeat: repeat;
    image-rendering: pixelated;
    -webkit-backface-visibility: hidden;
    backface-visibility: hidden;
}

.trade-map__frame-side--near,
.trade-map__frame-side--right {
    transform-origin: 0 0;
}

.trade-map__frame-side--left {
    transform-origin: 100% 0;
}

/* SOMEBODY OFF A CROPPED MAP IS KEPT BUT NOT DRAWN -- see "The picture" in assets/js/trade_map.js. */
.trade-walker--outside-picture {
    visibility: hidden;
}

/* THE CANVAS IS THE MAP'S OWN SURFACE, so nothing inside the world looks any different for being framed. */
.trade-map__gallery-canvas {
    background-color: var(--colour-trade-map-surface);
}

/* HUNG, IT CASTS A HARD SHADOW ON THE WALL: spread by the frame's thickness so it is the framed picture's shape, and not
   blurred, because nothing else here is. */
.trade-map__gallery.is-hanging .trade-map__gallery-canvas {
    box-shadow: 10px 12px 0 24px rgba(0, 0, 0, 0.28);
}

.trade-map__frame-part,
.trade-map__gallery-lamp {
    background-repeat: repeat;
    image-rendering: pixelated;
}

.trade-map__gallery-lamp {
    background-size: 100% 100%;
}

/* THE LIGHT FROM THE LAMP: a cone from the hood's width to past the frame, in stepped bands rather than a smooth fade, as
   pixel art would shade it. Under the canvas, so it is the wall round the picture that is lit. */
.trade-map__gallery-light {
    clip-path: polygon(calc(50% - 26px) 0, calc(50% + 26px) 0, 100% 100%, 0 100%);
    background: linear-gradient(
        to bottom,
        rgba(255, 236, 170, 0.30) 0 14%,
        rgba(255, 236, 170, 0.21) 14% 36%,
        rgba(255, 236, 170, 0.13) 36% 62%,
        rgba(255, 236, 170, 0.06) 62% 88%,
        rgba(255, 236, 170, 0) 88%
    );
}

/* THE BRASS PLAQUE, in the frame's palette: a bright top edge, a dark bottom one, a screw at each end. */
.trade-map__gallery-plaque {
    padding: 5px 16px 4px;
    border: 2px solid #2e1a06;
    background: linear-gradient(
        to bottom,
        #fbe7a1 0 2px,
        #e8bd55 2px 50%,
        #c08a2a 50% calc(100% - 2px),
        #7a4e12 calc(100% - 2px)
    );
    box-shadow: 4px 4px 0 rgba(0, 0, 0, 0.28);
    color: #2e1a06;
    font-family: var(--font-family-base);
    font-size: 11px;
    font-weight: 700;
    line-height: 1.2;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    white-space: nowrap;
}

.trade-map__gallery-plaque::before,
.trade-map__gallery-plaque::after {
    content: "";
    position: absolute;
    top: 50%;
    width: 2px;
    height: 2px;
    margin-top: -1px;
    background: #2e1a06;
}

.trade-map__gallery-plaque::before {
    left: 5px;
}

.trade-map__gallery-plaque::after {
    right: 5px;
}

/* STEERING WITH A FINGER: a ring where it went down, as far across as full speed is, and a knob where the finger is
   now. FINGER_STEER_FULL_PIXELS in assets/js/trade_map.js is this ring's radius. See steerByFinger(). */
.trade-map__steer {
    position: absolute;
    left: 0;
    top: 0;
    width: 180px;
    height: 180px;
    margin: -90px 0 0 -90px;
    border: 2px solid rgba(255, 255, 255, 0.6);
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.14);
    pointer-events: none;
}

.trade-map__steer-knob {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 44px;
    height: 44px;
    margin: -22px 0 0 -22px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.72);
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
}

/* THE GROUND, AS ONE BOX THAT CAN BE LAID BACK.

   EVERY LAYER STANDS ON ONE PLANE -- tiles, track and pins -- so tilting the map
   is one rotation of this, not four that have to agree. assets/js/trade_map.js
   sizes and rotates it; see the note there on why a CSS transform is exactly
   right for a plane and exactly wrong for a sphere.

   preserve-3d IS LOAD BEARING. It is what lets a pin be turned back out of the
   ground to face the viewer. Without it the counter-rotation is flattened into
   this element's plane and every pin stays lying down. */
.trade-map__stage {
    position: absolute;
    inset: 0;
    transform-style: preserve-3d;
}

.trade-map__tiles,
.trade-map__markers,
.trade-map__track {
    position: absolute;
    inset: 0;

    /* THE 3D SPACE HAS TO REACH THE PINS, AND EVERY LAYER BETWEEN CAN END IT.
       transform-style is not inherited: it says whether *this* element's
       children keep their depth, so one flat element anywhere between the
       tilted stage and a pin collapses the pin's counter-rotation into the
       screen plane. The symptom is a pin that stays lying down on the ground
       -- measured as a 38 by 20 box where an upright one is 38 by 38 -- while
       its own transform reads as perfectly correct, which is what makes it
       worth a note rather than a shrug. */
    transform-style: preserve-3d;
}

/* THE CORNER EVERY TILE POSITION IS MEASURED FROM.

   A LAYER OF TILES IS SHOWN AT A ZOOM IT WAS NOT DRAWN FOR -- that is what makes
   the zoom a movement rather than a cut, since tiles only exist at whole levels
   and the view passes through everything between them. placeTileLayer() works
   out one translate and one scale for the whole layer, and both are measured
   from this corner. Changing it moves the map. */
.trade-map__tiles {
    transform-origin: 0 0;
}

/* THE PREVIOUS ZOOM'S TILES, STANDING IN WHILE THE NEW ONES LOAD.
   
   Every tile key carries its zoom, so changing zoom made the whole visible set
   unwanted at once and the map showed its own surface colour until the new
   images arrived -- the white flash. This layer keeps the old picture, scaled
   to line up, until the real tiles land on top of it.
   
   IT IS PLACED THE SAME WAY THE LIVE LAYER IS, by placeTileLayer(), and takes
   its top left transform-origin from .trade-map__tiles above along with it.
   
   IT TAKES NO CLICKS AND NO SCREEN READER'S ATTENTION. It is a stand-in for
   something already on its way, not content. */
.trade-map__tiles--ghost {
    pointer-events: none;
}

/* A LAYER LAID OVER THE GROUND -- soil, and whatever else a page lays there --
   drawn see-through so the roads under it still say where it is. See
   setOverlay() in assets/js/trade_map.js.

   THE SEE-THROUGH IS ON EACH TILE, NOT THE LAYER. Opacity below one on the
   layer would make it a group the browser flattens, and a flattened layer on
   the tilted map would stop keeping its depth -- see the note on
   transform-style above. On the tiles it costs nothing: they are flat already.

   A SQUARE THE SITE COULD NOT FILL IS HIDDEN rather than drawn as the browser's
   broken-picture mark, which would read as a fault in the map. */
.trade-map__overlay {
    pointer-events: none;
}

.trade-map__overlay-tile {
    opacity: var(--trade-map-overlay-opacity, 0.6);
}

.trade-map__overlay-tile.is-empty {
    visibility: hidden;
}

.trade-map__markers {
    /* THE MARKER LAYER MUST NOT EAT THE DRAG. It covers the whole map, so
       without this every pointerdown would land on it rather than the viewport
       and the map would never pan. The markers themselves take pointer events
       back, below. */
    pointer-events: none;
}

.trade-map__tile {
    position: absolute;
    top: 0;
    left: 0;
    width: 256px;
    height: 256px;

    /* A HALF PIXEL GAP APPEARS BETWEEN TILES WITHOUT THIS on fractional device
       pixel ratios, drawing a grid of pale lines across the whole map. */
    outline: 1px solid transparent;

    /* Dragging an image is the browser's default and it fights the pan. */
    user-select: none;
    -webkit-user-drag: none;
}

/* ---------------------------------------------------------------------------
 * The track
 *
 * A ROUTE RATHER THAN A SET OF PLACES. Markers answer "what is here"; a track
 * answers "where did it go", and a log with an order to it needs the second.
 * The line is one <polyline> however many points it has, which is what makes a
 * few hundred fixes cost nothing to draw.
 *
 * IT TAKES NO POINTER EVENTS, so a drag that begins on the route still pans the
 * map rather than stopping dead on the line.
 * ------------------------------------------------------------------------ */

.trade-map__track {
    pointer-events: none;
    overflow: visible;
}

/* ---------------------------------------------------------------------------
 * Boundary outlines
 *
 * CONTEXT, NOT CONTENT. Nobody opens the map to look at a county; they look at
 * where something is in relation to one. So the lines are thin, unfilled and
 * well short of the text they are drawn over -- enough to read the shape of a
 * boundary, not enough to compete with the place names underneath.
 *
 * NO FILL, DELIBERATELY. A tinted county would be a second surface laid over
 * the map, and three thousand of them would be a quilt with the map somewhere
 * beneath it.
 * ------------------------------------------------------------------------ */

.trade-map__boundaries {
    position: absolute;
    inset: 0;
    pointer-events: none;

    /* STRETCHED THROUGH A ZOOM, NOT REPAINTED AT EVERY STEP OF IT. Without this
       the browser redrew every path at each new scale of the animation, which
       is megabytes of rings and roads: scaling the sheets alone, with nothing
       rebuilt, stalled a frame for 406 ms with the depth rings, fertility rings
       and roads on, and 218 ms with only the sea and the borders. Told the
       transform will change, it scales the picture it already has -- 94 ms at
       worst, the frame it was switched on in -- and the sheet is drawn sharp
       again when the zoom settles and it is rebuilt at the new level. Panning
       was never the problem: a translation stalled nothing either way. */
    will-change: transform;

    /* THE PATH IS DRAWN WIDER THAN THE WINDOW ON PURPOSE. It is built for a
       patch of world larger than the view so that a drag has somewhere to go
       before it must be built again -- see buildBoundaryPath() -- and an SVG
       clips to its own box unless told otherwise. */
    overflow: visible;

    /* THE CORNER ALL OF ITS ARITHMETIC IS MEASURED FROM, the same as the tile
       layers'. Left at the default the layer would scale about its middle
       through a zoom and the outlines would swim across the ground. */
    transform-origin: 0 0;
}

.trade-map__boundary-line {
    fill: none;
    stroke: var(--colour-trade-map-boundary, rgba(90, 78, 66, 0.45));
    stroke-width: 1;
    stroke-linejoin: round;
    vector-effect: non-scaling-stroke;
}

.trade-map__track-line {
    fill: none;
    stroke: var(--colour-accent);
    stroke-width: 3;
    stroke-linejoin: round;
    stroke-linecap: round;
    opacity: 0.85;
}

/* ---------------------------------------------------------------------------
 * Pins
 * ------------------------------------------------------------------------ */

.trade-marker {
    position: absolute;
    top: 0;
    left: 0;
    display: grid;
    place-items: center;
    width: var(--trade-marker-size);
    height: var(--trade-marker-size);
    padding: 0;
    border: 2px solid rgba(255, 255, 255, 0.85);
    border-radius: 50%;
    font-size: 0.8rem;
    font-weight: var(--font-weight-semibold);
    line-height: 1;
    cursor: pointer;

    /* THE PIN STANDS ON ITS COORDINATE. The middle of its bottom edge is the
       place it marks -- a sign is planted where it stands, not centred over it --
       so the margins put that edge on the point rather than the pin's own top
       left corner. The same anchoring a walker gets from its soles; see
       .trade-walker.

       AND IT IS STOOD UP ABOUT THAT SAME EDGE, which is what keeps the tilted
       map honest. Turned about its middle, half of every pin was below the
       ground plane, and the tiles it stands on drew over that half: each pin
       came out sliced through the waist. Pivoted at the base, the whole pin is
       on the viewer's side of the ground and nothing can cut it. See
       drawMarkers() in assets/js/trade_map.js for the counter-rotation. */
    transform-origin: 50% 100%;
    margin-top: calc(var(--trade-marker-size) * -1);
    margin-left: calc(var(--trade-marker-size) / -2);

    /* Taken back from the layer above, which gave them up so the map can pan. */
    pointer-events: auto;

    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
}

.trade-marker:focus-visible {
    outline: 2px solid var(--colour-accent);
    outline-offset: 2px;
}

/* The picture inside a pin. WHICH picture is the module's business: a mask
   image is set per marker kind in the module that defines that kind. */
.trade-marker::before {
    content: "";
    width: 1.3rem;
    height: 1.3rem;
    background-color: currentColor;
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-position: center;
    mask-position: center;
    -webkit-mask-size: contain;
    mask-size: contain;
    pointer-events: none;
}

/* THE COUNT MOVES OUT OF THE MIDDLE, because the icon is there now. A number on
   top of a picture is neither. It sits on the shoulder of the pin instead,
   which is where a count belongs and where it can overflow the circle without
   crowding it.

   :empty IS DOING REAL WORK HERE. A marker with no badge has a span with no
   child nodes, and this hides it -- no JavaScript change, and no blank chip
   hanging off every pin on the map. */
.trade-marker__face {
    position: absolute;
    top: -0.3rem;
    right: -0.3rem;
    min-width: 1.05rem;
    padding: 0 0.2rem;
    border-radius: 0.6rem;
    background-color: var(--colour-card-background);
    color: var(--colour-card-text);
    font-size: 0.65rem;
    line-height: 1.05rem;
    text-align: center;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
    pointer-events: none;
}

.trade-marker__face:empty {
    display: none;
}

/* ---------------------------------------------------------------------------
 * Walkers: members' avatars on the ground, on every view of the map
 *
 * assets/js/trade_map.js stands each one on its footing and turns it upright,
 * and assets/js/globe_view.js stands it out from the earth; the page decides
 * where they go. Here is only the shape of one, and the few states it can be in.
 * ------------------------------------------------------------------------ */

/* THE SAME 3D SPACE AS THE PINS, and taking no clicks: the map underneath has to
   keep receiving the drags that pan it and the taps that send your own avatar
   somewhere. */
.trade-map__walkers {
    position: absolute;
    inset: 0;
    transform-style: preserve-3d;
    pointer-events: none;
}

/* ON EVERY VIEW, flat, tilted and globe, unless the reader has put them away --
   the page's checkbox sets the layer's hidden attribute; see setWalkersShown()
   in assets/js/trade_map.js and assets/js/globe_view.js. */

/* ITS FOOTING IS ITS SOLES, NOT THE BOTTOM OF ITS BOX. An avatar is drawn on a
   square canvas whose ground line sits a little above the bottom, and
   --trade-walker-soles is how far down the box that line is -- handed on from
   the server, which knows the canvas (AVATAR_GROUND_LINE / AVATAR_CANVAS_SIZE).
   The margins put the soles on the layer's origin, so the translate the script
   writes stands them on the coordinate, and the turn upright on the tilted map
   happens about the line they stand on.

   THE BOX IS --trade-walker-size, which the script sets for the zoom -- see
   describeWalkerBoxPixels(). Every avatar gets the same box; how tall each one
   stands in it is its own height, drawn into the figure, so a tall member next
   to a short one is taller by exactly the difference they chose. */
/* NOBODY IS DRAWN WHILE THE ZOOM IS MOVING, nor until the ground for the zoom it
   landed on has been drawn under them. Hidden rather than emptied: the figures,
   their names and their bubbles are all still there, and come back in place
   without being built again. See placeAllWalkers() in assets/js/trade_map.js. */
/* WHAT STANDS IN FRONT OF A FIGURE, drawn over it: a patch of the plants' sheets on the flat map, and a card turned up
   with the figure on the tilted one. Placed, sized and turned by the script -- see coverWalker() in
   assets/js/trade_map.js. */
.trade-walker__cover {
    position: absolute;
    left: 0;
    top: 0;
    image-rendering: pixelated;
    pointer-events: none;
}

.trade-walker__cover[hidden] {
    display: none;
}

.trade-map__walkers--waiting {
    visibility: hidden;
}

.trade-walker {
    --trade-walker-box: var(--trade-walker-size, var(--trade-figure-size));

    position: absolute;
    left: 0;
    top: 0;
    width: var(--trade-walker-box);
    height: var(--trade-walker-box);
    margin-left: calc(var(--trade-walker-box) / -2);
    margin-top: calc(var(--trade-walker-box) * var(--trade-walker-soles, 0.96875) * -1);
    transform-origin: 50% calc(var(--trade-walker-soles, 0.96875) * 100%);
}

/* WALKED OUT OF SIGHT RATHER THAN BLINKED OUT. A member who goes offline, or whose home this viewer may not know,
   leaves the map over a moment instead of disappearing between two frames. Put on whichever parts a surface drew the
   walker from -- the figure, the bubble over its head, the canvas that covers it -- which is why this is one rule and
   not a modifier of each. See removeWalker() in assets/js/trade_map.js. */
.trade-walker--leaving {
    opacity: 0;
    transition: opacity 900ms ease-out;
    pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
    .trade-walker--leaving {
        transition: none;
    }
}

/* A SHADOW AT ITS FEET, so a figure looks stood on the map rather than pasted
   over it. Drawn in the figure's own upright plane, as a flat ellipse centred on
   the soles, which from the tilt the map is viewed at reads as the same thing.
   In shares of the box, so it grows and shrinks with the figure. */
.trade-walker::before {
    content: "";
    position: absolute;
    left: 32%;
    top: calc(var(--trade-walker-soles, 0.96875) * 100%);
    width: 36%;
    height: 7%;
    translate: 0 -50%;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.28);
}

.trade-walker__figure {
    position: relative;
    display: block;
    width: 100%;
    height: 100%;
    transform-origin: 50% calc(var(--trade-walker-soles, 0.96875) * 100%);
}

/* A PERSON, NOT A PORTRAIT. The figure is the one the farm scene draws -- full
   length, on nothing -- and the site's .avatar would still give it a white tile,
   a border and rounded corners, which is right in a list of members and wrong
   for somebody walking across a map. The scene takes the tile away from its
   figures for the same reason. */
.trade-walker__figure .avatar {
    display: block;
    width: 100%;
    height: 100%;
    background: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    overflow: visible;
}

/* FACING THE WAY IT IS GOING. Only the figure turns; the bubble above it stays
   readable. */
.trade-walker--facing-left .trade-walker__figure {
    transform: scaleX(-1);
}

/* A LITTLE BOB WHILE WALKING, which is most of what makes a figure sliding across
   a map read as somebody walking. Not for anybody who has asked for less motion. */
/* A SHARE OF THE BOX, so the step is the same size to the figure at every zoom
   -- and never less than a pixel, or a small figure's walk would not show. */
@keyframes trade-walker-step {
    0%, 100% { translate: 0 0; }
    50%      { translate: 0 min(-1px, -3%); }
}

.trade-walker--walking .trade-walker__figure {
    animation: trade-walker-step 0.42s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
    .trade-walker--walking .trade-walker__figure {
        animation: none;
    }
}

/* YOUR OWN AVATAR, marked so it can be found among everybody else's: a ring on
   the ground round its feet. */
.trade-walker--self::after {
    content: "";
    position: absolute;
    left: 22%;
    top: calc(var(--trade-walker-soles, 0.96875) * 100%);
    width: 56%;
    height: 13%;
    translate: 0 -50%;
    border: 2px solid var(--colour-accent, #2f6f4e);
    border-radius: 50%;
}

/* WHAT AN AVATAR SAYS, over its head. Short and quiet: it is a line of a
   conversation going on around the map, not a notice. */
.trade-walker__bubble {
    position: absolute;
    left: 50%;
    bottom: calc(100% + 0.3rem);
    translate: -50% 0;
    width: max-content;
    max-width: 11rem;
    padding: 0.25rem 0.45rem;
    border-radius: 0.5rem;
    background: var(--colour-card-background, #ffffff);
    color: var(--colour-card-text, #1a1a19);
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
    font-size: 0.68rem;
    line-height: 1.25;
    text-align: center;
    white-space: normal;
}

/* THE TAIL, pointing down at whoever is speaking. */
/* A NAME OVER THE HEAD, for somebody showing as online and for a visitor -- see modules/trade/map_walkers.php. Small,
   and on a dark ground of its own so it reads over grass and water alike; what they say goes above it. */
.trade-walker__name {
    position: absolute;
    left: 50%;
    bottom: calc(100% + 0.1rem);
    translate: -50% 0;
    width: max-content;
    max-width: 9rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    padding: 0.05rem 0.35rem;
    border-radius: 0.4rem;
    background: rgba(12, 16, 20, 0.62);
    color: #ffffff;
    font-size: 0.62rem;
    line-height: 1.3;
    pointer-events: none;
}

.trade-walker--named .trade-walker__bubble {
    bottom: calc(100% + 1.3rem);
}

.trade-walker__bubble::after {
    content: "";
    position: absolute;
    left: 50%;
    top: 100%;
    translate: -50% 0;
    border: 0.3rem solid transparent;
    border-top-color: var(--colour-card-background, #ffffff);
}

/* A TOWN OR CITY: the dot is the place and the name stands over it.
   ---------------------------------------------------------------------------
   THE LABEL IS PLACED WITH ITS BOTTOM EDGE ON THE POINT -- see
   drawBoundaryLabels() in assets/js/trade_map.js -- so the dot hanging half
   below that edge is centred exactly on the coordinate, and the name sits clear
   above it rather than covering the thing it names. */
.trade-map__boundary-label--place {
    padding-bottom: 0.3rem;
    font-weight: var(--font-weight-semibold);
}

.trade-map__boundary-label--place::after {
    content: "";
    position: absolute;
    left: 50%;
    bottom: -0.16rem;
    width: 0.32rem;
    height: 0.32rem;
    margin-left: -0.16rem;
    border: 1px solid rgba(255, 255, 255, 0.85);
    border-radius: 50%;
    background-color: currentColor;
}

/* A COUNTY SEAT: the mark stands on the courthouse.
   ---------------------------------------------------------------------------
   NO HALO AND NO WEIGHT. The place marks below are words and need lifting off
   the ground they are written on; this is a picture, and outlining it only
   makes it muddy. It is placed with its bottom edge on the point -- see
   drawBoundaryLabels() in assets/js/trade_map.js -- so what sits on the
   courthouse is the foot of the building, which is where a building meets the
   ground it is on. */
.trade-map__boundary-label--seat {
    /* A SHADE LARGER THAN A NAME, because it is a picture and a picture at the
       size of the letters beside it reads as smaller than them. Written against
       the same token so it grows with the names rather than staying put while
       they move. */
    font-size: calc(var(--font-size-base, 1rem) * 1.15 * var(--trade-map-label-scale, 1));
    line-height: 1;
    text-shadow: none;

    /* THE MARK IS THE WHOLE OF IT, so nothing should catch the pointer except
       the mark. A label box is wider than what is drawn in it. */
    padding: 0;
}

/* ---------------------------------------------------------------------------
 * Map furniture
 * ------------------------------------------------------------------------ */

.trade-map__controls {
    position: absolute;
    top: var(--container-padding-tight);
    right: var(--container-padding-tight);
    display: flex;
    flex-direction: column;
    gap: var(--space-extra-small);
}

.trade-map__control {
    width: 2rem;
    height: 2rem;
    padding: 0;
    border: 1px solid var(--colour-border);
    border-radius: var(--radius-small);
    background-color: var(--colour-card-background);
    color: var(--colour-card-text);
    font-size: 1rem;
    line-height: 1;
    cursor: pointer;
}

.trade-map__status {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    max-width: 24rem;
    margin: 0;
    padding: var(--container-padding-tight);
    border-radius: var(--radius-small);
    background-color: var(--colour-card-background);
    color: var(--colour-card-text);
    text-align: center;
    box-shadow: 0 1px 6px rgba(0, 0, 0, 0.2);
}

.trade-map__attribution {
    position: absolute;
    right: 0;

    /* ABOVE A TOOLBAR ALONG THE MAP'S FOOT, where the page has one -- see .trade-map-toolbar in module_maps.css. */
    bottom: var(--trade-map-toolbar-space, 0px);
    margin: 0;
    padding: 0.15rem 0.4rem;
    background-color: rgba(255, 255, 255, 0.75);
    color: #1c2230;
    font-size: 0.7rem;

    /* REQUIRED, NOT DECORATIVE. OpenStreetMap's licence asks for visible credit
       wherever its tiles are shown, so this is not something to remove for
       tidiness -- it comes off when the tiles do. */
    border-top-left-radius: var(--radius-small);
}

/* ---------------------------------------------------------------------------
 * The globe
 *
 * THE SAME WORLD, REPROJECTED ONTO A SPHERE. assets/js/globe_view.js reads the
 * tile pictures back and works out, for every pixel of the disc, which place on
 * the earth it is looking at -- so the coordinates are exact and the pins go in
 * the same places they do on the flat map.
 *
 * IT LIES OVER THE FLAT MAP RATHER THAN REPLACING IT. Both views hold the same
 * markers and the toggle only changes which one is shown, so switching costs
 * nothing and neither view loses where it was looking.
 *
 * NOTHING HERE POSITIONS A PIN, for the same reason nothing above does: the
 * positions change as the globe turns and are written from the script.
 * ------------------------------------------------------------------------ */

.globe {
    position: absolute;
    inset: 0;

    /* SPACE, AROUND THE EARTH. Everything off the disc is cleared to nothing,
       so this is what surrounds it -- and it was the map's ground colour, which
       made the globe a sand disc on sand. Black, with the star field in front of
       it and behind the canvas; see buildStarField() in globe_view.js. */
    background-color: #000;

    cursor: grab;

    /* The globe handles its own dragging, so the browser must not also try to
       pan the page when a drag starts on it. */
    touch-action: none;
}

.globe.is-dragging {
    cursor: grabbing;
}

.globe__canvas {
    /* THE BACKING STORE IS SIZED IN DEVICE PIXELS AND THE ELEMENT IN CSS ONES,
       which is the whole of how the canvas stays sharp on a dense screen --
       see MAXIMUM_RENDER_SCALE. Without this the canvas would be drawn at its
       pixel size and the globe would be the wrong size on half of all phones. */
    display: block;
    width: 100%;
    height: 100%;

    /* POSITIONED, SO IT PAINTS OVER THE STARS. The star field is absolutely
       placed, and an absolute element paints above anything that is not
       positioned whatever the order of the markup. Positioned too, the canvas
       comes after it in the markup and so in front of it. */
    position: relative;
}

/* THE DESK GLOBE'S TWO CANVASES, behind the earth and in front of it: pixel art the size of the globe's own pixels,
   stretched without smoothing. Never in the way of a drag. See "The desk globe" in globe_view.js. */
.globe__scene {
    position: absolute;
    left: 0;
    top: 0;
    pointer-events: none;
    image-rendering: pixelated;
}

/* THE PLANTS STANDING ON THE EARTH AND THE LINES OVER THEM, each a canvas laid exactly over the earth's, so the wind
   can draw the plants again without the earth under them. Never in the way of a drag. See drawPlantLayer() in
   globe_view.js. */
.globe__plants,
.globe__lines {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
}

/* WHAT STANDS IN FRONT OF SOMEBODY ON THE GLOBE, over them and under anybody further forward. Placed and sized by the
   script. See coverWalker() in globe_view.js. */
.globe__cover {
    position: absolute;
    left: 0;
    top: 0;
    transform-origin: 0 0;
    pointer-events: none;
}

.globe__cover[hidden] {
    display: none;
}

/* ON A DESK THE GLOBE IS IN A ROOM, not in space: the reader's surface colour lit from above in their accent, as the
   gallery's wall is, and no stars. */
.globe.globe--scene-desk {
    overflow: hidden;
    background-color: var(--colour-trade-map-surface);
    background-image:
        radial-gradient(
            90% 70% at 50% 0%,
            color-mix(in srgb, var(--colour-accent, #c08a2a) 18%, transparent) 0%,
            transparent 72%
        ),
        radial-gradient(
            140% 120% at 50% 40%,
            transparent 48%,
            color-mix(in srgb, #000000 34%, transparent) 100%
        );
}

/* THE STARS: vector circles, so they stay pin sharp at any size or pixel density.
   They fill the globe's box and never move -- the earth turns, the sky does not. */
.globe__stars {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
}

.globe__star {
    fill: #fff;
}

.globe__markers {
    position: absolute;
    inset: 0;

    /* THE PIN LAYER MUST NOT EAT THE DRAG, exactly as the flat map's does not.
       The pins themselves take pointer events back through .trade-marker. */
    pointer-events: none;
}

/* A PIN ROUND THE BACK IS GONE, NOT FAINT. It is hidden with the attribute
   rather than a class so it is out of the accessibility tree too: a market
   nobody can see is also a market nobody should be able to tab to. */
.globe__markers .trade-marker[hidden] {
    display: none;
}

/* THE PEOPLE ON THE GLOBE, above its pins and taking no clicks, like the flat
   map's. Each figure is a .trade-walker turned to stand out from the surface by
   the script -- see "Walkers" in assets/js/globe_view.js. */
.globe__walkers {
    position: absolute;
    inset: 0;
    overflow: hidden;
    pointer-events: none;
}

/* ON THE GLOBE A BUBBLE IS PLACED ON ITS OWN, upright beside a figure that may be
   standing sideways or hanging head down, so it gives up the flat map's place
   above the head and the tail that points at it -- and it is drawn over every
   figure, since it is the one thing in the crowd somebody reads. */
.trade-walker__bubble--globe {
    left: 0;
    top: 0;
    bottom: auto;
    translate: none;
    z-index: 20000;
}

.trade-walker__bubble--globe::after {
    display: none;
}

.trade-walker__bubble--globe.is-behind {
    display: none;
}

/* WHICH OF THE THREE VIEWS IS SHOWING. They are a group of buttons rather than
   one that cycles, so the one you are in has to look like it -- aria-pressed
   already says so to a screen reader and this is the same statement in paint. */
.trade-map__views {
    display: grid;
    gap: var(--space-extra-small);
}

.trade-map__view[aria-pressed="true"] {
    background-color: var(--colour-accent);
    color: var(--colour-accent-contrast, #fff);
}

/* ---------------------------------------------------------------------------
 * The tilted view
 *
 * THE DISTANCE IS FADED BECAUSE IT STOPS. A real horizon needs the far ground
 * drawn from lower zoom levels -- a tile up there is squeezed into a few pixels
 * and fetching it at full detail is waste -- and that is a bigger change than
 * this one. So the ground reaches a bounded distance and the last of it is
 * washed out into the surface colour, which reads as haze rather than as the
 * map having run out.
 *
 * IT IS OVER THE PINS AS WELL AS THE TILES, deliberately. A pin at the very back
 * is a place too far away to be picking out, and one drawn bright against faded
 * ground would be the first thing the eye went to.
 *
 * AND IT GOES SOFT AS WELL AS PALE, WHICH IS THE SAME FACT TWICE. Distance
 * costs contrast and it costs focus, so the veil and the blur are one element
 * with one falloff rather than two effects that have to be kept in step. The
 * mask is what makes it a gradient: it fades the element's own tint and the
 * blur it is applying to what is behind it together, so there is no line
 * anywhere across the map where either of them starts.
 *
 * ONLY THE DISTANCE, NOT THE FOREGROUND. A camera tilt-shift throws the near
 * field out of focus too, and on a map the near field is the part being read.
 * Blurring it would be an effect at the cost of the thing the effect is
 * decorating.
 * ------------------------------------------------------------------------ */

.trade-map__viewport.is-tilted::after {
    content: "";
    position: absolute;

    /* TALLER THAN THE OLD HAZE AND WEAKER THROUGHOUT. Reaching further down
       with less in it is what makes it read as air rather than as a panel laid
       over the top of the map.

       BUT NOT PAST HALF WAY. At 58% the soft edge reached the middle of the
       screen, where somebody is reading the trees and the grass, and the blur
       started on the ground they were looking at. */
    inset: 0 0 auto 0;
    height: 50%;

    /* A FLAT TINT, LEFT TO THE MASK TO GRADE. Putting a gradient here as well
       would fade it twice, which is how the first version of this ended up
       opaque at the top and gone by a third of the way down. */
    background-color: color-mix(
        in srgb, var(--colour-trade-map-surface) 48%, transparent
    );

    /* SLIGHT, AND SLIGHT IS THE POINT. Two pixels is enough to separate the far
       ground from the near without making the distance unreadable -- somebody
       should still be able to tell which country they are looking at up there.

       PREFIXED FOR SAFARI, which shipped this behind -webkit- and still needs
       it. Where it is unsupported the veil above is the whole effect, which is
       the previous behaviour and is not a broken page. */
    -webkit-backdrop-filter: blur(2px);
    backdrop-filter: blur(2px);

    -webkit-mask-image: linear-gradient(to bottom, #000 0%, transparent 100%);
    mask-image: linear-gradient(to bottom, #000 0%, transparent 100%);

    /* IT IS WEATHER, NOT A CONTROL. A drag that begins in the haze still pans
       the map underneath it. */
    pointer-events: none;
}

/* A LAYER TICK BOX, IN THE SETTINGS PANEL. The label wraps the input so the
   words are part of the target -- a bare checkbox is a nine pixel thing to hit
   on a phone. */
.trade-map__layer-toggle {
    display: flex;
    gap: var(--space-extra-small);
    align-items: center;
    cursor: pointer;

    /* BIG ENOUGH TO HIT WITH A THUMB. The label and its box together measured
       26 pixels tall, which is fine for a mouse and short of the roughly 44 a
       finger wants; the padding is what closes that without moving anything
       else in the panel. */
    min-height: 2.75rem;
    padding-block: var(--space-extra-small);
}

/* ---------------------------------------------------------------------------
 * Names on the outlines
 *
 * SIZED AND WEIGHTED FROM THE SITE'S OWN TYPE, and they were not. They were
 * pinned at 0.6875rem and weight 500 on the argument that a name in the site's
 * text would read as a caption laid over the map rather than as part of it.
 * That argument was wrong twice over: eleven pixels is small enough to be hard
 * to read at all, and pinning it meant somebody who turned the site's text up
 * -- which is offered, and is the setting a person turns up because they need
 * it -- moved every word on the site except the ones on the map.
 *
 * SO THEY COME FROM THE TOKENS NOW: the quiet size, the base family, and the
 * base weight with the same +100 lift the pinned 500 used to be. Change the
 * site's text size or its font and the map follows.
 *
 * AND THEY GROW AS THE MAP COMES IN, by --trade-map-label-scale, which the
 * script writes from the zoom -- see labelScaleAtZoom() in trade_map.js. Far
 * out a name labels a country and wants to be out of the way; close in it
 * labels a street's worth of ground and is the thing being read.
 *
 * A HALO INSTEAD OF A BACKGROUND. Cartography's answer to text over a busy
 * ground is to outline the letters in the ground's own colour; a filled box
 * behind each name would punch three dozen holes in the map.
 *
 * CENTRED ON THE PLACE. The script positions the element's origin at the
 * county's middle, and the translate here is what puts the middle of the words
 * there rather than their top left corner.
 * ------------------------------------------------------------------------ */

.trade-map__boundary-labels {
    position: absolute;
    inset: 0;
    pointer-events: none;
    transform-style: preserve-3d;

    /* HOW MUCH BIGGER THAN THE SITE'S QUIET TEXT THE NAMES ARE DRAWN. Written
       here as the default so the labels are legible with no script at all, and
       overwritten on this element by drawBoundaryLabels() as the zoom moves. */
    --trade-map-label-scale: 1;
}

.trade-map__boundary-label {
    position: absolute;
    top: 0;
    left: 0;

    /* EVERY TRANSFORM ON THESE IS MEASURED FROM THE TOP LEFT, which is what
       lets the script write "shift by half my own size, then turn, then go to
       the place" as one string and have the turn happen about the middle of
       the words. With the default 50% 50% origin the shift and the turn fight
       each other. A percentage margin cannot do this job: percentages there
       resolve against the *container's* width, not the element's. */
    transform-origin: 0 0;

    /* THE SITE'S OWN TEXT SIZE, times however much the zoom is worth.
       
       THE BODY SIZE AND NOT THE QUIET ONE, which was tried first and is what
       the old pinned 0.6875rem worked out to anyway on a site whose text is set
       small -- eleven pixels, the size that was hard to read. A name on a map
       is not a caption under something else; it is the only writing on that
       part of the screen and there is nothing beside it to be quiet against.
       
       The fallbacks are the computed defaults of these tokens, so a page that
       somehow loses the stylesheet still draws readable names rather than
       none. */
    font-family: var(--font-family-base, system-ui, sans-serif);
    font-size: calc(var(--font-size-base, 1rem) * var(--trade-map-label-scale, 1));

    /* A HUNDRED OVER THE SITE'S OWN WEIGHT, which is exactly what the pinned
       500 was against the default 400 -- kept as an offset so it follows a
       site set lighter or heavier, the way --font-weight-semibold does. */
    font-weight: calc(var(--font-weight-base, 400) + 100);
    line-height: 1.2;
    white-space: nowrap;
    text-align: center;

    color: var(--colour-trade-map-label, rgba(52, 44, 38, 0.9));
    text-shadow:
        0 0 2px var(--colour-trade-map-surface),
        0 0 3px var(--colour-trade-map-surface),
        0 0 4px var(--colour-trade-map-surface);

    /* A NAME IS NOT A TARGET. Clicking one has to pan the map underneath it. */
    pointer-events: none;
}

/* A LABEL NOBODY EVER SEES, which is how the globe finds out what a label is
   set in. The globe draws its names onto a canvas, and a canvas inherits no
   CSS -- so one real label is put in its container and measured. See
   labelTypeFromStyleSheet() in assets/js/globe_view.js.

   HIDDEN RATHER THAN UNDRAWN. display: none would do as well for the size,
   which is inherited, but visibility keeps it an ordinary element with an
   ordinary computed style and nothing here has to know which properties
   survive which. It has no text, so it covers nothing either way.

   AND NO SCALE OF ITS OWN, said outright rather than left to the fallback: the
   number read off it has to be the unscaled size, because the globe applies
   the zoom curve itself. */
.trade-map__label-probe {
    visibility: hidden;
    --trade-map-label-scale: 1;
}

/* THE SAME TRICK FOR THE GLOBE'S FILLS: a drawing nobody sees, wearing the flat
   map's classes, so the canvas can read what colour the sea, the lakes, the
   rivers and the fertility rings are. See readLayerStyles() in globe_view.js.
   No size and no place in the layout, so it moves nothing. */
.trade-map__style-probe {
    position: absolute;
    width: 0;
    height: 0;
    overflow: hidden;
    visibility: hidden;
    pointer-events: none;
}

/* THE SEA, AS ONE FILLED PATH UNDER THE COASTLINES IT IS CUT FROM.

   EVENODD IS THE WHOLE RULE. The path is a box with every coastline ring inside
   it, so a point is painted when an even number of rings surround it -- nought
   out at sea, one anywhere ashore, two on an island in a lake. The default
   nonzero rule would count winding direction instead and fill the land as well,
   because Natural Earth does not promise which way round a ring is drawn. It
   also paints the seams between neighbours' copies of a border as water; see
   .trade-map__ground-cover for what is done about that.

   SOLID, AND THAT IS THE POINT OF IT. It was see-through so as not to hide the
   layer underneath, which turned out to be exactly backwards: what is
   underneath at sea is SoilGrids guessing, and it guesses. Stray patches of
   clay and sand sit out in the Atlantic, and a sea you can see through shows
   them. Opaque, the water layer is also the mask that puts them away. */
.trade-map__ocean {
    fill: var(--colour-trade-map-sea, #2e6f9e);
    fill-rule: evenodd;
    fill-opacity: 1;
    stroke: none;

    /* A SEA IS NOT A TARGET, for the same reason a county name is not: the drag
       has to reach the map underneath it. */
    pointer-events: none;
}

/* THE GROUND, PAINTED BACK OVER THE SEA'S SEAMS while the map is bare ground.

   THE MAP'S OWN SURFACE COLOUR, which is sand with the Ground layer ticked and
   the dark open ground without it -- so it matches whichever is showing and
   needs no rule of its own for either.

   NONZERO, AND FILL ONLY. A point inside two countries' copies of a border is
   ground, not the sea even-odd made of it. It had a hairline stroke as well, to
   close the gaps where two copies part instead of crossing, and that was
   measured: the stroke took every frame of a drag at zoom four from 31 ms to
   94. The gaps it closed are a pixel wide; the fill alone costs nothing a drag
   can feel. */
.trade-map__ground-cover {
    fill: var(--colour-trade-map-surface);

    /* EVEN-ODD, AS THE SEA IS: the land's outline has no borders to overlap, and its
       holes are water -- the Caspian is a ring of coast with the sea inside it. */
    fill-rule: evenodd;
    stroke: none;
    pointer-events: none;
}

/* THE LAKES, FILLED FROM THEIR OWN RINGS. Lighter than the sea, because they
   are not it and a map that painted both the same would be saying the Great
   Lakes are a gulf.

   EVENODD AGAIN, and here it is what puts the islands in Superior back: Natural
   Earth gives them as further rings of the same shape, and an even number of
   rings around a point means it is not water. */
/* STROKED IN ITS OWN COLOUR, and the stroke's width set by the map at each zoom: a lake is drawn
   bigger far out -- see MapRivers.lakeSwellAt() in assets/js/map_rivers.js. */
.trade-map__lakes {
    fill: var(--colour-trade-map-lake, #4d9dc9);
    fill-rule: evenodd;
    fill-opacity: 1;
    stroke: var(--colour-trade-map-lake, #4d9dc9);
    stroke-width: 0;
    stroke-linejoin: round;
    pointer-events: none;
}

/* A LAKE'S NAME, WRITTEN THE WAY AN ATLAS WRITES WATER: italic, and in the
   water's own colour rather than the land's. It is the oldest convention on a
   map and it does real work -- it says "this word names the blue, not the green
   underneath it" without needing a line or a box to say so.

   DARKER THAN THE FILL IT SITS ON. The lake is drawn at #4d9dc9 and a label in
   that colour would vanish into it; this is the same hue taken most of the way
   to black, which reads on the water and still reads on the land when a small
   lake's name overhangs its own shore.

   IT KEEPS THE HALO the other labels have -- see .trade-map__boundary-label --
   which is what carries it across the boundary between the two. */
.trade-map__boundary-label--lake {
    color: var(--colour-trade-map-lake-label, #14496b);
    font-style: italic;
}
/* THE FERTILITY BANDS, FIVE NESTED FILLS AND A LINE AT EVERY STEP -- a contour
   map of the ground, read the way a walker reads height.

   THE COLOURS ARE MAPS_FERTILITY_BANDS, from climate_reference.php, kept here
   as literals and not as variables because they are that layer's own ramp and
   nothing else on the map may take them. Tan for ground that will grow little,
   through to the darkest green. If the ramp changes there, it changes here.

   EVENODD, LIKE THE LAKES. Marching squares gives a hole its own ring and makes
   no promise about which way round either is drawn, so the rule that counts
   crossings is the only one that puts the holes back.

   THE LINE IS THE BAND'S OWN EDGE, STROKED. On the rasterised version this was
   a sixth colour found by comparing neighbouring squares; here the ring already
   is the line, so it costs a stroke and stays a hairline at every zoom -- which
   was the whole point of not painting it back. */
.trade-map__fertility {
    fill-rule: evenodd;
    fill-opacity: 0.8;
    stroke: var(--colour-trade-map-fertility-line, #6b6b4a);
    stroke-width: 0.6px;
    stroke-opacity: 0.55;
    pointer-events: none;
}

.trade-map__fertility--1 { fill: #e0c0a0; }
.trade-map__fertility--2 { fill: #c0c080; }
.trade-map__fertility--3 { fill: #a0c060; }
.trade-map__fertility--4 { fill: #60a040; }
.trade-map__fertility--5 { fill: #206020; }

/* THE DEPTH RINGS, A CONTOUR MAP OF THE SEA READ THE OTHER WAY UP. Each band is
   the water deeper than one line, filled a shade darker than the water outside
   it, and the shades are the Ocean depth layer's key -- MAPS_OCEAN_DEPTH_STEPS
   in terrain_layers.php -- from the edge of the shelf down, so a ring and a
   change of shade there are the same place. Shallower than the first ring is the
   sea itself, turned the shelf colour below while these are up.

   OPAQUE, UNLIKE THE FERTILITY BANDS. Those shade the ground and let it show;
   under these there is only more sea. The line is the band's own edge, stroked,
   as the fertility bands' is. See tools/map_data/build_depth_bands.py. */
.trade-map__depth {
    fill-rule: evenodd;
    fill-opacity: 1;
    stroke: var(--colour-trade-map-depth-line, #0a2540);
    stroke-width: 0.6px;
    stroke-opacity: 0.3;
    pointer-events: none;
}

.trade-map__depth--1 { fill: #7cc0dc; }
.trade-map__depth--2 { fill: #4f9cc8; }
.trade-map__depth--3 { fill: #3a82b6; }
.trade-map__depth--4 { fill: #2b6aa0; }
.trade-map__depth--5 { fill: #205688; }
.trade-map__depth--6 { fill: #17436e; }
.trade-map__depth--7 { fill: #0f3154; }

/* THE SHADINGS DRAWN AS OUTLINES: the soil textures, the soil groups, the climate
   zones and the elevation rings. One path per class, its colour set by the script
   from the layer's own key -- through the style object, which the security policy
   allows -- so a module can offer a shading without a rule being added here.

   UNDER THE SEA, LIKE THE FERTILITY RINGS, whose overshoot at the coast the sea
   trims the same way. See writeShadingPaths() in trade_map.js. */
.trade-map__shading {
    fill-rule: evenodd;
    fill-opacity: 0.75;
    stroke: var(--colour-trade-map-shading-line, #4a4438);
    stroke-width: 0.5px;
    stroke-opacity: 0.35;
    pointer-events: none;
}

/* SEA LEVEL, WHERE A SHADING HAS ONE: the coastline, drawn as a line of its own, over
   every tint above it. A contour between two tints is the edge of a colour; the shore is
   the one edge on the terrain rings somebody could stand on, and it reads as a shore.
   The globe reads this rule too -- see readLayerStyles() in globe_view.js. */
.trade-map__shading--shoreline {
    fill: none;
    stroke: var(--colour-trade-map-shore-line, #24465f);
    stroke-width: 1px;
    stroke-opacity: 0.85;
}

/* THE PLANTS' CANVAS, placed by the script at the corner of the patch it was
   painted for, inside a sheet the map moves like any other. PIXELATED, so a zoom
   animation that stretches it before it is repainted keeps the art's hard edges
   rather than smearing them. See writeVegetationCanvas() in trade_map.js. */
.trade-map__vegetation-canvas {
    position: absolute;
    left: 0;
    top: 0;
    image-rendering: pixelated;
    pointer-events: none;
}

/* THE GLOW ROUND THE TREE, SHRUB OR FLOWER UNDER THE POINTER, on the plants' own sheet so the tilt lays it
   back with them. A cell of the plant's drawing to a pixel, kept square when it is stretched to the plant's
   size, and breathing slowly so it reads as a highlight rather than as part of the drawing. See pickPlant()
   in assets/js/trade_map.js. */
.trade-map__vegetation-aura {
    position: absolute;
    left: 0;
    top: 0;
    image-rendering: pixelated;
    pointer-events: none;
    animation: trade-map-aura-breathe 1.6s ease-in-out infinite;
}

@keyframes trade-map-aura-breathe {
    0%,
    100% {
        opacity: 1;
    }

    50% {
        opacity: 0.55;
    }
}

@media (prefers-reduced-motion: reduce) {
    .trade-map__vegetation-aura {
        animation: none;
    }
}

/* THE RELIEF'S CANVAS, placed like the plants' but left smooth: it is half the
   screen's resolution and a shade, not art, and scaled up with hard edges it showed
   as squares. See writeReliefCanvas() in trade_map.js. */
.trade-map__relief-canvas {
    position: absolute;
    left: 0;
    top: 0;
    pointer-events: none;
}

/* THE GRASS METER, shown only with ?grass=gpu or ?grass=canvas on the page's
   address: what the grass costs on this machine. In the corner clear of the zoom
   buttons and the key. See writeVegetationMeter() in trade_map.js. */
.trade-map__grass-meter {
    position: absolute;
    top: var(--container-padding-tight);
    left: var(--container-padding-tight);
    z-index: 5;
    padding: var(--container-padding-tight);
    border-radius: var(--radius-medium);
    background-color: rgba(12, 16, 22, 0.82);
    color: #e8f0e0;
    font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
    font-size: 0.75rem;
    line-height: 1.45;
    white-space: pre;
    pointer-events: none;
}

/* THE SEA AS THE SHALLOWS, while the depth rings are drawn on it: the water
   between the coast and the first ring, in the shade the depth layer's key gives
   the continental shelf. After .trade-map__ocean, so it wins. */
.trade-map__ocean--shelf {
    fill: var(--colour-trade-map-shelf, #a4d9ea);
}

/* A RIVER IS A STROKE AND NOT A FILL, which is the whole difference between
   this and the sea. Its width is written by the script, in pixels, from how
   many metres across the water is -- see writeRiverPaths() in trade_map.js.

   ROUNDED AT THE ENDS AND THE JOINS. Reaches meet end to end, and a mitred join
   on a meander throws a spike off the outside of every bend. */
.trade-map__river {
    fill: none;
    stroke: var(--colour-trade-map-river, #3f8fbf);
    stroke-linecap: round;
    stroke-linejoin: round;
    pointer-events: none;
}

/* A RIVER'S BANKS, two strokes under the water: earth outside, and the lighter sand at the
   water's edge -- the way a game map draws a river's shore. Widths are the script's; see
   map_rivers.js. */
.trade-map__river-bank,
.trade-map__river-sand,
.trade-map__river-rapids {
    fill: none;
    stroke-linecap: round;
    stroke-linejoin: round;
    pointer-events: none;
}

.trade-map__river-bank {
    stroke: var(--colour-trade-map-river-bank, #b98a56);
}

.trade-map__river-sand {
    stroke: var(--colour-trade-map-river-sand, #e3c692);
}

/* WHITE WATER over the stretches steep enough for rapids. */
.trade-map__river-rapids {
    stroke: var(--colour-trade-map-river-rapids, #eef8fb);
    stroke-opacity: 0.85;
}

/* A RIVER'S MOUTH, opening out into the sea and fading from the river's water into the sea's
   along a gradient whose stops take these colours. */
.trade-map__estuary {
    stroke: none;
    pointer-events: none;
}

.trade-map__estuary-river {
    stop-color: var(--colour-trade-map-river, #3f8fbf);
}

.trade-map__estuary-sea {
    stop-color: var(--colour-trade-map-sea, #2e6f9e);
}

.trade-map__estuary-sea--shelf {
    stop-color: var(--colour-trade-map-shelf, #a4d9ea);
}

/* THE ROADS BETWEEN THE COUNTY SEATS, drawn as a road atlas draws them: a line
   of ink at one width whatever the zoom, not a width of ground like the rivers.
   A highway to scale is a hairline until zoom twelve, and a road that cannot be
   seen cannot be followed. The interstates and US highways wider than the state
   routes. The globe reads these too -- see readLayerStyles() in globe_view.js. */
.trade-map__road {
    fill: none;
    stroke: var(--colour-trade-map-road, #8a5a3c);
    stroke-width: 1.2px;
    stroke-opacity: 0.85;
    stroke-linecap: round;
    stroke-linejoin: round;
    pointer-events: none;
}

.trade-map__road--primary {
    stroke: var(--colour-trade-map-road-primary, #b0412e);
    stroke-width: 2px;
}

/* THE GROUND, WHICH IS THE MAP WITH NOTHING ON IT. Bare sand, the same colour
   the fertility layer thins towards -- see MAPS_FERTILITY_THIN_TO in
   modules/maps/terrain_layers.php.

   IT MOVES THE VARIABLE RATHER THAN PAINTING A BACKGROUND, so everything that
   reads the surface colour follows it: the map's own ground, the haze at the
   horizon of the tilted view, and the halo behind every label. A name ringed in
   pale grey on sand would look pasted on. */
.trade-map--ground {
    --colour-trade-map-surface: #e3ce8c;
}

