/* ===========================================================================
   TeraMarker — the shared marker stylesheet
   ===========================================================================

   One rendering of `TeraUniversalMarkerPreset::toJsConfig()`, for every surface
   that paints a TeraMarker into a Mapbox map.

   WHY THIS FILE EXISTS
   --------------------
   The preset is already single-sourced on the server: `toJsConfig()` is one
   method with ~15 call sites. The PAINTER was not. Six surfaces each carried
   their own copy of these rules, and they had drifted — the scope editor
   preview rendered a lighter shadow than the scope embed it is supposed to be
   a preview OF, and the generator invented `.tera-marker-active` where
   everyone else says `.active`. That kind of split is how the inverting-preset
   bug got fixed on one surface and left standing on its twin.

   Of the 44 rules involved, 38 were already byte-identical across surfaces.
   This file is those 38, plus a decision on each of the six that were not.

   THE CONTRACT
   ------------
   Markup, either shape:

       <div class="tera-marker size-lg">            <-- Mapbox root
         <div class="tera-marker-inner">            <-- everything visual
           <span class="ico ico-n">…</span>         <-- normal glyph
           <span class="ico ico-h">…</span>         <-- highlighted glyph
           <span class="ico ico-a">…</span>         <-- active glyph
         </div>
       </div>

       <div class="tera-marker-wrapper">            <-- Mapbox root (labelled)
         <div class="tera-marker size-lg">…</div>
         <div class="tera-marker-label">…</div>
       </div>

   The builder sets eleven custom properties inline on the outer element and
   then never touches style again. State is a CLASS, and the stylesheet
   interpolates:

       --m-fill --m-border-color --m-border-width
       --m-hl-fill --m-hl-border-color --m-hl-border-width --m-hl-scale
       --m-active-fill --m-active-border-color --m-active-border-width
       --m-active-scale

   States: (resting) · :hover / .hl · .active

   `.hl` is the hover appearance driven from somewhere other than the pointer —
   a list row being hovered elsewhere on the page. Same declarations on
   purpose: two ways in, one appearance.

   HARD RULE #1 — MARKERS MOVE INSTANTLY
   -------------------------------------
   Mapbox writes `transform: translate(…)` onto whichever element you hand it
   as the marker root. Two consequences, both load-bearing:

     1. Nothing on that root may transition or animate. A transition there
        animates PIN POSITION on every pan and zoom.
     2. Nothing may overwrite that root's `transform`. Scale the INNER instead.
        (This is exactly the bug that put scope pins in the container corner:
        `el.style.transform = 'scale(1.3)'` replaced the translate.)

   The state scale is therefore derived as a size RATIO and applied to
   `.tera-marker-inner`, so the outer box never resizes and its anchor never
   shifts.

   ADOPTED BY
   ----------
   teramaps/catalogs/embed · teramaps/scopes/embed · teramaps/scopes/edit_with_preview
   Pending: teramaps_generator ×2 · teraevents/public/_view_map

   Anything surface-specific — the catalog's `.tera-nearby` recommendation
   pins, its `.lugmark` luggage badge, a page's own z-index ladder — stays with
   that surface. This file owns the marker, nothing around it.
   =========================================================================== */


/* --- 1. The Mapbox root -----------------------------------------------------
   Belt and braces: transition AND animation, prefixed, on every element that
   can end up being the root. The prefixed properties came from the scope
   editor and the rest from the embeds; keeping the superset costs nothing and
   the failure it prevents — pins gliding around during a pan — is ugly.

   The preset's decorative animations are unaffected: they ride on a dedicated
   `.tera-marker-fx` child, never the root. */
.mapboxgl-marker,
.tera-marker,
.tera-marker-wrapper {
    transition: none !important;
    -webkit-transition: none !important;
    -moz-transition: none !important;
    animation: none !important;
    -webkit-animation: none !important;
}

/* The current-scene halo is a soft box-shadow that has to bleed past the disc. */
.mapboxgl-marker { overflow: visible; }


/* --- 2. Outer element — pure layout box, no visual styling ---------------- */
.tera-marker {
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    box-sizing: border-box;
}

/* Fixed sizes. */
.tera-marker.size-xs  { width: 24px; height: 24px; }
.tera-marker.size-sm  { width: 32px; height: 32px; }
.tera-marker.size-md  { width: 40px; height: 40px; }
.tera-marker.size-lg  { width: 48px; height: 48px; }
.tera-marker.size-xl  { width: 56px; height: 56px; }
.tera-marker.size-2xl { width: 64px; height: 64px; }

