🎉 Première version du projet
10
.babelrc
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
"@babel/preset-env"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
["@babel/plugin-transform-runtime", {
|
||||||
|
"regenerator": true
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
}
|
18
.editorconfig
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
insert_final_newline = true
|
||||||
|
end_of_line = lf
|
||||||
|
|
||||||
|
[*.html]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.{js,json,yml}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.js]
|
||||||
|
quote_type = single
|
33
.eslintrc.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
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"],
|
||||||
|
"no-var": 2,
|
||||||
|
},
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
213
.gitignore
vendored
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
|
||||||
|
# Created by https://www.toptal.com/developers/gitignore/api/node,code,linux,vim,windows,macos
|
||||||
|
# Edit at https://www.toptal.com/developers/gitignore?templates=node,code,linux,vim,windows,macos
|
||||||
|
|
||||||
|
### Code ###
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
*.code-workspace
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
.env.test
|
||||||
|
.env*.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
### Vim ###
|
||||||
|
# Swap
|
||||||
|
[._]*.s[a-v][a-z]
|
||||||
|
!*.svg # comment out if you don't need vector files
|
||||||
|
[._]*.sw[a-p]
|
||||||
|
[._]s[a-rt-v][a-z]
|
||||||
|
[._]ss[a-gi-z]
|
||||||
|
[._]sw[a-p]
|
||||||
|
|
||||||
|
# Session
|
||||||
|
Session.vim
|
||||||
|
Sessionx.vim
|
||||||
|
|
||||||
|
# Temporary
|
||||||
|
.netrwhist
|
||||||
|
# Auto-generated tag files
|
||||||
|
tags
|
||||||
|
# Persistent undo
|
||||||
|
[._]*.un~
|
||||||
|
|
||||||
|
### 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.toptal.com/developers/gitignore/api/node,code,linux,vim,windows,macos
|
4
.prettierrc.js
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
module.exports = {
|
||||||
|
singleQuote: true,
|
||||||
|
semi: false
|
||||||
|
};
|
20
LICENCE
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
Copyright (c) 2020 Ministère de l'intérieur
|
||||||
|
Copyright (c) 2020 Johann Pardanaud
|
||||||
|
|
||||||
|
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.
|
44
README.md
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
# Générateur de certificat de déplacement
|
||||||
|
|
||||||
|
## Développer
|
||||||
|
|
||||||
|
### Installer le projet
|
||||||
|
|
||||||
|
```console
|
||||||
|
git clone https://github.com/LAB-MI/attestation-deplacement-derogatoire-q4-2020.git
|
||||||
|
cd attestation-deplacement-derogatoire-q4-2020
|
||||||
|
npm i
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
## Générer et tester le code de production
|
||||||
|
|
||||||
|
### Tester le code de production en local
|
||||||
|
|
||||||
|
#### Générer le code de production pour tester que le build fonctionne en entier
|
||||||
|
|
||||||
|
```console
|
||||||
|
npm run build:dev
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Tester le code de production en local
|
||||||
|
|
||||||
|
```console
|
||||||
|
npx serve dist
|
||||||
|
```
|
||||||
|
|
||||||
|
Et visiter http://localhost:5000
|
||||||
|
|
||||||
|
Le code à déployer sera le contenu du dossier `dist`
|
||||||
|
|
||||||
|
## Crédits
|
||||||
|
|
||||||
|
Ce projet a été réalisé à partir d'un fork du dépôt [deplacement-covid-19](https://github.com/nesk/deplacement-covid-19) de lui-même 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)
|
24761
package-lock.json
generated
Normal file
94
package.json
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
{
|
||||||
|
"name": "attestation-couvre-feu",
|
||||||
|
"version": "1.0.3",
|
||||||
|
"description": "Générateur d'attestation de déplacement dérogatoire'",
|
||||||
|
"main": "certificate.js",
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint src/**/*.js",
|
||||||
|
"preformat": "prettier --write \"src/**/*.js\"",
|
||||||
|
"format": "npm run lint -- --fix",
|
||||||
|
"start": "cross-env VERSION=localversion parcel ./src/index.html",
|
||||||
|
"start:grid": "cross-env VERSION=localversion parcel ./src/grid.html",
|
||||||
|
"clean:dist": "rimraf dist",
|
||||||
|
"prebuild": "run-p lint clean:dist",
|
||||||
|
"build:simple": "cross-env-shell VERSION=$npm_package_version parcel build --public-url $PUBLIC_URL ./src/index.html ./src/robots.txt ./src/sitemap.xml",
|
||||||
|
"build": "cross-env npm run build:simple",
|
||||||
|
"postbuild": "cross-env-shell react-snap",
|
||||||
|
"prebuild:ci": "run-p lint clean:dist",
|
||||||
|
"build:ci": "cross-env npm run build:simple",
|
||||||
|
"postbuild:ci": "cross-env-shell react-snap",
|
||||||
|
"build:dev": "cross-env PUBLIC_URL='/' npm run build:simple",
|
||||||
|
"preserve": "npm run build",
|
||||||
|
"serve": "serve dist",
|
||||||
|
"serve:dist": "serve dist"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/lab-mi/attestation-deplacement-derogatoire-q4-2020"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/lab-mi/attestation-deplacement-derogatoire-q4-2020/issues"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.12.3",
|
||||||
|
"@babel/plugin-transform-runtime": "^7.12.1",
|
||||||
|
"babel-eslint": "^10.1.0",
|
||||||
|
"copy-and-watch": "^0.1.5",
|
||||||
|
"cross-env": "^7.0.2",
|
||||||
|
"eslint": "^7.12.1",
|
||||||
|
"eslint-config-standard": "^16.0.0",
|
||||||
|
"eslint-plugin-import": "^2.22.1",
|
||||||
|
"eslint-plugin-node": "^11.1.0",
|
||||||
|
"eslint-plugin-promise": "^4.2.1",
|
||||||
|
"eslint-plugin-standard": "^4.0.2",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
|
"parcel-bundler": "^1.12.4",
|
||||||
|
"parcel-plugin-sw-cache": "^0.3.1",
|
||||||
|
"postcss-current-selector": "0.0.3",
|
||||||
|
"postcss-nested": "^4.2.3",
|
||||||
|
"postcss-nested-ancestors": "^2.0.0",
|
||||||
|
"postcss-preset-env": "^6.7.0",
|
||||||
|
"prettier": "^2.1.2",
|
||||||
|
"react-snap": "^1.23.0",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
|
"serve": "^11.3.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-svg-core": "^1.2.32",
|
||||||
|
"@fortawesome/free-solid-svg-icons": "^5.15.1",
|
||||||
|
"bootstrap": "^4.5.3",
|
||||||
|
"pdf-lib": "^1.11.2",
|
||||||
|
"qrcode": "^1.4.4"
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"last 5 versions"
|
||||||
|
],
|
||||||
|
"reactSnap": {
|
||||||
|
"source": "dist",
|
||||||
|
"minifyHtml": {
|
||||||
|
"collapseWhitespace": false,
|
||||||
|
"removeComments": false
|
||||||
|
},
|
||||||
|
"puppeteerArgs": [
|
||||||
|
"--no-sandbox",
|
||||||
|
"--disable-setuid-sandbox"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"cache": {
|
||||||
|
"globPatterns": [
|
||||||
|
"**/*.{html,js,css,jpg,png,pdf,svg,eot,ttf,woff,woff2}"
|
||||||
|
],
|
||||||
|
"disablePlugin": false,
|
||||||
|
"inDev": true,
|
||||||
|
"strategy": "default",
|
||||||
|
"clearDist": false,
|
||||||
|
"templatedURLs": {
|
||||||
|
"./": [
|
||||||
|
"index.html"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
postcss.config.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
autoprefixer: {
|
||||||
|
grid: true,
|
||||||
|
},
|
||||||
|
'postcss-preset-env': {},
|
||||||
|
'postcss-nested-ancestors': {},
|
||||||
|
'postcss-nested': {},
|
||||||
|
'postcss-current-selector': {},
|
||||||
|
},
|
||||||
|
}
|
1
src/MIN_Interieur_RVB.svg
Normal file
After Width: | Height: | Size: 20 KiB |
1
src/MIN_Interieur_RVB_dark.svg
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
src/certificate.pdf
Normal file
195
src/confidentialite.html
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
<!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">
|
||||||
|
<picture>
|
||||||
|
<source srcset="/MIN_Interieur_RVB_dark.svg" media="(prefers-color-scheme: dark)">
|
||||||
|
<img src="/MIN_Interieur_RVB.svg" alt="Ministère de l'intérieur. Liberté, égalité, fraternité." class="logo" role="presentation" aria-hidden="true">
|
||||||
|
</picture>
|
||||||
|
<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. Le code source
|
||||||
|
de ce générateur d'attestation est ouvert. Il peut être consulté
|
||||||
|
sur <a href="https://github.com/LAB-MI/attestation-deplacement-derogatoire-q4-2020">
|
||||||
|
https://github.com/LAB-MI/attestation-deplacement-derogatoire-q4-2020</a>.
|
||||||
|
</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">
|
||||||
|
<caption>Liste des cookies utilisés sur ce site/caption>
|
||||||
|
<thead>
|
||||||
|
<tr class="header-row">
|
||||||
|
<th scope="col">Nom du Cookie</th>
|
||||||
|
<th scope="col">Finalité</th>
|
||||||
|
<th scope="col">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-line 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-line 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-line footer-link">Informations du
|
||||||
|
gouvernement sur le Covid-19</a>
|
||||||
|
<p class="footer-line">
|
||||||
|
Plus d’infos au<a class="num-08" href="tel:0800130000">
|
||||||
|
0 800 130 000</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
<script src="./js/confidentialite.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
666
src/css/main.css
Normal file
|
@ -0,0 +1,666 @@
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (font: -apple-system-body) {
|
||||||
|
html {
|
||||||
|
font: -apple-system-body !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
html {
|
||||||
|
background: #222;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 20px;
|
||||||
|
background: white;
|
||||||
|
padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
body {
|
||||||
|
background: #222;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
form, #alert-print {
|
||||||
|
margin: 30px auto;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2em;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
h1 {
|
||||||
|
color: white;
|
||||||
|
background-color: white;
|
||||||
|
background: #222;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
h1.flex.flex-wrap {
|
||||||
|
border: 1px solid white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: large;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
h2 {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
margin-top: 40px;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
footer {
|
||||||
|
background: #222;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
border: 1px solid #ced4da;
|
||||||
|
border-radius: .25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #000191;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
a {
|
||||||
|
color: #A3A3FF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
p {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
max-width: 800px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#form-profile .form-radio .form-check {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#form-profile .form-radio-label .form-check-label {
|
||||||
|
font-weight: 400;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
#form-profile .form-radio-label .form-check-label {
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
label {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control::placeholder {
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, select, textarea, .form-control, .form-control:focus {
|
||||||
|
background: #333;
|
||||||
|
color: white;
|
||||||
|
border: 1px solid white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus {
|
||||||
|
background: #555;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#form-generate .form-radio .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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Chrome, Safari, Edge, Opera */
|
||||||
|
input::-webkit-outer-spin-button,
|
||||||
|
input::-webkit-inner-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox */
|
||||||
|
input[type=number] {
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
::placeholder {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 (prefers-color-scheme: dark) {
|
||||||
|
.covid-title {
|
||||||
|
background: #2929ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
.covid-title {
|
||||||
|
flex-grow: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.covid-subtitle {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 0.40em;
|
||||||
|
padding: 0.3em;
|
||||||
|
background-color: #e1000f;
|
||||||
|
text-transform:uppercase ;
|
||||||
|
color: #ffffff;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-title {
|
||||||
|
border-bottom: 3px solid #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlighted {
|
||||||
|
font-weight: bold;
|
||||||
|
border-bottom: 2px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footnote-title {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.msg-info {
|
||||||
|
text-align: left;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.msg-alert {
|
||||||
|
text-align: left;
|
||||||
|
color: red;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-desc {
|
||||||
|
font-style :italic;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.covid-subtitle {
|
||||||
|
background: #ad000c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.msg-info {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-attestation {
|
||||||
|
padding: 0.9em;
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-weight: bold;
|
||||||
|
color: white;
|
||||||
|
background-color: #000191;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-attestation:hover {
|
||||||
|
background-color: #3031C1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.github {
|
||||||
|
font-size: 0.7em;
|
||||||
|
text-align: center;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.github {
|
||||||
|
color: white
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.github-link {
|
||||||
|
color: #000191;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.github-link {
|
||||||
|
color: #A3A3FF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.github-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-mi {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1em;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.label-mi {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.validity {
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
width: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.validity {
|
||||||
|
color: #DDD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding-top: 3em;
|
||||||
|
padding-bottom: 3em;
|
||||||
|
background: #222;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-links {
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 500px;
|
||||||
|
background: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.main-footer {
|
||||||
|
background: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-links {
|
||||||
|
background: #444;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-line {
|
||||||
|
display: block;
|
||||||
|
margin: 0.75em;
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
color: #0a81ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-link:focus {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.num-08 {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #00a94f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.num-08:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
color: #0A81FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stores-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confidentialite {
|
||||||
|
.cookies {
|
||||||
|
border-collapse: collapse;
|
||||||
|
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
border: 1px solid #000191;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
td, th {
|
||||||
|
border-color: #A3A3FF;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.name-col {
|
||||||
|
color: #A3A3FF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 (prefers-color-scheme: dark) {
|
||||||
|
.titre-3 {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.titre-4 {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.titre-2 {
|
||||||
|
color: #A3A3FF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (min-width:600px) {
|
||||||
|
.hide-on-desktop, * [aria-labelledby='hide-on-desktop'] {
|
||||||
|
display: none;
|
||||||
|
max-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
[id^="footnote"] {
|
||||||
|
margin: 30px auto;
|
||||||
|
max-width: 400px;
|
||||||
|
font-size: 0.8em;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
[id^="footnote"] {
|
||||||
|
color:white;
|
||||||
|
background: #222;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#update-alert {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 1em;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-info {
|
||||||
|
color: #030494;
|
||||||
|
background-color: #caf8ff;
|
||||||
|
border-color: #caf8ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-info {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #030494;
|
||||||
|
border-color: #030494;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fieldset {
|
||||||
|
margin-left: -2em;
|
||||||
|
margin-right: -2em;
|
||||||
|
padding-left: 2em;
|
||||||
|
padding-right: 2em;
|
||||||
|
border: 3px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
padding-left: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fieldset-error {
|
||||||
|
border: 3px solid red;
|
||||||
|
}
|
BIN
src/favicons/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 9.7 KiB |
BIN
src/favicons/android-chrome-512x512.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
src/favicons/apple-touch-icon-120x120-precomposed.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
src/favicons/apple-touch-icon-120x120.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
src/favicons/apple-touch-icon-precomposed.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
src/favicons/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 16 KiB |
9
src/favicons/browserconfig.xml
Normal file
|
@ -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>
|
BIN
src/favicons/favicon-16x16.png
Normal file
After Width: | Height: | Size: 613 B |
BIN
src/favicons/favicon-32x32.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
src/favicons/favicon.ico
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
src/favicons/mstile-150x150.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
38
src/favicons/safari-pinned-tab.svg
Normal file
|
@ -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>
|
After Width: | Height: | Size: 2.3 KiB |
43
src/favicons/site.webmanifest
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
"name": "Générateur d'attestation de déplacement dérogatoire",
|
||||||
|
"short_name": "Déplacement covid-19",
|
||||||
|
"description": "L'application officielle du gouvernement pour la génération d'attestation de déplacement dérogatoire dématérialisée.",
|
||||||
|
"categories": ["government", "health"],
|
||||||
|
"lang": "fr-FR",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "android-chrome-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "android-chrome-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "apple-touch-icon.png",
|
||||||
|
"sizes": "180x180",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "apple-touch-icon-precomposed.png",
|
||||||
|
"sizes": "180x180",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "apple-touch-icon-120x120.png",
|
||||||
|
"sizes": "120x120",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "apple-touch-icon-120x120-precomposed.png",
|
||||||
|
"sizes": "120x120",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"orientation": "portrait-primary",
|
||||||
|
"theme_color": "#ced4da",
|
||||||
|
"background_color": "#000191",
|
||||||
|
"display": "minimal-ui"
|
||||||
|
}
|
BIN
src/fonts/marianne-bold-webfont.woff
Normal file
BIN
src/fonts/marianne-bold-webfont.woff2
Normal file
BIN
src/fonts/marianne-regular-webfont.woff
Normal file
BIN
src/fonts/marianne-regular-webfont.woff2
Normal file
143
src/form-data.json
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
[
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "firstname",
|
||||||
|
"type": "text",
|
||||||
|
"contentType": "firstname",
|
||||||
|
"label": "Prénom",
|
||||||
|
"autocomplete": "given-name",
|
||||||
|
"placeholder": "Camille"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "lastname",
|
||||||
|
"type": "text",
|
||||||
|
"contentType": "lastname",
|
||||||
|
"label": "Nom",
|
||||||
|
"autocomplete": "family-name",
|
||||||
|
"placeholder": "Dupont"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "birthday",
|
||||||
|
"type": "text",
|
||||||
|
"contentType": "birthday",
|
||||||
|
"label": "Date de naissance",
|
||||||
|
"autocomplete": "birthday",
|
||||||
|
"pattern": "^([0][1-9]|[1-2][0-9]|30|31)\/([0][1-9]|10|11|12)\/(19[0-9][0-9]|20[0-1][0-9]|2020)",
|
||||||
|
"maxlength": 10,
|
||||||
|
"placeholder": "01/01/1970"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "placeofbirth",
|
||||||
|
"type": "text",
|
||||||
|
"contentType": "cityofbirth",
|
||||||
|
"label": "Lieu de naissance",
|
||||||
|
"autocomplete": "off",
|
||||||
|
"placeholder": "Paris"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "address",
|
||||||
|
"type": "text",
|
||||||
|
"contentType": "address",
|
||||||
|
"label": "Adresse",
|
||||||
|
"autocomplete": "adress-line1",
|
||||||
|
"placeholder": "999 avenue de France"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "city",
|
||||||
|
"type": "text",
|
||||||
|
"contentType": "city",
|
||||||
|
"label": "Ville",
|
||||||
|
"autocomplete": "address-level2",
|
||||||
|
"placeholder": "Paris"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "zipcode",
|
||||||
|
"type": "number",
|
||||||
|
"contentType": "zipcode",
|
||||||
|
"label": "Code Postal",
|
||||||
|
"autocomplete": "postal-code",
|
||||||
|
"placeholder": "75001",
|
||||||
|
"inputmode": "numeric",
|
||||||
|
"pattern": "[0-9]{5}",
|
||||||
|
"min": 1000,
|
||||||
|
"max": 99999,
|
||||||
|
"minlength":4,
|
||||||
|
"maxlength":5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "creationDate",
|
||||||
|
"type": "date",
|
||||||
|
"contentType": "creationDate",
|
||||||
|
"label": "Date de création",
|
||||||
|
"isHidden": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "creationHour",
|
||||||
|
"type": "time",
|
||||||
|
"contentType": "creationHour",
|
||||||
|
"label": "Heure de création",
|
||||||
|
"isHidden": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "datesortie",
|
||||||
|
"type": "date",
|
||||||
|
"contentType": "datesortie",
|
||||||
|
"label": "Date de sortie",
|
||||||
|
"pattern": "^([0][1-9]|[1-2][0-9]|30|31)\/([0][1-9]|10|11|12)\/(19[0-9][0-9]|20[0-1][0-9]|2020)",
|
||||||
|
"autocomplete": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "heuresortie",
|
||||||
|
"type": "time",
|
||||||
|
"contentType": "heuresortie",
|
||||||
|
"label": "Heure de sortie",
|
||||||
|
"autocomplete": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "reason",
|
||||||
|
"type": "list",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"code": "travail",
|
||||||
|
"label": "Déplacements entre le domicile et le lieu d'exercice de l'activité professionnelle ou les déplacements professionnels ne pouvant être différés <a class=\"footnote\" id=\"footnote2\" href=\"#footnote2\">[2]</a> ;"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "achats",
|
||||||
|
"label": "Déplacements pour effectuer des achats de fournitures nécessaires à l'activité professionnelle, des achats de première nécessité <a class=\"footnote\" id=\"footnote3\" href=\"#footnote3\">[3]</a> dans des établissements dont les activités demeurent autorisées (liste sur gouvernement.fr) et les livraisons à domicile ;"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "sante",
|
||||||
|
"label": "Consultations et soins ne pouvant être assurés à distance et ne pouvant être différés et l’achat de médicaments ;"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "famille",
|
||||||
|
"label": "Déplacements pour motif familial impérieux, pour l'assistance aux personnes vulnérables et précaires ou la garde d'enfants ;"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "handicap",
|
||||||
|
"label": "Déplacements des personnes en situation de handicap et de leur accompagnant ;"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "sport_animaux",
|
||||||
|
"label": "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 ."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "convocation",
|
||||||
|
"label": " Convocation judiciaire ou administrative et rendez-vous dans un service public ;"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "missions",
|
||||||
|
"label": " Participation à des missions d'intérêt général sur demande de l'autorité administrative ;"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "enfants",
|
||||||
|
"label": "Déplacement pour chercher les enfants à l’école et à l’occasion de leurs activités périscolaires ;"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
14
src/form.html
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Essai génération du formulaire</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="wrapper">
|
||||||
|
<form id="form-profile" accept-charset="UTF-8"></form>
|
||||||
|
</div>
|
||||||
|
<script src="./js/form.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
12
src/grid.html
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="content-type" content="text/html; charset=utf-8" /> </head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<label for="file">Sélectionner le pdf</label>
|
||||||
|
<input type="file" id="file" name="file">
|
||||||
|
|
||||||
|
<script src="./js/pdf-grid-helper.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
116
src/index.html
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
<!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 la déclaration de 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éclaration, 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 la déclaration de 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/attestation-couvre-feu-covid-19/" />
|
||||||
|
<meta property="og:url" content="https://media.interieur.gouv.fr/attestation-couvre-feu-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 la déclaration de 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>Attestation de déplacement dérogatoire</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header role="banner" class="wrapper">
|
||||||
|
<picture>
|
||||||
|
<source srcset="/MIN_Interieur_RVB_dark.svg" media="(prefers-color-scheme: dark)">
|
||||||
|
<img src="/MIN_Interieur_RVB.svg" alt="Ministère de l'intérieur. Liberté, égalité, fraternité."
|
||||||
|
class="logo" role="presentation" aria-hidden="true">
|
||||||
|
</picture>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h1 class="flex flex-wrap">
|
||||||
|
<span class="covid-title">
|
||||||
|
COVID-19
|
||||||
|
</span>
|
||||||
|
<span class="covid-subtitle">
|
||||||
|
Attestation de déplacement dérogatoire
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
<p class="text-desc">
|
||||||
|
En application des mesures générales nécessaires pour faire face à l’épidémie de covid-19
|
||||||
|
dans le cadre de l’état d’urgence sanitaire
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main role="main">
|
||||||
|
<p
|
||||||
|
class="alert alert-danger d-none"
|
||||||
|
role="alert"
|
||||||
|
id="alert-facebook"
|
||||||
|
></p>
|
||||||
|
|
||||||
|
<div class="wrapper">
|
||||||
|
<form id="form-profile" accept-charset="UTF-8"></form>
|
||||||
|
<p class="text-center mt-5">
|
||||||
|
<button type="button" id="generate-btn" class="btn btn-primary btn-attestation"><span ><i class="fa fa-file-pdf inline-block mr-1"></i> 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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="">
|
||||||
|
<p id="footnotes">
|
||||||
|
<span id="footnote1">
|
||||||
|
[1] Les personnes souhaitant bénéficier de l'une de ces exceptions doivent se munir s'il y a lieu, lors de leurs déplacements hors de leur domicile, d'un document leur permettant de justifier que le déplacement considéré entre dans le champ de l'une de ces exceptions.
|
||||||
|
</span><br>
|
||||||
|
<span id="footnote2">
|
||||||
|
[2] A utiliser par les travailleurs non salariés, lorsqu'ils ne peuvent disposer d'un justificatif de déplacement établi par leur employeur.
|
||||||
|
</span><br>
|
||||||
|
<span id="footnote3">
|
||||||
|
[3] Y compris les acquisitions à titre gratuit (distribution de denrées alimentaires...) et les déplacements liés à la perception de prestations sociales et au retrait d'espèces.
|
||||||
|
</span><br>
|
||||||
|
</p>
|
||||||
|
<p class="github">
|
||||||
|
Le code source de ce service est consultable sur <a href="https://github.com/LAB-MI/attestation-deplacement-derogatoire-q4-2020" class="github-link">GitHub</a>.
|
||||||
|
</p>
|
||||||
|
<p class="label-mi">
|
||||||
|
Ministère de l'Intérieur - DNUM - SDIT
|
||||||
|
</p>
|
||||||
|
<picture class="center">
|
||||||
|
<source srcset="/logo_dnum_dark.svg" media="(prefers-color-scheme: dark)">
|
||||||
|
<img class="center" src="/logo_dnum.svg" alt="logo dnum">
|
||||||
|
</picture>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<footer role="contentinfo" class="main-footer">
|
||||||
|
<div class="footer-links">
|
||||||
|
<a href="./confidentialite.html" title="Confidentialité - nouvelle page" target="_blank" class="footer-line 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-line 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-line footer-link">Informations du gouvernement sur le Covid-19</a>
|
||||||
|
<div class="footer-line" >Plus d’infos au <a class="num-08" href="tel:0800130000" title="Numéro vert - appel gratuit depuis un poste fixe en France">0 800 130 000</a></div>
|
||||||
|
<p class="footer-line" id="version"></p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<div class="alert alert-info d-none" id="update-alert">
|
||||||
|
Une nouvelle version est disponible. Cliquer sur le bouton pour l'obtenir.
|
||||||
|
<p class="text-right">
|
||||||
|
<button id="reload-btn" class="btn btn-info">Mettre à jour</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="./js/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
35
src/js/check-updates.js
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { $ } from './dom-utils'
|
||||||
|
|
||||||
|
// Ce fichier est généré au build par le plugin parcel-plugin-sw-cache
|
||||||
|
const swName = '../sw.js'
|
||||||
|
window.isUpdateAvailable = new Promise(function (resolve, reject) {
|
||||||
|
// lazy way of disabling service workers while developing
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker
|
||||||
|
.register(swName)
|
||||||
|
.then((registration) => {
|
||||||
|
registration.onupdatefound = () => {
|
||||||
|
const installingWorker = registration.installing
|
||||||
|
installingWorker.onstatechange = () => {
|
||||||
|
switch (installingWorker.state) {
|
||||||
|
case 'installed':
|
||||||
|
if (navigator.serviceWorker.controller) {
|
||||||
|
// new update available
|
||||||
|
resolve(true)
|
||||||
|
} else {
|
||||||
|
// no update available
|
||||||
|
resolve(false)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => console.error('[SW ERROR]', err)) // eslint-disable-line no-console
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
window.isUpdateAvailable.then((isAvailable) => {
|
||||||
|
$('#reload-btn').addEventListener('click', () => window.location.reload())
|
||||||
|
$('#update-alert').classList.remove('d-none')
|
||||||
|
})
|
2
src/js/confidentialite.js
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
import 'bootstrap/dist/css/bootstrap.min.css'
|
||||||
|
import '../css/main.css'
|
43
src/js/dom-utils.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
export const $ = (...args) => document.querySelector(...args)
|
||||||
|
export const $$ = (...args) => [...document.querySelectorAll(...args)]
|
||||||
|
|
||||||
|
const plainAttributes = [
|
||||||
|
'for',
|
||||||
|
'inputmode',
|
||||||
|
'minlength',
|
||||||
|
'maxlength',
|
||||||
|
'min',
|
||||||
|
'max',
|
||||||
|
'pattern',
|
||||||
|
]
|
||||||
|
|
||||||
|
export const createElement = (tag, attrs) => {
|
||||||
|
const el = document.createElement(tag)
|
||||||
|
plainAttributes.forEach(plainAttr => {
|
||||||
|
if (attrs && plainAttr in attrs && attrs[plainAttr]) {
|
||||||
|
el.setAttribute(plainAttr, attrs[plainAttr])
|
||||||
|
}
|
||||||
|
if (attrs) {
|
||||||
|
delete attrs[plainAttr]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
Object.assign(el, attrs)
|
||||||
|
return el
|
||||||
|
}
|
||||||
|
|
||||||
|
export const appendTo = el => domNodes => {
|
||||||
|
if (domNodes[Symbol.iterator]) {
|
||||||
|
el.append(...domNodes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
el.append(domNodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function downloadBlob (blob, fileName) {
|
||||||
|
const link = createElement('a')
|
||||||
|
const url = URL.createObjectURL(blob)
|
||||||
|
link.href = url
|
||||||
|
link.download = fileName
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
}
|
15
src/js/facebook-util.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { $ } from './dom-utils'
|
||||||
|
|
||||||
|
export function warnFacebookBrowserUserIfNecessary () {
|
||||||
|
if (isFacebookBrowser()) {
|
||||||
|
const alertFacebookElt = $('#alert-facebook')
|
||||||
|
alertFacebookElt.value =
|
||||||
|
"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."
|
||||||
|
alertFacebookElt.classList.remove('d-none')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// see: https://stackoverflow.com/a/32348687/1513045
|
||||||
|
function isFacebookBrowser () {
|
||||||
|
const ua = navigator.userAgent || navigator.vendor || window.opera
|
||||||
|
return ua.includes('FBAN') || ua.includes('FBAV')
|
||||||
|
}
|
161
src/js/form-util.js
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
import { $, $$, downloadBlob } from './dom-utils'
|
||||||
|
import { addSlash, getFormattedDate } from './util'
|
||||||
|
import pdfBase from '../certificate.pdf'
|
||||||
|
import { generatePdf } from './pdf-util'
|
||||||
|
|
||||||
|
const conditions = {
|
||||||
|
'#field-firstname': {
|
||||||
|
length: 1,
|
||||||
|
},
|
||||||
|
'#field-lastname': {
|
||||||
|
length: 1,
|
||||||
|
},
|
||||||
|
'#field-birthday': {
|
||||||
|
pattern: /^([0][1-9]|[1-2][0-9]|30|31)\/([0][1-9]|10|11|12)\/(19[0-9][0-9]|20[0-1][0-9]|2020)/g,
|
||||||
|
},
|
||||||
|
'#field-placeofbirth': {
|
||||||
|
length: 1,
|
||||||
|
},
|
||||||
|
'#field-address': {
|
||||||
|
length: 1,
|
||||||
|
},
|
||||||
|
'#field-city': {
|
||||||
|
length: 1,
|
||||||
|
},
|
||||||
|
'#field-zipcode': {
|
||||||
|
pattern: /\d{5}/g,
|
||||||
|
},
|
||||||
|
'#field-datesortie': {
|
||||||
|
pattern: /\d{4}-\d{2}-\d{2}/g,
|
||||||
|
},
|
||||||
|
'#field-heuresortie': {
|
||||||
|
pattern: /\d{2}:\d{2}/g,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateAriaFields () {
|
||||||
|
return Object.keys(conditions)
|
||||||
|
.map((field) => {
|
||||||
|
const fieldData = conditions[field]
|
||||||
|
const pattern = fieldData.pattern
|
||||||
|
const length = fieldData.length
|
||||||
|
const isInvalidPattern = pattern && !$(field).value.match(pattern)
|
||||||
|
const isInvalidLength = length && !$(field).value.length
|
||||||
|
|
||||||
|
const isInvalid = !!(isInvalidPattern || isInvalidLength)
|
||||||
|
|
||||||
|
$(field).setAttribute('aria-invalid', isInvalid)
|
||||||
|
if (isInvalid) {
|
||||||
|
$(field).focus()
|
||||||
|
}
|
||||||
|
return isInvalid
|
||||||
|
})
|
||||||
|
.includes(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setReleaseDateTime (releaseDateInput) {
|
||||||
|
const loadedDate = new Date()
|
||||||
|
releaseDateInput.value = getFormattedDate(loadedDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getProfile (formInputs) {
|
||||||
|
const fields = {}
|
||||||
|
for (const field of formInputs) {
|
||||||
|
let value = field.value
|
||||||
|
if (field.id === 'field-datesortie') {
|
||||||
|
const dateSortie = field.value.split('-')
|
||||||
|
value = `${dateSortie[2]}/${dateSortie[1]}/${dateSortie[0]}`
|
||||||
|
}
|
||||||
|
fields[field.id.substring('field-'.length)] = value
|
||||||
|
}
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getReasons (reasonInputs) {
|
||||||
|
const reasons = reasonInputs
|
||||||
|
.filter(input => input.checked)
|
||||||
|
.map(input => input.value).join(', ')
|
||||||
|
return reasons
|
||||||
|
}
|
||||||
|
|
||||||
|
export function prepareInputs (formInputs, reasonInputs, reasonFieldset, reasonAlert, snackbar) {
|
||||||
|
formInputs.forEach((input) => {
|
||||||
|
const exempleElt = input.parentNode.parentNode.querySelector('.exemple')
|
||||||
|
const validitySpan = input.parentNode.parentNode.querySelector('.validity')
|
||||||
|
if (input.placeholder && exempleElt) {
|
||||||
|
input.addEventListener('input', (event) => {
|
||||||
|
if (input.value) {
|
||||||
|
exempleElt.innerHTML = 'ex. : ' + input.placeholder
|
||||||
|
validitySpan.removeAttribute('hidden')
|
||||||
|
} else {
|
||||||
|
exempleElt.innerHTML = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
$('#field-birthday').addEventListener('keyup', function (event) {
|
||||||
|
event.preventDefault()
|
||||||
|
const input = event.target
|
||||||
|
const key = event.keyCode || event.charCode
|
||||||
|
if (key !== 8 && key !== 46) {
|
||||||
|
input.value = addSlash(input.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
reasonInputs.forEach(radioInput => {
|
||||||
|
radioInput.addEventListener('change', function (event) {
|
||||||
|
const isInError = reasonInputs.every(input => !input.checked)
|
||||||
|
reasonFieldset.classList.toggle('fieldset-error', isInError)
|
||||||
|
reasonAlert.classList.toggle('hidden', !isInError)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
$('#generate-btn').addEventListener('click', async (event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
const reasons = getReasons(reasonInputs)
|
||||||
|
if (!reasons) {
|
||||||
|
reasonFieldset.classList.add('fieldset-error')
|
||||||
|
reasonAlert.classList.remove('hidden')
|
||||||
|
reasonFieldset.scrollIntoView && reasonFieldset.scrollIntoView()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const invalid = validateAriaFields()
|
||||||
|
if (invalid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(getProfile(formInputs), reasons)
|
||||||
|
|
||||||
|
const pdfBlob = await generatePdf(getProfile(formInputs), reasons, pdfBase)
|
||||||
|
|
||||||
|
const creationInstant = new Date()
|
||||||
|
const creationDate = creationInstant.toLocaleDateString('fr-CA')
|
||||||
|
const creationHour = creationInstant
|
||||||
|
.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' })
|
||||||
|
.replace(':', '-')
|
||||||
|
|
||||||
|
downloadBlob(pdfBlob, `attestation-${creationDate}_${creationHour}.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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function prepareForm () {
|
||||||
|
const formInputs = $$('#form-profile input')
|
||||||
|
const snackbar = $('#snackbar')
|
||||||
|
const reasonInputs = [...$$('input[name="field-reason"]')]
|
||||||
|
const reasonFieldset = $('#reason-fieldset')
|
||||||
|
const reasonAlert = reasonFieldset.querySelector('.msg-alert')
|
||||||
|
const releaseDateInput = $('#field-datesortie')
|
||||||
|
setReleaseDateTime(releaseDateInput)
|
||||||
|
prepareInputs(formInputs, reasonInputs, reasonFieldset, reasonAlert, snackbar)
|
||||||
|
}
|
159
src/js/form.js
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
import 'bootstrap/dist/css/bootstrap.min.css'
|
||||||
|
|
||||||
|
import '../css/main.css'
|
||||||
|
|
||||||
|
import formData from '../form-data.json'
|
||||||
|
|
||||||
|
import { $, appendTo, createElement } from './dom-utils'
|
||||||
|
|
||||||
|
const createTitle = () => {
|
||||||
|
const h2 = createElement('h2', { className: 'titre-2', innerHTML: 'Remplissez en ligne votre déclaration numérique : ' })
|
||||||
|
const p = createElement('p', { className: 'msg-info', innerHTML: 'Tous les champs sont obligatoires.' })
|
||||||
|
return [h2, p]
|
||||||
|
}
|
||||||
|
// createElement('div', { className: 'form-group' })
|
||||||
|
|
||||||
|
const createFormGroup = ({
|
||||||
|
autocomplete = false,
|
||||||
|
autofocus = false,
|
||||||
|
inputmode,
|
||||||
|
label,
|
||||||
|
max,
|
||||||
|
min,
|
||||||
|
maxlength,
|
||||||
|
minlength,
|
||||||
|
name,
|
||||||
|
pattern,
|
||||||
|
placeholder = '',
|
||||||
|
type = 'text',
|
||||||
|
}) => {
|
||||||
|
const formGroup = createElement('div', { className: 'form-group' })
|
||||||
|
const labelAttrs = {
|
||||||
|
for: `field-${name}`,
|
||||||
|
id: `field-${name}-label`,
|
||||||
|
innerHTML: label,
|
||||||
|
}
|
||||||
|
const labelEl = createElement('label', labelAttrs)
|
||||||
|
|
||||||
|
const inputGroup = createElement('div', { className: 'input-group align-items-center' })
|
||||||
|
const inputAttrs = {
|
||||||
|
autocomplete,
|
||||||
|
autofocus,
|
||||||
|
className: 'form-control',
|
||||||
|
id: `field-${name}`,
|
||||||
|
inputmode,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
minlength,
|
||||||
|
maxlength,
|
||||||
|
name,
|
||||||
|
pattern,
|
||||||
|
placeholder,
|
||||||
|
required: true,
|
||||||
|
type,
|
||||||
|
}
|
||||||
|
|
||||||
|
const input = createElement('input', inputAttrs)
|
||||||
|
|
||||||
|
const validityAttrs = {
|
||||||
|
className: 'validity',
|
||||||
|
}
|
||||||
|
const validity = createElement('span', validityAttrs)
|
||||||
|
|
||||||
|
const appendToFormGroup = appendTo(formGroup)
|
||||||
|
appendToFormGroup(labelEl)
|
||||||
|
appendToFormGroup(inputGroup)
|
||||||
|
|
||||||
|
const appendToInputGroup = appendTo(inputGroup)
|
||||||
|
appendToInputGroup(input)
|
||||||
|
appendToInputGroup(validity)
|
||||||
|
|
||||||
|
return formGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
const createReasonField = (reasonData) => {
|
||||||
|
const formReasonAttrs = { className: 'form-checkbox align-items-center' }
|
||||||
|
const formReason = createElement('div', formReasonAttrs)
|
||||||
|
const appendToReason = appendTo(formReason)
|
||||||
|
|
||||||
|
const id = `checkbox-${reasonData.code}`
|
||||||
|
const inputReasonAttrs = {
|
||||||
|
className: 'form-check-input',
|
||||||
|
type: 'checkbox',
|
||||||
|
id,
|
||||||
|
name: 'field-reason',
|
||||||
|
value: reasonData.code,
|
||||||
|
}
|
||||||
|
const inputReason = createElement('input', inputReasonAttrs)
|
||||||
|
|
||||||
|
const labelAttrs = { innerHTML: reasonData.label, className: 'form-checkbox-label', for: id }
|
||||||
|
const label = createElement('label', labelAttrs)
|
||||||
|
|
||||||
|
appendToReason([inputReason, label])
|
||||||
|
return formReason
|
||||||
|
}
|
||||||
|
|
||||||
|
const createReasonFieldset = (reasonsData) => {
|
||||||
|
const fieldsetAttrs = {
|
||||||
|
id: 'reason-fieldset',
|
||||||
|
className: 'fieldset',
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldset = createElement('fieldset', fieldsetAttrs)
|
||||||
|
const appendToFieldset = appendTo(fieldset)
|
||||||
|
|
||||||
|
const legendAttrs = {
|
||||||
|
className: 'legend titre 3 ',
|
||||||
|
innerHTML: 'Choisissez un motif de déplacement',
|
||||||
|
}
|
||||||
|
const legend = createElement('legend', legendAttrs)
|
||||||
|
|
||||||
|
const textAlertAttrs = { className: 'msg-alert hidden', innerHTML: 'Veuillez choisir un motif' }
|
||||||
|
const textAlert = createElement('p', textAlertAttrs)
|
||||||
|
|
||||||
|
const textSubscribeReasonAttrs = {
|
||||||
|
innerHTML: `certifie que mon déplacement est lié au motif suivant (cocher la case) autorisé en application des
|
||||||
|
mesures générales nécessaires pour faire face à l'épidémie de Covid19 dans le cadre de l'état
|
||||||
|
d'urgence sanitaire <a class="footnote" id="footnote1" href="#footnote1">[1]</a> :`,
|
||||||
|
}
|
||||||
|
|
||||||
|
const textSubscribeReason = createElement('p', textSubscribeReasonAttrs)
|
||||||
|
|
||||||
|
const reasonsFields = reasonsData.items.map(createReasonField)
|
||||||
|
|
||||||
|
appendToFieldset([legend, textAlert, textSubscribeReason, ...reasonsFields])
|
||||||
|
// Créer un form-checkbox par motif
|
||||||
|
return fieldset
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createForm () {
|
||||||
|
const form = $('#form-profile')
|
||||||
|
// Évite de recréer le formulaire s'il est déjà créé par react-snap (ou un autre outil de prerender)
|
||||||
|
if (form.innerHTML !== '') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const appendToForm = appendTo(form)
|
||||||
|
|
||||||
|
const formFirstPart = formData
|
||||||
|
.flat(1)
|
||||||
|
.filter(field => field.key !== 'reason')
|
||||||
|
.filter(field => !field.isHidden)
|
||||||
|
.map((field,
|
||||||
|
index) => {
|
||||||
|
const formGroup = createFormGroup({
|
||||||
|
autofocus: index === 0,
|
||||||
|
...field,
|
||||||
|
name: field.key,
|
||||||
|
})
|
||||||
|
|
||||||
|
return formGroup
|
||||||
|
})
|
||||||
|
|
||||||
|
const reasonsData = formData
|
||||||
|
.flat(1)
|
||||||
|
.find(field => field.key === 'reason')
|
||||||
|
|
||||||
|
const reasonFieldset = createReasonFieldset(reasonsData)
|
||||||
|
appendToForm([...createTitle(), ...formFirstPart, reasonFieldset])
|
||||||
|
}
|
6
src/js/icons.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { library, dom } from '@fortawesome/fontawesome-svg-core'
|
||||||
|
import { faFilePdf } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
|
library.add(faFilePdf)
|
||||||
|
|
||||||
|
dom.watch()
|
15
src/js/main.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import 'bootstrap/dist/css/bootstrap.min.css'
|
||||||
|
|
||||||
|
import '../css/main.css'
|
||||||
|
|
||||||
|
import './icons'
|
||||||
|
import './check-updates'
|
||||||
|
import { prepareForm } from './form-util'
|
||||||
|
import { warnFacebookBrowserUserIfNecessary } from './facebook-util'
|
||||||
|
import { addVersion } from './util'
|
||||||
|
import { createForm } from './form'
|
||||||
|
|
||||||
|
warnFacebookBrowserUserIfNecessary()
|
||||||
|
createForm()
|
||||||
|
prepareForm()
|
||||||
|
addVersion(process.env.VERSION)
|
67
src/js/pdf-grid-helper.js
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import { PDFDocument, StandardFonts, rgb } from 'pdf-lib'
|
||||||
|
|
||||||
|
export async function generatePdf (e) {
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
const pdfBase = document.getElementById('file').files[0]
|
||||||
|
const buffer = await pdfBase.arrayBuffer()
|
||||||
|
|
||||||
|
const pdfDoc = await PDFDocument.load(buffer)
|
||||||
|
|
||||||
|
const pages = pdfDoc.getPages()
|
||||||
|
|
||||||
|
const page1 = pages[0]
|
||||||
|
|
||||||
|
const font = await pdfDoc.embedFont(StandardFonts.Helvetica)
|
||||||
|
const drawText = (text, x, y, size = 11) => {
|
||||||
|
page1.drawText(text, { x, y, size, font })
|
||||||
|
}
|
||||||
|
|
||||||
|
let x
|
||||||
|
let y
|
||||||
|
for (x = 25; x < 1000; x += 25) {
|
||||||
|
for (y = 25; y < 1000; y += 25) {
|
||||||
|
drawText('.', {
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
size: 11,
|
||||||
|
font: font,
|
||||||
|
color: rgb(0.95, 0.1, 0.1),
|
||||||
|
})
|
||||||
|
drawText(`${x}`, {
|
||||||
|
x: x + 3,
|
||||||
|
y: y,
|
||||||
|
size: 7,
|
||||||
|
font: font,
|
||||||
|
color: rgb(0, 0, 0),
|
||||||
|
})
|
||||||
|
drawText(`${y}`, {
|
||||||
|
x: x + 3,
|
||||||
|
y: y - 6,
|
||||||
|
size: 7,
|
||||||
|
font: font,
|
||||||
|
color: rgb(0, 0, 0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pdfDoc.addPage()
|
||||||
|
|
||||||
|
const pdfBytes = await pdfDoc.save()
|
||||||
|
|
||||||
|
// Trigger the browser to download the PDF document
|
||||||
|
|
||||||
|
const pdfAsBlob = new Blob([pdfBytes], { type: 'application/pdf' })
|
||||||
|
downloadBlob(pdfAsBlob, 'grid.pdf', 'application/pdf')
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadBlob (blob, fileName) {
|
||||||
|
const link = document.createElement('a')
|
||||||
|
const url = URL.createObjectURL(blob)
|
||||||
|
link.href = url
|
||||||
|
link.download = fileName
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelector('#file').addEventListener('change', generatePdf)
|
140
src/js/pdf-util.js
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
import { generateQR } from './util'
|
||||||
|
import { PDFDocument, StandardFonts } from 'pdf-lib'
|
||||||
|
|
||||||
|
const ys = {
|
||||||
|
travail: 578,
|
||||||
|
achats: 533,
|
||||||
|
sante: 477,
|
||||||
|
famille: 435,
|
||||||
|
handicap: 396,
|
||||||
|
sport_animaux: 358,
|
||||||
|
convocation: 295,
|
||||||
|
missions: 255,
|
||||||
|
enfants: 211,
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generatePdf (profile, reasons, pdfBase) {
|
||||||
|
const creationInstant = new Date()
|
||||||
|
const creationDate = creationInstant.toLocaleDateString('fr-FR')
|
||||||
|
const creationHour = creationInstant
|
||||||
|
.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' })
|
||||||
|
.replace(':', 'h')
|
||||||
|
|
||||||
|
const {
|
||||||
|
lastname,
|
||||||
|
firstname,
|
||||||
|
birthday,
|
||||||
|
placeofbirth,
|
||||||
|
address,
|
||||||
|
zipcode,
|
||||||
|
city,
|
||||||
|
datesortie,
|
||||||
|
heuresortie,
|
||||||
|
} = profile
|
||||||
|
|
||||||
|
const data = [
|
||||||
|
`Cree le: ${creationDate} a ${creationHour}`,
|
||||||
|
`Nom: ${lastname}`,
|
||||||
|
`Prenom: ${firstname}`,
|
||||||
|
`Naissance: ${birthday} a ${placeofbirth}`,
|
||||||
|
`Adresse: ${address} ${zipcode} ${city}`,
|
||||||
|
`Sortie: ${datesortie} a ${heuresortie}`,
|
||||||
|
`Motifs: ${reasons}`,
|
||||||
|
].join(';\n ')
|
||||||
|
|
||||||
|
const existingPdfBytes = await fetch(pdfBase).then((res) => res.arrayBuffer())
|
||||||
|
|
||||||
|
const pdfDoc = await PDFDocument.load(existingPdfBytes)
|
||||||
|
|
||||||
|
// set pdf metadata
|
||||||
|
pdfDoc.setTitle('COVID-19 - Déclaration de déplacement')
|
||||||
|
pdfDoc.setSubject('Attestation de déplacement dérogatoire')
|
||||||
|
pdfDoc.setKeywords([
|
||||||
|
'covid19',
|
||||||
|
'covid-19',
|
||||||
|
'attestation',
|
||||||
|
'déclaration',
|
||||||
|
'déplacement',
|
||||||
|
'officielle',
|
||||||
|
'gouvernement',
|
||||||
|
])
|
||||||
|
pdfDoc.setProducer('DNUM/SDIT')
|
||||||
|
pdfDoc.setCreator('')
|
||||||
|
pdfDoc.setAuthor("Ministère de l'intérieur")
|
||||||
|
|
||||||
|
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}`, 119, 696)
|
||||||
|
drawText(birthday, 119, 674)
|
||||||
|
drawText(placeofbirth, 297, 674)
|
||||||
|
drawText(`${address} ${zipcode} ${city}`, 133, 652)
|
||||||
|
|
||||||
|
reasons
|
||||||
|
.split(', ')
|
||||||
|
.forEach(reason => {
|
||||||
|
drawText('x', 84, ys[reason], 18)
|
||||||
|
})
|
||||||
|
|
||||||
|
let locationSize = getIdealFontSize(font, profile.city, 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.city, 105, 177, locationSize)
|
||||||
|
drawText(`${profile.datesortie}`, 91, 153, 11)
|
||||||
|
drawText(`${profile.heuresortie}`, 264, 153, 11)
|
||||||
|
|
||||||
|
// const shortCreationDate = `${creationDate.split('/')[0]}/${
|
||||||
|
// creationDate.split('/')[1]
|
||||||
|
// }`
|
||||||
|
// drawText(shortCreationDate, 314, 189, locationSize)
|
||||||
|
|
||||||
|
// // Date création
|
||||||
|
// drawText('Date de création:', 479, 130, 6)
|
||||||
|
// drawText(`${creationDate} à ${creationHour}`, 470, 124, 6)
|
||||||
|
|
||||||
|
const generatedQR = await generateQR(data)
|
||||||
|
|
||||||
|
const qrImage = await pdfDoc.embedPng(generatedQR)
|
||||||
|
|
||||||
|
page1.drawImage(qrImage, {
|
||||||
|
x: page1.getWidth() - 156,
|
||||||
|
y: 100,
|
||||||
|
width: 92,
|
||||||
|
height: 92,
|
||||||
|
})
|
||||||
|
|
||||||
|
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 getIdealFontSize (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
|
||||||
|
}
|
35
src/js/util.js
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import QRCode from 'qrcode'
|
||||||
|
|
||||||
|
export function generateQR (text) {
|
||||||
|
const opts = {
|
||||||
|
errorCorrectionLevel: 'M',
|
||||||
|
type: 'image/png',
|
||||||
|
quality: 0.92,
|
||||||
|
margin: 1,
|
||||||
|
}
|
||||||
|
return QRCode.toDataURL(text, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pad2Zero (str) {
|
||||||
|
return String(str).padStart(2, '0')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFormattedDate (date) {
|
||||||
|
const year = date.getFullYear()
|
||||||
|
const month = pad2Zero(date.getMonth() + 1) // Les mois commencent à 0
|
||||||
|
const day = pad2Zero(date.getDate())
|
||||||
|
return `${year}-${month}-${day}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addSlash (str) {
|
||||||
|
return str
|
||||||
|
.replace(/^(\d{2})$/g, '$1/')
|
||||||
|
.replace(/^(\d{2})\/(\d{2})$/g, '$1/$2/')
|
||||||
|
.replace(/\/\//g, '/')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addVersion (version) {
|
||||||
|
document.getElementById(
|
||||||
|
'version',
|
||||||
|
).innerHTML = `${new Date().getFullYear()} - ${version}`
|
||||||
|
}
|
1
src/logo_dnum.svg
Normal file
After Width: | Height: | Size: 30 KiB |
1
src/logo_dnum_dark.svg
Normal file
After Width: | Height: | Size: 23 KiB |
1
src/robots.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Sitemap: https://media.interieur.gouv.fr/attestation-couvre-feu-covid-19/sitemap.xml
|
27
src/sitemap.xml
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<urlset
|
||||||
|
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
|
||||||
|
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
|
||||||
|
<!-- created with Free Online Sitemap Generator www.xml-sitemaps.com -->
|
||||||
|
|
||||||
|
|
||||||
|
<url>
|
||||||
|
<loc>https://media.interieur.gouv.fr/attestation-couvre-feu-covid-19/</loc>
|
||||||
|
<lastmod>2020-04-06T04:22:03+00:00</lastmod>
|
||||||
|
<priority>1.00</priority>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://media.interieur.gouv.fr/attestation-couvre-feu-covid-19/confidentialite.html</loc>
|
||||||
|
<lastmod>2020-04-06T04:22:03+00:00</lastmod>
|
||||||
|
<priority>0.80</priority>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://media.interieur.gouv.fr/attestation-couvre-feu-covid-19/index.html</loc>
|
||||||
|
<lastmod>2020-04-06T04:22:03+00:00</lastmod>
|
||||||
|
<priority>0.64</priority>
|
||||||
|
</url>
|
||||||
|
|
||||||
|
|
||||||
|
</urlset>
|