Add swedish mailer to chatcontrol.dk

This still needs translation work (index.html) and potentially more
politicians in data/meps_dk.json
This commit is contained in:
om 2023-08-29 23:17:03 +02:00
parent 3c0f0dd902
commit 93ed0137a6
1725 changed files with 136918 additions and 0 deletions

0
mejla/data/.gitkeep Normal file
View File

126
mejla/data/meps_dk.json Normal file
View File

@ -0,0 +1,126 @@
{
"Greens": [
{
"namn": "Margrete Auken",
"epostadress": "margrete.auken@europarl.eu",
"institution": "EU parlament",
"uppdrag": [
"MEP"
]
},
{
"namn": "Kira Marie Peter-hansen",
"epostadress": "kira.peter-hansen@europarl.eu",
"institution": "EU parlament",
"uppdrag": [
"MEP"
]
}
],
"Renew": [
{
"namn": "Asger Christensen",
"epostadress": "asger.christensen@europarl.eu",
"institution": "EU parlament",
"uppdrag": [
"MEP"
]
},
{
"namn": "Morten Løkkegaard",
"epostadress": "morten.lokkegaard@europarl.eu",
"institution": "EU parlament",
"uppdrag": [
"MEP"
]
},
{
"namn": "Karen Melchior",
"epostadress": "karen.melchior@europarl.eu",
"institution": "EU parlament",
"uppdrag": [
"MEP"
]
},
{
"namn": "Morten Petersen",
"epostadress": "mortenhelveg.petersen@europarl.eu",
"institution": "EU parlament",
"uppdrag": [
"MEP"
]
},
{
"namn": "Erik Poulsen",
"epostadress": "erik.poulsen@europarl.eu",
"institution": "EU parlament",
"uppdrag": [
"MEP"
]
},
{
"namn": "Bergur Løkke Rasmussen",
"epostadress": "bergurlokke.rasmussen@europarl.eu",
"institution": "EU parlament",
"uppdrag": [
"MEP"
]
}
],
"S": [
{
"namn": "Niels Fuglsang",
"epostadress": "niels.fuglsang@europarl.eu",
"institution": "EU parlament",
"uppdrag": [
"MEP"
]
},
{
"namn": "Christel Schaldemose",
"epostadress": "christel.schaldemose@europarl.eu",
"institution": "EU parlament",
"uppdrag": [
"MEP"
]
},
{
"namn": "Marianne Vind",
"epostadress": "marianne.vind@europarl.eu",
"institution": "EU parlament",
"uppdrag": [
"MEP"
]
}
],
"V": [
{
"namn": "Nikolaj Villumsen",
"epostadress": "nikolaj.villumsen@europarl.eu",
"institution": "EU parlament",
"uppdrag": [
"MEP"
]
}
],
"ID": [
{
"namn": "Anders Vistisen",
"epostadress": "anders.vistisen@europarl.eu",
"institution": "EU parlament",
"uppdrag": [
"MEP"
]
}
],
"EPP": [
{
"namn": "Pernille Weiss",
"epostadress": "pernille.weiss@europarl.eu",
"institution": "EU parlament",
"uppdrag": [
"MEP"
]
}
]
}

276
mejla/index.html Normal file
View File

