/* ==========================================================
   CH NET GUARD — Contact Form 3D Card (Phase 7)
   ----------------------------------------------------------
   Loaded near the end of the CSS chain. Transforms the contact form from a
   static panel into an always-on 3D card:

     - Cursor-follow tilt (desktop)
     - Cursor-tracking radial glow on the surface
     - Cyber HUD corner brackets
     - Field-focus 3D lift (focused field translates forward
       in Z space with a teal underline that wipes in)
     - Subtle ambient breathing rotation when idle on desktop
     - Mobile keeps the field-lift effect but drops the
       cursor tilt + glow (they don't make sense for touch)

   We give the wrap data-3d-card so all of this only applies
   to the contact form, not other .contact-form-wrap usages.
   ========================================================== */

/* ----------------------------------------------------------
   ROOT — perspective + transform-style on the wrap so the
   inner form rotates as a 3D plane.
   ---------------------------------------------------------- */
.contact-form-wrap[data-3d-card] {
    position: relative;
    perspective: 1200px;
    perspective-origin: 50% 50%;
    /* Default vars — JS overwrites --cf-rx / --cf-ry on pointermove */
    --cf-rx: 0deg;
    --cf-ry: 0deg;
    --cf-mx: 50%;
    --cf-my: 50%;
}

.contact-form-wrap[data-3d-card] .contact-form {
    /* No transform-style: preserve-3d here. Originally we used it so the
       form sat in a 3D plane and children could translateZ. But that combo
       (preserve-3d + an animation rotating the form on idle) causes Chrome
       to lose hit-testing on the inputs over time — the form drifts into
       a rotated state and clicks stop registering. All field lifts are
       now translateY (2D), so we don't need the 3D plane at all. The
       rotateX/rotateY parallax still works in 2D space, with the wrap's
       perspective providing the apparent 3D look. */
    transform:
        rotateX(var(--cf-rx))
        rotateY(var(--cf-ry));
    transition: transform 280ms cubic-bezier(0.16, 1, 0.3, 1);
    will-change: transform;
}

/* While the user is actively moving the cursor over the card,
   reduce the transition so the tilt tracks faster. JS adds
   .is-tracking; on pointerleave it removes it and the card
   eases back to (0,0). */
.contact-form-wrap[data-3d-card].is-tracking .contact-form {
    transition: transform 80ms linear;
}

/* ----------------------------------------------------------
   CURSOR-FOLLOW GLOW
   A soft teal radial gradient layer sitting just under the
   form's content; reveals at cursor position. JS sets
   --cf-mx / --cf-my (in percent) on pointermove.
   ---------------------------------------------------------- */
.cf-glow {
    position: absolute;
    inset: 0;
    border-radius: var(--r-lg);
    pointer-events: none;
    z-index: 1;
    opacity: 0;
    transition: opacity 320ms var(--ease-out);
    background: radial-gradient(
        300px circle at var(--cf-mx) var(--cf-my),
        rgba(15, 181, 164, 0.16),
        rgba(15, 181, 164, 0.04) 35%,
        transparent 60%
    );
    mix-blend-mode: screen;
}
.contact-form-wrap[data-3d-card]:hover .cf-glow { opacity: 1; }

/* Make the form sit above the glow */
.contact-form-wrap[data-3d-card] .contact-form { position: relative; z-index: 2; }

/* ----------------------------------------------------------
   CORNER BRACKETS — match the quote modal aesthetic
   ---------------------------------------------------------- */
.cf-bracket {
    position: absolute;
    width: 22px;
    height: 22px;
    z-index: 3;
    pointer-events: none;
    border-style: solid;
    border-color: var(--accent-teal);
    border-width: 0;
    opacity: 0.55;
    transition: opacity 280ms var(--ease-out),
                width 280ms var(--ease-out),
                height 280ms var(--ease-out);
}
.cf-bracket--tl { top: -1px;    left: -1px;    border-top-width: 2px;    border-left-width: 2px; }
.cf-bracket--tr { top: -1px;    right: -1px;   border-top-width: 2px;    border-right-width: 2px; }
.cf-bracket--bl { bottom: -1px; left: -1px;    border-bottom-width: 2px; border-left-width: 2px; }
.cf-bracket--br { bottom: -1px; right: -1px;   border-bottom-width: 2px; border-right-width: 2px; }

