PageSourceSearch

https://logiks.com.br/logiks-mapa.js

js logiks.com.br collected 2026-10-08 09:53:53 UTC 10,764 bytes, 261 lines download raw bytes

1/**
2 * <logiks-mapa> — Web Component para o mapa interativo
3 * "Nossa Presença Nacional 2026" da Logiks Tecnologia.
4 *
5 * Uso:
6 *   <script src="logiks-mapa.js"></script>
7 *   <logiks-mapa></logiks-mapa>
8 *
9 * Atributos opcionais:
10 *   image="caminho/para/imagem.jpg"  → sobrescreve a imagem de fundo
11 *                                       (padrão: "mapa-logiks-bg.jpg")
12 *
13 * Para customizar dados (cidades/posições), edite os arrays MARKERS e LEGEND
14 * logo no início desta classe.
15 */
16
17class LogiksMapa extends HTMLElement {
18
19  // ============= CONFIGURAÇÃO (edite aqui se quiser mudar) =============
20  static MARKERS = [[33.15, 34.89, "Amazonas"], [60.26, 33.53, "Maranhão"], [66.74, 35.96, "Ceará"], [71.51, 37.69, "Rio Grande do Norte"], [71.99, 44.62, "Pernambuco"], [35.96, 49.06, "Rondônia"], [65.34, 52.53, "Bahia"], [70.2, 49.6, "Sergipe"], [56.1, 58.4, "Brasília"], [51.95, 61.86, "Goiás"], [60.25, 64.21, "Minas Gerais"], [54.4, 72.1, "São Paulo"], [63.27, 75.56, "Rio de Janeiro"], [50.25, 77.29, "Paraná"]];
21  static LEGEND  = ["Brasília", "Goiás", "Ceará", "Minas Gerais", "Bahia", "Rio Grande do Norte", "Maranhão", "Pernambuco", "Sergipe", "Rondônia", "Amazonas", "Paraná", "São Paulo", "Rio de Janeiro"];
22  static PILL_CITIES = [];
23  // =====================================================================
24
25  constructor() {
26    super();
27    this.attachShadow({ mode: 'open' });
28  }
29
30  connectedCallback() {
31    const image = this.getAttribute('image') || 'mapa-logiks-bg.jpg';
32    this.shadowRoot.innerHTML = this.#render(image);
33    this.#setupInteractions();
34  }
35
36  #render(imageSrc) {
37    const C = LogiksMapa;
38    const markersHTML = C.MARKERS.map(([x, y, city]) => `
39      <div class="marker" tabindex="0" data-city="${city}" style="left:${x}%;top:${y}%">
40        <div class="hit"></div>
41        <div class="ring"></div>
42        <div class="ring ring-2"></div>
43        <div class="glow"></div>
44        <div class="tip">${city}</div>
45      </div>`).join('');
46
47    const pillSet = new Set(C.PILL_CITIES);
48    const legendHTML = C.LEGEND.map(city => `
49        <div class="item${pillSet.has(city) ? ' pill' : ''}" data-city="${city}">
50          <span class="bullet">•</span><span>${city}</span>
51        </div>`).join('');
52
53    return `
54      <link rel="preconnect" href="https://fonts.googleapis.com">
55      <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
56      <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;700;800&display=swap" rel="stylesheet">
57      <style>
58        :host {
59          display: block;
60          width: 100%;
61          margin: 0 auto;
62          font-family: system-ui, -apple-system, sans-serif;
63        }
64        .stage {
65          position: relative;
66          width: 100%;
67          aspect-ratio: 1600 / 960;
68          background: #f5f4f1 url('${imageSrc}') center/cover no-repeat;
69          overflow: hidden;
70          box-shadow: 0 30px 80px -20px rgba(200, 110, 0, 0.25);
71          animation: stagein 1s ease-out;
72        }
73        @keyframes stagein {
74          from { opacity: 0; transform: scale(0.99); }
75          to   { opacity: 1; transform: scale(1); }
76        }
77
78        .shine {
79          position: absolute; top: 4%; left: 27%;
80          width: 47%; height: 9%; overflow: hidden; pointer-events: none;
81          border-radius: 12px;
82        }
83        .shine::after {
84          content: ''; position: absolute; top: 0; left: -50%;
85          width: 50%; height: 100%;
86          background: linear-gradient(90deg, transparent, rgba(255,255,255,0.55), transparent);
87          transform: skewX(-20deg);
88          animation: shine 4.5s ease-in-out infinite;
89        }
90        @keyframes shine {
91          0%, 35% { left: -50%; }
92          65%, 100% { left: 110%; }
93        }
94
95        .circuit {
96          position: absolute; left: 0; top: 0;
97          width: 28%; height: 100%; pointer-events: none;
98          background:
99            radial-gradient(circle at 15% 30%, rgba(232,136,30,0.5) 0%, transparent 1.5%),
100            radial-gradient(circle at 8% 55%,  rgba(232,136,30,0.5) 0%, transparent 1.2%),
101            radial-gradient(circle at 22% 75%, rgba(232,136,30,0.5) 0%, transparent 1.4%),
102            radial-gradient(circle at 5% 85%,  rgba(232,136,30,0.5) 0%, transparent 1%);
103          animation: flicker 3s ease-in-out infinite;
104          mix-blend-mode: multiply;
105        }
106        @keyframes flicker {
107          0%, 100% { opacity: 0; }
108          50%      { opacity: 0.85; }
109        }
110
111        .legend {
112          position: absolute; left: 78%; top: 28%; right: 1%; bottom: 3.5%;
113          display: flex; flex-direction: column; justify-content: space-between;
114          z-index: 3; padding: 0.4% 0;
115        }
116        .item {
117          display: flex; align-items: center; gap: 0.45em;
118          padding: 0.2em 0.7em;
119          font-family: 'Orbitron', system-ui, sans-serif;
120          font-size: clamp(10px, 1.25vw, 19px);
121          font-weight: 500;
122          letter-spacing: 0.02em;
123          color: #E89504;
124          border-radius: 999px;
125          transition: background 0.25s, color 0.2s, transform 0.25s, letter-spacing 0.25s;
126          cursor: pointer;
127          line-height: 1.1;
128        }
129        .bullet { font-size: 1.1em; line-height: 0;
129 margin-top: -0.05em; }
130        .pill { background: rgba(232, 149, 4, 0.18); }
131        .item:hover, .item.is-active {
132          font-weight: 800;
133          color: #a06600;
134          background: rgba(232, 149, 4, 0.32);
135          transform: translateX(4px);
136          letter-spacing: 0.2px;
137        }
138
139        .marker {
140          position: absolute; aspect-ratio: 1; width: 3.6%;
141          transform: translate(-50%, -50%);
142          cursor: pointer; outline: none; z-index: 4;
143        }
144        .hit { position: absolute; inset: -80%; border-radius: 50%; z-index: 1; }
145
146        .ring {
147          position: absolute; inset: 0; border-radius: 50%;
148          border: 2.5px solid #e8881e; opacity: 0;
149        }
150        .marker:hover .ring,
151        .marker:focus .ring,
152        .marker.is-active .ring { animation: pulse 2.2s ease-out infinite; }
153        .marker:hover .ring-2,
154        .marker:focus .ring-2,
155        .marker.is-active .ring-2 { animation-delay: 1.1s; }
156        @keyframes pulse {
157          0%   { transform: scale(1);   opacity: 1;    border-width: 3px; }
158          70%  {                          opacity: 0.15; border-width: 1px; }
159          100% { transform: scale(2.8); opacity: 0;    border-width: 1px; }
160        }
161
162        .glow {
163          position: absolute; inset: -25%; border-radius: 50%;
164          background: radial-gradient(circle, rgba(232,136,30,0.45) 0%, transparent 60%);
165          opacity: 0; transition: opacity 0.25s ease;
166        }
167        .marker:hover .glow,
168        .marker:focus .glow,
169        .marker.is-active .glow {
170          opacity: 1; animation: glow 1.8s ease-in-out infinite;
171        }
172        @keyframes glow {
173          0%, 100% { opacity: 0.55; transform: scale(0.95); }
174          50%      { opacity: 1;    transform: scale(1.15); }
175        }
176
177        .tip {
178          position: absolute; bottom: calc(100% + 14px); left: 50%;
179          transform: translateX(-50%) translateY(8px);
180          background: linear-gradient(135deg, #f29a3a 0%, #d96c00 100%);
181          color: white;
182          padding: 7px 16px; border-radius: 999px;
183          font-family: 'Orbitron', system-ui, sans-serif;
184          font-size: clamp(10px, 0.95vw, 15px);
185          font-weight: 700; white-space: nowrap; letter-spacing: 0.3px;
186          opacity: 0; pointer-events: none;
187          transition: opacity 0.22s ease, transform 0.22s ease;
188          box-shadow: 0 6px 18px rgba(232,136,30,0.45), 0 2px 4px rgba(0,0,0,0.1);
189          z-index: 10;
190        }
191        .tip::after {
192          content: ''; position: absolute; top: 100%; left: 50%;
193          transform: translateX(-50%);
194          border: 7px solid transparent; border-top-color: #d96c00;
195        }
196        .marker:hover .tip,
197        .marker:focus .tip,
198        .marker.is-active .tip {
199          opacity: 1; transform: translateX(-50%) translateY(0);
200        }
201      </style>
202
203      <div class="stage" part="stage">
204        <div class="shine"></div>
205        <div class="circuit"></div>
206        <div class="legend">${legendHTML}</div>
207        ${markersHTML}
208      </div>
209    `;
210  }
211
212  #setupInteractions() {
213    const root = this.shadowRoot;
214    const markers = root.querySelectorAll('.marker');
215    const items   = root.querySelectorAll('.item');
216
217    const findItem   = c => root.querySelector('.item[data-city="'   + c + '"]');
218    const findMarker = c => root.querySelector('.marker[data-city="' + c + '"]');
219
220    markers.forEach(marker => {
221      const city = marker.dataset.city;
222      marker.addEventListener('mouseenter', () => findItem(city)?.classList.add('is-active'));
223      marker.addEventListener('mouseleave', () => findItem(city)?.classList.remove('is-active'));
224      marker.addEventListener('focus',      () => findItem(city)?.classList.add('is-active'));
225      marker.addEventListener('blur',       () => findItem(city)?.classList.remove('is-active'));
226
227      marker.addEventListener('click', e => {
228        e.stopPropagation();
229        const wasActive = marker.classList.contains('is-active');
230        markers.forEach(m => m.classList.remove('is-active'));
231        items.forEach(i => i.classList.remove('is-active'));
232        if (!wasActive) {
233          marker.classList.add('is-active');
234          findItem(city)?.classList.add('is-active');
235        }
236        // Dispara um evento personalizado pra quem usar o componente conseguir reagir
237        this.dispatchEvent(new CustomEvent('city-selected', {
238          detail: { city }, bubbles: true, composed: true
239        }));
240      });
241
242      marker.addEventListener('keydown', e => {
243        if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); marker.click(); }
244      });
245    });
246
247    items.forEach(item => {
248      const city = item.dataset.city;
249      item.addEventListener('mouseenter', () => findMarker(city)?.classList.add('is-active'));
250      item.addEventListener('mouseleave', () => findMarker(city)?.classList.remove('is-active'));
251    });
252
253    // Clique fora do componente desativa qualquer marcador ativo
254    document.addEventListener('click', () => {
255      markers.forEach(m => m.classList.remove('is-active'));
256      items.forEach(i => i.classList.remove('is-active'));
257    });
258  }
259}
260
261customElements.define('logiks-mapa', LogiksMapa);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.