@ -0,0 +1,276 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<link rel="icon" href="data:," /><!-- no favicon for now -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="./style/index.css">
<script src="./scripts/utils.js" type="text/javascript"></script>
<script src="./scripts/riksdagen.js" type="text/javascript"></script>
<script type="text/javascript">
async function setup_mep_data() {
riksdagen = new Riksdagen(await load_mep_data()); // global variable
riksdagen.configureRecipientSelection('mejlverktyg-main')
}
</script>
<title>Stoppa Chat Control</title>
</head>
<body onload="setup_mep_data()">
<header>
<h1>Stoppa Chat&nbsp;Control 2.0</h1>
<h3>Vill du hjälpa till med att protestera Chat&nbsp;Control&nbsp;2.0 men du vet inte vad du ska skriva?</h3>
<p>
Här kan du välja bland olika alternativ för ämnesrad, inledning, argument, samt signera med ditt namn om du vill.
Du får hela mejlet genererat för dig att skicka på direkten!
</p>
</header>
<main oninput="riksdagen.generateOutput()" id="main">
<section class="bordered" id="select-recipient">
<h3>Välj mottagare</h3>
<p>
Först får du välja till vem du ska kontakta.
Du kan välja partiledare, EU-parlamentariker och ledamöter
i konstititutionsutskottet, justitieutskottet och EU-nämnden i riksdagen,
för respektive parti.
</p>
<div id="mejlverktyg-main">
</section>
<section class="bordered options options__subject">
<h3>Ämnesrad för e-post</h3>
<p>
Hur ska mejlet se ut i deras inkorg?
</p>
<input id="subject-1" type="radio" name="subject" checked>
<label for="subject-1">
Stoppa förslaget Chat Control
</label>
<input id="subject-2" type="radio" name="subject">
<label for="subject-2">
Säg nej till övervakningsförslaget från EU
</label>
<input id="subject-3" type="radio" name="subject">
<label for="subject-3">
Varning för övervakningsförslag från EU
</label>
<input id="subject-4" type="radio" name="subject">
<label for="subject-4">
Jag vill inte ha massövervakning
</label>
<input id="subject-5" type="radio" name="subject">
<label for="subject-5">
Chat Control är slutet för privat kommunikation
</label>
</section>
<section class="bordered options options__body">
<h3>Inledning</h3>
<input id="beginning-1" type="radio" name="beginning" checked>
<label for="beginning-1">
Hej <i class="placeholder" name="recipient__name"></i>,<br><br>
Jag kontaktar dig eftersom är du ledamot för <i class="placeholder" name="recipient__party"></i> och vill
uppmana dig att verka för att stoppa Chat Control 2.0.
</label>
<input id="beginning-2" type="radio" name="beginning">
<label for="beginning-2">
Kära <i class="placeholder" name="recipient__name"></i>,<br><br>
Har du hört talas om massövervakningsförslaget Chat Control 2.0? Det är ett nytt förslag från EU som skulle
innebära en massövervakning.
</label>
<input id="beginning-3" type="radio" name="beginning">
<label for="beginning-3">
Hej,<br><br>
Jag mailar dig gällande Chat Control 2.0.
</label>
</section>
<section class="bordered options options__body">
<h3>Introduktion om Chat Control</h3>
<p>
Börja med att introducera Chat Control till dem om det skulle vara så att de inte hört om det än.
</p>
<input id="introduction-1" type="radio" name="introduction" checked>
<label for="introduction-1">
EU-kommissionären Ylva Johansson har tagit fram ett förslag som kallas Chat Control 2.0. Det kallas så eftersom
om förslaget skulle implementeras skulle plattformar tvingas skanna sina användares privata kommunikation.
</label>
<input id="introduction-2" type="radio" name="introduction">
<label for="introduction-2">
Ett nytt förslag förhandlas nu på EU-nivå vilket handlar om att övervaka privat kommunikation. Det uttalade
syftet är att bekämpar CSAM-material (barnporr) men det kommer inte lyckas med det. Istället kommer vanligt folk
få all sin kommunikation övervakad.
</label>
<input id="introduction-3" type="radio" name="introduction">
<label for="introduction-3">
I maj 2022 lade EU-kommissionär Ylva Johansson fram förslaget som i folkmun kallas Chat Control 2.0. Förslaget
är tänkt att förebygga och bekämpa sexuella övergrepp mot barn. Målsättningen är lovvärd, men förslaget är
framtaget på felaktiga antaganden kring hur meddelandetjänster fungerar rent tekniskt.
</label>
</section>
<section class="bordered options options__body">
<h3>Allmänna argument</h3>
<input id="allmanna-argument-1" type="checkbox" name="allmanna-argument">
<label for="allmanna-argument-1">
I nuläget kan kommunikationsplattformar frivilligt övervaka sina användares kommunikation. Det är i sig
problematiskt att privat kommunikation övervakas men det stora problemet med förslaget med Chat Control är att
plattformarna nu tvingas att övervaka all kommunikation.
</label>
<input id="allmanna-argument-2" type="checkbox" name="allmanna-argument" checked>
<label for="allmanna-argument-2">
Förslaget underminerar all möjlighet till säker kommunikation. Detta ökar risken för självcensur och att
politiska meningsmotståndare, journalister och människorättsaktivister tystas.
</label>
<input id="allmanna-argument-3" type="checkbox" name="allmanna-argument" checked>
<label for="allmanna-argument-3">
Chat Control innebär i praktiken att kommunikationsappar med så kallad end-to-end-kryptering kommer tvingas att
installera bakdörrar så att innehållet ska kunna skannas.
</label>
<input id="allmanna-argument-4" type="checkbox" name="allmanna-argument">
<label for="allmanna-argument-4">
Det finns en koalition av organisationer som gått ihop för att stoppa förslaget under parollen Stop Scanning Me.
I det ingår bland annat EDRi, Women's Link Worldwide, Center for Democracy & Technology (CDT) och Electronic
Frontier Foundation (EFF). I ett öppet brev så uppmanar 124 föreningar från civilsamhället EU-parlamentariker
att dra tillbaka lagen.
</label>
<input id="allmanna-argument-5" type="checkbox" name="allmanna-argument" checked>
<label for="allmanna-argument-5">
Det finns en väldigt stor risk med ändamålsglidning. Idag säger man att det ska vara barnporr man ska skanna
efter. Imorgon blir det sannolikt terrorism och droghandel. Ju fler saker man skannar efter desto mindre tröskel
blir det att lägga till ytterligare en sak att skanna efter. Och vad för slags regeringar finns runt om i EU om
5 år? Om 10 år?
</label>
<input id="allmanna-argument-6" type="checkbox" name="allmanna-argument">
<label for="allmanna-argument-6">
Chat Control kommer innebära stora risker för källskydd. Journalistförbundet varnar för just detta i en
debattartikel i DN.
</label>
</section>
<section class="bordered options options__body">
<h3>Argument gällande mänskliga&nbsp;rättigheter</h3>
<input id="manskliga-rattigheter-1" type="checkbox" name="manskliga-rattigheter">
<label for="manskliga-rattigheter-1">
Chat Control-förslaget tar bort medborgarnas grundlagsbefästa, människorättsförankrade och
Europakonventionsskyddade rätt till privat kommunikation. Dessa brott mot mänskliga rättigheter erkänns av
förslagets författare (sida 13).
</label>
<input id="manskliga-rattigheter-2" type="checkbox" name="manskliga-rattigheter" checked>
<label for="manskliga-rattigheter-2">
Förslaget bryter mot Europakonventionen (artikel 8 och artikel 10) samt FN:s deklaration om mänskliga
rättigheter (artikel 12). I Europakonventionen, artikel 8, står det “Var och en har rätt till skydd för sitt
privat- och familjeliv, sitt hem och sin korrespondens.”
</label>
<input id="manskliga-rattigheter-3" type="checkbox" name="manskliga-rattigheter" checked>
<label for="manskliga-rattigheter-3">
FN:s människorättskommissionär bedömer det som osannolikt att förslagets krav på bakdörrar är förenligt med
internationell människorättslagstiftning (A/HRC/51/17, paragraf 28).
</label>
<input id="manskliga-rattigheter-4" type="checkbox" name="manskliga-rattigheter">
<label for="manskliga-rattigheter-4">
Förslaget bryter också mot Barnkonventionen (artikel 16) där det står: “Inget barn får utsättas för godtyckliga
eller olagliga ingripanden i sitt privat- och familjeliv, sitt hem eller sin korrespondens och inte heller för
olagliga angrepp på sin heder och sitt anseende.”
</label>
<input id="manskliga-rattigheter-5" type="checkbox" name="manskliga-rattigheter" checked>
<label for="manskliga-rattigheter-5">
UNICEF skriver följande mening i första principen i General Principles on Children's Online Privacy and Freedom
of Expression: “Barnens kommunikationsintegritet är hotad när deras inlägg, chattar, meddelanden eller samtal är
avlyssnade av stater eller andra aktörer, och barns informationsintegritet kan utsättas för risk när barnens
personliga data samlas in, sparas eller behandlas.” (Fritt översatt).
</label>
</section>
<section class="bordered options options__body">
<h3>Avslutning</h3>
<p>
Och till slut, signera med ditt namn.
</p>
<div id="enter-name">
<input type="text" id="sender__name" name="sender__name" placeholder="Ditt namn"
oninput="setPlaceholder('sender__name', this.value)">
</div>
<input id="ending-1" type="radio" name="ending" checked>
<label for="ending-1">
Jag hoppas att du och ditt parti kommer säga nej till det här förslaget.<br>
<br><br>
Med vänlig hälsning,<br>
<i class="placeholder" name="sender__name"></i>
</label>
<input id="ending-2" type="radio" name="ending">
<label for="ending-2">
Chat Control är ett stort hot mot demokratin. Vad gör du för att förhindra det här och andra
integritetskränkande förslag?<br>
<br><br>
Hälsningar,<br>
<i class="placeholder" name="sender__name"></i>
</label>
</section>
<section id="output" class="bordered">
<h3>Ditt mejl</h3>
<p>
Kopiera epostadress, ämnesrad och innehåll nedan eller klicka på knappen för att automatiskt öppna det i din
mejlklient.<br>
<br>
Om du inte är nöjd med resultatet kan du alltid skrolla upp och ändra dina val eller manuellt redigera texten
efteråt i din mejlklient.
</p>
<a id="output__mailto" href="">Skicka mejl</a>
<output id="output__email"></output>
<output id="output__subject"></output>
<output id="output__body"></output>
</section>
</main>
<footer>
<p>
Ikoner används under licens från Font Awesome.
</p>
</footer>
</body>
</html>