.contact-form-wrap[data-3d-card]:hover .cf-bracket {
    opacity: 1;
    width: 30px;
    height: 30px;
}

/* When a field inside the card is focused, the brackets switch to gold —
   a subtle "you're filling this in" signal. */
.contact-form-wrap[data-3d-card].is-typing .cf-bracket {
    border-color: var(--accent-gold);
    opacity: 1;
}

/* ----------------------------------------------------------
   CARD BORDER — slightly brighter, with a teal halo on hover
   ---------------------------------------------------------- */
.contact-form-wrap[data-3d-card] .contact-form {
    border-color: rgba(15, 181, 164, 0.18);
    box-shadow:
        0 16px 40px -16px rgba(0, 0, 0, 0.45),
        0 0 0 1px rgba(15, 181, 164, 0.04) inset;
    transition:
        transform 280ms cubic-bezier(0.16, 1, 0.3, 1),
        border-color 320ms var(--ease-out),
        box-shadow  320ms var(--ease-out);
}
.contact-form-wrap[data-3d-card]:hover .contact-form {
    border-color: rgba(15, 181, 164, 0.35);
    box-shadow:
        0 24px 50px -16px rgba(0, 0, 0, 0.55),
        0 0 24px -8px rgba(15, 181, 164, 0.22),
        0 0 0 1px rgba(15, 181, 164, 0.08) inset;
}

/* ----------------------------------------------------------
   FIELD-FOCUS LIFT
   When the user focuses a field, that field translates a few
   pixels toward the camera and the focus underline wipes in
   from the left. The base .field is unchanged so non-3D-card
   usages elsewhere on the site still look the same.

   We target the focus-within state on the wrapper because
   <input> can't be a parent of the label visually.
   ---------------------------------------------------------- */
.contact-form-wrap[data-3d-card] .field {
    position: relative;
    /* IMPORTANT: do NOT use transform-style: preserve-3d here, and do NOT
       use translateZ on the field. When the parent form is in a 3D rotated
       state (cursor parallax) and the field has its own 3D plane, Chrome's
       hit-testing routinely fails: the user clicks the input, the focus
       fires for a millisecond, but then the focus gets lost because the
       click coordinates miss the input after the rotation. Net effect:
       the user can't type into the form.

       Using translateY instead achieves the same visual lift (a few pixels
       toward the user) without involving the 3D hit-test pipeline. */
    transform: translateY(0);
    transition: transform 320ms cubic-bezier(0.16, 1, 0.3, 1);
}
.contact-form-wrap[data-3d-card] .field:focus-within {
    transform: translateY(-6px);
}

/* Focus underline — animated, full width on focus */
.contact-form-wrap[data-3d-card] .field::after {
    content: '';
    position: absolute;
    left: 0; right: 0; bottom: 0;
    height: 2px;
    background: linear-gradient(90deg,
        var(--accent-teal),
        var(--accent-teal-2),
        var(--accent-gold));
    transform: scaleX(0);
    transform-origin: left center;
    transition: transform 380ms cubic-bezier(0.16, 1, 0.3, 1);
    border-radius: 2px;
    pointer-events: none;
    box-shadow: 0 0 12px rgba(15, 181, 164, 0.45);
}
.contact-form-wrap[data-3d-card] .field:focus-within::after {
    transform: scaleX(1);
}

/* Soft glow halo behind the focused field.
   IMPORTANT: this used to be z-index: -1 which caused click events to be
   captured by a phantom element in the 3D layer rather than the actual
   input. We now use z-index: 0 and let the input sit naturally above it. */
.contact-form-wrap[data-3d-card] .field::before {
    content: '';
    position: absolute;
    inset: -6px;
    border-radius: 14px;
    background: radial-gradient(60% 80% at 50% 50%,
        rgba(15, 181, 164, 0.10),
        transparent 70%);
    opacity: 0;
    transition: opacity 320ms var(--ease-out);
    pointer-events: none;
    z-index: 0;
}
.contact-form-wrap[data-3d-card] .field:focus-within::before {
    opacity: 1;
}

/* Make sure the actual input element sits above the halo pseudo-element. */
.contact-form-wrap[data-3d-card] .field input,
.contact-form-wrap[data-3d-card] .field textarea {
    position: relative;
    z-index: 1;
}

/* Textarea field gets the same treatment, just a touch less lift since
   it's already a tall element. */
