deploy: b4d46e331515135fd852a09e78fd168614b541d7
21
.gitignore
поставляемый
@ -1,21 +0,0 @@
|
||||
node_modules
|
||||
|
||||
# Output
|
||||
.output
|
||||
.vercel
|
||||
/.svelte-kit
|
||||
/build
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Env
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
!.env.test
|
||||
|
||||
# Vite
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
0
.nojekyll
Обычный файл
1
.npmrc
@ -1 +0,0 @@
|
||||
engine-strict=true
|
||||
@ -1,4 +0,0 @@
|
||||
# Package Managers
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
yarn.lock
|
||||
@ -1,8 +0,0 @@
|
||||
{
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte"],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||
}
|
||||
18
README.md
@ -1,18 +0,0 @@
|
||||
To run this app locally:
|
||||
|
||||
- clone this repository
|
||||
- navigate into the folder of the repository on the command line
|
||||
- run `npm install`
|
||||
- run `npm run dev`
|
||||
|
||||
The command line log will give you a url to run the app locally in your browser.
|
||||
|
||||
## Building
|
||||
|
||||
To create a production version of your app:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
The files of the built app will be in the `build` folder. You can preview the production build with `npm run preview`.
|
||||
1
_app/env.js
Обычный файл
@ -0,0 +1 @@
|
||||
export const env={}
|
||||
1
_app/immutable/assets/0.DbPGuKpG.css
Обычный файл
@ -0,0 +1 @@
|
||||
:root{--bg: #F9F8F8;--transparentbg: #F9F8F8cb;--usa-blue: #3c3b6e;--usa-lightblue: #c9c7eb;--usa-red: #b22234;--usa-lightred: #c5888f;--text-darkgray: #5e4a4a;--font-01: Volkhov, serif;--font-02: Quicksand, sans-serif}body{background-color:var(--bg)}
|
||||
1
_app/immutable/assets/2.TrGWaNyh.css
Обычный файл
1
_app/immutable/assets/_layout.DbPGuKpG.css
Обычный файл
@ -0,0 +1 @@
|
||||
:root{--bg: #F9F8F8;--transparentbg: #F9F8F8cb;--usa-blue: #3c3b6e;--usa-lightblue: #c9c7eb;--usa-red: #b22234;--usa-lightred: #c5888f;--text-darkgray: #5e4a4a;--font-01: Volkhov, serif;--font-02: Quicksand, sans-serif}body{background-color:var(--bg)}
|
||||
1
_app/immutable/assets/_page.TrGWaNyh.css
Обычный файл
3
_app/immutable/chunks/entry.Cu_RPa2W.js
Обычный файл
4
_app/immutable/chunks/index.CuQfD_Qi.js
Обычный файл
@ -0,0 +1,4 @@
|
||||
var J=Object.defineProperty;var K=(t,e,n)=>e in t?J(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n;var R=(t,e,n)=>K(t,typeof e!="symbol"?e+"":e,n);import{n as x,A as Q,B as U,f as F,C as v,D as N,E as I,F as V,G as k,H as z,b as q,I as T,J as W,K as X,L as Y,M as B,N as Z,O as tt,P as et,Q as nt,R as st}from"./scheduler.BeEXgHAC.js";const L=typeof window<"u";let it=L?()=>window.performance.now():()=>Date.now(),j=L?t=>requestAnimationFrame(t):x;const y=new Set;function G(t){y.forEach(e=>{e.c(t)||(y.delete(e),e.f())}),y.size!==0&&j(G)}function rt(t){let e;return y.size===0&&j(G),{promise:new Promise(n=>{y.add(e={c:t,f:n})}),abort(){y.delete(e)}}}const S=new Map;let O=0;function at(t){let e=5381,n=t.length;for(;n--;)e=(e<<5)-e^t.charCodeAt(n);return e>>>0}function ot(t,e){const n={stylesheet:U(e),rules:{}};return S.set(t,n),n}function D(t,e,n,s,c,f,l,i=0){const u=16.666/s;let r=`{
|
||||
`;for(let d=0;d<=1;d+=u){const g=e+(n-e)*f(d);r+=d*100+`%{${l(g,1-g)}}
|
||||
`}const $=r+`100% {${l(n,1-n)}}
|
||||
}`,o=`__svelte_${at($)}_${i}`,m=Q(t),{stylesheet:p,rules:a}=S.get(m)||ot(m,t);a[o]||(a[o]=!0,p.insertRule(`@keyframes ${o} ${$}`,p.cssRules.length));const _=t.style.animation||"";return t.style.animation=`${_?`${_}, `:""}${o} ${s}ms linear ${c}ms 1 both`,O+=1,o}function ft(t,e){const n=(t.style.animation||"").split(", "),s=n.filter(e?f=>f.indexOf(e)<0:f=>f.indexOf("__svelte")===-1),c=n.length-s.length;c&&(t.style.animation=s.join(", "),O-=c,O||ut())}function ut(){j(()=>{O||(S.forEach(t=>{const{ownerNode:e}=t.stylesheet;e&&F(e)}),S.clear())})}let w;function lt(){return w||(w=Promise.resolve(),w.then(()=>{w=null})),w}function A(t,e,n){t.dispatchEvent(V(`${e?"intro":"outro"}${n}`))}const E=new Set;let h;function yt(){h={r:0,c:[],p:h}}function wt(){h.r||v(h.c),h=h.p}function ct(t,e){t&&t.i&&(E.delete(t),t.i(e))}function xt(t,e,n,s){if(t&&t.o){if(E.has(t))return;E.add(t),h.c.push(()=>{E.delete(t),s&&(n&&t.d(1),s())}),t.o(e)}else s&&s()}const dt={duration:0};function vt(t,e,n,s){let f=e(t,n,{direction:"both"}),l=s?0:1,i=null,u=null,r=null,$;function o(){r&&ft(t,r)}function m(a,_){const d=a.b-l;return _*=Math.abs(d),{a:l,b:a.b,d,duration:_,start:a.start,end:a.start+_,group:a.group}}function p(a){const{delay:_=0,duration:d=300,easing:g=k,tick:C=x,css:M}=f||dt,P={start:it()+_,b:a};a||(P.group=h,h.r+=1),"inert"in t&&(a?$!==void 0&&(t.inert=$):($=t.inert,t.inert=!0)),i||u?u=P:(M&&(o(),r=D(t,l,a,d,_,g,M)),a&&C(0,1),i=m(P,d),I(()=>A(t,a,"start")),rt(b=>{if(u&&b>u.start&&(i=m(u,d),u=null,A(t,i.b,"start"),M&&(o(),r=D(t,l,i.b,i.duration,0,g,f.css))),i){if(b>=i.end)C(l=i.b,1-l),A(t,i.b,"end"),u||(i.b?o():--i.group.r||v(i.group.c)),i=null;else if(b>=i.start){const H=b-i.start;l=i.a+i.d*g(H/i.duration),C(l,1-l)}}return!!(i||u)}))}return{run(a){N(f)?lt().then(()=>{f=f({direction:a?"in":"out"}),p(a)}):p(a)},end(){o(),i=u=null}}}function bt(t,e,n){const s=t.$$.props[e];s!==void 0&&(t.$$.bound[s]=n,n(t.$$.ctx[s]))}function Et(t){t&&t.c()}function St(t,e){t&&t.l(e)}function _t(t,e,n){const{fragment:s,after_update:c}=t.$$;s&&s.m(e,n),I(()=>{const f=t.$$.on_mount.map(Z).filter(N);t.$$.on_destroy?t.$$.on_destroy.push(...f):v(f),t.$$.on_mount=[]}),c.forEach(I)}function $t(t,e){const n=t.$$;n.fragment!==null&&(X(n.after_update),v(n.on_destroy),n.fragment&&n.fragment.d(e),n.on_destroy=n.fragment=null,n.ctx=[])}function ht(t,e){t.$$.dirty[0]===-1&&(tt.push(t),et(),t.$$.dirty.fill(0)),t.$$.dirty[e/31|0]|=1<<e%31}function Ot(t,e,n,s,c,f,l=null,i=[-1]){const u=Y;B(t);const r=t.$$={fragment:null,ctx:[],props:f,update:x,not_equal:c,bound:z(),on_mount:[],on_destroy:[],on_disconnect:[],before_update:[],after_update:[],context:new Map(e.context||(u?u.$$.context:[])),callbacks:z(),dirty:i,skip_bound:!1,root:e.target||u.$$.root};l&&l(r.root);let $=!1;if(r.ctx=n?n(t,e.props||{},(o,m,...p)=>{const a=p.length?p[0]:m;return r.ctx&&c(r.ctx[o],r.ctx[o]=a)&&(!r.skip_bound&&r.bound[o]&&r.bound[o](a),$&&ht(t,o)),m}):[],r.update(),$=!0,v(r.before_update),r.fragment=s?s(r.ctx):!1,e.target){if(e.hydrate){nt();const o=q(e.target);r.fragment&&r.fragment.l(o),o.forEach(F)}else r.fragment&&r.fragment.c();e.intro&&ct(t.$$.fragment),_t(t,e.target,e.anchor),st(),T()}B(u)}class Ct{constructor(){R(this,"$$");R(this,"$$set")}$destroy(){$t(this,1),this.$destroy=x}$on(e,n){if(!N(n))return x;const s=this.$$.callbacks[e]||(this.$$.callbacks[e]=[]);return s.push(n),()=>{const c=s.indexOf(n);c!==-1&&s.splice(c,1)}}$set(e){this.$$set&&!W(e)&&(this.$$.skip_bound=!0,this.$$set(e),this.$$.skip_bound=!1)}}const mt="4";typeof window<"u"&&(window.__svelte||(window.__svelte={v:new Set})).v.add(mt);export{Ct as S,xt as a,Et as b,wt as c,St as d,$t as e,vt as f,yt as g,bt as h,Ot as i,rt as l,_t as m,it as n,ct as t};
|
||||
1
_app/immutable/chunks/scheduler.BeEXgHAC.js
Обычный файл
1
_app/immutable/chunks/stores.Lq868Yjj.js
Обычный файл
@ -0,0 +1 @@
|
||||
import{s as e}from"./entry.Cu_RPa2W.js";const r=()=>{const s=e;return{page:{subscribe:s.page.subscribe},navigating:{subscribe:s.navigating.subscribe},updated:s.updated}},b={subscribe(s){return r().page.subscribe(s)}};export{b as p};
|
||||
2
_app/immutable/entry/app.eRIxyuLO.js
Обычный файл
1
_app/immutable/entry/start.Cp5tm-R9.js
Обычный файл
@ -0,0 +1 @@
|
||||
import{a as t}from"../chunks/entry.Cu_RPa2W.js";export{t as start};
|
||||
1
_app/immutable/nodes/0.caO7FUPE.js
Обычный файл
@ -0,0 +1 @@
|
||||
import{s as l,l as r,u as i,m as u,o as _}from"../chunks/scheduler.BeEXgHAC.js";import{S as f,i as c,t as p,a as m}from"../chunks/index.CuQfD_Qi.js";const d=!0,S=Object.freeze(Object.defineProperty({__proto__:null,prerender:d},Symbol.toStringTag,{value:"Module"}));function $(n){let s;const a=n[1].default,e=r(a,n,n[0],null);return{c(){e&&e.c()},l(t){e&&e.l(t)},m(t,o){e&&e.m(t,o),s=!0},p(t,[o]){e&&e.p&&(!s||o&1)&&i(e,a,t,t[0],s?_(a,t[0],o,null):u(t[0]),null)},i(t){s||(p(e,t),s=!0)},o(t){m(e,t),s=!1},d(t){e&&e.d(t)}}}function g(n,s,a){let{$$slots:e={},$$scope:t}=s;return n.$$set=o=>{"$$scope"in o&&a(0,t=o.$$scope)},[t,e]}class v extends f{constructor(s){super(),c(this,s,g,$,l,{})}}export{v as component,S as universal};
|
||||
1
_app/immutable/nodes/1.Crpz-BfZ.js
Обычный файл
@ -0,0 +1 @@
|
||||
import{s as x,e as u,t as h,a as S,c as d,b as v,d as g,f as m,g as j,i as _,h as b,j as E,n as $,k}from"../chunks/scheduler.BeEXgHAC.js";import{S as q,i as y}from"../chunks/index.CuQfD_Qi.js";import{p as C}from"../chunks/stores.Lq868Yjj.js";function H(i){var f;let a,s=i[0].status+"",r,o,n,p=((f=i[0].error)==null?void 0:f.message)+"",c;return{c(){a=u("h1"),r=h(s),o=S(),n=u("p"),c=h(p)},l(e){a=d(e,"H1",{});var t=v(a);r=g(t,s),t.forEach(m),o=j(e),n=d(e,"P",{});var l=v(n);c=g(l,p),l.forEach(m)},m(e,t){_(e,a,t),b(a,r),_(e,o,t),_(e,n,t),b(n,c)},p(e,[t]){var l;t&1&&s!==(s=e[0].status+"")&&E(r,s),t&1&&p!==(p=((l=e[0].error)==null?void 0:l.message)+"")&&E(c,p)},i:$,o:$,d(e){e&&(m(a),m(o),m(n))}}}function P(i,a,s){let r;return k(i,C,o=>s(0,r=o)),[r]}class B extends q{constructor(a){super(),y(this,a,P,H,x,{})}}export{B as component};
|
||||
5
_app/immutable/nodes/2.C2nigCiV.js
Обычный файл
1
_app/version.json
Обычный файл
@ -0,0 +1 @@
|
||||
{"version":"1728748945060"}
|
||||
@ -1,23 +0,0 @@
|
||||
import js from '@eslint/js';
|
||||
import svelte from 'eslint-plugin-svelte';
|
||||
import prettier from 'eslint-config-prettier';
|
||||
import globals from 'globals';
|
||||
|
||||
/** @type {import('eslint').Linter.Config[]} */
|
||||
export default [
|
||||
js.configs.recommended,
|
||||
...svelte.configs['flat/recommended'],
|
||||
prettier,
|
||||
...svelte.configs['flat/prettier'],
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
ignores: ['build/', '.svelte-kit/', 'dist/']
|
||||
}
|
||||
];
|
||||
|
До Ширина: | Высота: | Размер: 1.5 KiB После Ширина: | Высота: | Размер: 1.5 KiB |
@ -1,7 +0,0 @@
|
||||
export default [
|
||||
{
|
||||
//"id": "1tSZ6hKWKR0u3pIxvm3L_6SO3ab2yqjM8ww7GMBU3EwE",
|
||||
"id": "1sDuhhouZ1IYsi355slAtY2SeF6781vO8AvhVL-EE_3A",
|
||||
"filepath": "src/data/copy.json"
|
||||
}
|
||||
]
|
||||
|
До Ширина: | Высота: | Размер: 79 KiB После Ширина: | Высота: | Размер: 79 KiB |
|
До Ширина: | Высота: | Размер: 188 KiB После Ширина: | Высота: | Размер: 188 KiB |
|
До Ширина: | Высота: | Размер: 139 KiB После Ширина: | Высота: | Размер: 139 KiB |
|
До Ширина: | Высота: | Размер: 99 KiB После Ширина: | Высота: | Размер: 99 KiB |
|
До Ширина: | Высота: | Размер: 86 KiB После Ширина: | Высота: | Размер: 86 KiB |
|
До Ширина: | Высота: | Размер: 184 KiB После Ширина: | Высота: | Размер: 184 KiB |
|
До Ширина: | Высота: | Размер: 76 KiB После Ширина: | Высота: | Размер: 76 KiB |
|
До Ширина: | Высота: | Размер: 120 KiB После Ширина: | Высота: | Размер: 120 KiB |
|
До Ширина: | Высота: | Размер: 125 KiB После Ширина: | Высота: | Размер: 125 KiB |
|
До Ширина: | Высота: | Размер: 102 KiB После Ширина: | Высота: | Размер: 102 KiB |
|
До Ширина: | Высота: | Размер: 75 KiB После Ширина: | Высота: | Размер: 75 KiB |
|
До Ширина: | Высота: | Размер: 77 KiB После Ширина: | Высота: | Размер: 77 KiB |
|
До Ширина: | Высота: | Размер: 81 KiB После Ширина: | Высота: | Размер: 81 KiB |
|
До Ширина: | Высота: | Размер: 44 KiB После Ширина: | Высота: | Размер: 44 KiB |
|
До Ширина: | Высота: | Размер: 160 KiB После Ширина: | Высота: | Размер: 160 KiB |
|
До Ширина: | Высота: | Размер: 68 KiB После Ширина: | Высота: | Размер: 68 KiB |
|
До Ширина: | Высота: | Размер: 99 KiB После Ширина: | Высота: | Размер: 99 KiB |
|
До Ширина: | Высота: | Размер: 66 KiB После Ширина: | Высота: | Размер: 66 KiB |
|
До Ширина: | Высота: | Размер: 108 KiB После Ширина: | Высота: | Размер: 108 KiB |
|
До Ширина: | Высота: | Размер: 89 KiB После Ширина: | Высота: | Размер: 89 KiB |
|
До Ширина: | Высота: | Размер: 107 KiB После Ширина: | Высота: | Размер: 107 KiB |
|
До Ширина: | Высота: | Размер: 75 KiB После Ширина: | Высота: | Размер: 75 KiB |
|
До Ширина: | Высота: | Размер: 93 KiB После Ширина: | Высота: | Размер: 93 KiB |
|
До Ширина: | Высота: | Размер: 4.7 KiB После Ширина: | Высота: | Размер: 4.7 KiB |
|
До Ширина: | Высота: | Размер: 87 KiB После Ширина: | Высота: | Размер: 87 KiB |
|
До Ширина: | Высота: | Размер: 160 KiB После Ширина: | Высота: | Размер: 160 KiB |
|
До Ширина: | Высота: | Размер: 136 KiB После Ширина: | Высота: | Размер: 136 KiB |
|
До Ширина: | Высота: | Размер: 177 KiB После Ширина: | Высота: | Размер: 177 KiB |
|
До Ширина: | Высота: | Размер: 133 KiB После Ширина: | Высота: | Размер: 133 KiB |
|
До Ширина: | Высота: | Размер: 97 KiB После Ширина: | Высота: | Размер: 97 KiB |
|
До Ширина: | Высота: | Размер: 106 KiB После Ширина: | Высота: | Размер: 106 KiB |
|
До Ширина: | Высота: | Размер: 104 KiB После Ширина: | Высота: | Размер: 104 KiB |
|
До Ширина: | Высота: | Размер: 164 KiB После Ширина: | Высота: | Размер: 164 KiB |
|
До Ширина: | Высота: | Размер: 37 KiB После Ширина: | Высота: | Размер: 37 KiB |
|
До Ширина: | Высота: | Размер: 64 KiB После Ширина: | Высота: | Размер: 64 KiB |
|
До Ширина: | Высота: | Размер: 73 KiB После Ширина: | Высота: | Размер: 73 KiB |
|
До Ширина: | Высота: | Размер: 85 KiB После Ширина: | Высота: | Размер: 85 KiB |
|
До Ширина: | Высота: | Размер: 82 KiB После Ширина: | Высота: | Размер: 82 KiB |
|
До Ширина: | Высота: | Размер: 108 KiB После Ширина: | Высота: | Размер: 108 KiB |
|
До Ширина: | Высота: | Размер: 67 KiB После Ширина: | Высота: | Размер: 67 KiB |
|
До Ширина: | Высота: | Размер: 78 KiB После Ширина: | Высота: | Размер: 78 KiB |
|
До Ширина: | Высота: | Размер: 200 KiB После Ширина: | Высота: | Размер: 200 KiB |
|
До Ширина: | Высота: | Размер: 131 KiB После Ширина: | Высота: | Размер: 131 KiB |
|
До Ширина: | Высота: | Размер: 100 KiB После Ширина: | Высота: | Размер: 100 KiB |
|
До Ширина: | Высота: | Размер: 160 KiB После Ширина: | Высота: | Размер: 160 KiB |
|
До Ширина: | Высота: | Размер: 213 KiB После Ширина: | Высота: | Размер: 213 KiB |
|
До Ширина: | Высота: | Размер: 5.8 KiB После Ширина: | Высота: | Размер: 5.8 KiB |
|
До Ширина: | Высота: | Размер: 673 B После Ширина: | Высота: | Размер: 673 B |
|
До Ширина: | Высота: | Размер: 1.2 KiB После Ширина: | Высота: | Размер: 1.2 KiB |
|
До Ширина: | Высота: | Размер: 365 KiB После Ширина: | Высота: | Размер: 365 KiB |
|
До Ширина: | Высота: | Размер: 67 KiB После Ширина: | Высота: | Размер: 67 KiB |
54
index.html
Обычный файл
|
До Ширина: | Высота: | Размер: 6.9 KiB После Ширина: | Высота: | Размер: 6.9 KiB |
|
До Ширина: | Высота: | Размер: 4.9 KiB После Ширина: | Высота: | Размер: 4.9 KiB |
2882
package-lock.json
сгенерированный
36
package.json
@ -1,36 +0,0 @@
|
||||
{
|
||||
"name": "interference2024",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"format": "prettier --write .",
|
||||
"gdoc": "node tasks/fetch-google.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^3.0.0",
|
||||
"@sveltejs/adapter-static": "^3.0.5",
|
||||
"@sveltejs/kit": "^2.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||
"@types/eslint": "^9.6.0",
|
||||
"archieml": "^0.5.0",
|
||||
"d3-array": "^3.2.4",
|
||||
"d3-fetch": "^3.0.1",
|
||||
"d3-format": "^3.1.0",
|
||||
"d3-scale": "^4.0.2",
|
||||
"d3-shape": "^3.2.0",
|
||||
"d3-time-format": "^4.1.0",
|
||||
"eslint": "^9.0.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-svelte": "^2.36.0",
|
||||
"globals": "^15.0.0",
|
||||
"prettier": "^3.1.1",
|
||||
"prettier-plugin-svelte": "^3.1.2",
|
||||
"svelte": "^4.2.7",
|
||||
"vite": "^5.0.3"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
export function slidable(node) {
|
||||
let x;
|
||||
let left;
|
||||
|
||||
function handleMousedown(event) {
|
||||
x = event.clientX;
|
||||
|
||||
node.dispatchEvent(
|
||||
new CustomEvent('slidestart', {
|
||||
detail: { x },
|
||||
})
|
||||
);
|
||||
|
||||
window.addEventListener('mousemove', handleMousemove);
|
||||
window.addEventListener('mouseup', handleMouseup);
|
||||
}
|
||||
|
||||
function handleMousemove(event) {
|
||||
const dx = event.clientX - x;
|
||||
x = event.clientX;
|
||||
|
||||
node.dispatchEvent(
|
||||
new CustomEvent('slide', {
|
||||
detail: { x, dx },
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function handleMouseup(event) {
|
||||
x = event.clientX;
|
||||
left = node.offsetLeft;
|
||||
|
||||
node.dispatchEvent(
|
||||
new CustomEvent('slideend', {
|
||||
detail: { x, left },
|
||||
})
|
||||
);
|
||||
|
||||
window.removeEventListener('mousemove', handleMousemove);
|
||||
window.removeEventListener('mouseup', handleMouseup);
|
||||
}
|
||||
|
||||
node.addEventListener('mousedown', handleMousedown);
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
node.removeEventListener('mousedown', handleMousedown);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
19
src/app.html
@ -1,19 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.2/css/versions/bulma-no-dark-mode.css">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Volkhov:ital,wght@0,400;0,700;1,400&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Quicksand:wght@400;500;700&display=swap" rel="stylesheet">
|
||||
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="images/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="images/favicon-16x16.png">
|
||||
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -1 +0,0 @@
|
||||
{"meta":{"title":"Interference 2024","subtitle":"Foreign Interference Attribution Tracker","og_site_name":"Interference Tracker 2024","og_description":"The DFRLab's Foreign Interference Attribution Tracker (FIAT) is an interactive, open-source database that captures allegations of foreign interference relevant to the 2024 election.","og_url":"https://interference2020.org/","og_image":""},"intro":[{"id":"intro","type":"text","text":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"},{"id":"overview","type":"concealed-text","title":"Overview","text":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"},{"id":"how-to-use","type":"concealed-text","title":"How To Use This Tool","text":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"}],"moreInfo":[{"id":"methodology","type":"concealed-text","title":"Methodology","text":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"},{"id":"case-selection","type":"concealed-text","title":"Case Selection","text":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"},{"id":"about","type":"text","label":"About This Project","text":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"},{"id":"about-dfrlab","type":"text","title":"About The DFRLab","text":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."}]}
|
||||
@ -1,69 +0,0 @@
|
||||
<script>
|
||||
export let sidebarOpen
|
||||
import { tweened } from 'svelte/motion';
|
||||
import { cubicOut } from 'svelte/easing';
|
||||
|
||||
const upperY = tweened(28, {
|
||||
duration: 500,
|
||||
easing: cubicOut
|
||||
});
|
||||
const lowerY = tweened(100, {
|
||||
duration: 500,
|
||||
easing: cubicOut
|
||||
});
|
||||
const strokeWidth = tweened(14, {
|
||||
duration: 500,
|
||||
easing: cubicOut
|
||||
});
|
||||
|
||||
const circleRadius = tweened(0, {
|
||||
duration: 500,
|
||||
easing: cubicOut
|
||||
});
|
||||
|
||||
$: if(sidebarOpen){
|
||||
upperY.set(140)
|
||||
lowerY.set(28)
|
||||
strokeWidth.set(0)
|
||||
circleRadius.set(0)
|
||||
}
|
||||
$: if(!sidebarOpen){
|
||||
upperY.set(28)
|
||||
lowerY.set(140)
|
||||
strokeWidth.set(14)
|
||||
circleRadius.set(14)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="hamburger-container">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 128 160">
|
||||
<g id="home" class={"dark"}>
|
||||
<line x1={8} y1={$upperY} x2={120} y2={28}/>
|
||||
<circle cx={44} cy={28} r={$circleRadius} style:stroke-width={$strokeWidth}></circle>
|
||||
<line x1={8} y1={80} x2={120} y2={80} style:stroke-width={$strokeWidth}/>
|
||||
<circle cx={100} cy={80} r={$circleRadius} style:stroke-width={$strokeWidth}></circle>
|
||||
<line x1={8} y1={$lowerY} x2={120} y2={140}/>
|
||||
<circle cx={70} cy={140} r={$circleRadius} style:stroke-width={$strokeWidth}></circle>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.hamburger-container {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
float: right;
|
||||
}
|
||||
line {
|
||||
transition: stroke 1s;
|
||||
stroke-width: 14;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
circle {
|
||||
stroke-width: 14;
|
||||
fill: white;
|
||||
}
|
||||
.dark {
|
||||
stroke: #000000;
|
||||
}
|
||||
</style>
|
||||
@ -1,41 +0,0 @@
|
||||
<script>
|
||||
export let cx;
|
||||
export let cy;
|
||||
export let r;
|
||||
export let fill;
|
||||
export let opacity;
|
||||
export let stroke;
|
||||
export let strokeWidth;
|
||||
export let caseData;
|
||||
export let hoveredCaseData;
|
||||
export let tooltipX;
|
||||
export let tooltipY;
|
||||
export let showTooltip;
|
||||
export let tooltipType;
|
||||
|
||||
function handleMouseOver(event) {
|
||||
tooltipType = 'case'
|
||||
showTooltip = true;
|
||||
tooltipX = event.clientX;
|
||||
tooltipY = event.clientY;
|
||||
hoveredCaseData = caseData;
|
||||
}
|
||||
function handleMouseOut() {
|
||||
//showTooltip = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<circle
|
||||
{cx}
|
||||
{cy}
|
||||
{r}
|
||||
{fill}
|
||||
{opacity}
|
||||
{stroke}
|
||||
stroke-width={strokeWidth}
|
||||
stroke-opacity={1}
|
||||
on:mouseover={handleMouseOver}
|
||||
on:focus={handleMouseOver}
|
||||
on:mouseout={handleMouseOut}
|
||||
on:blur={handleMouseOut}
|
||||
/>
|
||||
@ -1,20 +0,0 @@
|
||||
<script>
|
||||
import CaseCard from "./CaseCard.svelte";
|
||||
export let modalOpen
|
||||
export let activeCaseData
|
||||
|
||||
function closeModal(){
|
||||
modalOpen = false
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div id="card-modal" class={modalOpen ? "modal is-active" : "modal"}>
|
||||
<div class="modal-background" on:click={closeModal}></div>
|
||||
<div class="modal-content">
|
||||
{#if activeCaseData}
|
||||
<CaseCard cardData={activeCaseData} expanded={true} ></CaseCard>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="modal-close" on:click={closeModal}></div>
|
||||
</div>
|
||||
@ -1,169 +0,0 @@
|
||||
<script>
|
||||
import { fade } from 'svelte/transition';
|
||||
import { utcFormat } from 'd3-time-format';
|
||||
import { platformFilter, actorNationFilter, sourceFilter } from '../../stores/filters';
|
||||
import ScoreBar from '$lib/components/ScoreBar.svelte';
|
||||
import ScoreQuestions from '$lib/components/ScoreQuestions.svelte';
|
||||
import { slide } from 'svelte/transition';
|
||||
|
||||
export let cardData;
|
||||
export let expanded;
|
||||
export let modalOpen;
|
||||
export let activeCaseData;
|
||||
|
||||
let openCase = function () {
|
||||
modalOpen = true;
|
||||
activeCaseData = cardData;
|
||||
};
|
||||
|
||||
let scoreQuestionsExpanded = false;
|
||||
</script>
|
||||
|
||||
<div class="card" transition:fade>
|
||||
<div class="header">
|
||||
<div class="card-header-title">
|
||||
<h2 class="is-size-5">{cardData.short_title}</h2>
|
||||
</div>
|
||||
{#if expanded}
|
||||
<div class="card-content">
|
||||
<div class="score-bars">
|
||||
<div class="score-bar-wrapper">
|
||||
<ScoreBar value={cardData.credibility} maxValue={5} />
|
||||
<p>Credibility</p>
|
||||
</div>
|
||||
<div class="score-bar-wrapper">
|
||||
<ScoreBar value={cardData.objectivity} maxValue={3} />
|
||||
<p>Objectivity</p>
|
||||
</div>
|
||||
<div class="score-bar-wrapper">
|
||||
<ScoreBar value={cardData.evidence} maxValue={5} />
|
||||
<p>Evidence</p>
|
||||
</div>
|
||||
<div class="score-bar-wrapper">
|
||||
<ScoreBar value={cardData.transparency} maxValue={5} />
|
||||
<p>Transparency</p>
|
||||
</div>
|
||||
<span
|
||||
class="score-info-icon disable-select"
|
||||
on:click|self={() => (scoreQuestionsExpanded = !scoreQuestionsExpanded)}
|
||||
>
|
||||
{scoreQuestionsExpanded ? 'X' : '?'}
|
||||
</span>
|
||||
</div>
|
||||
{#if scoreQuestionsExpanded}
|
||||
<div class="score-questions-container" transition:slide|local>
|
||||
<ScoreQuestions {cardData}></ScoreQuestions>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="card-image">
|
||||
<figure class="image">
|
||||
<img src={`/images/${cardData.attribution_id}.jpg`} />
|
||||
</figure>
|
||||
{#if expanded}
|
||||
<div class="image-credit">
|
||||
Image: <a
|
||||
href={cardData.image_credit_url == 'attribution_url'
|
||||
? cardData.attribution_url_x
|
||||
: cardData.image_credit_url}
|
||||
target="_blank">{cardData.image_credit}</a
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="content">
|
||||
{#if expanded}
|
||||
<p>{utcFormat('%B %-d, %Y')(new Date(cardData.attribution_date))}</p>
|
||||
<p><a href={cardData.attribution_url_x} target="_blank">{cardData.source}</a></p>
|
||||
{/if}
|
||||
<p>{cardData.short_description}</p>
|
||||
{#if expanded}
|
||||
<p>
|
||||
<button class="button is-info is-small" on:click={sourceFilter.selectOne(cardData.source)}
|
||||
>{cardData.source}</button
|
||||
>
|
||||
{#each cardData.actor_nation as nation}
|
||||
<button class="button is-danger is-small" on:click={actorNationFilter.selectOne(nation)}
|
||||
>{nation}</button
|
||||
>
|
||||
{/each}
|
||||
{#each cardData.platform as platform}
|
||||
<button class="button is-link is-small" on:click={platformFilter.selectOne(platform)}
|
||||
>{platform}</button
|
||||
>
|
||||
{/each}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if !expanded}
|
||||
<footer class="card-footer">
|
||||
<button on:click={openCase(cardData.attribution_id)} class="card-footer-item"
|
||||
>Open case</button
|
||||
>
|
||||
</footer>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/*div.card {
|
||||
max-width: 780px;
|
||||
}*/
|
||||
.image-credit {
|
||||
padding: 0px 24px;
|
||||
font-size: 0.8rem;
|
||||
position: relative;
|
||||
height: 24px;
|
||||
top: -24px;
|
||||
color: white;
|
||||
background-color: black;
|
||||
opacity: 0.5;
|
||||
line-height: 1.7;
|
||||
}
|
||||
.image-credit a {
|
||||
color: white;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.score-bars {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
.score-bars span.score-info-icon {
|
||||
width: 1.3rem;
|
||||
height: 1.3rem;
|
||||
margin: 0;
|
||||
padding: 0 auto 0.1rem auto;
|
||||
font-size: 0.8rem;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
color: var(--usa-lightred);
|
||||
border: 2px solid var(--text-darkgray);
|
||||
border-radius: 2px;
|
||||
background-color: var(--text-darkgray);
|
||||
transition: all 400ms ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.score-bars span.score-info-icon:hover {
|
||||
color: var(--text-darkgray);
|
||||
background-color: var(--usa-lightred);
|
||||
}
|
||||
|
||||
.score-bar-wrapper {
|
||||
flex: 1 1 0;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.score-bar-wrapper p {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
button {
|
||||
margin: 0.2rem;
|
||||
}
|
||||
</style>
|
||||
@ -1,53 +0,0 @@
|
||||
<script>
|
||||
import { utcFormat } from 'd3-time-format';
|
||||
export let cases;
|
||||
</script>
|
||||
|
||||
<div class="table-container">
|
||||
<table class="table is-striped is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Title</td>
|
||||
<td>Description</td>
|
||||
<td>Attribution date</td>
|
||||
<td>Source</td>
|
||||
<td>Source category</td>
|
||||
<td>Actor nation</td>
|
||||
<td>Campaign</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each cases as attrCase}
|
||||
{#if attrCase.show}
|
||||
<tr id={'case-' + attrCase.attribution_id}>
|
||||
<td>{attrCase.short_title}</td>
|
||||
<td>{attrCase.short_description}</td>
|
||||
<td>{utcFormat('%B %d, %Y')(new Date(attrCase.attribution_date))}</td>
|
||||
<td>{attrCase.source}</td>
|
||||
<td>{attrCase.source_category}</td>
|
||||
<td
|
||||
>{#each attrCase.actor_nation as nation, i}
|
||||
{attrCase.actor_nation.length != i + 1 ? nation + ', ' : nation}
|
||||
{/each}</td
|
||||
>
|
||||
<td>
|
||||
{#each attrCase.campaign as camp, i}
|
||||
{attrCase.campaign.length != i + 1 ? camp + ', ' : camp}
|
||||
{/each}
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
thead {
|
||||
font-weight: bold;
|
||||
}
|
||||
.table-container {
|
||||
max-height: 800px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
</style>
|
||||
@ -1,68 +0,0 @@
|
||||
<script>
|
||||
export let displayDataAs
|
||||
export let selectedSorting
|
||||
|
||||
const sortOptions = [
|
||||
{ id: 'attribution_date', label: 'Attribution Date', type: 'date' },
|
||||
{ id: 'attribution_score', label: 'Attribution Score', type: 'number' },
|
||||
{ id: 'actor_nation', label: 'Actor Nation', type: 'array' },
|
||||
{ id: 'platform', label: 'Platform', type: 'array' },
|
||||
{ id: 'source', label: 'Source', type: 'array' },
|
||||
{ id: 'source_category', label: 'Source Category', type: 'string' }
|
||||
];
|
||||
|
||||
</script>
|
||||
|
||||
<div class="container cases-controls">
|
||||
<div class="cases-control">
|
||||
<div class="buttons has-addons">
|
||||
<button
|
||||
class={displayDataAs == 'Table'
|
||||
? 'button is-dark is-selected is-small'
|
||||
: 'button is-small'}
|
||||
on:click={() => {
|
||||
displayDataAs = 'Table';
|
||||
}}>Table</button
|
||||
>
|
||||
<button
|
||||
class={displayDataAs == 'Cards'
|
||||
? 'button is-dark is-selected is-small'
|
||||
: 'button is-small'}
|
||||
on:click={() => {
|
||||
displayDataAs = 'Cards';
|
||||
}}>Cards</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cases-control">
|
||||
<label for="sort-select" class="sort-label">Sort cases by </label>
|
||||
<div class="select is-small">
|
||||
<select bind:value={selectedSorting} id="sort-select">
|
||||
{#each sortOptions as sortOpt}
|
||||
<option value={sortOpt}>
|
||||
{sortOpt.label}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cases-control">
|
||||
<a
|
||||
href="https://fiat-2024-processed-data.s3.us-west-2.amazonaws.com/fiat_2024_attribution_data.csv"
|
||||
class="button is-small">Download the data</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.cases-controls {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.cases-control {
|
||||
display: inline-block;
|
||||
margin-right: 3rem;
|
||||
}
|
||||
.sort-label {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
</style>
|
||||
@ -1,47 +0,0 @@
|
||||
<script>
|
||||
// custom checkbox
|
||||
export let id;
|
||||
export let checked = false;
|
||||
</script>
|
||||
|
||||
<input type="checkbox"
|
||||
id="checkbox-{id}"
|
||||
class:checked
|
||||
on:click|stopPropagation />
|
||||
<label for="checkbox-{id}" class="choice-entry-name">
|
||||
<slot></slot>
|
||||
</label>
|
||||
|
||||
<style>
|
||||
label {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
padding-left: 1.5rem;
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
display: none;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
input[type="checkbox"] + label::before {
|
||||
display: block;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
margin-top: 3px;
|
||||
margin-right: 0.4rem;
|
||||
border: 2px solid var(--usa-blue);
|
||||
border-radius: 3px;
|
||||
background-color: var(--bg);
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0.5rem;
|
||||
}
|
||||
|
||||
input[type="checkbox"].checked + label::before {
|
||||
box-shadow: inset 0px 0px 0px 3px var(--bg);
|
||||
background-color: var(--usa-blue);
|
||||
}
|
||||
</style>
|
||||
@ -1,77 +0,0 @@
|
||||
<script>
|
||||
export let title
|
||||
export let text
|
||||
export let id
|
||||
</script>
|
||||
|
||||
<input id={id} class="toggle" type="checkbox" />
|
||||
<label for={id} class="lbl-toggle top">{title}</label>
|
||||
<div class="collapsible-content">
|
||||
<p>{@html text}</p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
input.toggle[type='checkbox'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.lbl-toggle {
|
||||
display: block;
|
||||
margin-left: -0.3rem;
|
||||
padding: 0.2rem 0;
|
||||
font-size: 0.9rem;
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
color: var(--text-black);
|
||||
cursor: pointer;
|
||||
transition: all 200ms ease;
|
||||
}
|
||||
|
||||
.lbl-toggle.top {
|
||||
font-size: 0.85rem;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.lbl-toggle::before {
|
||||
content: ' ';
|
||||
display: inline-block;
|
||||
border-top: 5px solid transparent;
|
||||
border-bottom: 5px solid transparent;
|
||||
border-left: 5px solid currentColor;
|
||||
vertical-align: middle;
|
||||
margin-top: 2px;
|
||||
transform: translateX(-0.6rem) translateY(-2px);
|
||||
transition: transform 200ms ease-out;
|
||||
}
|
||||
|
||||
.collapsible-content {
|
||||
max-height: 0px;
|
||||
overflow: hidden;
|
||||
transition: max-height 200ms ease-in-out;
|
||||
}
|
||||
|
||||
.collapsible-content h4,
|
||||
.collapsible-content h5 {
|
||||
margin: 1.2rem 1rem 0 1rem;
|
||||
}
|
||||
|
||||
.collapsible-content p {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.toggle:checked + .lbl-toggle + .collapsible-content {
|
||||
max-height: 10000px;
|
||||
border: 1px solid var(--dfrlab-lightgray);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.toggle:checked + .lbl-toggle::before {
|
||||
transform: rotate(90deg) translateY(0.6rem);
|
||||
}
|
||||
|
||||
.toggle:checked + .lbl-toggle {
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
</style>
|
||||
@ -1,164 +0,0 @@
|
||||
<script>
|
||||
import Dropdown from '$lib/components/Dropdown.svelte';
|
||||
import Slider from '$lib/components/Slider.svelte';
|
||||
import DateRangeSlider from '$lib/components/DateRangeSlider.svelte';
|
||||
import SearchText from '$lib/components/SearchText.svelte';
|
||||
import Share from '$lib/components/Share.svelte';
|
||||
import { utcFormat } from 'd3-time-format';
|
||||
//import { attributionScoreScale } from '../../stores/scales';
|
||||
import {
|
||||
platformFilter,
|
||||
actorNationFilter,
|
||||
sourceFilter,
|
||||
sourceCategoryFilter,
|
||||
methodFilter,
|
||||
campaignFilter,
|
||||
selectAllFilters,
|
||||
attributionScoreFilter,
|
||||
attributionScoreDef,
|
||||
textSearchFilter,
|
||||
timeRangeFilter,
|
||||
fullTimeRange,
|
||||
defaultTimeRange
|
||||
} from '../../stores/filters';
|
||||
|
||||
const timeFormat = utcFormat('%m/%d/%y')
|
||||
|
||||
export let cases;
|
||||
|
||||
$: timeDummyRange = ($defaultTimeRange[0] - $fullTimeRange[0])/($fullTimeRange[1] - $fullTimeRange[0])
|
||||
? [($defaultTimeRange[0] - $fullTimeRange[0])/($fullTimeRange[1] - $fullTimeRange[0])*10, 10]
|
||||
: [0, 10]
|
||||
|
||||
function handleButtonClick() {
|
||||
selectAllFilters();
|
||||
timeRangeFilter.set($defaultTimeRange);
|
||||
//timeDummyRange = [0, 10]
|
||||
timeDummyRange = [($defaultTimeRange[0] - $fullTimeRange[0])/($fullTimeRange[1] - $fullTimeRange[0])*10, 10]
|
||||
/*contextData.unselectAll();
|
||||
$highlightPolarization = false;
|
||||
$highlightCib = false;
|
||||
if ($originalTimeDomain) {
|
||||
$timeScale.domain($originalTimeDomain);
|
||||
$timeScale = $timeScale;
|
||||
$originalTimeDomain = null;
|
||||
}*/
|
||||
}
|
||||
|
||||
function addCount(filter, property, cases) {
|
||||
return filter.map((d) => ({
|
||||
...d,
|
||||
count: cases
|
||||
.map((d) => d[property])
|
||||
.flat()
|
||||
.filter((a) => a === d.id).length,
|
||||
liveCount: cases
|
||||
.filter((d) => d.show)
|
||||
.map((d) => d[property])
|
||||
.flat()
|
||||
.filter((a) => a === d.id).length
|
||||
}));
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if cases}
|
||||
<div class={"controls-wrapper"}>
|
||||
<div class="grid is-col-min-8">
|
||||
<SearchText
|
||||
searchString={$textSearchFilter}
|
||||
label="Search"
|
||||
on:change={(e) => ($textSearchFilter = e.detail)}
|
||||
on:reset={() => textSearchFilter.reset()}
|
||||
/>
|
||||
<Slider
|
||||
value={$attributionScoreFilter}
|
||||
label={`Attribution Score: ${$attributionScoreFilter[0]} - ${$attributionScoreFilter[1]}`}
|
||||
min={attributionScoreDef[0]}
|
||||
max={attributionScoreDef[1]}
|
||||
showHandleLabels={false}
|
||||
startColor={'#ffffff'}
|
||||
stopColor={'#000000'}
|
||||
on:changed={(e) => ($attributionScoreFilter = e.detail)}
|
||||
/>
|
||||
<Dropdown
|
||||
items={addCount($actorNationFilter, 'actor_nation', cases)}
|
||||
label="Actor Nation"
|
||||
on:itemsAdded={(e) => actorNationFilter.select(e.detail)}
|
||||
on:itemsRemoved={(e) => actorNationFilter.unselect(e.detail)}
|
||||
></Dropdown>
|
||||
<Dropdown
|
||||
items={addCount($platformFilter, 'medium', cases)}
|
||||
label="Platform"
|
||||
on:itemsAdded={(e) => platformFilter.select(e.detail)}
|
||||
on:itemsRemoved={(e) => platformFilter.unselect(e.detail)}
|
||||
></Dropdown>
|
||||
<Dropdown
|
||||
items={addCount($sourceFilter, 'source', cases)}
|
||||
label="Source"
|
||||
on:itemsAdded={(e) => sourceFilter.select(e.detail)}
|
||||
on:itemsRemoved={(e) => sourceFilter.unselect(e.detail)}
|
||||
></Dropdown>
|
||||
<Dropdown
|
||||
items={addCount($sourceCategoryFilter, 'source_category', cases)}
|
||||
label="Source Category"
|
||||
on:itemsAdded={(e) => sourceCategoryFilter.select(e.detail)}
|
||||
on:itemsRemoved={(e) => sourceCategoryFilter.unselect(e.detail)}
|
||||
></Dropdown>
|
||||
<Dropdown
|
||||
items={addCount($methodFilter, 'methods', cases)}
|
||||
label="Method"
|
||||
on:itemsAdded={(e) => methodFilter.select(e.detail)}
|
||||
on:itemsRemoved={(e) => methodFilter.unselect(e.detail)}
|
||||
></Dropdown>
|
||||
<Dropdown
|
||||
items={addCount($campaignFilter, 'campaign', cases)}
|
||||
label="Campaign"
|
||||
on:itemsAdded={(e) => campaignFilter.select(e.detail)}
|
||||
on:itemsRemoved={(e) => campaignFilter.unselect(e.detail)}
|
||||
></Dropdown>
|
||||
<DateRangeSlider
|
||||
value={timeDummyRange}
|
||||
label={`Date Range: ${timeFormat($timeRangeFilter[0])} - ${timeFormat($timeRangeFilter[1])}`}
|
||||
min={0}
|
||||
max={10}
|
||||
showHandleLabels={false}
|
||||
startColor={'#ffffff'}
|
||||
stopColor={'#000000'}
|
||||
/>
|
||||
<button class="reset-filters" on:click={() => handleButtonClick()}> Reset </button>
|
||||
<Share />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
button {
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
button.reset-filters {
|
||||
align-self: flex-end;
|
||||
min-width: 100px;
|
||||
height: 1.7rem;
|
||||
max-height: 1.7rem;
|
||||
margin: 0.3rem 0.3rem 0 0.3rem;
|
||||
padding: 0.1rem 0.3rem;
|
||||
font-family: var(--font-02);
|
||||
font-size: 0.8rem;
|
||||
font-weight: normal;
|
||||
line-height: 1.3rem;
|
||||
color: var(--usa-blue);
|
||||
background-color: var(--bg);
|
||||
border: 2px solid var(--usa-blue);
|
||||
border-radius: 3px;
|
||||
outline: none;
|
||||
overflow: hidden;
|
||||
transition: all 200ms ease;
|
||||
}
|
||||
|
||||
button.reset-filters:hover {
|
||||
color: var(--bg);
|
||||
background-color: var(--usa-blue);
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@ -1,162 +0,0 @@
|
||||
<script>
|
||||
// a custom slider for time range selection
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { scaleLinear, scaleTime } from 'd3-scale';
|
||||
import { slidable } from '../../actions/slidable';
|
||||
import { timeRangeFilter, fullTimeRange, defaultTimeRange } from '../../stores/filters';
|
||||
|
||||
export let lockInMode = false;
|
||||
export let label = '';
|
||||
export let showLabel = true;
|
||||
export let min = 0;
|
||||
export let max = 10;
|
||||
export let value;
|
||||
export let showHandleLabels = true;
|
||||
export let startColor = 'white';
|
||||
export let middleColor = null;
|
||||
export let stopColor = 'rgb(255, 0, 0)';
|
||||
export let barOpacity = 1;
|
||||
export let showBorder = true;
|
||||
|
||||
// Convert the numeric value numbers to dates
|
||||
$: convertScale = scaleTime().domain($defaultTimeRange).range(value)
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const handleWidth = 17;
|
||||
|
||||
const pos = {
|
||||
left: 0,
|
||||
right: 0
|
||||
};
|
||||
|
||||
let sliderWidth = 0;
|
||||
|
||||
function handleSlide(e, side) {
|
||||
const newPos = pos[side] + e.detail.dx;
|
||||
|
||||
if (newPos < 0 || newPos > sliderWidth) return;
|
||||
if (side === 'left' && newPos > pos.right) return;
|
||||
if (side === 'left' && newPos < scale.range()[0]) return;
|
||||
if (side === 'right' && newPos < pos.left) return;
|
||||
if (side === 'right' && newPos > scale.range()[1]) return;
|
||||
|
||||
pos[side] = newPos;
|
||||
}
|
||||
|
||||
function handleSlideEnd(e, side) {
|
||||
if (lockInMode) {
|
||||
dispatch('changed', [Math.round(scale.invert(pos.left), 0),
|
||||
Math.round(scale.invert(pos.right), 0)]);
|
||||
} else {
|
||||
dispatch('changed', [scale.invert(pos.left), scale.invert(pos.right)]);
|
||||
$timeRangeFilter = [convertScale.invert(scale.invert(pos.left)), convertScale.invert(scale.invert(pos.right))]
|
||||
}
|
||||
}
|
||||
|
||||
$: scale = scaleLinear()
|
||||
.domain([min, max])
|
||||
.range([handleWidth / 2, sliderWidth - 1.7 * handleWidth]);
|
||||
|
||||
$: pos.left = scale(value[0]) || 0;
|
||||
$: pos.right = scale(value[1]) || 0;
|
||||
|
||||
$: background = `linear-gradient(90deg, ${startColor}, ${middleColor ? middleColor + ', ' : ''}${stopColor})`;
|
||||
//$: background = `linear-gradient(90deg, #ffffff, #000000)`;
|
||||
</script>
|
||||
|
||||
<div class="slider"
|
||||
bind:clientWidth={sliderWidth}
|
||||
style="--handle-width: {handleWidth}px;">
|
||||
{#if (showLabel)}
|
||||
<div class="label">
|
||||
{label}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="slider-body" class:border={showBorder}>
|
||||
<div class="slider-selected-range"
|
||||
style="width: {sliderWidth - 2 * handleWidth}px;
|
||||
margin-left: {1 * handleWidth}px;
|
||||
opacity: {barOpacity};
|
||||
background: {background};"></div>
|
||||
<div class="slider-handle"
|
||||
class:no-label={!showHandleLabels}
|
||||
style="left: {(Math.abs(value[0] - value[1]) < 0.1) ? pos.left - 5 : pos.left}px;"
|
||||
use:slidable
|
||||
on:slide={(e) => handleSlide(e, 'left')}
|
||||
on:slideend={(e) => handleSlideEnd(e, 'left')}>
|
||||
<span class="disable-select">{showHandleLabels ? Math.round(scale.invert(pos.left), 0) : ''}</span>
|
||||
</div>
|
||||
<div class="slider-handle"
|
||||
class:no-label={!showHandleLabels}
|
||||
style="left: {(Math.abs(value[0] - value[1]) < 0.1) ? pos.right + 5 : pos.right}px;"
|
||||
use:slidable
|
||||
on:slide={(e) => handleSlide(e, 'right')}
|
||||
on:slideend={(e) => handleSlideEnd(e, 'right')}>
|
||||
<span class="disable-select">{showHandleLabels ? Math.round(scale.invert(pos.right), 0) : ''}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.slider {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: var(--font-02);
|
||||
width: 200px;
|
||||
max-width: 200px;
|
||||
margin: 0.3rem 0.3rem 0 0.3rem;
|
||||
position: relative;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin: 0 0 0.1rem 0;
|
||||
font-size: 0.7rem;
|
||||
color: var(--usa-blue);
|
||||
}
|
||||
|
||||
.slider-body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 1.7rem;
|
||||
padding: 0.1rem 0;
|
||||
font-size: 0.7rem;
|
||||
background-color: var(--bg);
|
||||
border: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.border {
|
||||
border: 2px solid var(--usa-blue);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.slider-selected-range {
|
||||
height: 8px;
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.slider-handle {
|
||||
width: var(--handle-width);
|
||||
height: var(--handle-width);
|
||||
border: 2px solid var(--usa-blue);
|
||||
border-radius: 50%;
|
||||
background-color: var(--bg);
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
.slider-handle > span {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 0.7rem;
|
||||
text-align: center;
|
||||
color: var(--usa-blue);
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
||||
@ -1,241 +0,0 @@
|
||||
<script>
|
||||
// a custom dropdown
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { slide } from 'svelte/transition';
|
||||
import { sortConsistently } from '$lib/utils/misc';
|
||||
|
||||
import Checkbox from '$lib/components/Checkbox.svelte';
|
||||
|
||||
export let items = [];
|
||||
export let label = '';
|
||||
export let nameField = 'id';
|
||||
export let hideOneHitWonders = false;
|
||||
export let superior = false;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let elem;
|
||||
let expanded = false;
|
||||
|
||||
function handleBodyClick() {
|
||||
expanded = false
|
||||
}
|
||||
|
||||
function toggleExpanded() {
|
||||
expanded = !expanded;
|
||||
}
|
||||
|
||||
function selectAll() {
|
||||
dispatch('itemsAdded', items.map((d) => d.id));
|
||||
}
|
||||
|
||||
function unselectAll() {
|
||||
dispatch('itemsRemoved', items.map((d) => d.id));
|
||||
}
|
||||
|
||||
function handleDropdownClick() {
|
||||
toggleExpanded();
|
||||
}
|
||||
|
||||
function handleChoiceClick(id) {
|
||||
if (!items.filter((d) => d.selected).map((d) => d.id).includes(id)) {
|
||||
dispatch('itemsAdded', id);
|
||||
} else {
|
||||
dispatch('itemsRemoved', id);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:body on:click={(e) => handleBodyClick(e)}></svelte:body>
|
||||
|
||||
<div class="dropdown" bind:this={elem}>
|
||||
<div class="label">
|
||||
{label}
|
||||
</div>
|
||||
<div class="selected-items" on:click|stopPropagation={handleDropdownClick}>
|
||||
<span class="selected-items-icon"></span>
|
||||
<span class="selected-items-text">
|
||||
{items.filter((d) => d.selected).length === 0
|
||||
? 'none'
|
||||
: (items.every((d) => d.selected && items.length > 1)
|
||||
? 'all'
|
||||
: items.filter((d) => d.selected).map((d) => d[nameField]).join(', '))}
|
||||
</span>
|
||||
<button class="selected-items-arrow">
|
||||
<svg class:expanded width="15" height="10">
|
||||
<path d="M0 0L15 0L7.5 10Z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="choice-wrapper">
|
||||
{#if (expanded)}
|
||||
<div class="choice" transition:slide class:superior>
|
||||
<div class="choice-controls">
|
||||
<button class="choice-controls-selectall" on:click|stopPropagation={selectAll}>Select all</button>
|
||||
<button class="choice-controls-unselectall" on:click|stopPropagation={unselectAll}>Unselect all</button>
|
||||
</div>
|
||||
<ul class="choice-list">
|
||||
{#each items.sort((a, b) => -sortConsistently(a, b, 'id', 'id')) as item, i (item.id)}
|
||||
{#if (!(hideOneHitWonders && item.count === 1))}
|
||||
<li on:click|stopPropagation>
|
||||
<Checkbox id="{label}-{i}"
|
||||
checked={item.selected}
|
||||
on:click={() => handleChoiceClick(item.id)}>
|
||||
<span class="choice-entry-name">{item[nameField]}</span>
|
||||
{#if (item.liveCount)}
|
||||
<span class="choice-entry-count">({item.liveCount})</span>
|
||||
{:else if (item.source)}
|
||||
<span class="choice-entry-source">({item.source})</span>
|
||||
{/if}
|
||||
</Checkbox>
|
||||
</li>
|
||||
{/if}
|
||||
{/each}
|
||||
</ul>
|
||||
{#if (hideOneHitWonders)}
|
||||
<p class="info">{label}s with only one result in the dataset are hidden.</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.dropdown {
|
||||
align-self: flex-end;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: var(--font-02);
|
||||
width: 200px;
|
||||
max-width: 200px;
|
||||
min-width: 200px;
|
||||
margin: 0.3rem 0.3rem 0 0.3rem;
|
||||
position: relative;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin: 0 0 0.1rem 0;
|
||||
font-size: 0.7rem;
|
||||
color: var(--usa-blue);
|
||||
}
|
||||
|
||||
.selected-items {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
height: 1.7rem;
|
||||
padding: 0.1rem 0.3rem;
|
||||
font-size: 0.8rem;
|
||||
background-color: var(--bg);
|
||||
border: 2px solid var(--usa-blue);
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.selected-items-text {
|
||||
width: 100%;
|
||||
color: var(--text-black);
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.selected-items-arrow {
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.selected-items-arrow svg {
|
||||
margin-top: 0.2rem;
|
||||
stroke: none;
|
||||
fill: var(--usa-blue);
|
||||
transition: transform 400ms ease;
|
||||
}
|
||||
|
||||
.selected-items-arrow svg.expanded{
|
||||
transform: rotate(-540deg);
|
||||
}
|
||||
|
||||
.choice-wrapper {
|
||||
width: 100%;
|
||||
height: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.choice {
|
||||
width: 100%;
|
||||
border: 2px solid var(--usa-lightblue);
|
||||
border-radius: 3px;
|
||||
background-color: var(--bg);
|
||||
position: absolute;
|
||||
z-index: 12000;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.superior {
|
||||
z-index: 100000 !important;
|
||||
}
|
||||
|
||||
.choice-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.choice-controls button {
|
||||
margin: 0.5rem;
|
||||
padding: 0.2rem 0.5rem;
|
||||
color: var(--bg);
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button.choice-controls-selectall {
|
||||
background-color: var(--usa-lightblue);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
button.choice-controls-unselectall {
|
||||
background-color: var(--usa-lightred);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
ul.choice-list {
|
||||
width: 100%;
|
||||
max-height: 600px;
|
||||
overflow-y: scroll;
|
||||
list-style-type: none;
|
||||
font-size: 0.8rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
ul.choice-list li {
|
||||
padding: 0.4rem 0.5rem;
|
||||
cursor: pointer;
|
||||
background-color: var(--bg);
|
||||
transition: background-color 200ms ease;
|
||||
}
|
||||
|
||||
ul.choice-list li:hover {
|
||||
background-color: var(--usa-lightblue);
|
||||
}
|
||||
|
||||
.choice-entry-count, .choice-entry-source {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.choice-entry-source {
|
||||
display: block;
|
||||
}
|
||||
|
||||
p.info {
|
||||
padding: 0.4rem 0.5rem;
|
||||
font-size: 0.7rem;
|
||||
color: var(--dfrlab-gray);
|
||||
}
|
||||
</style>
|
||||
@ -1,53 +0,0 @@
|
||||
<script>
|
||||
import { fade } from 'svelte/transition';
|
||||
import { utcFormat } from 'd3-time-format';
|
||||
|
||||
export let tooltipX;
|
||||
export let tooltipY;
|
||||
export let hoveredEventData;
|
||||
export let width;
|
||||
|
||||
</script>
|
||||
|
||||
<div
|
||||
transition:fade="{{ duration: 250 }}"
|
||||
class="event-tooltip"
|
||||
style="
|
||||
top:{tooltipY}px;
|
||||
left:{tooltipX < width - 300 ? tooltipX + 10 : tooltipX - 300 - 10}px;
|
||||
"
|
||||
>
|
||||
<p class="date">{utcFormat('%B %d, %Y')(hoveredEventData.date)}</p>
|
||||
<h2>{hoveredEventData.Title}</h2>
|
||||
<p class="description">{hoveredEventData.Description}</p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.event-tooltip {
|
||||
position: fixed;
|
||||
/*border: 1px solid #ccc;*/
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.2);
|
||||
padding: 5px;
|
||||
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.2);
|
||||
transform: translate(0%, -50%);
|
||||
z-index: 1000;
|
||||
max-width: 300px;
|
||||
max-height: 400px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.date {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0.2rem 0;
|
||||
font-size: 0.9rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
@ -1,64 +0,0 @@
|
||||
<script>
|
||||
import copy from '../../data/copy.json';
|
||||
</script>
|
||||
|
||||
<section class="content title section" style:background-image={"url('images/fiat_2024_banner_background.jpg')"}>
|
||||
<div class="container has-text-centered">
|
||||
<div class="logos">
|
||||
<a href="https://www.atlanticcouncil.org"
|
||||
><img src="logos/ac.svg" alt="Atlantic Council Logo" /></a
|
||||
>
|
||||
<a
|
||||
class="smaller"
|
||||
href="https://www.atlanticcouncil.org/programs/digital-forensic-research-lab/"
|
||||
><img src="logos/dfrlab.svg" alt="Digital Forensic Research Lab Logo" /></a
|
||||
>
|
||||
</div>
|
||||
<h1 class="title is-size-1">{copy.meta.title}</h1>
|
||||
<h2 class="subtitle is-size-4 has-text-weight-bold">{copy.meta.subtitle}</h2>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
section.content.title {
|
||||
padding-top: 1rem;
|
||||
position: relative;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
max-width: 800px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
section.title .logos {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 2.2rem;
|
||||
}
|
||||
|
||||
section.title .logos a {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
section.title .logos a:not(:last-child) {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
section.title .logos a img {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
section.title .logos a.smaller {
|
||||
height: 80%;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-family: var(--font-01);
|
||||
color: var(--usa-blue);
|
||||
}
|
||||
.subtitle {
|
||||
font-family: var(--font-02);
|
||||
color: var(--usa-blue);
|
||||
}
|
||||
</style>
|
||||
@ -1,70 +0,0 @@
|
||||
<script>
|
||||
export let width
|
||||
export let margins
|
||||
export let radiusScale
|
||||
export let opacityScale
|
||||
|
||||
$: minOpacity = opacityScale.domain()[0]
|
||||
$: maxOpacity = opacityScale.domain()[1]
|
||||
$: opacityRange = maxOpacity - minOpacity
|
||||
$: opacities = [
|
||||
minOpacity,
|
||||
minOpacity + opacityRange/5,
|
||||
minOpacity + opacityRange*2/5,
|
||||
minOpacity + opacityRange*3/5,
|
||||
minOpacity + opacityRange*4/5,
|
||||
maxOpacity]
|
||||
|
||||
let height = 60
|
||||
</script>
|
||||
|
||||
<svg {width} height={height}>
|
||||
<g transform={`translate(${margins.left},${0})`}>
|
||||
<text
|
||||
x={72}
|
||||
y={12}
|
||||
text-anchor={'middle'}
|
||||
>Breakout scale</text>
|
||||
{#each radiusScale.domain() as rad,i}
|
||||
<circle
|
||||
cx={12 - radiusScale(rad) + i*30}
|
||||
cy={32}
|
||||
r={radiusScale(rad)}
|
||||
fill={'#555555'}
|
||||
></circle>
|
||||
{/each}
|
||||
<text
|
||||
x={(width - margins.left - margins.right)/2}
|
||||
y={12}
|
||||
text-anchor={'middle'}
|
||||
>Offline mobilization</text>
|
||||
<circle
|
||||
cx={(width - margins.left - margins.right)/2}
|
||||
cy={32}
|
||||
r={12}
|
||||
fill={'none'}
|
||||
stroke={'#000000'}
|
||||
stroke-width={1.5}
|
||||
></circle>
|
||||
<circle
|
||||
cx={(width - margins.left - margins.right)/2}
|
||||
cy={32}
|
||||
r={9}
|
||||
fill={'#555555'}
|
||||
></circle>
|
||||
<text
|
||||
x={width - margins.left - margins.right - 72}
|
||||
y={12}
|
||||
text-anchor={'middle'}
|
||||
>Attribution score</text>
|
||||
{#each opacities as op,i}
|
||||
<circle
|
||||
cx={width - margins.left - margins.right - 150 + i*30}
|
||||
cy={32}
|
||||
r={9}
|
||||
fill={'#555555'}
|
||||
opacity={opacityScale(op)}
|
||||
></circle>
|
||||
{/each}
|
||||
</g>
|
||||
</svg>
|
||||
@ -1,31 +0,0 @@
|
||||
<script>
|
||||
// a score bar used on case tooltips
|
||||
export let value = 0;
|
||||
export let minValue = 0;
|
||||
export let maxValue = 1;
|
||||
|
||||
$: relState = (value - minValue) / (maxValue - minValue);
|
||||
</script>
|
||||
|
||||
<div class="score-bar">
|
||||
<span class="inner-score-bar"
|
||||
style="width: {relState * 100}%;"></span>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.score-bar {
|
||||
width: 100%;
|
||||
max-width: 70px;
|
||||
min-height: 10px;
|
||||
border: 1px solid var(--text-darkgray);
|
||||
border-radius: 3px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.inner-score-bar {
|
||||
height: 100%;
|
||||
background-color: var(--text-darkgray);
|
||||
border: none;
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
||||
@ -1,86 +0,0 @@
|
||||
<script>
|
||||
import { questions } from '$lib/inputs/scores';
|
||||
export let cardData;
|
||||
</script>
|
||||
|
||||
<div class="score-questions">
|
||||
{#each questions as quest}
|
||||
<h4>{quest.label}: {cardData[quest.category]}/{quest.questions.length}</h4>
|
||||
<ul>
|
||||
{#each quest.questions as q}
|
||||
<li>
|
||||
<input type="checkbox" checked={cardData[q.column] == '1'} />
|
||||
<span class="checkmark"></span>
|
||||
<p>{q.label}</p>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
ul {
|
||||
margin: 0.3rem 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin: 1rem 0 0 0;
|
||||
font-size: 0.8rem;
|
||||
font-weight: bold;
|
||||
color: var(--text-black);
|
||||
}
|
||||
|
||||
li {
|
||||
display: block;
|
||||
margin: 0.3rem 0;
|
||||
padding-left: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
li p {
|
||||
font-family: var(--font-02);
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.5;
|
||||
color: var(--text-black);
|
||||
}
|
||||
|
||||
li input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.checkmark {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 0;
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
background-color: var(--bg);
|
||||
}
|
||||
|
||||
.checkmark:after {
|
||||
content: '';
|
||||
display: none;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
li input:checked ~ .checkmark:after {
|
||||
display: block;
|
||||
}
|
||||
|
||||
li .checkmark:after {
|
||||
left: 5px;
|
||||
top: 2px;
|
||||
width: 3px;
|
||||
height: 7px;
|
||||
border: solid var(--usa-blue);
|
||||
border-width: 0 3px 3px 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,138 +0,0 @@
|
||||
<script>
|
||||
// free text search bar
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
export let searchString = '';
|
||||
export let label = '';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
function reset() {
|
||||
searchString = '';
|
||||
search();
|
||||
}
|
||||
|
||||
function handleKeyUp(e) {
|
||||
if (e && e.keyCode === 13) search();
|
||||
}
|
||||
|
||||
function handleGoClick() {
|
||||
search();
|
||||
}
|
||||
|
||||
function search() {
|
||||
dispatch('change', searchString);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="search-text">
|
||||
<div class="label">
|
||||
<p>{label}</p>
|
||||
<p>|</p>
|
||||
<span on:click={() => reset()}>Reset</span>
|
||||
</div>
|
||||
<div class="search">
|
||||
<input id="table-search-field"
|
||||
type="text"
|
||||
placeholder="Type and press enter"
|
||||
bind:value={searchString}
|
||||
on:keyup={(e) => handleKeyUp(e)} />
|
||||
{#if (searchString !== '')}
|
||||
<span class="button-fields">
|
||||
<span class="reset"
|
||||
class:active={searchString}
|
||||
on:click={() => searchString = ''}>
|
||||
x
|
||||
</span>
|
||||
<span class="go"
|
||||
class:active={searchString}
|
||||
on:click={() => handleGoClick()}>
|
||||
Go
|
||||
</span>
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.search-text {
|
||||
align-self: flex-end;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: var(--font-02);
|
||||
width: 200px;
|
||||
max-width: 200px;
|
||||
min-width: 200px;
|
||||
margin: 0.3rem 0.3rem 0 0.3rem;
|
||||
position: relative;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: flex;
|
||||
margin: 0 0 0.1rem 0;
|
||||
font-size: 0.7rem;
|
||||
color: var(--usa-blue);
|
||||
}
|
||||
|
||||
.label > * {
|
||||
padding-right: 0.2rem;
|
||||
}
|
||||
|
||||
.label span {
|
||||
margin: 0 -0.1rem;
|
||||
padding: 0 0.1rem;
|
||||
color: var(--usa-blue);
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
transition: all 200ms ease;
|
||||
}
|
||||
|
||||
.label span:hover {
|
||||
color: var(--bg);
|
||||
background-color: var(--usa-blue);
|
||||
}
|
||||
|
||||
.search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
height: 1.7rem;
|
||||
font-size: 0.8rem;
|
||||
background-color: var(--bg);
|
||||
border: 2px solid var(--usa-blue);
|
||||
border-radius: 3px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0.1rem 2.3rem 0.1rem 0.3rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-black);
|
||||
background-color: var(--bg);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.button-fields {
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0.2rem;
|
||||
right: 0.3rem;
|
||||
z-index: 10001;
|
||||
}
|
||||
|
||||
.reset {
|
||||
display: none;
|
||||
margin-right: 0.3rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.go {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||