241
mejla/scripts/riksdagen.js Normal file
View File

@ -0,0 +1,241 @@
const politicians_data_path = "./data/meps_dk.json";
class Riksdagen {
constructor (members) {
this.members = members;
// Add internal reference to party for each member.
for (const party of Object.keys(this.members)) {
for (let index = 0; index < this.members[party].length; index++) {
this.members[party][index]['parti'] = party
}
}
}
getPartyName (abbreviation) {
/*
Get full party name from abbreviated form.
*/
switch (abbreviation) {
case 'Greens':
return 'Group of the Greens/European Free Alliance'
case 'Renew':
return 'Renew Europe Group'
case 'S':
return 'Socialdemokraterne'
case 'V':
return 'Venstre'
case 'ID':
return 'Identitet og Demokrati'
case 'EPP':
return 'Det Europæiske Folkepartis Gruppe'
default:
return abbreviation
}
}
get parties () {
/*
Get list of abbreviations of all parties.
*/
return Object.keys(this.members).sort()
}
get recipient () {
/*
Get information about selected member.
*/
const member = this.memberSelector.querySelector(':checked')
if (!member) return null;
const party = member.dataset.party
const index = parseInt(member.dataset.index)
return this.members[party][index]
}
configureRecipientSelection (recipientSelectorId) {
/*
Load list of parties and members for recipient selection.
*/
// Create elements to host list of parties
// and list of members of the selected party
const recipientSelector = document.getElementById(recipientSelectorId)
this.partySelector = document.createElement('fieldset')
this.partySelector.setAttribute('class', 'select-party')
recipientSelector.appendChild(this.partySelector)
this.memberSelector = document.createElement('div')
this.memberSelector.setAttribute('class', 'select-member')
recipientSelector.appendChild(this.memberSelector)
// Set up party selection
for (const party of this.parties) {
const partyId = `party-${party}`
// Create radio button
const input = document.createElement('input')
input.setAttribute('type', 'radio')
input.setAttribute('id', partyId)
input.setAttribute('name', 'party')
input.setAttribute('value', party)
input.setAttribute('required', 'required')
// Create label
const label = document.createElement('label')
label.setAttribute('for', partyId)
label.setAttribute('class', partyId)
label.append(this.getPartyName(party))
// Add elements to list
this.partySelector.append(input)
this.partySelector.append(label)
}
// On selecting a party,
// generate list for member selection.
this.partySelector.addEventListener('input', () => {
const activeElement = document.activeElement
this.memberSelector.innerHTML = ""
const selectedParty = this.partySelector.querySelector('input[name="party"]:checked').value
const prio = {
"MEP": 0,
/*
"Partiledare": 1,
"EU-parlamentet": 2,
"Gruppledare": 3,
"Konstitutionsutskottet": 4,
"Justitieutskottet": 5,
"EU-nämnden": 6,
*/
};
// Sort members beforehand
// First show the members with uppdrag "Partiledare",
// then the people with uppdrag "EU-parlamentet", etc.
const members = this.members[selectedParty].sort((a, b) => {
if (prio[a['uppdrag'][0]] > prio[b['uppdrag'][0]]) {
return 1;
} else if (prio[a['uppdrag'][0]] < prio[b['uppdrag'][0]]) {
return -1;
} else {
return a['namn'] > b['namn'] ? 1 : -1
}
})
// let euHeader = document.createElement('h2')
// euHeader.innerHTML = "EU-parlamentet"
// this.memberSelector.appendChild(euHeader)
for (const [index, member] of members.entries()) {
const memberId = `member-${selectedParty}-${index}`
const radio = document.createElement('input')
radio.setAttribute('type', 'radio')
radio.setAttribute('class', 'member')
radio.setAttribute('name', 'member')
radio.setAttribute('id', memberId)
radio.setAttribute('value', index)
radio.setAttribute('data-party', selectedParty)
radio.setAttribute('data-index', index)
const label = document.createElement('label')
label.setAttribute('for', memberId)
label.setAttribute('class', 'member')
label.setAttribute('title', member['namn'])
//label.append(member['namn'])
const uppdragString = member["uppdrag"].join(", ")
label.append(member['namn']+" ("+uppdragString+")")
this.memberSelector.appendChild(radio)
this.memberSelector.appendChild(label)
// Select the first option.
if (index == 0) label.click();
}
// Return focus to the selected party
activeElement.focus()
})
// On selecting a member,
// set placeholders for recipient information.
this.memberSelector.addEventListener('input', () => {
const recipient = this.recipient
setPlaceholder('recipient__party', this.getPartyName(recipient['parti']))
setPlaceholder('recipient__name', recipient['namn'])
setPlaceholder('recipient__epostadress', recipient['epostadress'])
})
}
get subject () {
/*
Get selected subject.
*/
const subjectInput = document.querySelector('.options__subject input:checked')
const subjectLabel = document.querySelector(`.options__subject label[for="${subjectInput.id}"]`)
return subjectLabel.innerText
}
get senderName () {
/*
Get sender name.
*/
return document.getElementById('sender__name').value
}
generateOutput () {
/*
Generate subject and body for E-mail.
*/
const recipient = this.recipient
// Don't generate email if recipient is not selected.
if (!recipient) return;
if (!this.senderName) return;
// Set subject
const subject = document.getElementById('output__subject')
subject.innerText = this.subject
// Set body
const body = document.getElementById('output__body')
let bodyContents = ""
for (const options of document.querySelectorAll('.options__body input:checked + label')) {
bodyContents += `${options.innerText.trim()}\n\n`;
}
body.innerText = bodyContents.trim()
// Set mailto-link
const email = document.getElementById('output__email')
const emailAddress = recipient['epostadress']
email.innerText = emailAddress
const encodedSubject = encodeURIComponent(this.subject)
const encodedBody = encodeURIComponent(bodyContents)
const mailto = document.getElementById('output__mailto')
const mailtoURL = `mailto:${emailAddress}?subject=${encodedSubject}&body=${encodedBody}`
mailto.setAttribute('href', mailtoURL)
}
}
async function load_mep_data() {
const response = await fetch(politicians_data_path);
const result = await response.json();
return result;
}