.contact-form-wrap[data-3d-card] .field-textarea:focus-within {
    transform: translateY(-4px);
}

/* ----------------------------------------------------------
   SUBMIT BUTTON — kept in normal layout. We used to translateZ
   it forward 6px, but without preserve-3d on the parent that
   was a no-op visually, and it created an extra transform layer
   that hurt hit-testing alongside the rotated form.
   ---------------------------------------------------------- */
.contact-form-wrap[data-3d-card] .form-actions {
    position: relative;
}

/* ----------------------------------------------------------
   AMBIENT IDLE BREATHING — INTENTIONALLY REMOVED.
   This used to be a slow ±1° rotation on the form when idle, to make
   it feel alive. It turned out to be the root cause of a subtle bug:
   after the breathing animation rotated the form by some non-zero
   amount, Chrome's input hit-testing on the rotated form would start
   failing — cursor wouldn't change to text-cursor on hover, clicks
   wouldn't focus the inputs.
   The cursor parallax (when the user moves their mouse over the card)
   already provides plenty of "live" feel; we don't need breathing on
   top of it. Leaving this comment so future maintainers don't get
   tempted to add it back.
   ---------------------------------------------------------- */

/* ----------------------------------------------------------
   TOUCH / MOBILE — drop the 3D tilt + glow (no cursor to track);
   keep the field-focus lift since it works on virtual keyboards
   appearing too. Also remove the breathing animation.
   ---------------------------------------------------------- */
@media (hover: none), (pointer: coarse) {
    .contact-form-wrap[data-3d-card] {
        perspective: none;
    }
    .contact-form-wrap[data-3d-card] .contact-form {
        transform: none !important;
        animation: none !important;
    }
    .cf-glow { display: none; }
    /* Brackets stay — they look great on mobile too, just no hover state */
    .cf-bracket { opacity: 0.7; }
}

/* ----------------------------------------------------------
   REDUCED MOTION
   All transforms snap; no breathing; field-focus underline still
   appears but instantly (it's a UX necessity, not decoration).
   ---------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
    .contact-form-wrap[data-3d-card] .contact-form {
        transform: none !important;
        animation: none !important;
        transition: border-color 200ms linear, box-shadow 200ms linear;
    }
    .contact-form-wrap[data-3d-card] .field,
    .contact-form-wrap[data-3d-card] .field:focus-within {
        transform: none !important;
        transition: none !important;
    }
    .contact-form-wrap[data-3d-card] .field::after {
        transition: transform 1ms linear;
    }
    .cf-glow { display: none; }
}

/* ==========================================================
   FULL 3D ENTRANCE TREATMENT (Phase 8)
   ----------------------------------------------------------
   Adds a quote-modal-style cinematic entrance to the contact
   form card: it flies in from 3D space, the scan-sweep crosses
   the card, and the fields cascade in one after another.

   The entrance state lives on the wrap as classes:
     - .cf-pre-entrance  → set on initial render. Card is in its
                           hidden 3D pose, content hidden.
     - .cf-entering      → entrance is playing. Card transforms
                           to face-on, content cascade armed.
     - .cf-revealed      → entrance done, content visible, the
                           regular hover-tilt system takes over.

   The wrap itself gets the entrance transform; the inner .contact-form
   continues to handle the cursor parallax independently. They compose
   via nested transforms because the wrap has preserve-3d.
   ========================================================== */

.contact-form-wrap[data-3d-card] {
    /* preserve-3d is only needed during the entrance animation, where
       the wrap uses translateZ(-360px). After the wrap reaches
       .cf-revealed, transform is none, and keeping preserve-3d active
       interferes with child hit-testing over time. We drop it below. */
    transform-style: preserve-3d;
}
.contact-form-wrap[data-3d-card].cf-revealed {
    /* Flatten the 3D context once entrance is done — the cursor parallax
       still works fine in 2D (rotateX/rotateY without perspective context
       look identical at these small angles given the wrap's perspective). */
    transform-style: flat;
}

/* Initial pose: card is rotated edge-on, deep in Z, faded out.
   Gated on html.js-ready (set by the inline head script before any
   paint) so that no-JS users see the card in its final position
   instead of getting stuck in the pre-entrance pose forever.

   The !important on both opacity and transform defends against any
   other class on the same element (e.g. data-reveal's .is-visible)
   trying to override them before the entrance has fired. Without
   these, the form can end up rendered fully opaque but in the deep-Z
   hidden pose — looking like it's just gone from the page. */