/* Mid variants — fixed POI markers. */
.tera-marker.size-xs-mid  { width: 18px; height: 18px; }
.tera-marker.size-sm-mid  { width: 24px; height: 24px; }
.tera-marker.size-md-mid  { width: 30px; height: 30px; }
.tera-marker.size-lg-mid  { width: 36px; height: 36px; }
.tera-marker.size-xl-mid  { width: 42px; height: 42px; }
.tera-marker.size-2xl-mid { width: 48px; height: 48px; }

/* Reduced variants — non-selected markers in Show All mode. */
.tera-marker.size-xs-reduced  { width: 12px; height: 12px; opacity: 0.7; }
.tera-marker.size-sm-reduced  { width: 16px; height: 16px; opacity: 0.7; }
.tera-marker.size-md-reduced  { width: 20px; height: 20px; opacity: 0.7; }
.tera-marker.size-lg-reduced  { width: 24px; height: 24px; opacity: 0.7; }
.tera-marker.size-xl-reduced  { width: 28px; height: 28px; opacity: 0.7; }
.tera-marker.size-2xl-reduced { width: 32px; height: 32px; opacity: 0.7; }


/* --- 3. Inner element — the disc ------------------------------------------
   Carries shape, fill, border, shadow, and the only animated transform on the
   marker. Reads its colours and scales from the custom properties set inline
   on the outer at build time, so `:hover` and `.active` interpolate without
   any JS state-apply.

   Shadow is `0 2px 8px rgba(0,0,0,0.3)` — the embeds' value. The scope editor
   preview had `0 2px 4px rgba(0,0,0,0.2)`, which made the preview quietly
   lighter than the thing it is a preview of. */
.tera-marker-inner {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    border-style: solid;
    box-shadow: 0 2px 8px rgba(0,0,0,0.3);
    background: var(--m-fill);
    border-color: var(--m-border-color);
    border-width: var(--m-border-width, 2px);
    transform: scale(1);
    transform-origin: center;
    box-sizing: border-box;
    will-change: transform, background-color, border-color, border-width, box-shadow;
    transition:
        transform        0.2s cubic-bezier(0.4, 0, 0.2, 1),
        background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1),
        border-color     0.2s cubic-bezier(0.4, 0, 0.2, 1),
        border-width     0.2s cubic-bezier(0.4, 0, 0.2, 1),
        box-shadow       0.2s cubic-bezier(0.4, 0, 0.2, 1);
}


/* --- 4. Icon layers --------------------------------------------------------
   One layer per distinct content colour; CSS picks which is visible, so the
   glyph inverts along WITH the disc instead of vanishing into it.

   This is not decoration. On 360ATL every preset inverts between states — a
   red disc with a white glyph becomes a white disc with a red glyph — so a
   highlight that swapped only the disc left white-on-white and the pin read as
   empty. Every selector that changes the disc must change the layer too. */
.tera-marker-inner .ico {
    display: none;
    width: 100%;
    height: 100%;
    align-items: center;
    justify-content: center;
}
.tera-marker-inner .ico-n { display: flex; }

.tera-marker:hover .tera-marker-inner .ico-n,
.tera-marker-wrapper:hover .tera-marker-inner .ico-n,
.tera-marker.hl .tera-marker-inner .ico-n,
.tera-marker-wrapper.hl .tera-marker-inner .ico-n { display: none; }

.tera-marker:hover .tera-marker-inner .ico-h,
.tera-marker-wrapper:hover .tera-marker-inner .ico-h,
.tera-marker.hl .tera-marker-inner .ico-h,
.tera-marker-wrapper.hl .tera-marker-inner .ico-h { display: flex; }

.tera-marker.active .tera-marker-inner .ico-n,
.tera-marker.active .tera-marker-inner .ico-h,
.tera-marker.tera-marker-active .tera-marker-inner .ico-n,
.tera-marker.tera-marker-active .tera-marker-inner .ico-h,
.tera-marker-wrapper.active .tera-marker-inner .ico-n,
.tera-marker-wrapper.active .tera-marker-inner .ico-h { display: none; }

.tera-marker.active .tera-marker-inner .ico-a,
.tera-marker.tera-marker-active .tera-marker-inner .ico-a,
.tera-marker-wrapper.active .tera-marker-inner .ico-a { display: flex; }