12
mejla/scripts/utils.js Normal file
View File

@ -0,0 +1,12 @@
function setPlaceholder(name, value) {
/*
Set the contents of a placeholder element.
A placeholder is denoted as <i class="placeholder" name="myPlaceholder"></i>
*/
for (const placeholder of document.querySelectorAll(`i.placeholder[name="${name}"]`)) {
placeholder.innerText = value
}
}

435
mejla/style/index.css Normal file
View File

@ -0,0 +1,435 @@
@import url('../wp-content/uploads/fonts/fonts/css/all.min.css');
@import url('../wp-content/uploads/fonts/font-awesome/css/all.min.css');
:root {
--lightpurple: #dac7ea;
--darkpurple: #441d66;
--scrollbar-track: white;
--scrollbar-handle: var(--lightpurple);
--scrollbar-hover: var(--lightpurple);
--border-radius: 0.3rem;
}
html, body {
margin: 0;
padding: 0;
font-family: 'Lato', sans-serif;
}
body {
width: 100%;
height: 100%;
overflow-y: scroll;
}
header {
margin: 3rem auto;
padding-inline: 1ch;
max-width: 50ch;
text-align: left;
}
main {
margin: 0 auto;
padding-inline: 1ch;
max-width: 50ch;
}
footer {
margin: 6rem auto 2rem auto;
max-width: 50ch;
color: #565657;
text-align: center;
font-size: 0.8rem;
}
footer a[href] {
color: #565657;
}
h1 {
color: var(--darkpurple);
text-align: center;
font-size: 2.3rem;
font-family: 'Roboto Slab', serif;
}
.bordered {
margin-block: 1rem;
padding: 1rem;
display: block;
border: 1px solid var(--lightpurple);
border-radius: var(--border-radius);
}
section > h3:first-of-type {
margin-bottom: 1rem;
display: block;
font-size: 0.8rem;
text-align: center;
text-transform: uppercase;
}
/* SCROLLBAR */
/* Overall */
::-webkit-scrollbar {
width: calc(var(--border-radius) * 2);
}
/* Track */
::-webkit-scrollbar-track {
background: var(--scrollbar-track);
}
/* Handle */
::-webkit-scrollbar-thumb {
background: var(--scrollbar-handle);
border-radius: var(--border-radius);
}
/* On hover */
::-webkit-scrollbar-thumb:hover {
background: var(--scrollbar-hover);
}
/*************/
/* SELECT RECIPIENT */
#select-recipient {
border-bottom: none;
border-radius: var(--border-radius) var(--border-radius) 0 0;
margin-bottom: 0;
}
.select-party {
border: 1px solid var(--lightpurple);
border-top: none;
border-bottom: none;
margin: 0;
padding: 0;
display: flex;
flex-wrap: wrap;
align-content: stretch;
justify-content: center;
gap: 0.5ch;
overflow-y: scroll;
}
.select-party:invalid ~ section {
display: none;
}
.select-party input {
clip: rect(0,0,0,0) !important;
position: absolute;
}
.select-party label {
padding: 0.2rem 1ch;
flex-grow: 1;
flex-basis: 0.5;
font-size: 0.8rem;
max-width: max-content;
color: var(--darkpurple);
border: 1px solid var(--lightpurple);
border-radius: 10rem;
user-select: none;
}
.select-party input:checked + label {
color: white;
background-color: var(--darkpurple) !important;
border-color: var(--darkpurple) !important;
}
.select-party label::before,
.select-member label::before {
display: none;
}
.select-party label:hover {
cursor: pointer;
}
.select-member {
border: 1px solid var(--lightpurple);
border-top: none;
border-radius: 0 0 var(--border-radius) var(--border-radius);
padding: 1rem;
display: grid;
/* grid-template-columns: 1fr 1fr 1fr; */
grid-template-columns: 1fr;
overflow-y: auto;
}
.select-member input {
clip: rect(0,0,0,0) !important;
position: absolute;
}
.member {
margin: 0.2em;
padding: 0.1em 1ch;
display: block;
font-size: 0.8em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
height: 1.5em;
border: 1px solid transparent;
border-radius: 1.5em;
break-inside: avoid-column;
user-select: none;
}
input[type=radio]:checked + label,
input[type=checkbox]:checked + label {
background-color: var(--lightpurple);
border-color: var(--lightpurple);
}
input[type=radio] + label::before,
input[type=checkbox] + label::before {
float: left;
color: var(--darkpurple);
}
input[type=checkbox] + label::before {
content: '\f0c8';
}
input[type=checkbox]:checked + label::before {
font-weight: bold;
content: '\f14a';
}
input[type=radio] + label::before {
content: '\f111';
}
input[type=radio]:checked + label::before {
font-weight: bold;
content: '\f058';
}
input[type=radio],
input[type=checkbox] {
clip: rect(0,0,0,0) !important;
position: absolute;
}
label:hover {
cursor: pointer;
}
/********************/
/* OPTIONS */
.options > label {
margin-block: 0.5rem;
padding: 1rem;
display: block;
border-radius: calc(var(--border-radius) / 2);
}
.options > label:has(+ label) {
margin-bottom: 0.5rem;
}
.options > label input {
display: none;
}
.options > label:hover {
cursor: pointer;
}
.options > label label {
width: 100%;
background-color: transparent;
border-radius: 5px;
}
.options > input:checked {
background-color: var(--lightpurple);
}
.options > input:checked::before {
content: '\f058';
font-weight: bold;
}
.options > label::before {
margin-right: 1ch;
font-family: 'Font Awesome 5 Free';
font-weight: normal;
font-style: normal;
}
.options > label:has(> input[type="radio"])::before {
content: '\f111';
color: var(--darkpurple);
}
.options > label:has(> input[type="radio"]:checked)::before {
content: '\f058';
font-weight: bold;
}
.options > label:has(> input[type="checkbox"])::before {
content: '\f0c8';
color: var(--darkpurple);
}
.options > label:has(> input[type="checkbox"]:checked)::before {
content: '\f14a';
font-weight: bold;
}
.options:has(:first-child > input:required:invalid) > label {
display: none;
}
/***********/
/* OUTPUT */
#output {
background-color: var(--lightpurple);
}
#output__email,
#output__subject,
#output__body {
display: block;
background-color: white;
border: 1px solid var(--lightpurple);
border-radius: var(--border-radius);
}
#enter-name label {
font-size: 0.8rem;
text-transform: uppercase;
font-weight: bold;
display: block;
text-align: center;
}
#enter-name input {
margin: 0.25rem 0;
padding: 0.5rem;
font-size: 0.8rem;
border-color: var(--lightpurple);
border-radius: var(--border-radius);
width: calc(100% - 2rem);
}
#enter-name input:focus-visible {
outline-color: var(--darkpurple);
}
#output__email {
margin-bottom: 0.25rem;
padding: 0.5rem;
font-size: 0.8rem;
}
#output__email::before {
margin-right: 0.5ch;
content: 'Till:';
color: var(--darkpurple);
user-select: none;
}
#output__subject {
margin-bottom: 1rem;
padding: 0.5rem;
font-size: 0.8rem;
}
#output__subject::before {
margin-right: 0.5ch;
content: 'Ämne:';
color: var(--darkpurple);
user-select: none;
}
#output__body {
padding: 1rem;
font-size: 0.9rem;
}
#output__mailto {
margin-bottom: 1rem;
padding: 0.5em;
display: block;
color: white;
background-color: var(--darkpurple);
font-size: 1em;
text-align: center;
text-transform: uppercase;
text-decoration: none;
border-radius: var(--border-radius);
}
#output:has(#output__mailto[href=""]) {
display: none;
}
/**********/