html.js-ready .contact-form-wrap[data-3d-card]:not(.cf-entering):not(.cf-revealed) {
    transform:
        translateZ(-360px)
        rotateX(22deg)
        rotateY(-42deg)
        scale(0.88) !important;
    opacity: 0 !important;
}

/* The actual entrance — transition from pre-entrance pose to face-on.
   The .cf-entering class triggers it; we keep .cf-revealed for the
   final state so the inner cursor parallax can run cleanly without
   the wrap's transform interfering. */
.contact-form-wrap[data-3d-card].cf-entering {
    transform: translateZ(0) rotateX(0deg) rotateY(0deg) scale(1) !important;
    opacity: 1 !important;
    transition:
        transform 880ms cubic-bezier(0.16, 1, 0.3, 1),
        opacity 520ms var(--ease-out);
}

.contact-form-wrap[data-3d-card].cf-revealed {
    transform: none !important;
    opacity: 1 !important;
}

/* ----------------------------------------------------------
   POST-SUBMIT HARD RESET (Phase 8.1)
   Set by the Alpine form component after a successful submit
   via wrap.classList.add('cf-post-submit'). Forces the form
   back to a clean, fully-interactive state in case any of the
   3D/parallax/cascade rules left it in a partial state that
   blocks pointer events on the input fields.

   Removed automatically when the user starts interacting again
   — see contact-3d.js — but as long as it's on, it overrides
   every other 3D rule with explicit !important flat values.
   ---------------------------------------------------------- */
.contact-form-wrap[data-3d-card].cf-post-submit,
.contact-form-wrap[data-3d-card].cf-post-submit .contact-form {
    transform: none !important;
    transition: none !important;
    animation: none !important;
}
.contact-form-wrap[data-3d-card].cf-post-submit .field,
.contact-form-wrap[data-3d-card].cf-post-submit .field:focus-within {
    transform: none !important;
    transition: none !important;
}
.contact-form-wrap[data-3d-card].cf-post-submit .field input,
.contact-form-wrap[data-3d-card].cf-post-submit .field textarea {
    pointer-events: auto !important;
    /* Defensive against any z-index / stacking-context weirdness leftover
       from the 3D rotation cycle. */
    position: relative;
    z-index: 10;
}
.contact-form-wrap[data-3d-card].cf-post-submit .cf-glow,
.contact-form-wrap[data-3d-card].cf-post-submit::after {
    opacity: 0 !important;
    pointer-events: none !important;
}

/* ----------------------------------------------------------
   HOLOGRAPHIC SCAN SWEEP
   A vertical teal bar that crosses the card left-to-right
   exactly once when the entrance plays.
   ---------------------------------------------------------- */
.contact-form-wrap[data-3d-card]::after {
    content: '';
    position: absolute;
    top: 0;
    left: -30%;
    width: 28%;
    height: 100%;
    background: linear-gradient(
        90deg,
        transparent 0%,
        rgba(15, 181, 164, 0.04) 30%,
        rgba(20, 215, 195, 0.22) 50%,
        rgba(15, 181, 164, 0.04) 70%,
        transparent 100%
    );
    pointer-events: none;
    opacity: 0;
    z-index: 5;
    border-radius: var(--r-lg);
    mix-blend-mode: screen;
}
.contact-form-wrap[data-3d-card].cf-entering::after {
    animation: cf-scan 1100ms var(--ease-out) 200ms 1;
}
@keyframes cf-scan {
    0%   { opacity: 0; left: -30%; }
    20%  { opacity: 1; }
    100% { opacity: 0; left: 105%; }
}

/* ----------------------------------------------------------
   CASCADING FIELD REVEAL
   While in the pre-entrance and entering states, hide the
   inner content until each row has had its cue.
   We target direct children of .contact-form so the head, every
   form-row, every standalone field, the response banner, and
   the actions block each get their own slot.
   ---------------------------------------------------------- */
/* While in the pre-entrance and entering states, hide the inner content
   until each row has had its cue. Gated on js-ready so no-JS users get
   the fully-populated form. */
html.js-ready .contact-form-wrap[data-3d-card]:not(.cf-revealed) .contact-form > *:not(.hp-field) {
    opacity: 0;
    transform: translateY(20px);
}