/* --- 5. Highlighted (hover / .hl) ---------------------------------------- */
.tera-marker:hover .tera-marker-inner,
.tera-marker-wrapper:hover .tera-marker-inner,
.tera-marker.hl .tera-marker-inner,
.tera-marker-wrapper.hl .tera-marker-inner {
    transform:    scale(var(--m-hl-scale, 1.15));
    background:   var(--m-hl-fill,         var(--m-fill));
    border-color: var(--m-hl-border-color, var(--m-border-color));
    border-width: var(--m-hl-border-width, var(--m-border-width, 2px));
}


/* --- 6. Active -------------------------------------------------------------
   Deliberately AFTER `:hover` so it wins for a marker that is both.

   `.tera-marker-active` is the generator's spelling of the same state, carried
   here as an alias so the two vocabularies can coexist while surfaces migrate.
   It is a transition measure — once the generator is on the shared builder it
   should say `.active` like everything else, and this alias goes. */
.tera-marker.active .tera-marker-inner,
.tera-marker.tera-marker-active .tera-marker-inner,
.tera-marker-wrapper.active .tera-marker-inner {
    transform:    scale(var(--m-active-scale, 1.3));
    background:   var(--m-active-fill,         var(--m-fill));
    border-color: var(--m-active-border-color, var(--m-border-color));
    border-width: var(--m-active-border-width, var(--m-border-width, 2px));
    box-shadow:   0 4px 14px rgba(0,0,0,0.4);
}

/* Icon scaling rides along with the disc.

   DESCENDANT, not child. An inverting preset nests each icon inside its own
   `.ico` layer, so `>` would miss every one of them -- and inverting presets
   are exactly the case this whole file exists to get right. The catalog's copy
   used `>`; the embed's used the descendant form, and the embed was correct.

   The opacity leg came from TeraEvents, whose glyphs cross-fade; harmless
   where nothing changes opacity. */
.tera-marker-inner img,
.tera-marker-inner .material-icons {
    transition:
        transform 0.2s cubic-bezier(0.4, 0, 0.2, 1),
        opacity   0.15s ease-out;
}

/* Raster/SVG marker content, sized off the disc rather than its own attributes. */
.tera-marker img {
    display: block;
    object-fit: contain;
    max-width: 70%;
    max-height: 70%;
}

.tera-marker .material-icons {
    line-height: 1;
}


/* --- 7. Label ("show name always") -----------------------------------------
   Flex column, NOT position:absolute — an absolutely-positioned label needs a
   positioned ancestor, and making the marker root `position: relative` is what
   caused the original marker drift.

   `pointer-events: none` came from the editor and the ellipsis from the embed;
   both are wanted. A label wide enough to need truncating is also wide enough
   to swallow clicks meant for the pin behind it. */
.tera-marker-wrapper {
    display: flex;
    flex-direction: column;
    align-items: center;
}

.tera-marker-label {
    margin-top: 4px;
    padding: 2px 8px;
    border-radius: 4px;
    font-size: 11px;
    font-weight: 500;
    white-space: nowrap;
    pointer-events: none;
    background: rgba(0, 0, 0, 0.75);
    color: #fff;
    max-width: 150px;
    overflow: hidden;
    text-overflow: ellipsis;
}


/* --- 8. Tooltip ------------------------------------------------------------
   Above the pin, never interactive — a tooltip that can be hovered is a
   tooltip that flickers. Opacity and visibility only: the pin must not move,
   and neither must this. */
