@ -0,0 +1,10 @@ | |||
{ | |||
"presets": [ | |||
"@babel/preset-env" | |||
], | |||
"plugins": [ | |||
["@babel/plugin-transform-runtime", { | |||
"regenerator": true | |||
}] | |||
] | |||
} |
@ -0,0 +1,14 @@ | |||
root = true | |||
[*] | |||
charset = utf-8 | |||
trim_trailing_whitespace = false | |||
insert_final_newline = true | |||
[*.html] | |||
indent_style = space | |||
indent_size = 4 | |||
[*.{js,json,yml}] | |||
indent_style = space | |||
indent_size = 2 |
@ -0,0 +1,32 @@ | |||
module.exports = { | |||
root: true, | |||
env: { | |||
node: true, | |||
browser: true, | |||
}, | |||
extends: ["standard"], | |||
rules: { | |||
"no-console": process.env.NODE_ENV === "production" ? "error" : "off", | |||
"no-debugger": process.env.NODE_ENV === "production" ? "error" : "off", | |||
"comma-dangle": [2, "always-multiline"], | |||
}, | |||
overrides: [ | |||
{ | |||
files: ["**/__tests__/*.js", "**/tests/unit/**/*.spec.js"], | |||
env: { | |||
jest: true | |||
} | |||
} | |||
], | |||
parserOptions: { | |||
parser: "babel-eslint" | |||
}, | |||
overrides: [ | |||
{ | |||
files: ["**/__tests__/*.js", "**/tests/unit/**/*.spec.js"], | |||
env: { | |||
jest: true | |||
} | |||
} | |||
] | |||
}; |
@ -0,0 +1,187 @@ | |||
# Created by https://www.gitignore.io/api/node,code,linux,macos,windows | |||
# Edit at https://www.gitignore.io/?templates=node,code,linux,macos,windows | |||
### Code ### | |||
.vscode/* | |||
!.vscode/settings.json | |||
!.vscode/tasks.json | |||
!.vscode/launch.json | |||
!.vscode/extensions.json | |||
### Linux ### | |||
*~ | |||
# temporary files which can be created if a process still has a handle open of a deleted file | |||
.fuse_hidden* | |||
# KDE directory preferences | |||
.directory | |||
# Linux trash folder which might appear on any partition or disk | |||
.Trash-* | |||
# .nfs files are created when an open file is removed but is still being accessed | |||
.nfs* | |||
### macOS ### | |||
# General | |||
.DS_Store | |||
.AppleDouble | |||
.LSOverride | |||
# Icon must end with two \r | |||
Icon | |||
# Thumbnails | |||
._* | |||
# Files that might appear in the root of a volume | |||
.DocumentRevisions-V100 | |||
.fseventsd | |||
.Spotlight-V100 | |||
.TemporaryItems | |||
.Trashes | |||
.VolumeIcon.icns | |||
.com.apple.timemachine.donotpresent | |||
# Directories potentially created on remote AFP share | |||
.AppleDB | |||
.AppleDesktop | |||
Network Trash Folder | |||
Temporary Items | |||
.apdisk | |||
### Node ### | |||
# Logs | |||
logs | |||
*.log | |||
npm-debug.log* | |||
yarn-debug.log* | |||
yarn-error.log* | |||
lerna-debug.log* | |||
# Diagnostic reports (https://nodejs.org/api/report.html) | |||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json | |||
# Runtime data | |||
pids | |||
*.pid | |||
*.seed | |||
*.pid.lock | |||
# Directory for instrumented libs generated by jscoverage/JSCover | |||
lib-cov | |||
# Coverage directory used by tools like istanbul | |||
coverage | |||
*.lcov | |||
# nyc test coverage | |||
.nyc_output | |||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) | |||
.grunt | |||
# Bower dependency directory (https://bower.io/) | |||
bower_components | |||
# node-waf configuration | |||
.lock-wscript | |||
# Compiled binary addons (https://nodejs.org/api/addons.html) | |||
build/Release | |||
# Dependency directories | |||
node_modules/ | |||
jspm_packages/ | |||
# TypeScript v1 declaration files | |||
typings/ | |||
# TypeScript cache | |||
*.tsbuildinfo | |||
# Optional npm cache directory | |||
.npm | |||
# Optional eslint cache | |||
.eslintcache | |||
# Optional REPL history | |||
.node_repl_history | |||
# Output of 'npm pack' | |||
*.tgz | |||
# Yarn Integrity file | |||
.yarn-integrity | |||
# dotenv environment variables file | |||
.env | |||
.env.test | |||
# parcel-bundler cache (https://parceljs.org/) | |||
.cache | |||
# next.js build output | |||
.next | |||
# nuxt.js build output | |||
.nuxt | |||
# rollup.js default build output | |||
dist/ | |||
# Uncomment the public line if your project uses Gatsby | |||
# https://nextjs.org/blog/next-9-1#public-directory-support | |||
# https://create-react-app.dev/docs/using-the-public-folder/#docsNav | |||
# public | |||
# Storybook build outputs | |||
.out | |||
.storybook-out | |||
# vuepress build output | |||
.vuepress/dist | |||
# Serverless directories | |||
.serverless/ | |||
# FuseBox cache | |||
.fusebox/ | |||
# DynamoDB Local files | |||
.dynamodb/ | |||
# Temporary folders | |||
tmp/ | |||
temp/ | |||
### Windows ### | |||
# Windows thumbnail cache files | |||
Thumbs.db | |||
Thumbs.db:encryptable | |||
ehthumbs.db | |||
ehthumbs_vista.db | |||
# Dump file | |||
*.stackdump | |||
# Folder config file | |||
[Dd]esktop.ini | |||
# Recycle Bin used on file shares | |||
$RECYCLE.BIN/ | |||
# Windows Installer files | |||
*.cab | |||
*.msi | |||
*.msix | |||
*.msm | |||
*.msp | |||
# Windows shortcuts | |||
*.lnk | |||
# End of https://www.gitignore.io/api/node,code,linux,macos,windows |
@ -0,0 +1,19 @@ | |||
Copyright (c) Ministère de l'intérieur | |||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||
of this software and associated documentation files (the "Software"), to deal | |||
in the Software without restriction, including without limitation the rights | |||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
copies of the Software, and to permit persons to whom the Software is | |||
furnished to do so, subject to the following conditions: | |||
The above copyright notice and this permission notice shall be included in | |||
all copies or substantial portions of the Software. | |||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
THE SOFTWARE. |
@ -0,0 +1,31 @@ | |||
# Générateur de certificat de déplacement | |||
## Développer | |||
### Installer le projet | |||
```console | |||
$ git clone https://github.com/lab-mi/deplacement-codiv-19.git | |||
$ cd deplacement-codiv-19 | |||
$ npm i | |||
$ npm start | |||
``` | |||
### Générer le code de production | |||
```console | |||
$ npm run build | |||
``` | |||
Le code à déployer sera dans le dossier `dist` | |||
## Crédits | |||
Ce projet a été réalisé à partir d'un fork du dépôt [covid-19-certificate](https://github.com/nesk/covid-19-certificate) de [Johann Pardanaud](https://github.com/nesk). | |||
Les projets open source suivants ont été utilisés pour le développement de ce service : | |||
- [PDF-LIB](https://pdf-lib.js.org/) | |||
- [qrcode](https://github.com/soldair/node-qrcode) | |||
- [Bootstrap](https://getbootstrap.com/) | |||
- [Font Awesome](https://fontawesome.com/license) |
@ -0,0 +1,257 @@ | |||
import 'bootstrap/dist/css/bootstrap.min.css' | |||
import { PDFDocument, StandardFonts } from 'pdf-lib' | |||
import QRCode from 'qrcode' | |||
import pdfBase from './certificate.pdf' | |||
import './main.css' | |||
const $ = (...args) => document.querySelector(...args) | |||
const $$ = (...args) => [...document.querySelectorAll(...args)] | |||
var year, month, day | |||
const generateQR = async text => { | |||
try { | |||
var opts = { | |||
errorCorrectionLevel: 'M', | |||
type: 'image/png', | |||
quality: 0.92, | |||
margin: 1, | |||
} | |||
return await QRCode.toDataURL(text, opts) | |||
} catch (err) { | |||
console.error(err) | |||
} | |||
} | |||
function pad (str) { | |||
return String(str).padStart(2, '0') | |||
} | |||
function setDateNow (date) { | |||
year = date.getFullYear() | |||
month = pad(date.getMonth() + 1) // Les mois commencent à 0 | |||
day = pad(date.getDate()) | |||
} | |||
document.addEventListener('DOMContentLoaded', setReleaseDateTime) | |||
function setReleaseDateTime () { | |||
const loadedDate = new Date() | |||
setDateNow(loadedDate) | |||
const releaseDateInput = document.querySelector('#field-datesortie') | |||
releaseDateInput.value = `${year}-${month}-${day}` | |||
const hour = pad(loadedDate.getHours()) | |||
const minute = pad(loadedDate.getMinutes()) | |||
const releaseTimeInput = document.querySelector('#field-heuresortie') | |||
releaseTimeInput.value = `${hour}:${minute}` | |||
} | |||
function saveProfile () { | |||
for (const field of $$('#form-profile input')) { | |||
if (field.id === 'field-datesortie') { | |||
var dateSortie = field.value.split('-') | |||
localStorage.setItem(field.id.substring('field-'.length), `${dateSortie[2]}/${dateSortie[1]}/${dateSortie[0]}`) | |||
} else { | |||
localStorage.setItem(field.id.substring('field-'.length), field.value) | |||
} | |||
} | |||
} | |||
function getProfile () { | |||
const fields = {} | |||
for (let i = 0; i < localStorage.length; i++) { | |||
const name = localStorage.key(i) | |||
fields[name] = localStorage.getItem(name) | |||
} | |||
return fields | |||
} | |||
function idealFontSize (font, text, maxWidth, minSize, defaultSize) { | |||
let currentSize = defaultSize | |||
let textWidth = font.widthOfTextAtSize(text, defaultSize) | |||
while (textWidth > maxWidth && currentSize > minSize) { | |||
textWidth = font.widthOfTextAtSize(text, --currentSize) | |||
} | |||
return (textWidth > maxWidth) ? null : currentSize | |||
} | |||
async function generatePdf (profile, reasons) { | |||
const generatedDate = new Date() | |||
setDateNow(generatedDate) | |||
const creationDate = `${day}/${month}/${year}` | |||
const hour = pad(generatedDate.getHours()) | |||
const minute = pad(generatedDate.getMinutes()) | |||
const creationHour = `${hour}h${minute}` | |||
const { lastname, firstname, birthday, lieunaissance, address, zipcode, town, datesortie, heuresortie } = profile | |||
const releaseHours = String(heuresortie).substring(0, 2) | |||
const releaseMinutes = String(heuresortie).substring(3, 5) | |||
const data = [ | |||
`Cree le: ${creationDate} a ${creationHour}`, | |||
`Nom: ${lastname}`, | |||
`Prenom: ${firstname}`, | |||
`Naissance: ${birthday} a ${lieunaissance}`, | |||
`Adresse: ${address} ${zipcode} ${town}`, | |||
`Sortie: ${datesortie} a ${releaseHours}h${releaseMinutes}`, | |||
`Motifs: ${reasons}`, | |||
].join('; ') | |||
const existingPdfBytes = await fetch(pdfBase).then(res => res.arrayBuffer()) | |||
const pdfDoc = await PDFDocument.load(existingPdfBytes) | |||
const page1 = pdfDoc.getPages()[0] | |||
const font = await pdfDoc.embedFont(StandardFonts.Helvetica) | |||
const drawText = (text, x, y, size = 11) => { | |||
page1.drawText(text, { x, y, size, font }) | |||
} | |||
drawText(`${firstname} ${lastname}`, 123, 686) | |||
drawText(birthday, 123, 661) | |||
drawText(lieunaissance, 92, 638) | |||
drawText(`${address} ${zipcode} ${town}`, 134, 613) | |||
if (reasons.includes('travail')) { | |||
drawText('x', 76, 527, 19) | |||
} | |||
if (reasons.includes('courses')) { | |||
drawText('x', 76, 478, 19) | |||
} | |||
if (reasons.includes('sante')) { | |||
drawText('x', 76, 436, 19) | |||
} | |||
if (reasons.includes('famille')) { | |||
drawText('x', 76, 400, 19) | |||
} | |||
if (reasons.includes('sport')) { | |||
drawText('x', 76, 345, 19) | |||
} | |||
if (reasons.includes('judiciaire')) { | |||
drawText('x', 76, 298, 19) | |||
} | |||
if (reasons.includes('missions')) { | |||
drawText('x', 76, 260, 19) | |||
} | |||
let locationSize = idealFontSize(font, profile.town, 83, 7, 11) | |||
if (!locationSize) { | |||
alert('Le nom de la ville risque de ne pas être affiché correctement en raison de sa longueur. ' + | |||
'Essayez d\'utiliser des abréviations ("Saint" en "St." par exemple) quand cela est possible.') | |||
locationSize = 7 | |||
} | |||
drawText(profile.town, 111, 226, locationSize) | |||
if (reasons !== '') { | |||
// Date sortie | |||
drawText(`${profile.datesortie}`, 92, 200) | |||
drawText(releaseHours, 200, 201) | |||
drawText(releaseMinutes, 220, 201) | |||
} | |||
// Date création | |||
drawText('Date de création:', 464, 150, 7) | |||
drawText(`${creationDate} à ${creationHour}`, 455, 144, 7) | |||
const generatedQR = await generateQR(data) | |||
const qrImage = await pdfDoc.embedPng(generatedQR) | |||
page1.drawImage(qrImage, { | |||
x: page1.getWidth() - 170, | |||
y: 155, | |||
width: 100, | |||
height: 100, | |||
}) | |||
pdfDoc.addPage() | |||
const page2 = pdfDoc.getPages()[1] | |||
page2.drawImage(qrImage, { | |||
x: 50, | |||
y: page2.getHeight() - 350, | |||
width: 300, | |||
height: 300, | |||
}) | |||
const pdfBytes = await pdfDoc.save() | |||
return new Blob([pdfBytes], { type: 'application/pdf' }) | |||
} | |||
function downloadBlob (blob, fileName) { | |||
const link = document.createElement('a') | |||
var url = URL.createObjectURL(blob) | |||
link.href = url | |||
link.download = fileName | |||
document.body.appendChild(link) | |||
link.click() | |||
} | |||
function getAndSaveReasons () { | |||
const values = $$('input[name="field-reason"]:checked') | |||
.map(x => x.value) | |||
.join('-') | |||
localStorage.setItem('reasons', values) | |||
return values | |||
} | |||
// see: https://stackoverflow.com/a/32348687/1513045 | |||
function isFacebookBrowser () { | |||
const ua = navigator.userAgent || navigator.vendor || window.opera | |||
return ua.includes('FBAN') || ua.includes('FBAV') | |||
} | |||
if (isFacebookBrowser()) { | |||
$('#alert-facebook').classList.remove('d-none') | |||
} | |||
function addSlash () { | |||
this.value = this.value.replace(/^(\d{2})$/g, '$1/') | |||
this.value = this.value.replace(/^(\d{2})\/(\d{2})$/g, '$1/$2/') | |||
} | |||
$('#field-birthday').addEventListener('keyup', addSlash) | |||
const snackbar = $('#snackbar') | |||
$('#form-profile').addEventListener('submit', async event => { | |||
event.preventDefault() | |||
saveProfile() | |||
const reasons = getAndSaveReasons() | |||
const pdfBlob = await generatePdf(getProfile(), reasons) | |||
downloadBlob(pdfBlob, 'attestation.pdf') | |||
snackbar.classList.remove('d-none') | |||
setTimeout(() => snackbar.classList.add('show'), 100) | |||
setTimeout(function () { | |||
snackbar.classList.remove('show') | |||
setTimeout(() => snackbar.classList.add('d-none'), 500) | |||
}, 6000) | |||
}) | |||
$$('input').forEach(input => { | |||
const exempleElt = input.parentNode.parentNode.querySelector('.exemple') | |||
if (input.placeholder && exempleElt) { | |||
input.addEventListener('input', (event) => { | |||
if (input.value) { | |||
exempleElt.innerHTML = 'ex. : ' + input.placeholder | |||
} else { | |||
exempleElt.innerHTML = '' | |||
} | |||
}) | |||
} | |||
}) | |||
function addVersion () { | |||
document.getElementById('version').innerHTML = `${new Date().getFullYear()} - ${process.env.VERSION}` | |||
} | |||
addVersion() |
@ -0,0 +1,188 @@ | |||
<!DOCTYPE html> | |||
<html lang="fr"> | |||
<head> | |||
<meta charset="UTF-8" /> | |||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> | |||
<meta name="msapplication-TileColor" content="#603cba" /> | |||
<meta name="msapplication-config" content="./favicons/browserconfig.xml" /> | |||
<meta name="theme-color" content="#ffffff" /> | |||
<link rel="apple-touch-icon" sizes="180x180" href="./favicons/apple-touch-icon.png" /> | |||
<link rel="icon" type="image/png" sizes="32x32" href="./favicons/favicon-32x32.png" /> | |||
<link rel="icon" type="image/png" sizes="16x16" href="./favicons/favicon-16x16.png" /> | |||
<link rel="manifest" href="./favicons/site.webmanifest" /> | |||
<link rel="mask-icon" href="./favicons/safari-pinned-tab.svg" color="#21bf73" /> | |||
<title>COVID-19 – Générateur d'attestation de déplacement</title> | |||
</head> | |||
<body class="confidentialite"> | |||
<div class="container"> | |||
<header class="wrapper"> | |||
<img class="logo" src="/MIN_Interieur_RVB.svg" alt="logo_ministère_interieur" /> | |||
<div class="header-content"> | |||
<h1 class="flex flex-wrap"> | |||
<div class="covid-title"> | |||
COVID-19 | |||
</div> | |||
<div class="covid-subtitle"> | |||
Générateur d'attestation de déplacement | |||
</div> | |||
</h1> | |||
</div> | |||
</header> | |||
<div class="wrapper"> | |||
<h2 class="titre-2">Politique de confidentialité</h2> | |||
<h3 class="titre-3">Collecte des données personnelles</h3> | |||
<p> | |||
Les informations saisies dans ce générateur d’attestation de | |||
déplacement ne font l’objet d’aucune collecte par le ministère | |||
de l’Intérieur. Ces données personnelles<sup>1</sup> sont | |||
exclusivement stockées dans le terminal (ordinateur, tablette, | |||
smartphone) utilisé pour générer l’attestation. | |||
</p> | |||
<em>1 - Prénom, Nom, Date de naissance, Lieu de naissance, Adresse, | |||
Code postal, Ville.</em> | |||
<h3 class="titre-3">Traceurs et cookies</h3> | |||
<p> | |||
Lors de la consultation du générateur, des cookies sont déposés | |||
sur le terminal utilisé. Ces cookies sont utilisés pour | |||
sécuriser le service proposé. | |||
</p> | |||
<h4 class="titre-4">Définition d’un cookie</h4> | |||
<p> | |||
Un "cookie" est une suite d'informations, généralement de petite | |||
taille et identifié par un nom, qui peut être transmis à votre | |||
navigateur par un site web sur lequel vous vous connectez. Votre | |||
navigateur web le conserve pendant une certaine durée, et le | |||
renvoie au serveur web chaque fois que vous vous y | |||
re-connecterez. Les cookies ont de multiples usages : ils | |||
peuvent servir à mémoriser votre identifiant client auprès d'un | |||
site marchand, le contenu courant de votre panier d'achat, un | |||
identifiant permettant de tracer votre navigation pour des | |||
finalités statistiques ou publicitaires, etc. | |||
</p> | |||
<h4 class="titre-4">Cookies utilisés</h4> | |||
<p> | |||
Différents cookies techniques sont utilisés sur ce générateur | |||
d’attestation de déplacement : | |||
</p> | |||
<table class="cookies"> | |||
<thead> | |||
<tr class="header-row"> | |||
<th>Nom du Cookie</th> | |||
<th>Finalité</th> | |||
<th>Durée de conservation</th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
<tr> | |||
<td data-label="Nom du Cookie" class="name-col">incap_ses_*</td> | |||
<td data-label="Finalité" > | |||
Protection DDoS Incapsula et Pare-feu pour application | |||
web : cookie servant à relier les requêtes HTTP à une | |||
session. La réouverture du navigateur et l’accès au même | |||
site sont considérés comme des visites différentes. Pour | |||
maintenir les sessions existantes (cookie de session). | |||
</td> | |||
<td data-label="Durée de conservation" >Supprimé après la fermeture du navigateur.</td> | |||
</tr> | |||
<tr> | |||
<td data-label="Nom du Cookie" class="name-col">nlbi_*</td> | |||
<td data-label="Finalité" > | |||
Protection DDoS Incapsula et Pare-feu pour application | |||
web : cookie équilibreur de charge qui garantit que les | |||
requêtes d’un client sont envoyées au même serveur | |||
d’origine. | |||
</td> | |||
<td data-label="Durée de conservation" >Supprimé après la fermeture du navigateur.</td> | |||
</tr> | |||
<tr> | |||
<td data-label="Nom du Cookie" class="name-col">visid_incap_*</td> | |||
<td data-label="Finalité" > | |||
Protection DDoS Incapsula et Pare-feu pour application | |||
web : cookie servant à relier certaines sessions à un | |||
visiteur spécifique (visiteur représentant un ordinateur | |||
spécifique). Afin d’identifier les clients ayant déjà | |||
consulté Incapsula. Le seul cookie qui perdure pour une | |||
durée de 12 mois. | |||
</td> | |||
<td data-label="Durée de conservation" >12 mois.</td> | |||
</tr> | |||
<tr> | |||
<td data-label="Nom du Cookie" class="name-col">__utm*</td> | |||
<td data-label="Finalité" > | |||
Cookie de classification Incapsula : pour voir comment | |||
le client réagit à et gère un cookie mal formé afin | |||
d’identifier la nature de ce client. | |||
</td> | |||
<td data-label="Durée de conservation" >900 secondes maximum.</td> | |||
</tr> | |||
</tbody> | |||
</table> | |||
<p> | |||
Vous pouvez refuser ces cookies en configurant les paramètres de | |||
votre navigateur. | |||
</p> | |||
<h4 class="titre-4"> | |||
Moyens d'opposition au dépôt des cookies via votre navigateur | |||
</h4> | |||
<p> | |||
<strong>Si vous utilisez Firefox : </strong>Cliquez sur le | |||
bouton de menu et sélectionnez « Options ». Ensuite, | |||
sélectionnez le panneau « Vie privée et sécurité ». Rendez-vous | |||
à la section « Protection renforcée contre le pistage ». | |||
Choisissez la protection personnalisé [bouton radio | |||
« Personnalisé »]. Décochez la case « cookies ». Cliquez sur le | |||
bouton « Actualisez tous les onglets » si vous souhaitez | |||
appliquer ces changements à la navigation en cours au sein de | |||
votre navigateur. | |||
</p> | |||
<p> | |||
<strong>Si vous utilisez Chrome : </strong>En haut à droite, | |||
cliquez sur « Plus > Paramètres ». En bas, cliquez sur « | |||
Paramètres avancés ». Dans la section "Confidentialité et | |||
sécurité", cliquez sur « Paramètres des sites». Cliquez sur « | |||
Cookies et données des sites». Désactivez l’option « Autoriser | |||
les sites à enregistrer/lire les données des cookies ». | |||
</p> | |||
<p> | |||
<strong>Si vous utilisez Internet Explorer : </strong>Sélectionnez le bouton « Outils », puis « Options | |||
Internet ». | |||
Sélectionnez l’onglet « Confidentialité » puis sous « Paramètres | |||
», sélectionnez « Avancé ». Cochez la case "Ignorer la gestion | |||
automatique des cookies", puis sélectionner "Refuser" dans la | |||
colonne "Cookies tierces parties". | |||
</p> | |||
<p> | |||
Pour plus d’informations sur le sujet, n’hésitez pas à visiter | |||
le | |||
<a href="https://www.cnil.fr/fr/cookies-et-autres-traceurs">site de la CNIL</a>. | |||
</p> | |||
</div> | |||
</div> | |||
<div class="btn-wrapper"> | |||
<a href="./index.html" class="btn-generateur"> | |||
Retour au générateur | |||
</a> | |||
</div> | |||
<footer class="main-footer"> | |||
<div class="footer-links"> | |||
<a class="footer-link">Confidentialité</a> | |||
<a href="https://www.interieur.gouv.fr/Infos-du-site/Mentions-legales" target="_blank" title="Mentions légales - nouvelle page" | |||
class="footer-link">Mentions légales</a> | |||
<a href="https://www.gouvernement.fr/info-coronavirus" target="_blank" title="Information du gouvernement sur le Covid-19 - nouvelle page" class="footer-link">Informations du | |||
gouvernement sur le Covid-19</a> | |||
<p class="footer-link"> | |||
Plus d’infos au<a class="num-08" href="tel:0800130000"> | |||
0 800 130 000</a> | |||
</p> | |||
</div> | |||
</footer> | |||
<script src="./confidentialite.js"></script> | |||
</body> | |||
</html> |
@ -0,0 +1,2 @@ | |||
import 'bootstrap/dist/css/bootstrap.min.css' | |||
import './main.css' |
@ -0,0 +1,9 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<browserconfig> | |||
<msapplication> | |||
<tile> | |||
<square150x150logo src="mstile-150x150.png"/> | |||
<TileColor>#603cba</TileColor> | |||
</tile> | |||
</msapplication> | |||
</browserconfig> |
@ -0,0 +1,38 @@ | |||
<?xml version="1.0" standalone="no"?> | |||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" | |||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> | |||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" | |||
width="1024.000000pt" height="1024.000000pt" viewBox="0 0 1024.000000 1024.000000" | |||
preserveAspectRatio="xMidYMid meet"> | |||
<metadata> | |||
Created by potrace 1.11, written by Peter Selinger 2001-2013 | |||
</metadata> | |||
<g transform="translate(0.000000,1024.000000) scale(0.100000,-0.100000)" | |||
fill="#000000" stroke="none"> | |||
<path d="M4858 10236 c-1 -2 -50 -6 -108 -10 -58 -3 -116 -8 -130 -11 -14 -2 | |||
-52 -7 -85 -10 -105 -9 -381 -52 -395 -60 -3 -2 -26 -6 -50 -10 -25 -4 -126 | |||
-27 -225 -52 -661 -165 -1268 -457 -1830 -879 -82 -62 -161 -123 -175 -136 | |||
-14 -13 -55 -48 -90 -78 -243 -205 -529 -511 -739 -790 -39 -52 -75 -100 -80 | |||
-106 -22 -27 -153 -228 -218 -334 -178 -293 -340 -637 -453 -965 -69 -202 | |||
-140 -458 -174 -635 -2 -14 -9 -47 -15 -75 -13 -62 -20 -106 -55 -360 -37 | |||
-258 -43 -912 -11 -1106 2 -13 6 -51 10 -84 3 -33 8 -76 10 -95 11 -75 27 | |||
-176 32 -205 3 -16 7 -39 8 -50 7 -43 16 -89 20 -94 2 -4 6 -24 9 -46 6 -38 | |||
50 -211 95 -375 93 -339 299 -812 504 -1155 105 -175 101 -169 107 -175 3 -3 | |||
27 -39 54 -80 50 -77 254 -351 301 -405 14 -16 49 -57 78 -90 149 -172 466 | |||
-486 549 -543 13 -9 32 -25 43 -35 92 -87 397 -307 599 -432 309 -190 707 | |||
-377 1035 -485 206 -68 328 -103 400 -117 14 -2 28 -6 31 -8 5 -3 121 -28 231 | |||
-50 56 -11 257 -45 299 -50 289 -35 363 -39 675 -39 245 -1 407 5 510 19 17 2 | |||
59 7 95 10 36 4 74 9 85 11 11 1 40 6 65 9 81 11 257 44 377 71 65 14 124 27 | |||
133 29 8 2 56 15 105 30 50 15 106 31 125 36 56 14 255 83 380 132 260 103 | |||
475 207 735 357 160 92 510 340 675 478 218 182 544 511 671 677 13 17 26 32 | |||
29 35 14 11 143 183 212 280 66 94 228 346 228 355 0 2 20 37 44 77 45 77 188 | |||
360 220 438 11 25 27 63 37 85 125 284 284 828 325 1115 3 19 7 46 9 60 12 62 | |||
23 148 31 225 3 33 7 71 10 85 25 158 25 830 -1 1020 -8 63 -16 129 -21 174 | |||
-3 27 -6 52 -8 55 -2 3 -7 31 -11 61 -3 30 -8 62 -10 70 -2 8 -7 29 -9 45 -3 | |||
17 -10 55 -16 85 -6 30 -14 66 -16 80 -44 226 -187 679 -284 895 -10 22 -32 | |||
74 -50 115 -51 118 -200 404 -280 535 -221 366 -482 698 -781 995 -113 113 | |||
-258 245 -332 303 -15 12 -39 32 -52 44 -36 32 -280 213 -350 260 -387 258 | |||
-753 444 -1160 588 -242 86 -581 177 -780 209 -36 6 -82 14 -110 20 -11 2 -45 | |||
7 -75 11 -30 4 -62 9 -71 10 -29 6 -116 15 -294 32 -50 4 -642 12 -647 9z"/> | |||
</g> | |||
</svg> |
@ -0,0 +1,19 @@ | |||
{ | |||
"name": "", | |||
"short_name": "", | |||
"icons": [ | |||
{ | |||
"src": "android-chrome-192x192.png", | |||
"sizes": "192x192", | |||
"type": "image/png" | |||
}, | |||
{ | |||
"src": "android-chrome-512x512.png", | |||
"sizes": "512x512", | |||
"type": "image/png" | |||
} | |||
], | |||
"theme_color": "#ffffff", | |||
"background_color": "#ffffff", | |||
"display": "standalone" | |||
} |
@ -0,0 +1,273 @@ | |||
<!DOCTYPE html> | |||
<html lang="fr"> | |||
<head> | |||
<meta charset="UTF-8"> | |||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> | |||
<meta name="msapplication-TileColor" content="#603cba"> | |||
<meta name="msapplication-config" content="./favicons/browserconfig.xml"> | |||
<meta name="theme-color" content="#ffffff"> | |||
<meta name="title" content="Générateur d'attestation de déplacement dérogatoire - COVID-19"> | |||
<meta name="description" content="Ce service officiel génère une version numérique de l’attestation déplacement covid-19 à présenter aux forces de sécurité lors d’un contrôle."> | |||
<meta name="keywords" content="covid19, covid-19, attestation, déplacement, officielle, gouvernement"> | |||
<meta name="robots" content="index, follow"> | |||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> | |||
<meta name="language" content="French"> | |||
<meta property="og:title" content="Générateur d'attestation de déplacement dérogatoire - COVID-19" /> | |||
<meta property="og:locale" content="fr_FR" /> | |||
<meta property="og:description" content="Ce service officiel génère une version numérique de l’attestation déplacement covid-19 à présenter aux forces de sécurité lors d’un contrôle." /> | |||
<link rel="canonical" href="https://media.interieur.gouv.fr/deplacement-covid-19/" /> | |||
<meta property="og:url" content="https://media.interieur.gouv.fr/deplacement-covid-19/" /> | |||
<meta property="og:site_name" content="Générateur d'attestation de déplacement dérogatoire - COVID-19" /> | |||
<script type='application/ld+json'>{"@context":"http://www.schema.org","@type":"GovernmentOrganization","name":"Générateur d'attestation de déplacement dérogatoire - COVID-19","description":"Ce service officiel génère une version numérique de l’attestation déplacement covid-19 à présenter aux forces de sécurité lors d’un contrôle.","address":{"@type":"PostalAddress","addressCountry":"France"}}</script> | |||
<link rel="apple-touch-icon" sizes="180x180" href="./favicons/apple-touch-icon.png"> | |||
<link rel="icon" type="image/png" sizes="32x32" href="./favicons/favicon-32x32.png"> | |||
<link rel="icon" type="image/png" sizes="16x16" href="./favicons/favicon-16x16.png"> | |||
<link rel="manifest" href="./favicons/site.webmanifest"> | |||
<link rel="mask-icon" href="./favicons/safari-pinned-tab.svg" color="#21bf73"> | |||
<title>COVID-19 – Générateur d'attestation de déplacement dérogatoire</title> | |||
</head> | |||
<body> | |||
<header class="wrapper"> | |||
<img class="logo" src="/MIN_Interieur_RVB.svg" alt=""> | |||
<div> | |||
<h1 class="flex flex-wrap"> | |||
<span class="covid-title"> | |||
COVID-19 | |||
</span> | |||
<span class="covid-subtitle"> | |||
Générateur d'attestation de déplacement dérogatoire | |||
</span> | |||
</h1> | |||
<p class="text-alert"> | |||
Les données saisies sont stockées exclusivement sur votre téléphone ou votre ordinateur. Aucune information n'est collectée par le Ministère de l'Intérieur. | |||
L'attestation pdf générée contient un QR Code. Ce code-barres graphique permet de lire les informations portées dans votre attestation au moment de leur saisie. | |||
Il peut être déchiffré à l'aide de tout type de lecteur de QR code générique. | |||
</p> | |||
</div> | |||
</header> | |||
<div class="wrapper"> | |||
<form id="form-profile"> | |||
<h2 class="titre-2">Remplissez en ligne votre attestation numérique :</h2> | |||
<div class="form-group"> | |||
<label for="field-firstname" id="field-firstname-label">Prénom</label> | |||
<div class="input-group align-items-center"> | |||
<input | |||
type="text" | |||
class="form-control" | |||
id="field-firstname" | |||
name="firstname" | |||
autocomplete="given-name" | |||
placeholder="Jean" | |||
aria-labelledby="field-firstname-label" | |||
required | |||
autofocus | |||
> | |||
<span class="validity"></span> | |||
</div> | |||
<p class="exemple"></p> | |||
</div> | |||
<div class="form-group"> | |||
<label for="field-lastname" id="field-lastname-label">Nom</label> | |||
<div class="input-group align-items-center"> | |||
<input | |||
type="text" | |||
class="form-control" | |||
id="field-lastname" | |||
name="lastname" | |||
autocomplete="family-name" | |||
placeholder="Dupont" | |||
aria-labelledby="field-lastname-label" | |||
required | |||
autofocus | |||
> | |||
<span class="validity"></span> | |||
</div> | |||
<p class="exemple"></p> | |||
</div> | |||
<div class="form-group"> | |||
<label for="field-birthday" id="field-birthday-label">Date de naissance (au format jj/mm/aaaa)</label> | |||
<div class="input-group align-items-center"> | |||
<input | |||
type="text" | |||
inputmode="numeric" | |||
class="form-control" | |||
id="field-birthday" | |||
name="birthday" | |||
autocomplete="bday" | |||
placeholder="01/01/1970" | |||
maxlength="10" | |||
aria-labelledby="field-birthday-label" | |||
required | |||
> | |||
<span class="validity"></span> | |||
</div> | |||
<p class="exemple"></p> | |||
</div> | |||
<div class="form-group"> | |||
<label for="field-lieunaissance" id="field-lieunaissance-label">Lieu de naissance</label> | |||
<div class="input-group align-items-center"> | |||
<input | |||
type="text" | |||
class="form-control" | |||
id="field-lieunaissance" | |||
name="lieunaissance" | |||
aria-labelledby="field-lieunaissance-label" | |||
placeholder="Lyon" | |||
required | |||
> | |||
<span class="validity"></span> | |||
</div> | |||
<p class="exemple"></p> | |||
</div> | |||
<div class="form-group"> | |||
<label for="field-address" id="field-address-label">Adresse</label> | |||
<div class="input-group align-items-center"> | |||
<input | |||
type="text" | |||
class="form-control" | |||
id="field-address" | |||
name="address" | |||
autocomplete="address-line1" | |||
aria-labelledby="field-address-label" | |||
placeholder="999 avenue de france" | |||
required | |||
> | |||
<span class="validity"></span> | |||
</div> | |||
<p class="exemple"></p> | |||
</div> | |||
<div class="form-group"> | |||
<label for="field-town" id="field-town-label">Ville</label> | |||
<div class="input-group align-items-center"> | |||
<input | |||
type="text" | |||
class="form-control" | |||
id="field-town" | |||
name="town" | |||
autocomplete="address-level1" | |||
aria-labelledby="field-town-label" | |||
placeholder="Paris" | |||
required | |||
> | |||
<span class="validity"></span> | |||
</div> | |||
<p class="exemple"></p> | |||
</div> | |||
<div class="form-group"> | |||
<label for="field-zipcode" id="field-zipcode-label" >Code Postal</label> | |||
<div class="input-group align-items-center"> | |||
<input | |||
type="number" | |||
min="00000" | |||
max="99999" | |||
class="form-control" | |||
id="field-zipcode" | |||
name="zipcode" | |||
autocomplete="zipcode" | |||
minlength="4" | |||
maxlength="5" | |||
aria-labelledby="field-zipcode-label" | |||
placeholder="75001" | |||
required | |||
> | |||
<span class="validity"></span> | |||
</div> | |||
<p class="exemple"></p> | |||
</div> | |||
<h3>Choisissez un motif de sortie</h3> | |||
<div class="form-check"> | |||
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-travail" value="travail"> | |||
<label class="form-check-label" for="checkbox-travail">Déplacements entre le domicile et le lieu d’exercice de l’activité professionnelle, lorsqu'ils sont indispensables à l'exercice d’activités ne pouvant être organisées sous forme de télétravail ou déplacements professionnels ne pouvant être différés.</label> | |||
</div> | |||
<div class="form-check"> | |||
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-courses" value="courses"> | |||
<label class="form-check-label" for="checkbox-courses">Déplacements pour effectuer des achats de fournitures nécessaires à l’activité professionnelle et des achats de première nécessité dans des établissements dont les activités demeurent autorisées <a href="https://www.service-public.fr/particuliers/actualites/A13921" target="_blank">(liste sur gouvernement.fr)</a>.</label> | |||
</div> | |||
<div class="form-check"> | |||
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-sante" value="sante"> | |||
<label class="form-check-label" for="checkbox-sante">Consultations et soins ne pouvant être assurés à distance et ne pouvant être différés ; consultations et soins des patients atteints d'une affection de longue durée.</label> | |||
</div> | |||
<div class="form-check"> | |||
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-famille" value="famille"> | |||
<label class="form-check-label" for="checkbox-famille">Déplacements pour motif familial impérieux, pour l’assistance aux personnes vulnérables ou la garde d’enfants.</label> | |||
</div> | |||
<div class="form-check"> | |||
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-sport" value="sport"> | |||
<label class="form-check-label" for="checkbox-sport">Déplacements brefs, dans la limite d'une heure quotidienne et dans un rayon maximal d'un kilomètre autour du domicile, liés soit à l'activité physique individuelle des personnes, à l'exclusion de toute pratique sportive collective et de toute proximité avec d'autres personnes, soit à la promenade avec les seules personnes regroupées dans un même domicile, soit aux besoins des animaux de compagnie.</label> | |||
</div> | |||
<div class="form-check"> | |||
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-judiciaire" value="judiciaire"> | |||
<label class="form-check-label" for="checkbox-judiciaire">Convocation judiciaire ou administrative.</label> | |||
</div> | |||
<div class="form-check"> | |||
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-missions" value="missions"> | |||
<label class="form-check-label" for="checkbox-missions">Participation à des missions d’intérêt général sur demande de l’autorité administrative.</label> | |||
</div> | |||
<div class="form-group"> | |||
<label for="field-datesortie">Date de sortie</label> | |||
<div class="input-group align-items-center"> | |||
<input type="date" class="form-control" id="field-datesortie" name="datesortie" placeholder="JJ/MM/YYYY" required> | |||
<span class="validity"></span> | |||
</div> | |||
</div> | |||
<div class="form-group"> | |||
<label for="field-heure">Heure de sortie</label> | |||
<div class="input-group align-items-center"> | |||
<input type="time" class="form-control" id="field-heuresortie" name="heure" required> | |||
<span class="validity"></span> | |||
</div> | |||
</div> | |||
<p class="text-center mt-5"> | |||
<button type="submit" class="btn btn-primary btn-attestation"> <span class="btn-text">Générer mon attestation</span></button> | |||
</p> | |||
<div class="bg-primary d-none" id="snackbar"> | |||
L'attestation est téléchargée sur votre appareil. | |||
</div> | |||
</form> | |||
</div> | |||
<div class=""> | |||
<p class="label-mi"> | |||
Ministère de l'Intérieur - DNUM - SDIT | |||
</p> | |||
<img class="center" src="/logo_dnum.svg" alt="logo dnum"> | |||
</div> | |||
<footer class="main-footer"> | |||
<div class="footer-links"> | |||
<a href="./confidentialite.html" title="Confidentialité - nouvelle page" target="_blank" class="footer-link">Confidentialité</a> | |||
<a href="https://www.interieur.gouv.fr/Infos-du-site/Mentions-legales" title="Mentions légales - nouvelle page" target="_blank" class="footer-link">Mentions légales</a> | |||
<a href="https://www.gouvernement.fr/info-coronavirus" title="Information du gouvernement sur le Covid-19 - nouvelle page" target="_blank" class="footer-link">Informations du gouvernement sur le Covid-19</a> | |||
<div class="footer-link" >Plus d’infos au<a class="num-08" href="tel:0800130000"> 0 800 130 000</a></div> | |||
<p class="footer-link" id="version"></p> | |||
</div> | |||
</footer> | |||
<div class="alert alert-danger d-none" role="alert" id="alert-facebook"> | |||
ATTENTION !! Vous utilisez actuellement le navigateur Facebook, ce générateur ne fonctionne pas correctement au | |||
sein de ce navigateur ! Merci d'ouvrir Chrome sur Android ou bien Safari sur iOS. | |||
</div> | |||
<script src="./certificate.js"></script> | |||
</body> | |||
</html> |
@ -0,0 +1,367 @@ | |||
@font-face { | |||
font-family: 'marianne-bold'; | |||
src: url('./fonts/marianne-bold-webfont.woff2') format('woff2'), | |||
url('./fonts/marianne-bold-webfont.woff') format('woff'); | |||
font-weight: normal; | |||
font-style: normal; | |||
} | |||
@font-face { | |||
font-family: 'marianne-regular'; | |||
src: url('./fonts/marianne-regular-webfont.woff2') format('woff2'), | |||
url('./fonts/marianne-regular-webfont.woff') format('woff'); | |||
font-weight: normal; | |||
font-style: normal; | |||
} | |||
body { | |||
margin: 20px; | |||
} | |||
form, #alert-print { | |||
margin: 30px auto; | |||
max-width: 400px; | |||
} | |||
h1 { | |||
font-size: 2em; | |||
} | |||
/* Small devices (landscape phones, 576px and up) */ | |||
@media (min-width: 576px) { | |||
h1 { | |||
font-size: 2.5em; | |||
} | |||
} | |||
/* Medium devices (tablets, 768px and up) */ | |||
@media (min-width: 768px) { | |||
h1 { | |||
font-size: 3em; | |||
} | |||
} | |||
svg { | |||
height: 1em; | |||
} | |||
h1.flex.flex-wrap { | |||
display: flex; | |||
flex-wrap: wrap; | |||
} | |||
h2 { | |||
font-size: large; | |||
} | |||
footer { | |||
margin-top: 40px; | |||
} | |||
canvas { | |||
border: 1px solid #ced4da; | |||
border-radius: .25rem; | |||
} | |||
.wrapper { | |||
max-width: 800px; | |||
margin-left: auto; | |||
margin-right: auto; | |||
} | |||
#form-profile .form-check { | |||
margin-bottom: 1rem; | |||
} | |||
#form-profile .form-check-label { | |||
font-weight: 400; | |||
transform: translateY(-2px); | |||
} | |||
#form-generate .form-check { | |||
margin: 10px; | |||
} | |||
#alert-facebook { | |||
position: fixed; | |||
top: 20px; | |||
left: 20px; | |||
right: 20px; | |||
} | |||
#alert-print { | |||
margin: 1rem auto 0; | |||
} | |||
#date-selector-group { | |||
position: relative; | |||
overflow: hidden; | |||
} | |||
#date-selector-group-sortie { | |||
position: relative; | |||
overflow: hidden; | |||
} | |||
#date-selector { | |||
position: absolute; | |||
top: 0; | |||
left: 50%; | |||
height: 100%; | |||
transform: translateX(-50%); /* center the input to avoid reset buttons */ | |||
opacity: 0; | |||
z-index: 1; | |||
cursor: pointer; | |||
} | |||
#date-selector-sortie { | |||
position: absolute; | |||
top: 0; | |||
left: 50%; | |||
height: 100%; | |||
transform: translateX(-50%); /* center the input to avoid reset buttons */ | |||
opacity: 0; | |||
z-index: 1; | |||
cursor: pointer; | |||
} | |||
input:invalid+span:after { | |||
content: '✖'; | |||
padding-left: 0.3em; | |||
} | |||
input:valid+span:after { | |||
content: '✓'; | |||
padding-left: 0.3em; | |||
} | |||
::placeholder { | |||
font-style: italic; | |||
} | |||
.exemple { | |||
margin-top: 0.2em; | |||
font-style: italic; | |||
color: #999999; | |||
height: 1em; | |||
} | |||
.logo { | |||
width: 50%; | |||
} | |||
.covid-title { | |||
display: flex; | |||
align-items: center; | |||
padding: 0.3em; | |||
font-family: 'marianne-regular', Arial, Helvetica, sans-serif; | |||
font-size: 0.50em; | |||
color: #ffffff; | |||
background-color: #000191; | |||
} | |||
@media (min-width: 992px) { | |||
.covid-title { | |||
flex-grow: 0; | |||
} | |||
} | |||
.covid-subtitle { | |||
display: flex; | |||
align-items: center; | |||
font-size: 0.40em; | |||
padding: 0.3em; | |||
background-color: #e1000f; | |||
text-transform:uppercase ; | |||
color: #ffffff; | |||
flex-grow: 1; | |||
} | |||
.text-alert{ | |||
text-align: left; | |||
color: #000000; | |||
} | |||
.btn-attestation { | |||
padding: 0.8em; | |||
font-size: 1.2em; | |||
font-weight: bold; | |||
color: #ffffff; | |||
background-color: #000191; | |||
border-radius: 0.5em; | |||
} | |||
.github { | |||
font-size: 0.7em; | |||
text-align: center; | |||
color: #000000; | |||
} | |||
.github-link { | |||
color: #000191; | |||
} | |||
.label-mi { | |||
text-align: center; | |||
font-size: 1em; | |||
color: #000000 | |||
} | |||
.main-footer { | |||
display: flex; | |||
justify-content: center; | |||
padding-top: 3em; | |||
padding-bottom: 3em; | |||
background-color:#000000; | |||
color: white; | |||
} | |||
.footer-links { | |||
margin: 0 auto; | |||
max-width: 500px; | |||
} | |||
.footer-link { | |||
display: block; | |||
margin: 0.75em; | |||
font-size: 0.9em; | |||
color: #ffffff; | |||
} | |||
.footer-link:hover { | |||
text-decoration: none; | |||
color: #ffffff; | |||
} | |||
.footer-link:focus { | |||
color: #ffffff; | |||
} | |||
.num-08 { | |||
font-weight: bold; | |||
color: #00a94f; | |||
} | |||
.confidentialite { | |||
.cookies { | |||
border-collapse: collapse; | |||
td, | |||
th { | |||
border: 1px solid #000191; | |||
padding: 8px; | |||
} | |||
.header-row { | |||
font-weight: bold; | |||
color: #ffffff; | |||
background-color: #000191; | |||
text-transform: uppercase; | |||
text-align: center; | |||
} | |||
.name-col { | |||
font-weight: bold; | |||
padding: 8px 16px; | |||
color: #000191; | |||
} | |||
} | |||
.btn-wrapper { | |||
display: flex; | |||
justify-content: center; | |||
.btn-generateur { | |||
padding: 0.8em; | |||
font-size: 1.2em; | |||
font-weight: bold; | |||
color: #ffffff; | |||
background-color: #000191; | |||
border-radius: 0.5em; | |||
text-decoration: none; | |||
color: #ffffff; | |||
} | |||
} | |||
em { | |||
font-size: .8rem; | |||
} | |||
} | |||
.titre-2 { | |||
text-align: left; | |||
font-size: 1.5rem; | |||
font-weight: bold; | |||
color: #000191; | |||
} | |||
.titre-3 { | |||
text-align: left; | |||
font-size: 1.25rem; | |||
font-weight: bold; | |||
color: #000000; | |||
} | |||
.titre-4 { | |||
text-align: left; | |||
font-size: 1rem; | |||
font-weight: bold; | |||
color: #000000; | |||
} | |||
@media (max-width: 800px){ | |||
table thead { | |||
display: none; | |||
} | |||
table tr{ | |||
display: block; | |||
margin-bottom: 40px; | |||
} | |||
table td { | |||
display: flex; | |||
text-align: left; | |||
min-height: 3rem; | |||
} | |||
table td:before { | |||
content: attr(data-label); | |||
font-weight: bold; | |||
color: #000191; | |||
width: 100px; | |||
margin-right: 8px; | |||
flex-shrink: 0; | |||
} | |||
} | |||
#snackbar { | |||
min-width: 250px; | |||
color: #fff; | |||
text-align: center; | |||
border-radius: 3px; | |||
padding: 16px; | |||
position: fixed; | |||
z-index: 1; | |||
left: 50%; | |||
bottom: 30px; | |||
font-size: 17px; | |||
transform: translateX(-50%); | |||
box-shadow: 0 0 8px 1px #fff; | |||
opacity: 0; | |||
transition: all 0.5s ease-in-out; | |||
} | |||
#snackbar.show { | |||
opacity: 1; | |||
} | |||
.center { | |||
display: block; | |||
margin-left: auto; | |||
margin-right: auto; | |||
} |
@ -0,0 +1,56 @@ | |||
{ | |||
"name": "deplacement-covid-19", | |||
"version": "0.0.1", | |||
"description": "Générateur d'attestation de déplacement dérogatoire'", | |||
"main": "certificate.js", | |||
"scripts": { | |||
"lint": "eslint ./*.js", | |||
"start": "VERSION=${VERSION:-localversion} parcel index.html robots.txt", | |||
"prebuild": "npm run lint && rimraf dist", | |||
"build": "PUBLIC_URL=${PUBLIC_URL:-/deplacement-covid-19} ; parcel build --public-url ${PUBLIC_URL} index.html robots.txt", | |||
"postbuild": "PUBLIC_URL=${PUBLIC_URL:-/deplacement-covid-19} react-snap" | |||
}, | |||
"repository": { | |||
"type": "git", | |||
"url": "git+https://github.com/lab-mi/deplacement-codiv-19" | |||
}, | |||
"keywords": [], | |||
"author": "", | |||
"license": "MIT", | |||
"bugs": { | |||
"url": "https://github.com/lab-mi/deplacement-covid-19/issues" | |||
}, | |||
"devDependencies": { | |||
"@babel/core": "^7.9.0", | |||
"@babel/plugin-transform-runtime": "^7.9.0", | |||
"babel-eslint": "^10.1.0", | |||
"eslint": "^6.8.0", | |||
"eslint-config-standard": "^14.1.1", | |||
"eslint-plugin-import": "^2.20.1", | |||
"eslint-plugin-node": "^11.0.0", | |||
"eslint-plugin-promise": "^4.2.1", | |||
"eslint-plugin-standard": "^4.0.1", | |||
"parcel-bundler": "^1.12.4", | |||
"postcss-current-selector": "0.0.3", | |||
"postcss-nested": "^4.2.1", | |||
"postcss-nested-ancestors": "^2.0.0", | |||
"postcss-preset-env": "^6.7.0", | |||
"react-snap": "^1.23.0", | |||
"rimraf": "^3.0.2" | |||
}, | |||
"dependencies": { | |||
"bootstrap": "^4.4.1", | |||
"pdf-lib": "^1.4.1", | |||
"qrcode": "^1.4.4" | |||
}, | |||
"browserslist": [ | |||
"last 5 versions" | |||
], | |||
"reactSnap": { | |||
"source": "dist", | |||
"minifyHtml": { | |||
"collapseWhitespace": false, | |||
"removeComments": false | |||
} | |||
} | |||
} |
@ -0,0 +1,11 @@ | |||
module.exports = { | |||
plugins: { | |||
autoprefixer: { | |||
grid: true, | |||
}, | |||
'postcss-preset-env': {}, | |||
'postcss-nested-ancestors': {}, | |||
'postcss-nested': {}, | |||
'postcss-current-selector': {}, | |||
}, | |||
} |
@ -0,0 +1 @@ | |||
User-agent: * |