/* Station picker map - styles for includes/maps/grec_station_map.html
   Load this on any page that includes the map, or it renders unstyled:

       <link rel="stylesheet" href="{% static 'css/grec-station-map.css' %}">
       {% include "includes/maps/grec_station_map.html" %}
       <script src="{% static 'js/grec-station-map.js' %}" defer></script>

   Self-contained, the same way pjm-map.css is: every rule is scoped to .gp-map
   and the palette lives on .gp-map, so the map does not depend on any page's
   stylesheet. The frame around it - the panel, the reset button, the readout -
   is page chrome and stays with that page; see grec-calculator.css.

   The class names the script toggles:
       .gp-map.is-zoomed          a state is selected
       .gp-pick.is-active         that state
       .gp-pick.is-dimmed         every other state
       .gp-station.is-visible     a town in the selected state
       .gp-station.is-selected    the chosen town

   To recolour one instance, set the custom properties below on a wrapper. */

.gp-map {
    --gp-bg: #f4f4ef;
    --gp-canada: #ebebe6;
    --gp-ctx: #e5e6df;
    --gp-state: #c7d0b3;
    --gp-state-hover: #aab98c;
    --gp-active: #818d61;
    --gp-dim: #e2e3dc;
    --gp-ink: #2c3e50;
    /* Cream with a dark ring, because the dots sit on the selected state's
       solid olive - a dark dot on that is nearly invisible. */
    --gp-dot: #fffdf7;
    --gp-dot-ring: #3d4430;
    --gp-selected: #c77d2e;
    --gp-line: #fff;

    /* Overwritten by the script on every zoom frame and whenever the map
       changes size. The fallback is the ratio the map has at its full width in
       a desktop column, so the dots stay sane if the script never runs. */
    --gp-unit: 0.9;

    display: block;
    width: 100%;
    height: auto;
    background-color: var(--gp-bg);
    /* Touch panning the page should not be hijacked by a tap on the map. */
    touch-action: manipulation;
}

/* --- Backdrop --------------------------------------------------------- */

.gp-map .gp-canada { fill: var(--gp-canada); }
.gp-map .gp-lakes { fill: var(--gp-bg); }

.gp-map .gp-ctx-state {
    fill: var(--gp-ctx);
    stroke: var(--gp-line);
    stroke-width: 1;
    stroke-linejoin: round;
    vector-effect: non-scaling-stroke;
}

/* --- Selectable states ------------------------------------------------ */

.gp-map .gp-pick { cursor: pointer; }

.gp-map .gp-state {
    fill: var(--gp-state);
    stroke: var(--gp-line);
    stroke-width: 1;
    stroke-linejoin: round;
    vector-effect: non-scaling-stroke;
    transition: fill .25s, opacity .25s;
}

.gp-map .gp-pick:hover .gp-state,
.gp-map .gp-pick:focus-visible .gp-state { fill: var(--gp-state-hover); }

.gp-map .gp-pick.is-active .gp-state { fill: var(--gp-active); }

/* "Unfocus the other parts": the states you did not pick fade back to the same
   grey as the out-of-region context, so only the chosen one carries colour. */
.gp-map .gp-pick.is-dimmed .gp-state {
    fill: var(--gp-dim);
    opacity: .75;
}

.gp-map .gp-pick.is-dimmed { cursor: default; }

/* Transparent hit targets, for the DC callout and the town dots. */
.gp-map .gp-hit {
    fill: transparent;
    stroke: none;
}

/* --- State abbreviations ---------------------------------------------- */

.gp-map .gp-labels { pointer-events: none; }

.gp-map .gp-abbr {
    font-family: inherit;
    font-size: 15px;
    font-weight: 700;
    letter-spacing: .04em;
    fill: var(--gp-ink);
    opacity: .65;
    transition: opacity .25s;
}

/* Once a state is chosen the map is zoomed into it, and the abbreviations of
   states half out of frame only add noise. */
.gp-map.is-zoomed .gp-labels { opacity: 0; }

/* --- Washington DC callout -------------------------------------------- */

.gp-map .gp-leader {
    stroke: var(--gp-ink);
    stroke-width: 1;
    opacity: .5;
    vector-effect: non-scaling-stroke;
}