/* The cascade runs once the wrap has reached .cf-revealed. */
.contact-form-wrap[data-3d-card].cf-revealed .contact-form > *:not(.hp-field) {
    opacity: 1;
    transform: translateY(0);
    transition:
        opacity 540ms var(--ease-out),
        transform 540ms cubic-bezier(0.16, 1, 0.3, 1);
}

/* Per-child delays — the form has at most ~7 children in normal use */
.contact-form-wrap[data-3d-card].cf-revealed .contact-form > *:nth-child(1)  { transition-delay: 120ms; }
.contact-form-wrap[data-3d-card].cf-revealed .contact-form > *:nth-child(2)  { transition-delay: 180ms; }
.contact-form-wrap[data-3d-card].cf-revealed .contact-form > *:nth-child(3)  { transition-delay: 240ms; }
.contact-form-wrap[data-3d-card].cf-revealed .contact-form > *:nth-child(4)  { transition-delay: 300ms; }
.contact-form-wrap[data-3d-card].cf-revealed .contact-form > *:nth-child(5)  { transition-delay: 360ms; }
.contact-form-wrap[data-3d-card].cf-revealed .contact-form > *:nth-child(6)  { transition-delay: 420ms; }
.contact-form-wrap[data-3d-card].cf-revealed .contact-form > *:nth-child(7)  { transition-delay: 480ms; }
.contact-form-wrap[data-3d-card].cf-revealed .contact-form > *:nth-child(8)  { transition-delay: 540ms; }
.contact-form-wrap[data-3d-card].cf-revealed .contact-form > *:nth-child(9)  { transition-delay: 600ms; }

/* Corner brackets snap in last — like the quote modal HUD-lock.
   Gated on js-ready so no-JS users see the brackets immediately. */
html.js-ready .contact-form-wrap[data-3d-card] .cf-bracket {
    opacity: 0;
    transition:
        opacity 280ms var(--ease-out),
        transform 280ms cubic-bezier(0.16, 1, 0.3, 1);
}
html.js-ready .contact-form-wrap[data-3d-card]:not(.cf-revealed) .cf-bracket--tl { transform: translate(-10px, -10px); }
html.js-ready .contact-form-wrap[data-3d-card]:not(.cf-revealed) .cf-bracket--tr { transform: translate( 10px, -10px); }
html.js-ready .contact-form-wrap[data-3d-card]:not(.cf-revealed) .cf-bracket--bl { transform: translate(-10px,  10px); }
html.js-ready .contact-form-wrap[data-3d-card]:not(.cf-revealed) .cf-bracket--br { transform: translate( 10px,  10px); }

.contact-form-wrap[data-3d-card].cf-revealed .cf-bracket {
    opacity: 0.75;
    transform: translate(0, 0);
}
.contact-form-wrap[data-3d-card].cf-revealed .cf-bracket--tl { transition-delay: 760ms; }
.contact-form-wrap[data-3d-card].cf-revealed .cf-bracket--tr { transition-delay: 800ms; }
.contact-form-wrap[data-3d-card].cf-revealed .cf-bracket--bl { transition-delay: 840ms; }
.contact-form-wrap[data-3d-card].cf-revealed .cf-bracket--br { transition-delay: 880ms; }

/* Subtle pulse on the brackets after they lock in — same beat as quote modal */
@keyframes cf-bracket-pulse {
    0%   { box-shadow: 0 0 0   rgba(15, 181, 164, 0); }
    40%  { box-shadow: 0 0 14px rgba(20, 215, 195, 0.7); }
    100% { box-shadow: 0 0 0   rgba(15, 181, 164, 0); }
}
.contact-form-wrap[data-3d-card].cf-revealed .cf-bracket {
    animation: cf-bracket-pulse 600ms var(--ease-out) 960ms 1;
}

/* ----------------------------------------------------------
   MOBILE — softer entrance (no flying-in-from-Z which can look
   weird on a narrow viewport), but keep the cascade.
   ---------------------------------------------------------- */
