Single-binary self-hosted website analytics on Rust axum: collector API, dashboards, world map, and PDF reports.
analyticsaxumdockerrustself-hostedsqliteviteweb-analytics
1@use "variables" as *;
2
3// ── CSS variable overrides ──
4// Bootstrap 5.3 writes defaults into `:root,[data-bs-theme=light]`. Use
5// `html:root` so we outrank the default block and enforce the dark palette.
6html:root {
7 --bs-body-bg: #{$body-bg};
8 --bs-body-color: #{$body-color};
9 --bs-body-bg-rgb: 14, 13, 10;
10 --bs-body-color-rgb: 221, 215, 205;
11 --bs-emphasis-color: #{$white};
12 --bs-secondary-bg: #13120e;
13 --bs-tertiary-bg: #18160f;
14 --bs-border-color: rgba(107, 158, 120, 0.12);
15 --bs-link-color-rgb: 125, 184, 140;
16 --bs-link-hover-color-rgb: 149, 204, 162;
17 --bs-heading-color: #{$white};
18}
19
20// ── Global ──
21
22body {
23 min-height: 100vh;
24 display: flex;
25 flex-direction: column;
26 -webkit-font-smoothing: antialiased;
27 -moz-osx-font-smoothing: grayscale;
28 letter-spacing: 0.01em;
29 background-color: $body-bg !important;
30 color: $body-color;
31}
32
33main { flex: 1; }
34
35.vh-100 { height: 100vh; }
36
37// ── Logo (rising-bars mark) ──
38// An ascending bar-chart silhouette — evokes growth / aggregate counts, which
39// is what Analytics is actually for. Rendered inline as SVG so it scales
40// cleanly and matches the favicon exactly.
41
42.logo {
43 display: inline-flex;
44 align-items: center;
45 justify-content: center;
46 width: 28px;
47 height: 28px;
48 text-decoration: none !important;
49 transition: transform 250ms ease;
50
51 svg {
52 width: 100%;
53 height: 100%;
54 display: block;
55 }
56
57 &:hover {
58 transform: translateY(-1px);
59 }
60
61 &.logo-sm {
62 width: 18px;
63 height: 18px;
64 }
65}
66
67// ── Navbar ──
68// `min-height` (not `height`) so the bar grows when the mobile collapse opens.
69// A fixed `height: 52px` clipped the open menu — items rendered into the
70// space below the bar but later siblings (breadcrumb-bar, main) painted on
71// top of them, so they read as completely invisible on phones.
72
73.navbar {
74 min-height: 52px;
75 padding: 0;
76 background: #090806 !important;
77 border-bottom: 1px solid rgba(107, 158, 120, 0.06);
78
79 > .container {
80 min-height: 52px;
81 }
82
83 .navbar-brand {
84 display: flex;
85 align-items: center;
86 gap: 0.6rem;
87 color: $white;
88 font-weight: 600;
89 letter-spacing: 0.1em;
90 text-transform: uppercase;
91 font-size: 0.78rem;
92 text-decoration: none;
93
94 .brand-text { color: $white; }
95 .brand-dot {
96 display: inline-block;
97 width: 6px; height: 6px; border-radius: 50%;
98 background: $green;
99 }
100 }
101
102 .navbar-toggler {
103 color: rgba(221, 215, 205, 0.7);
104 padding: 0.35rem 0.55rem;
105 font-size: 1rem;
106
107 &:focus { box-shadow: none; }
108 }
109
110 .nav-link {
111 font-size: 0.8rem;
112 font-weight: 500;
113 letter-spacing: 0.05em;
114 text-transform: lowercase;
115 padding-left: 1rem !important;
116 padding-right: 1rem !important;
117 color: rgba(221, 215, 205, 0.6);
118 position: relative;
119 transition: color 200ms ease;
120
121 &::after {
122 content: '';
123 position: absolute;
124 bottom: 2px;
125 left: 1rem;
126 right: 1rem;
127 height: 1px;
128 background: $green;
129 transform: scaleX(0);
130 transition: transform 250ms ease;
131 }
132
133 &:hover,
134 &.active { color: $white; }
135
136 &:hover::after,
137 &.active::after { transform: scaleX(1); }
138 }
139
140 // ── Mobile (under 768px): stacked drawer ──
141 // Bootstrap's collapse drops the menu to a second flex row inside the
142 // navbar at this breakpoint. Style it as a tray under the brand row:
143 // top-rule separator, full-width tap targets, left-accent for the active
144 // item (instead of the desktop underline, which reads weird on stacked
145 // left-aligned items).
146 @media (max-width: 767.98px) {
147 .navbar-collapse {
148 width: 100%;
149 border-top: 1px solid rgba(107, 158, 120, 0.08);
150 margin-top: 0.25rem;
151 padding: 0.4rem 0 0.6rem;
152 }
153
154 .navbar-nav {
155 margin-left: 0 !important;
156 width: 100%;
157 }
158
159 .nav-link {
160 padding: 0.65rem 0.75rem !important;
161 border-left: 2px solid transparent;
162 border-radius: 2px;
163 transition: background-color 150ms ease, color 150ms ease, border-color 150ms ease;
164
165 &::after { display: none; }
166
167 &:hover {
168 background: rgba(221, 215, 205, 0.04);
169 color: $white;
170 }
171
172 &.active {
173 color: $white;
174 border-left-color: $green;
175 background: rgba(107, 158, 120, 0.08);
176 }
177 }
178
179 .navbar-nav.ms-auto {
180 margin-top: 0.5rem;
181 padding-top: 0.6rem;
182 border-top: 1px solid rgba(107, 158, 120, 0.06);
183
184 .nav-item { width: 100%; }
185
186 .btn,
187 form,
188 .d-inline { width: 100%; }
189
190 .btn { width: 100%; }
191 }
192 }
193}
194
195// ── Breadcrumb bar ──
196
197.breadcrumb-bar {
198 background: rgba(107, 158, 120, 0.02);
199 border-bottom: 1px solid rgba(107, 158, 120, 0.04);
200
201 .breadcrumb { font-family: $font-family-monospace; }
202}
203
204// ── Section labels — amber caps, no prompt glyph ──
205
206.section-label {
207 font-size: 0.7rem;
208 font-weight: 600;
209 letter-spacing: 0.12em;
210 text-transform: uppercase;
211 color: rgba(201, 168, 76, 0.75);
212 font-family: $font-family-monospace;
213}
214
215// ── Cards (base) ──
216
217.card {
218 background: $card-bg;
219 border: 1px solid $card-border-color;
220 transition: border-color 250ms ease;
221
222 &:hover { border-color: rgba(107, 158, 120, 0.2); }
223}
224
225// ── Status dot ──
226
227.status-dot {
228 display: inline-block;
229 width: 8px; height: 8px;
230 border-radius: 50%;
231 margin-right: 0.55rem;
232 vertical-align: middle;
233 flex-shrink: 0;
234
235 &.status-dot-lg {
236 width: 14px; height: 14px;
237 margin-right: 0;
238 }
239
240 &.is-up { background: $green; box-shadow: 0 0 0 3px rgba(107, 158, 120, 0.12); }
241 &.is-down { background: $terracotta; box-shadow: 0 0 0 3px rgba(196, 112, 85, 0.12); }
242 &.is-warn { background: $amber; box-shadow: 0 0 0 3px rgba(201, 168, 76, 0.12); }
243 &.is-idle { background: #4a443c; }
244}
245
246// ── Visibility toggle (segmented pill) ──
247
248.toggle-label {
249 font-family: $font-family-monospace;
250 font-size: 0.68rem;
251 font-weight: 600;
252 letter-spacing: 0.12em;
253 text-transform: uppercase;
254 color: #847c72;
255 cursor: help;
256}
257
258.toggle-pill {
259 display: inline-flex;
260 align-items: stretch;
261 border: 1px solid rgba(107, 158, 120, 0.22);
262 border-radius: 999px;
263 overflow: hidden;
264 cursor: pointer;
265 background: #0e0d0a;
266 margin: 0;
267 user-select: none;
268 line-height: 1;
269
270 input {
271 position: absolute;
272 opacity: 0;
273 pointer-events: none;
274 width: 0; height: 0;
275 }
276
277 .toggle-pill-seg {
278 padding: 0.45rem 0.9rem;
279 font-family: $font-family-monospace;
280 font-size: 0.7rem;
281 font-weight: 600;
282 letter-spacing: 0.08em;
283 text-transform: uppercase;
284 color: #665f56;
285 transition: background 180ms ease, color 180ms ease;
286 }
287
288 input ~ .toggle-pill-private {
289 background: rgba(201, 168, 76, 0.12);
290 color: #ddc06a;
291 }
292
293 input:checked ~ .toggle-pill-private {
294 background: transparent;
295 color: #665f56;
296 }
297 input:checked ~ .toggle-pill-public {
298 background: $green-dim;
299 color: $green-bright;
300 }
301
302 &:hover {
303 border-color: rgba(107, 158, 120, 0.35);
304 }
305}
306
307// ── Metric tile ──
308
309.metric-tile {
310 background: $card-bg;
311 border: 1px solid $card-border-color;
312 border-radius: $border-radius;
313 padding: 1rem 1.15rem;
314 height: 100%;
315 display: flex;
316 flex-direction: column;
317
318 // Label + delta chip share the top row; label flex-grows and truncates,
319 // chip stays its natural width on the right.
320 .metric-header {
321 display: flex;
322 align-items: center;
323 gap: 0.5rem;
324 min-height: 1.3rem;
325 }
326
327 .metric-label {
328 flex: 1;
329 min-width: 0;
330 font-size: 0.68rem;
331 font-weight: 600;
332 letter-spacing: 0.12em;
333 text-transform: uppercase;
334 color: #847c72;
335 }
336
337 .metric-value {
338 font-size: 1.75rem;
339 font-weight: 700;
340 line-height: 1.1;
341 color: $white;
342 margin-top: 0.35rem;
343 letter-spacing: -0.01em;
344 word-break: break-word;
345 }
346
347 .metric-sub {
348 font-size: 0.72rem;
349 color: #847c72;
350 margin-top: auto;
351 padding-top: 0.35rem;
352 letter-spacing: 0.02em;
353 }
354
355 .metric-delta {
356 flex-shrink: 0;
357 font-family: $font-family-monospace;
358 font-size: 0.75rem;
359 font-weight: 600;
360 letter-spacing: 0.04em;
361 padding: 0.22rem 0.5rem;
362 border-radius: 2px;
363 border: 1px solid transparent;
364 line-height: 1;
365
366 &.is-up {
367 color: $green-bright;
368 background: $green-dim;
369 border-color: $green-border;
370 }
371 &.is-down {
372 color: #e38871;
373 background: $terracotta-dim;
374 border-color: $terracotta-border;
375 }
376 &.is-flat {
377 color: #847c72;
378 background: rgba(221, 215, 205, 0.04);
379 border-color: rgba(221, 215, 205, 0.08);
380 }
381 }
382
383 &.metric-accent-green {
384 border-color: $green-border;
385 .metric-value { color: $green-bright; }
386 }
387 &.metric-accent-amber {
388 border-color: $amber-border;
389 .metric-value { color: #ddc06a; }
390 }
391 &.metric-accent-danger {
392 border-color: $terracotta-border;
393 .metric-value { color: #d88870; }
394 }
395}
396
397// ── Chips (inline status/score) ──
398
399.chip {
400 display: inline-flex;
401 align-items: center;
402 gap: 0.4rem;
403 font-family: $font-family-monospace;
404 font-size: 0.7rem;
405 font-weight: 600;
406 letter-spacing: 0.05em;
407 text-transform: uppercase;
408 padding: 0.3rem 0.6rem;
409 border-radius: 2px;
410 border: 1px solid transparent;
411 line-height: 1;
412
413 &.chip-ok {
414 background: $green-dim;
415 color: $green-bright;
416 border-color: $green-border;
417 }
418 &.chip-down {
419 background: $terracotta-dim;
420 color: #e38871;
421 border-color: $terracotta-border;
422 }
423 &.chip-warn {
424 background: $amber-dim;
425 color: #ddc06a;
426 border-color: $amber-border;
427 }
428 &.chip-info {
429 background: rgba(126, 170, 184, 0.08);
430 color: #9ec5d2;
431 border-color: rgba(126, 170, 184, 0.22);
432 }
433 &.chip-muted {
434 background: rgba(221, 215, 205, 0.04);
435 color: #847c72;
436 border-color: rgba(221, 215, 205, 0.08);
437 }
438}
439
440// ── Footer ──
441
442footer {
443 padding-top: 4rem;
444 padding-bottom: 3rem;
445 position: relative;
446 background: #090806;
447 border-top: 1px solid rgba(107, 158, 120, 0.06);
448
449 .h5 {
450 font-size: 0.7rem;
451 letter-spacing: 0.12em;
452 text-transform: uppercase;
453 color: rgba(201, 168, 76, 0.55);
454 font-weight: 600;
455 }
456
457 p {
458 color: rgba(221, 215, 205, 0.5);
459 font-size: 0.85rem;
460 line-height: 1.75;
461
462 a {
463 color: rgba(221, 215, 205, 0.75);
464 text-decoration: underline;
465 text-underline-offset: 2px;
466
467 &:hover { color: $white; }
468 }
469 }
470
471 .links {
472 padding-top: 2rem;
473 padding-bottom: 1rem;
474 }
475}
476
477.footer-bar {
478 background: #050403;
479 border-top: 1px solid rgba(107, 158, 120, 0.04);
480 padding: 0.8rem 0;
481 color: rgba(221, 215, 205, 0.3);
482 font-size: 0.78rem;
483
484 small { color: rgba(221, 215, 205, 0.3); }
485
486 &-link {
487 color: rgba(221, 215, 205, 0.3);
488 transition: color 200ms ease;
489
490 &:hover { color: $white; }
491 }
492}
493
494// ── Terminal-style block ──
495
496.terminal-block {
497 background: rgba(9, 8, 6, 0.7);
498 border: 1px solid rgba(107, 158, 120, 0.15);
499 border-radius: $border-radius;
500 font-family: $font-family-monospace;
501 font-size: 0.8rem;
502 color: $green-bright;
503 padding: 1rem 1.2rem;
504 line-height: 1.8;
505
506 .t-line { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
507 .t-prompt { color: $amber; margin-right: 0.4rem; }
508 .t-out { color: $gray-200; }
509 .t-comment { color: #665f56; }
510 .t-key { color: $amber; }
511 .t-val { color: $green-bright; }
512 .t-cursor {
513 display: inline-block; width: 7px; height: 0.9em; vertical-align: -2px;
514 background: $green-bright;
515 animation: t-blink 1s steps(1) infinite;
516 }
517}
518
519@keyframes t-blink {
520 0%, 50% { opacity: 1; }
521 51%, 100% { opacity: 0; }
522}
523
524// ── Property row (dashboard listing) ──
525
526.property-row {
527 display: grid;
528 grid-template-columns: minmax(0, 1fr) auto;
529 gap: 1rem 1.5rem;
530 background: $card-bg;
531 border: 1px solid $card-border-color;
532 border-radius: $border-radius;
533 padding: 1rem 1.15rem;
534 margin-bottom: 0.75rem;
535 transition: border-color 200ms ease;
536
537 &:hover { border-color: rgba(107, 158, 120, 0.22); }
538
539 &.is-quiet { border-color: rgba(132, 124, 114, 0.25); }
540
541 .pr-main {
542 min-width: 0;
543 display: flex;
544 flex-direction: column;
545 gap: 0.35rem;
546 }
547
548 .pr-title {
549 font-weight: 600;
550 color: $white;
551 display: flex;
552 align-items: center;
553 min-width: 0;
554
555 a {
556 color: inherit;
557 text-decoration: none;
558 min-width: 0;
559 overflow: hidden;
560 text-overflow: ellipsis;
561 white-space: nowrap;
562
563 &:hover { color: $green-bright; }
564 }
565 }
566
567 .pr-id {
568 font-size: 0.72rem;
569 color: #665f56;
570 font-family: $font-family-monospace;
571 overflow: hidden;
572 text-overflow: ellipsis;
573 white-space: nowrap;
574 display: flex;
575 align-items: baseline;
576 gap: 0.5rem;
577
578 .pr-id-k {
579 text-transform: uppercase;
580 font-size: 0.6rem;
581 letter-spacing: 0.1em;
582 color: #847c72;
583 flex-shrink: 0;
584 }
585 }
586
587 .pr-side {
588 display: flex;
589 flex-direction: column;
590 justify-content: space-between;
591 align-items: flex-end;
592 gap: 0.75rem;
593 min-height: 68px;
594 }
595
596 .pr-meta {
597 display: flex;
598 gap: 0.85rem;
599 align-items: flex-end;
600 font-family: $font-family-monospace;
601
602 .pr-meta-cell {
603 display: flex;
604 flex-direction: column;
605 align-items: flex-start;
606 gap: 0.2rem;
607 min-width: 0;
608 }
609
610 .pr-meta-k {
611 color: #665f56;
612 text-transform: uppercase;
613 font-size: 0.6rem;
614 letter-spacing: 0.1em;
615 line-height: 1;
616 }
617
618 .pr-meta-v {
619 color: $gray-200;
620 font-size: 0.88rem;
621 font-weight: 600;
622 line-height: 1;
623 }
624 }
625
626 .pr-actions {
627 display: flex;
628 gap: 0.4rem;
629 }
630}
631
632@media (max-width: 767px) {
633 .property-row {
634 grid-template-columns: 1fr;
635
636 .pr-side {
637 min-height: 0;
638 align-items: stretch;
639 }
640 .pr-meta {
641 flex-wrap: wrap;
642 justify-content: flex-start;
643 }
644 .pr-actions {
645 justify-content: flex-end;
646 }
647 }
648}
649
650// ── Search form ──
651
652.search-form {
653 position: relative;
654
655 .search-icon {
656 position: absolute;
657 left: 0.9rem;
658 top: 50%;
659 transform: translateY(-50%);
660 color: #665f56;
661 pointer-events: none;
662 z-index: 2;
663 }
664
665 .search-input {
666 padding-left: 2.5rem;
667 font-size: 0.85rem;
668 border: 1px solid rgba(107, 158, 120, 0.15);
669 background: rgba(19, 18, 14, 0.7);
670 color: $body-color;
671 transition: border-color 200ms ease;
672
673 &::placeholder {
674 color: #665f56;
675 font-style: normal;
676 }
677
678 &:focus {
679 border-color: rgba(107, 158, 120, 0.4);
680 box-shadow: 0 0 0 2px rgba(107, 158, 120, 0.1);
681 background: #13120e;
682 }
683 }
684}
685
686// ── Dashboard toolbar ──
687
688.dashboard-toolbar {
689 display: flex;
690 gap: 0.5rem;
691 align-items: stretch;
692
693 .form-control {
694 height: 38px;
695 font-size: 0.85rem;
696 }
697
698 .btn {
699 height: 38px;
700 padding: 0 1.1rem;
701 display: inline-flex;
702 align-items: center;
703 white-space: nowrap;
704 }
705}
706
707// ── Chart panel ──
708
709.chart-panel {
710 background: $card-bg;
711 border: 1px solid $card-border-color;
712 border-radius: $border-radius;
713 padding: 1rem 1.15rem;
714
715 .chart-panel-header {
716 display: flex;
717 align-items: center;
718 justify-content: space-between;
719 margin-bottom: 0.75rem;
720 gap: 0.75rem;
721 }
722
723 .chart-panel-title {
724 font-size: 0.68rem;
725 font-weight: 600;
726 letter-spacing: 0.12em;
727 text-transform: uppercase;
728 color: rgba(201, 168, 76, 0.75);
729 font-family: $font-family-monospace;
730 }
731
732 .chart-panel-body {
733 position: relative;
734 }
735}
736
737// ── Insight / top-list table ──
738// Used for "top pages", "referrers", "UTM source" etc. — compact grid rows
739// that read like a ranked terminal listing rather than a Bootstrap card.
740
741.rank-list {
742 background: $card-bg;
743 border: 1px solid $card-border-color;
744 border-radius: $border-radius;
745 overflow: hidden;
746 height: 100%;
747 display: flex;
748 flex-direction: column;
749
750 .rank-list-header {
751 display: flex;
752 justify-content: space-between;
753 align-items: center;
754 background: rgba(107, 158, 120, 0.04);
755 padding: 0.6rem 1rem;
756 border-bottom: 1px solid rgba(107, 158, 120, 0.08);
757
758 .rank-list-title {
759 font-size: 0.68rem;
760 font-weight: 600;
761 letter-spacing: 0.12em;
762 text-transform: uppercase;
763 color: rgba(201, 168, 76, 0.75);
764 font-family: $font-family-monospace;
765 }
766
767 .rank-list-col {
768 font-size: 0.62rem;
769 font-weight: 600;
770 letter-spacing: 0.1em;
771 text-transform: uppercase;
772 color: #665f56;
773 font-family: $font-family-monospace;
774 }
775 }
776
777 .rank-list-body {
778 display: flex;
779 flex-direction: column;
780 flex: 1 1 auto;
781 }
782
783 .rank-list-row {
784 display: grid;
785 grid-template-columns: minmax(0, 1fr) auto;
786 gap: 0.75rem;
787 align-items: center;
788 padding: 0.5rem 1rem;
789 border-bottom: 1px solid rgba(107, 158, 120, 0.04);
790 font-size: 0.85rem;
791
792 &:last-child { border-bottom: none; }
793
794 .rank-label {
795 min-width: 0;
796 overflow: hidden;
797 text-overflow: ellipsis;
798 white-space: nowrap;
799 color: $gray-200;
800
801 a {
802 color: inherit;
803 text-decoration: none;
804
805 &:hover {
806 color: $green-bright;
807 text-decoration: underline;
808 }
809 }
810 }
811
812 .rank-count {
813 font-family: $font-family-monospace;
814 font-size: 0.78rem;
815 color: $amber;
816 font-weight: 600;
817 }
818 }
819
820 .rank-list-empty {
821 padding: 1.25rem 1rem;
822 color: #665f56;
823 font-size: 0.82rem;
824 text-align: center;
825 }
826}
827
828// ── Filter chip (active query filter indicator) ──
829
830.filter-chip {
831 display: inline-flex;
832 align-items: center;
833 gap: 0.5rem;
834 background: $amber-dim;
835 border: 1px solid $amber-border;
836 color: #ddc06a;
837 font-family: $font-family-monospace;
838 font-size: 0.75rem;
839 padding: 0.3rem 0.6rem;
840 border-radius: 2px;
841 max-width: 100%;
842
843 .filter-chip-label {
844 overflow: hidden;
845 text-overflow: ellipsis;
846 white-space: nowrap;
847 }
848
849 .filter-chip-clear {
850 background: transparent;
851 border: none;
852 color: #ddc06a;
853 cursor: pointer;
854 padding: 0;
855 line-height: 1;
856 opacity: 0.7;
857 transition: opacity 150ms ease;
858
859 &:hover { opacity: 1; }
860 }
861}
862
863// ── Date range form (property dashboard) ──
864
865.date-range-form {
866 display: grid;
867 grid-template-columns: 1fr 1fr 1fr;
868 gap: 0.5rem;
869
870 @media (max-width: 575px) {
871 grid-template-columns: 1fr;
872 }
873
874 .form-floating label {
875 font-size: 0.78rem;
876 }
877
878 .form-control,
879 .form-select {
880 font-size: 0.85rem;
881 }
882}
883
884// ── Auth split view ──
885
886.auth-shell {
887 min-height: calc(100vh - 52px);
888 display: grid;
889 grid-template-columns: 1fr 1fr;
890
891 @media (max-width: 991px) {
892 grid-template-columns: 1fr;
893 }
894
895 .auth-form-col {
896 padding: 3rem 2rem;
897 display: flex;
898 align-items: center;
899 justify-content: center;
900 }
901
902 .auth-visual-col {
903 position: relative;
904 overflow: hidden;
905 background: linear-gradient(145deg, #090806 0%, #0e0d0a 60%, #13120e 100%);
906 border-left: 1px solid rgba(107, 158, 120, 0.08);
907
908 @media (max-width: 991px) { display: none; }
909 }
910
911 .auth-form-wrap {
912 width: 100%;
913 max-width: 380px;
914 }
915}
916
917// ── Site-tag codebox (for the collector snippet modal) ──
918
919.codebox {
920 background: #090806;
921 border: 1px solid rgba(107, 158, 120, 0.15);
922 border-radius: $border-radius;
923 color: $green-bright;
924 font-family: $font-family-monospace;
925 font-size: 0.78rem;
926 line-height: 1.7;
927 padding: 1rem 1.1rem;
928 white-space: pre-wrap;
929 word-break: break-word;
930 resize: vertical;
931 min-height: 160px;
932 width: 100%;
933
934 &:focus {
935 outline: none;
936 border-color: $green-border;
937 box-shadow: 0 0 0 2px rgba(107, 158, 120, 0.1);
938 }
939}
940
941// ── World map ──
942// d3-geo renders an SVG inside #map. Tooltip styling lives inline in
943// property_map.js since it needs runtime positioning anyway.
944
945#map {
946 position: relative;
947
948 svg path {
949 transition: fill 80ms ease, stroke 80ms ease;
950 }
951}
952
953.chart-panel-action {
954 background: transparent;
955 border: 1px solid rgba(107, 158, 120, 0.25);
956 color: rgba(221, 215, 205, 0.7);
957 font-family: $font-family-monospace;
958 font-size: 0.62rem;
959 letter-spacing: 0.08em;
960 text-transform: uppercase;
961 padding: 0.25rem 0.55rem;
962 border-radius: $border-radius;
963 cursor: pointer;
964 transition: color 80ms, border-color 80ms;
965
966 &:hover {
967 color: #c9a84c;
968 border-color: rgba(201, 168, 76, 0.5);
969 }
970}