Binary file not shown.

View File

@ -0,0 +1,34 @@
Font Awesome Free License
-------------------------
Font Awesome Free is free, open source, and GPL friendly. You can use it for
commercial projects, open source projects, or really almost whatever you want.
Full Font Awesome Free license: https://fontawesome.com/license/free.
# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/)
In the Font Awesome Free download, the CC BY 4.0 license applies to all icons
packaged as SVG and JS file types.
# Fonts: SIL OFL 1.1 License (https://scripts.sil.org/OFL)
In the Font Awesome Free download, the SIL OFL license applies to all icons
packaged as web and desktop font files.
# Code: MIT License (https://opensource.org/licenses/MIT)
In the Font Awesome Free download, the MIT license applies to all non-font and
non-icon files.
# Attribution
Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font
Awesome Free files already contain embedded comments with sufficient
attribution, so you shouldn't need to do anything additional when using these
files normally.
We've kept attribution comments terse, so we ask that you do not actively work
to remove them from files, especially code. They're a great way for folks to
learn about Font Awesome.
# Brand Icons
All brand icons are trademarks of their respective owners. The use of these
trademarks does not indicate endorsement of the trademark holder by Font
Awesome, nor vice versa. **Please do not use brand logos for any purpose except
to represent the company, product, or service to which they refer.**

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,15 @@
/*!
* Font Awesome Free 5.13.1 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/
@font-face {
font-family: 'Font Awesome 5 Brands';
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/fa-brands-400.eot");
src: url("../webfonts/fa-brands-400.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.woff") format("woff"), url("../webfonts/fa-brands-400.ttf") format("truetype"), url("../webfonts/fa-brands-400.svg#fontawesome") format("svg"); }
.fab {
font-family: 'Font Awesome 5 Brands';
font-weight: 400; }

View File

@ -0,0 +1,5 @@
/*!
* Font Awesome Free 5.13.1 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/
@font-face{font-family:"Font Awesome 5 Brands";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:"Font Awesome 5 Brands";font-weight:400}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,15 @@
/*!
* Font Awesome Free 5.13.1 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/
@font-face {
font-family: 'Font Awesome 5 Free';
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/fa-regular-400.eot");
src: url("../webfonts/fa-regular-400.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.woff") format("woff"), url("../webfonts/fa-regular-400.ttf") format("truetype"), url("../webfonts/fa-regular-400.svg#fontawesome") format("svg"); }
.far {
font-family: 'Font Awesome 5 Free';
font-weight: 400; }

View File

@ -0,0 +1,5 @@
/*!
* Font Awesome Free 5.13.1 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/
@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-regular-400.eot);src:url(../webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.woff) format("woff"),url(../webfonts/fa-regular-400.ttf) format("truetype"),url(../webfonts/fa-regular-400.svg#fontawesome) format("svg")}.far{font-family:"Font Awesome 5 Free";font-weight:400}

View File

@ -0,0 +1,16 @@
/*!
* Font Awesome Free 5.13.1 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/
@font-face {
font-family: 'Font Awesome 5 Free';
font-style: normal;
font-weight: 900;
font-display: block;
src: url("../webfonts/fa-solid-900.eot");
src: url("../webfonts/fa-solid-900.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.woff") format("woff"), url("../webfonts/fa-solid-900.ttf") format("truetype"), url("../webfonts/fa-solid-900.svg#fontawesome") format("svg"); }
.fa,
.fas {
font-family: 'Font Awesome 5 Free';
font-weight: 900; }

View File

@ -0,0 +1,5 @@
/*!
* Font Awesome Free 5.13.1 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/
@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.fas{font-family:"Font Awesome 5 Free";font-weight:900}

View File

@ -0,0 +1,371 @@
/*!
* Font Awesome Free 5.13.1 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/
svg:not(:root).svg-inline--fa {
overflow: visible; }
.svg-inline--fa {
display: inline-block;
font-size: inherit;
height: 1em;
overflow: visible;
vertical-align: -.125em; }
.svg-inline--fa.fa-lg {
vertical-align: -.225em; }
.svg-inline--fa.fa-w-1 {
width: 0.0625em; }
.svg-inline--fa.fa-w-2 {
width: 0.125em; }
.svg-inline--fa.fa-w-3 {
width: 0.1875em; }
.svg-inline--fa.fa-w-4 {
width: 0.25em; }
.svg-inline--fa.fa-w-5 {
width: 0.3125em; }
.svg-inline--fa.fa-w-6 {
width: 0.375em; }
.svg-inline--fa.fa-w-7 {
width: 0.4375em; }
.svg-inline--fa.fa-w-8 {
width: 0.5em; }
.svg-inline--fa.fa-w-9 {
width: 0.5625em; }
.svg-inline--fa.fa-w-10 {
width: 0.625em; }
.svg-inline--fa.fa-w-11 {
width: 0.6875em; }
.svg-inline--fa.fa-w-12 {
width: 0.75em; }
.svg-inline--fa.fa-w-13 {
width: 0.8125em; }
.svg-inline--fa.fa-w-14 {
width: 0.875em; }
.svg-inline--fa.fa-w-15 {
width: 0.9375em; }
.svg-inline--fa.fa-w-16 {
width: 1em; }
.svg-inline--fa.fa-w-17 {
width: 1.0625em; }
.svg-inline--fa.fa-w-18 {
width: 1.125em; }
.svg-inline--fa.fa-w-19 {
width: 1.1875em; }
.svg-inline--fa.fa-w-20 {
width: 1.25em; }
.svg-inline--fa.fa-pull-left {
margin-right: .3em;
width: auto; }
.svg-inline--fa.fa-pull-right {
margin-left: .3em;
width: auto; }
.svg-inline--fa.fa-border {
height: 1.5em; }
.svg-inline--fa.fa-li {
width: 2em; }
.svg-inline--fa.fa-fw {
width: 1.25em; }
.fa-layers svg.svg-inline--fa {
bottom: 0;
left: 0;
margin: auto;
position: absolute;
right: 0;
top: 0; }
.fa-layers {
display: inline-block;
height: 1em;
position: relative;
text-align: center;
vertical-align: -.125em;
width: 1em; }
.fa-layers svg.svg-inline--fa {
-webkit-transform-origin: center center;
transform-origin: center center; }
.fa-layers-text, .fa-layers-counter {
display: inline-block;
position: absolute;
text-align: center; }
.fa-layers-text {
left: 50%;
top: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
-webkit-transform-origin: center center;
transform-origin: center center; }
.fa-layers-counter {
background-color: #ff253a;
border-radius: 1em;
-webkit-box-sizing: border-box;
box-sizing: border-box;
color: #fff;
height: 1.5em;
line-height: 1;
max-width: 5em;
min-width: 1.5em;
overflow: hidden;
padding: .25em;
right: 0;
text-overflow: ellipsis;
top: 0;
-webkit-transform: scale(0.25);
transform: scale(0.25);
-webkit-transform-origin: top right;
transform-origin: top right; }
.fa-layers-bottom-right {
bottom: 0;
right: 0;
top: auto;
-webkit-transform: scale(0.25);
transform: scale(0.25);
-webkit-transform-origin: bottom right;
transform-origin: bottom right; }
.fa-layers-bottom-left {
bottom: 0;
left: 0;
right: auto;
top: auto;
-webkit-transform: scale(0.25);
transform: scale(0.25);
-webkit-transform-origin: bottom left;
transform-origin: bottom left; }
.fa-layers-top-right {
right: 0;
top: 0;
-webkit-transform: scale(0.25);
transform: scale(0.25);
-webkit-transform-origin: top right;
transform-origin: top right; }
.fa-layers-top-left {
left: 0;
right: auto;
top: 0;
-webkit-transform: scale(0.25);
transform: scale(0.25);
-webkit-transform-origin: top left;
transform-origin: top left; }
.fa-lg {
font-size: 1.33333em;
line-height: 0.75em;
vertical-align: -.0667em; }
.fa-xs {
font-size: .75em; }
.fa-sm {
font-size: .875em; }
.fa-1x {
font-size: 1em; }
.fa-2x {
font-size: 2em; }
.fa-3x {
font-size: 3em; }
.fa-4x {
font-size: 4em; }
.fa-5x {
font-size: 5em; }
.fa-6x {
font-size: 6em; }
.fa-7x {
font-size: 7em; }
.fa-8x {
font-size: 8em; }
.fa-9x {
font-size: 9em; }
.fa-10x {
font-size: 10em; }
.fa-fw {
text-align: center;
width: 1.25em; }
.fa-ul {
list-style-type: none;
margin-left: 2.5em;
padding-left: 0; }
.fa-ul > li {
position: relative; }
.fa-li {
left: -2em;
position: absolute;
text-align: center;
width: 2em;
line-height: inherit; }
.fa-border {
border: solid 0.08em #eee;
border-radius: .1em;
padding: .2em .25em .15em; }
.fa-pull-left {
float: left; }
.fa-pull-right {
float: right; }
.fa.fa-pull-left,
.fas.fa-pull-left,
.far.fa-pull-left,
.fal.fa-pull-left,
.fab.fa-pull-left {
margin-right: .3em; }
.fa.fa-pull-right,
.fas.fa-pull-right,
.far.fa-pull-right,
.fal.fa-pull-right,
.fab.fa-pull-right {
margin-left: .3em; }
.fa-spin {
-webkit-animation: fa-spin 2s infinite linear;
animation: fa-spin 2s infinite linear; }
.fa-pulse {
-webkit-animation: fa-spin 1s infinite steps(8);
animation: fa-spin 1s infinite steps(8); }
@-webkit-keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg); }
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg); } }
@keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg); }
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg); } }
.fa-rotate-90 {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";
-webkit-transform: rotate(90deg);
transform: rotate(90deg); }
.fa-rotate-180 {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";
-webkit-transform: rotate(180deg);
transform: rotate(180deg); }
.fa-rotate-270 {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";
-webkit-transform: rotate(270deg);