.gp-map .gp-leader-dot {
    fill: var(--gp-ink);
    opacity: .6;
}

.gp-map .gp-abbr-callout { opacity: .85; }

.gp-map .gp-callout:hover .gp-abbr-callout,
.gp-map .gp-callout.is-active .gp-abbr-callout { fill: var(--gp-active); opacity: 1; }

.gp-map .gp-callout.is-dimmed { opacity: .3; }

/* The leader is drawn for the whole-region view; zoomed in it points off-screen. */
.gp-map.is-zoomed .gp-callout { opacity: 0; pointer-events: none; }

/* --- Town dots -------------------------------------------------------- */

/* Hidden until a state is chosen: 62 dots at once is a shotgun blast, and the
   point of the state step is to cut them down to the handful that apply. */
.gp-map .gp-station {
    opacity: 0;
    pointer-events: none;
    transition: opacity .25s;
}

.gp-map .gp-station.is-visible {
    opacity: 1;
    pointer-events: auto;
    cursor: pointer;
}

/* The r and font-size below multiply by --gp-unit, so a dot covers the same
   number of screen pixels in Illinois as in Washington DC - zoomed twenty times
   closer - and on a phone as on a laptop. The numbers in front of it are
   therefore CSS pixels as the reader sees them.

   r and font-size here are CSS geometry properties, so a browser that does not
   support them falls back to the r and font-size in the markup - the dots then
   grow with the zoom, which is worse but still usable. The attribute values are
   the whole-region sizes for that reason. */
.gp-map .gp-dot {
    r: calc(4px * var(--gp-unit));
    fill: var(--gp-dot);
    stroke: var(--gp-dot-ring);
    stroke-width: 1.4;
    vector-effect: non-scaling-stroke;
    transition: fill .15s, stroke .15s;
}

/* 22px across, so a dot is a usable target even under a thumb. */
.gp-map .gp-dot-hit { r: calc(11px * var(--gp-unit)); }

.gp-map .gp-station.is-visible:hover .gp-dot,
.gp-map .gp-station.is-visible:focus-visible .gp-dot {
    fill: var(--gp-selected);
    stroke: #fff;
}

.gp-map .gp-station.is-selected .gp-dot {
    fill: var(--gp-selected);
    stroke: #fff;
    stroke-width: 2;
}

.gp-map .gp-dot-label {
    font-family: inherit;
    font-size: calc(12px * var(--gp-unit));
    font-weight: 600;
    fill: var(--gp-ink);
    paint-order: stroke;
    stroke: var(--gp-bg);
    /* The halo behind the text has to shrink with the type, or it swallows it. */
    stroke-width: calc(3.5px * var(--gp-unit));
    stroke-linejoin: round;
    pointer-events: none;
    opacity: 0;
    transition: opacity .15s;
}

/* Every town in the open state wears its name. grec-station-map.js works out
   where each one goes; all this does is show them, and hold them back until the
   zoom has stopped and they have been placed. */
.gp-map .gp-station.is-visible .gp-dot-label,
.gp-map .gp-station.is-visible .gp-label-leader { opacity: 1; }

.gp-map.is-moving .gp-station.is-visible .gp-dot-label,
.gp-map.is-moving .gp-station.is-visible .gp-label-leader { opacity: 0; }

.gp-map .gp-station.is-visible:hover .gp-dot-label { fill: var(--gp-selected); }
.gp-map .gp-station.is-selected .gp-dot-label { fill: var(--gp-selected); }

/* Only drawn when the name had to be pushed clear of its neighbours. */
.gp-map .gp-label-leader {
    stroke: var(--gp-dot-ring);
    stroke-width: 1;
    opacity: 0;
    vector-effect: non-scaling-stroke;
    pointer-events: none;
    transition: opacity .15s, stroke .15s;
}

.gp-map .gp-station:not(.has-leader) .gp-label-leader { display: none; }

.gp-map .gp-station.is-visible:hover .gp-label-leader,
.gp-map .gp-station.is-selected .gp-label-leader { stroke: var(--gp-selected); }

@media (prefers-reduced-motion: reduce) {
    .gp-map .gp-state,
    .gp-map .gp-station,
    .gp-map .gp-dot,
    .gp-map .gp-dot-label,
    .gp-map .gp-abbr {
        transition: none;
    }
}