.tera-tip {
    position: absolute;
    left: 50%;
    bottom: calc(100% + 9px);
    transform: translateX(-50%);
    max-width: 220px;
    padding: 5px 9px;
    border-radius: 2px;
    /* The preset carries `tooltip.bgColor` / `textColor` and they were ignored
       everywhere — every tooltip on the platform rendered near-black regardless
       of what the tenant authored. The custom properties are set by whichever
       painter builds the tip; the literals below stay as the fallback so a
       preset with no tooltip colours looks exactly as it always did. */
    background: var(--tip-bg, #231f20);
    color: var(--tip-fg, #fff);
    font-size: 11.5px;
    font-weight: 600;
    line-height: 1.25;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    box-shadow: 0 2px 10px rgba(35, 31, 32, .35);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity .12s linear, visibility 0s linear .12s;
}
.tera-tip-sub {
    display: block;
    font-size: .88em;
    font-weight: 400;
    opacity: .8;
    margin-top: 1px;
    overflow: hidden;
    text-overflow: ellipsis;
}
.tera-tip::after {
    content: "";
    position: absolute;
    top: 100%;
    left: 50%;
    margin-left: -4px;
    border: 4px solid transparent;
    border-top-color: var(--tip-bg, #231f20);
}
.tera-marker:hover .tera-tip,
.tera-marker:focus-visible .tera-tip,
.tera-marker.hl .tera-tip {
    opacity: 1;
    visibility: visible;
    transition-delay: 0s;
}

/* A hovered pin, and its tooltip, come to the front of the pile.

   `!important` is load-bearing here, not laziness. The shared builder writes an
   inline z-index onto each marker root to rank pins by size (1-10), and an
   inline value outranks an ordinary rule — so without this a small pin could
   not lift above a large one when pointed at. A stylesheet `!important` does
   outrank inline, which is exactly the override wanted: size decides the
   resting order, the pointer decides the top. */
.tera-marker:hover,
.tera-marker.hl { z-index: 20 !important; }


/* --- 9. Preset badge -------------------------------------------------------
   Status disc — a checkmark or similar — pinned to a corner of the disc, 15%
   inset from the edge to match the teramarkers page preview. */
.tera-marker-badge {
    position: absolute;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 2px solid #fff;
    box-shadow: 0 2px 4px rgba(0,0,0,0.25);
    pointer-events: none;
    z-index: 6;
}
.tera-marker-badge.pos-top-left     { top: 15%;    left: 15%;  transform: translate(-50%,-50%); }
.tera-marker-badge.pos-top-right    { top: 15%;    right: 15%; transform: translate(50%,-50%); }
.tera-marker-badge.pos-bottom-left  { bottom: 15%; left: 15%;  transform: translate(-50%,50%); }
.tera-marker-badge.pos-bottom-right { bottom: 15%; right: 15%; transform: translate(50%,50%); }
.tera-marker-badge .material-icons { line-height: 1; }
.tera-marker-badge svg { width: 100%; height: 100%; }


/* --- 10. Decorative animations (preset.animation) --------------------------
   `none | pulse | shine | shine2 | heartbeat`, with `glow` as the legacy alias
   for shine2.

   All of them render on a dedicated overlay BEHIND the disc, and touch only
   that overlay's own box-shadow and transform — never Mapbox's root transform,
   never the inner disc's state-scale transform. That separation is why the
   `animation: none !important` guard in section 1 does not disable them. */
.tera-marker-fx {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    pointer-events: none;
    z-index: -1;
}
.tera-fx-pulse { animation: tera-fx-pulse 2s ease-out infinite; }
.tera-fx-glow,
.tera-fx-shine2 { animation: tera-fx-glow 2.4s ease-in-out infinite; }
.tera-fx-shine { box-shadow: 4px 4px 16px 0 var(--tera-anim-color, rgba(255,255,255,0.8)); }
.tera-fx-heartbeat { animation: tera-fx-heartbeat 1.6s ease-in-out infinite; }

@keyframes tera-fx-pulse {
    0%   { box-shadow: 0 0 0 0 var(--tera-anim-color, rgba(139,92,246,0.5)); }
    70%  { box-shadow: 0 0 0 12px rgba(0,0,0,0); }
    100% { box-shadow: 0 0 0 0 rgba(0,0,0,0); }
}
@keyframes tera-fx-glow {
    0%,100% { box-shadow: 0 0 12px 1px var(--tera-anim-color, rgba(139,92,246,0.6)); }
    50%     { box-shadow: 0 0 26px 6px var(--tera-anim-color, rgba(139,92,246,0.6)); }
}
@keyframes tera-fx-heartbeat {
    0%,26%,52%,100% { transform: scale(1); }
    13%,39%         { transform: scale(1.18); }
}

/* Current-scene halo — a static glow around the pin the visitor is standing
   in. No expanding ring: Mapbox's marker wrapper clipped it. Colour comes from
   the preset's animation colour, falling back to brand aqua. */
.tera-marker.tera-marker-current .tera-marker-inner {
    box-shadow:
        0 0 0 3px    rgba(var(--tera-current-glow-rgb, 46, 167, 159), 0.55),
        0 0 10px 4px rgba(var(--tera-current-glow-rgb, 46, 167, 159), 0.85),
        0 0 20px 8px rgba(var(--tera-current-glow-rgb, 46, 167, 159), 0.45);
}

/* ===========================================================================
   Animated (Lottie) content
   ===========================================================================
   The player is a custom element loaded lazily, so between first paint and
   `customElements.define` it is an unknown tag with no dimensions. Without the
   :defined guard the marker shows an empty disc for as long as the ~384KB
   player takes to arrive — which on a cold mobile connection is exactly when a
   visitor is looking at it.

   So: hide the player until it upgrades, show the static still meanwhile, then
   drop the still. The still is also the printed-map asset, so a marker that
   never animates is still the right picture.

   Order matters — the player is rendered BEFORE the still because CSS can only
   hide a LATER sibling. */
.tera-marker lottie-player:not(:defined) {
    display: none;
}
.tera-marker lottie-player:defined ~ .tera-marker-lottie-still {
    display: none;
}
.tera-marker .tera-marker-lottie,
.tera-marker .tera-marker-lottie-still {
    pointer-events: none;
}

/* ===========================================================================
   Cluster bubbles
   ===========================================================================
   Same disc as a pin, sized from the manifest's buckets rather than from the
   count, so two surfaces clustering by different algorithms still agree on how
   a group of places looks.

   Deliberately NO transition on the bubble or its number: a cluster count is
   live data, and CLAUDE.md §6.6 forbids animating it. It must change the
   instant the map does. */
.tera-marker.tera-marker-cluster {
    cursor: pointer;
}
.tera-marker.tera-marker-cluster .tera-marker-inner {
    transition: none;
}
.tera-marker.tera-marker-cluster .tera-marker-inner > span {
    transition: none;
    user-select: none;
}

/* ===========================================================================
   Category tag (top-left)
   ===========================================================================
   The (sub)category detail circle built by TeraMarker.renderTag(): a flag, an
   icon, a DAM image or 1-2 characters. Sized in % of the disc, centred on the
   disc's outer edge at the top-left 45° point. Lives inside `.tera-marker-inner`, so it follows the state scale
   without a transition of its own -- and it must not pick up the icon rules
   above (70% max size, transform transition), hence the explicit resets.

   Fixed corners platform-wide: top-left = category tag, top-right = counts,
   bottom-left = saved in Digital Luggage, bottom-right = visited badge. */
.tera-marker-inner { position: relative; }

/* Centre ON the disc's outer edge at the top-left 45° point. Absolute % offsets
   resolve against the disc's PADDING box (inside its border), so for padding box
   P and border B the point is P*(0.5 - 0.35355) - 0.70711*B from that box's
   corner. The border width differs per state, hence one rule per state below,
   reading the same custom properties as the disc itself. */
.tera-marker-tag {
    position: absolute;
    top:  calc(14.645% - 0.70711 * var(--m-border-width, 2px));
    left: calc(14.645% - 0.70711 * var(--m-border-width, 2px));
    transform: translate(-50%, -50%);
    border-radius: 50%;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    container-type: size;
    line-height: 1;
    z-index: 4;
    pointer-events: none;
    box-sizing: border-box;
    transition: none !important;
}
.tera-marker:hover .tera-marker-tag,
.tera-marker-wrapper:hover .tera-marker-tag,
.tera-marker.hl .tera-marker-tag,
.tera-marker-wrapper.hl .tera-marker-tag {
    top:  calc(14.645% - 0.70711 * var(--m-hl-border-width, var(--m-border-width, 2px)));
    left: calc(14.645% - 0.70711 * var(--m-hl-border-width, var(--m-border-width, 2px)));
}
.tera-marker.active .tera-marker-tag,
.tera-marker.tera-marker-active .tera-marker-tag,
.tera-marker-wrapper.active .tera-marker-tag {
    top:  calc(14.645% - 0.70711 * var(--m-active-border-width, var(--m-border-width, 2px)));
    left: calc(14.645% - 0.70711 * var(--m-active-border-width, var(--m-border-width, 2px)));
}
.tera-marker .tera-marker-tag img {
    display: block;
    max-width: none;
    max-height: none;
    min-width: 0;
    min-height: 0;
    filter: none;
    transition: none !important;
}
.tera-marker .tera-marker-tag img.tera-marker-tag-fill {
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.tera-marker .tera-marker-tag img.tera-marker-tag-icon {
    width: 62%;
    height: 62%;
    object-fit: contain;
}
.tera-marker-tag-text {
    font-weight: 700;
    line-height: 1;
    font-family: 'Source Sans Pro', system-ui, sans-serif;
    color: inherit;
}