@media (max-width: 768px) {
    html.js-ready .contact-form-wrap[data-3d-card]:not(.cf-entering):not(.cf-revealed) {
        transform: translateY(40px) scale(0.96) !important;
        opacity: 0 !important;
    }
    .contact-form-wrap[data-3d-card].cf-entering {
        transform: translateY(0) scale(1) !important;
        transition: transform 620ms cubic-bezier(0.16, 1, 0.3, 1), opacity 420ms var(--ease-out);
    }
    /* Scan sweep is too distracting on small screens; drop it */
    .contact-form-wrap[data-3d-card]::after { display: none; }
}

/* ----------------------------------------------------------
   REDUCED MOTION — skip the entrance entirely; show the final
   state from the start. Field-lift and underline already have
   their own reduced-motion overrides above.
   ---------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
    html.js-ready .contact-form-wrap[data-3d-card],
    .contact-form-wrap[data-3d-card].cf-entering,
    .contact-form-wrap[data-3d-card].cf-revealed {
        transform: none !important;
        opacity: 1 !important;
        transition: none !important;
    }
    .contact-form-wrap[data-3d-card]::after { display: none; }
    html.js-ready .contact-form-wrap[data-3d-card] .contact-form > *:not(.hp-field) {
        opacity: 1 !important;
        transform: none !important;
        transition: none !important;
    }
    html.js-ready .contact-form-wrap[data-3d-card] .cf-bracket {
        opacity: 0.75 !important;
        transform: none !important;
        animation: none !important;
        transition: none !important;
    }
}

/* ==========================================================
   CONTACT MINI-MAP (Phase 9)
   ----------------------------------------------------------
   A small Italy widget shown ABOVE the contact channels.
   contact-minimap.js mounts the Three.js scene into
   .contact-minimap-canvas; the overlay markup sits on top.
   ========================================================== */
.contact-minimap {
    position: relative;
    width: 100%;
    max-width: 320px;
    height: 180px;
    margin: 0 0 28px;
    border: 1px solid rgba(31, 37, 71, 0.7);
    border-radius: 12px;
    background:
        radial-gradient(ellipse 80% 60% at 50% 60%, rgba(8, 12, 28, 0.6), rgba(3, 4, 10, 0.95) 70%),
        linear-gradient(180deg, #050811, #03040A);
    overflow: hidden;
    box-shadow:
        inset 0 0 30px rgba(15, 181, 164, 0.04),
        0 12px 30px rgba(0, 0, 0, 0.35);
}

/* Corner brackets — match the rest of the cyber HUD vibe */
.contact-minimap::before,
.contact-minimap::after {
    content: '';
    position: absolute;
    width: 14px;
    height: 14px;
    pointer-events: none;
    z-index: 3;
}
.contact-minimap::before {
    top: 8px;
    left: 8px;
    border-top:  1px solid rgba(20, 215, 195, 0.5);
    border-left: 1px solid rgba(20, 215, 195, 0.5);
}
.contact-minimap::after {
    bottom: 8px;
    right: 8px;
    border-bottom: 1px solid rgba(20, 215, 195, 0.5);
    border-right:  1px solid rgba(20, 215, 195, 0.5);
}

.contact-minimap-canvas {
    position: absolute;
    inset: 0;
    z-index: 1;
}

.contact-minimap-overlay {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    padding: 12px 14px;
    pointer-events: none;
    font-family: 'JetBrains Mono', Consolas, monospace;
}

.contact-minimap-label {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    align-self: flex-start;
    padding: 4px 9px;
    background: rgba(5, 8, 17, 0.7);
    border: 1px solid rgba(20, 215, 195, 0.25);
    border-radius: 999px;
    font-size: 10px;
    letter-spacing: 0.10em;
    color: var(--text-secondary);
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(3px);
}
.contact-minimap-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--accent-teal-2);
    box-shadow: 0 0 6px var(--accent-teal-2);
    animation: minimap-dot-pulse 1.8s ease-in-out infinite;
}
@keyframes minimap-dot-pulse {
    0%, 100% { opacity: 1;   transform: scale(1); }
    50%      { opacity: 0.5; transform: scale(0.7); }
}

.contact-minimap-cities {
    align-self: flex-end;
    font-size: 11px;
    color: var(--text-muted);
    letter-spacing: 0.06em;
    text-shadow: 0 1px 3px rgba(0,0,0,0.7);
}

@media (max-width: 768px) {
    .contact-minimap { max-width: 100%; height: 160px; }
}

@media (prefers-reduced-motion: reduce) {
    .contact-minimap-dot { animation: none; }
}
