diff --git a/package-lock.json b/package-lock.json
index 9822cb629e38a5af4ca787050e0f9991e1e0136c..b27b35d195dff37546bccb6b01fed520938f9474 100755
--- a/package-lock.json
+++ b/package-lock.json
@@ -6,7 +6,7 @@
     "": {
       "hasInstallScript": true,
       "dependencies": {
-        "g1lib": "^3.5.5"
+        "g1lib": "^3.5.7"
       },
       "devDependencies": {
         "@fortawesome/fontawesome-free": "^5.15.2",
@@ -1004,9 +1004,9 @@
       }
     },
     "node_modules/g1lib": {
-      "version": "3.5.5",
-      "resolved": "https://registry.npmjs.org/g1lib/-/g1lib-3.5.5.tgz",
-      "integrity": "sha512-3JnCfMOc2ynTgCv4MsoeEAT9gGkwYMehFULOx6kMNY0tlPu1ytMI40UtcFPjCMUxnAXhBixtUTgbf8u1UxlgMA==",
+      "version": "3.5.7",
+      "resolved": "https://registry.npmjs.org/g1lib/-/g1lib-3.5.7.tgz",
+      "integrity": "sha512-gxCMQvn4wl8huRwUiW2zc2Bvul2ch+7VB2dHcbJkME1TLivpx/azljA3OGnhhfMJG93mGZvHoBxCFnqOqmqb1A==",
       "funding": {
         "url": "https://git.duniter.org/libs/g1lib.js"
       }
@@ -4456,9 +4456,9 @@
       "dev": true
     },
     "g1lib": {
-      "version": "3.5.5",
-      "resolved": "https://registry.npmjs.org/g1lib/-/g1lib-3.5.5.tgz",
-      "integrity": "sha512-3JnCfMOc2ynTgCv4MsoeEAT9gGkwYMehFULOx6kMNY0tlPu1ytMI40UtcFPjCMUxnAXhBixtUTgbf8u1UxlgMA=="
+      "version": "3.5.7",
+      "resolved": "https://registry.npmjs.org/g1lib/-/g1lib-3.5.7.tgz",
+      "integrity": "sha512-gxCMQvn4wl8huRwUiW2zc2Bvul2ch+7VB2dHcbJkME1TLivpx/azljA3OGnhhfMJG93mGZvHoBxCFnqOqmqb1A=="
     },
     "get-caller-file": {
       "version": "2.0.5",
diff --git a/package.json b/package.json
index faa6ae25e21131d114bef832a4c81a7418ceb682..cf2ebb526943a7670b10204f73cb6432af714296 100755
--- a/package.json
+++ b/package.json
@@ -16,7 +16,7 @@
     "build:stylus": "stylus --include-css src/stylus/_all.styl -o generated/public/style.css"
   },
   "dependencies": {
-    "g1lib": "^3.5.5"
+    "g1lib": "^3.5.7"
   },
   "devDependencies": {
     "@fortawesome/fontawesome-free": "^5.15.2",
diff --git a/src/js/logic/dico.mjs b/src/js/logic/dico.mjs
index 90705ca68b0ab9014a8967b0a34c4c7f8223d7f2..bce5fce64487037287e4d334464885bf21e71156 100755
--- a/src/js/logic/dico.mjs
+++ b/src/js/logic/dico.mjs
@@ -2,6 +2,7 @@ import {subscribe} from "./workerManager.mjs";
 import getCombiPerSec from "./speedBench.mjs";
 import {Dictionary} from "g1lib/browser/dictionary.mjs";
 import staticPreview from "./staticPreview.mjs";
+import {cacheScale} from "./search.mjs";
 
 let currentDico;
 let lastData;
@@ -52,6 +53,8 @@ export function getActualData(withSpeed = true) {
       accent:so.accent.value<0?undefined:so.accent.value,
       lowerCase:so.caps.value<0?undefined:so.caps.value,
       leetSpeak:0,
+      save:so.save.value,
+      cacheMax:cacheScale[so.cacheMax.value],
     },
     previewIndex: (window.browseIndex || 0),
   };
diff --git a/src/js/logic/saveResume.mjs b/src/js/logic/saveResume.mjs
new file mode 100644
index 0000000000000000000000000000000000000000..b451e583637fefc7ede3e6aae6febfbf1a2d5f3b
--- /dev/null
+++ b/src/js/logic/saveResume.mjs
@@ -0,0 +1,29 @@
+import {subscribe} from "./workerManager.mjs";
+import {swapKeyValue,sleep} from "./utils.mjs";
+
+export const saveScale = [];
+saveScale[0]=0;
+saveScale[1]=24*60*60;
+saveScale[2]=2*24*60*60;
+saveScale[3]=7*24*60*60;
+export const revertSaveScale = swapKeyValue(saveScale);
+
+const MIN_COMPUTE_TIME_FOR_SAVE = 3600;
+
+export let saveMode=0
+
+async function init(){
+  await sleep(1);
+  subscribe('smartPreview', updateSaveMode);
+  window.searchOptions.save.listen('saveResume',manualUpdate);
+}
+init();
+function manualUpdate(mode){
+  if(mode>=0) saveMode = saveScale[mode];
+}
+function updateSaveMode(data){
+  const smartDico = JSON.parse(data.dico);
+  if(smartDico.config.save<0) saveMode = smartDico.estimateDuration >= MIN_COMPUTE_TIME_FOR_SAVE ? saveScale[2] : 0;
+  else saveMode = saveScale[smartDico.config.save];
+  window.searchOptions.save.setComputed(revertSaveScale[saveMode]);
+}
diff --git a/src/js/logic/search.mjs b/src/js/logic/search.mjs
index 0ada2e7d409eb21c443b831d7b63fddfbfdf91a6..d71ea42d3d0859857f973a449094aeeabedc94b7 100644
--- a/src/js/logic/search.mjs
+++ b/src/js/logic/search.mjs
@@ -10,7 +10,9 @@ export let departTime;
 export let endTime;
 export let found;
 export let cacheActive = true;
-export const CACHE_MAX = 1_000_000;
+export const DEFAULT_CACHE_MAX = 1_000_000;
+export const cacheScale = [];
+
 const globalCache = {};
 
 export function duplicateCount() {
@@ -53,6 +55,7 @@ export function stopSearch() {
 function updateProgress(data) {
   if (data.bench) return;
   tested++;
+  if(found) endSearchTest();
   if (data.publicKey === pubKey) {
     endSearchTest(data);
   } else {
@@ -61,9 +64,9 @@ function updateProgress(data) {
       if (!globalCache[cacheKey]) globalCache[cacheKey] = {};
       globalCache[cacheKey][data.index] = true;
     }
-    if (Object.keys(globalCache).length > CACHE_MAX) cacheActive = false;
+    if (Object.keys(globalCache).length > getDico().config.cacheMax) cacheActive = false;
   }
-  if (tested >= getDico().length) endSearchTest()
+  if (tested >= getDico().length) endSearchTest();
 }
 
 function endSearchTest(somethingFound = undefined) {
diff --git a/src/js/logic/utils.mjs b/src/js/logic/utils.mjs
new file mode 100644
index 0000000000000000000000000000000000000000..8139f094085a459723bd451e3244f2db6d5f6401
--- /dev/null
+++ b/src/js/logic/utils.mjs
@@ -0,0 +1,10 @@
+export function swapKeyValue(object) {
+  const result = {};
+  for (const key in object) {
+    result[object[key]] = key;
+  }
+  return result;
+}
+export async function sleep(ms) {
+  return new Promise((resolve, reject) => setTimeout(resolve, ms));
+}
diff --git a/src/js/logic/workerManager.mjs b/src/js/logic/workerManager.mjs
index 64462bf30e464c9265b65d5502cfa19abe13b861..2a16ab87a734606ee4daad1f1f1a88cacbeadaff 100644
--- a/src/js/logic/workerManager.mjs
+++ b/src/js/logic/workerManager.mjs
@@ -1,3 +1,5 @@
+import {sleep} from "./utils.mjs";
+
 const workers = [];
 const MAX_ALLOWED_IN_QUEUE = 5;
 const subscriptionList = {};
@@ -56,6 +58,3 @@ export function unSubscribe(action,registeredFunc){
   const index = subscriptionList[action].indexOf(registeredFunc);
   subscriptionList[action].splice(index, 1);
 }
-async function sleep(ms) {
-  return new Promise((resolve, reject) => setTimeout(resolve, ms));
-}
diff --git a/src/js/ux/1_pubKey.mjs b/src/js/ux/1_pubKey.mjs
index eec4f61ec3c612e0025f0473ab4e780cc307c042..443e956425399243b8a7060e7b8f24eca44cbdff 100644
--- a/src/js/ux/1_pubKey.mjs
+++ b/src/js/ux/1_pubKey.mjs
@@ -14,8 +14,10 @@ export default function focus_1_pubKey(){
 export let pubKey;
 export function updatePubKey(){
     const pubKeyInputNode = document.getElementById('pubKey');
-    if(location.hash.match(/pubkey=/i) !== null)
-        pubKeyInputNode.value = location.hash.split('=')[1];
+    if(location.hash.match(/pubkey=/i) !== null){
+      pubKeyInputNode.value = location.hash.split('=')[1];
+      return location.hash = location.hash.split('=')[0];
+    }
     pubKey = pubKeyInputNode.value;
     const nextBtn = document.querySelector('.step_1_pubKey a.button');
     const errorArea = document.querySelector('.step_1_pubKey .warning');
diff --git a/src/js/ux/2_inputSecrets.mjs b/src/js/ux/2_inputSecrets.mjs
index 97d63b57818e55d42ddcf52ac71c2060e058cd50..8aba54a506e7109e5c0ae2c385713cf7e2b20dff 100755
--- a/src/js/ux/2_inputSecrets.mjs
+++ b/src/js/ux/2_inputSecrets.mjs
@@ -4,6 +4,9 @@ import {timeUnitStrings} from "pretty-ms";
 import {sendToWorker, subscribe} from "../logic/workerManager.mjs";
 import {getActualData} from "../logic/dico.mjs";
 import RangeOption from "./rangeOption.mjs";
+import {cacheScale, DEFAULT_CACHE_MAX} from "../logic/search.mjs";
+import {saveScale} from "../logic/saveResume.mjs";
+import {swapKeyValue} from "../logic/utils.mjs";
 
 const regexDesc = {
   '-1': 'Syntaxe : auto-sélection',
@@ -28,6 +31,36 @@ const capsDesc = {
   '1': `Majuscules : classiques`,
   '2': `Majuscules : fluctuantes`,
 }
+
+
+const saveDesc = {
+  '-1': `Reprendre : auto-sélection`,
+  '0': `Reprendre : désactivé par sécurité`,
+  '1': `Reprendre : possible pendant 24h`,
+  '2': `Reprendre : possible pendant 48h`,
+  '3': `Reprendre : possible pendant 7j`,
+}
+cacheScale[-1]=DEFAULT_CACHE_MAX;
+cacheScale[0]=0;
+cacheScale[1]=10_000;
+cacheScale[2]=100_000;
+cacheScale[3]=1_000_000;
+cacheScale[4]=10_000_000;
+cacheScale[5]=100_000_000;
+cacheScale[6]=1_000_000_000;
+const revertCacheScale = swapKeyValue(cacheScale.filter((v,i)=> i>=0 ));
+
+const cacheMaxDesc = {
+  '-1': `Cache : auto-sélection`,
+  '0': `Cache : désactivé`,
+  '1': `Cache : ${cacheScale[1].toLocaleString('fr-FR')}`,
+  '2': `Cache : ${cacheScale[2].toLocaleString('fr-FR')}`,
+  '3': `Cache : ${cacheScale[3].toLocaleString('fr-FR')}`,
+  '4': `Cache : ${cacheScale[4].toLocaleString('fr-FR')}`,
+  '5': `Cache : ${cacheScale[5].toLocaleString('fr-FR')}`,
+  '6': `Cache : ${cacheScale[6].toLocaleString('fr-FR')}`,
+//  '6': `Cache : ${new Intl.NumberFormat().format(cacheScale[6])}`,
+}
 export function init() {
   const secretsInputNode = document.getElementById('secrets');
   secretsInputNode.addEventListener('keyup', askPreview);
@@ -38,7 +71,8 @@ export function init() {
     'mirror': new RangeOption('mirror', -1, mirrorDesc, askPreview),
     'accent': new RangeOption('accent', -1, accentDesc, askPreview),
     'caps': new RangeOption('caps', -1, capsDesc, askPreview),
-    //TODO: AutoSave & resume
+    'save': new RangeOption('save', -1, saveDesc, askPreview),
+    'cacheMax': new RangeOption('cacheMax', -1, cacheMaxDesc, askPreview),
   };
   subscribe('fastPreview',updateFastPreviewDisplay);
   subscribe('smartPreview', updateSmartPreviewDisplay);
@@ -93,6 +127,7 @@ function updateSmartPreviewDisplay(data){
   so.accent.setComputed(sdc.accent || 0);
   so.caps.setComputed(sdc.lowerCase || 0);
   so.accent.setComputed(sdc.accent || 0);
+  so.cacheMax.setComputed(revertCacheScale[sdc.cacheMax]);
 
   try{window.displayBrowsePreview();}catch (e){}
 }
diff --git a/src/js/ux/3_inProgress.mjs b/src/js/ux/3_inProgress.mjs
index e954879fa36a8f6e0b8c171c33b4e727ced374dd..294624d4cb4b306d9678555443e85c281aeca80c 100644
--- a/src/js/ux/3_inProgress.mjs
+++ b/src/js/ux/3_inProgress.mjs
@@ -8,7 +8,7 @@ export default function init(){
   setInterval(showProgress,200);
 }
 init();
-const progressColor = getComputedStyle(document.getElementById('startSearch')).color;
+const progressColor = getComputedStyle(document.querySelector('.success h3')).color;
 const progressBar = document.getElementById('progress_bar');
 const bgColor = getComputedStyle(progressBar).backgroundColor;
 const timing = [];
diff --git a/src/js/ux/4_result.mjs b/src/js/ux/4_result.mjs
index 97d8157048862a525561a36be5b2a0a105b09f4e..381f5aba9a297a976aa60d2f021496e558e96551 100644
--- a/src/js/ux/4_result.mjs
+++ b/src/js/ux/4_result.mjs
@@ -21,8 +21,13 @@ export default function showResult(){
   document.getElementById('resSpent').innerHTML = timeFormat(timeSpent);
   document.getElementById('resSpeed').innerHTML = (1000*tested/timeSpent).toFixed(1);
   const duplicates = duplicateCount();
-  document.getElementById('resDuplicate').innerHTML = duplicates;
-  document.getElementById('resDupRatio').innerHTML = (100 * duplicates / tested).toPrecision(3);
+  if (duplicates) {
+    document.getElementById('resDup').classList.remove('hidden');
+    document.getElementById('resDuplicate').innerHTML = duplicates;
+    document.getElementById('resDupRatio').innerHTML = (100 * duplicates / tested).toPrecision(3);
+  } else {
+    document.getElementById('resDup').classList.add('hidden');
+  }
 }
 function timeFormat(milliseconds) {
   return prettyMilliseconds(Math.trunc(milliseconds / 1000)*1000, {
diff --git a/src/js/ux/nav.mjs b/src/js/ux/nav.mjs
index 2be619d147c57aa76bee7947ec84dc6a18ccbcd4..4fbc8fd0da9475e1e56fad03a3a03755f0a573a5 100644
--- a/src/js/ux/nav.mjs
+++ b/src/js/ux/nav.mjs
@@ -16,6 +16,14 @@ const setFocus = [
   ()=>0,
   showResult,
 ];
+
+const pages = [
+  'page_0_home',
+  'page_1_pubKey',
+  'page_2_inputSecrets',
+  'page_3_inProgress',
+  'page_4_result',
+];
 init();
 
 // internal navigation
@@ -25,8 +33,8 @@ function showPageFromHash(){
     const pageNumber = parseInt(window.location.hash.split('_')[1]);
     if(isNaN(pageNumber)) return window.location = '#page_0_home';
     if(pageNumber<2 || pubKey){
-      document.body.className = window.location.hash.slice(1)
-    } else window.location = '#page_0_home';
+      document.body.className = pages[pageNumber]
+    } else return window.location = '#page_0_home';
     setFocus[pageNumber]();
     if(pageNumber!==3) stopSearch();
   } catch (e){
diff --git a/src/js/ux/rangeOption.mjs b/src/js/ux/rangeOption.mjs
index fc6e4d18c2bc48886aa15f3a550940d105f6010f..78839f73128b3eea5ad14da57ea2f18974c5b092 100644
--- a/src/js/ux/rangeOption.mjs
+++ b/src/js/ux/rangeOption.mjs
@@ -78,6 +78,7 @@ export default function RangeOption(fieldId, initialValue, descArray, uiListener
     Object.keys(self.listeners).forEach(k=>self.listeners[k](mode));
   }
   checkBoxNode.addEventListener('change', ()=>uiChanged(hiddenNode.value = checkBoxNode.checked?-1:(rangeNode.value || 0)));
+  rangeNode.addEventListener('input', ()=>descLabel.innerHTML = descArray[rangeNode.value]);
   rangeNode.addEventListener('change', ()=>uiChanged(hiddenNode.value = rangeNode.value));
   if(typeof uiListener !== 'undefined') self.listen('default',uiListener)
   if(initialValue) self.setUserChoice(initialValue);
diff --git a/src/pages/fragments/browse.ejs b/src/pages/fragments/browse.ejs
index 9ef4f7b5ba7e929c6b126c313e0211e16dc9b237..d3dc5c6803fca449e685f5ecbb8f57103f0e8118 100644
--- a/src/pages/fragments/browse.ejs
+++ b/src/pages/fragments/browse.ejs
@@ -135,11 +135,14 @@
     return (index + staticDico.length) % staticDico.length;
   }
   function updateBrowseIndex(e){
-    if(e && e.key === "ArrowUp") return browseUp();
-    if(e && e.key === "ArrowDown") return browseDown();
+    if(e && e.key && e.key === "ArrowUp") return browseUp();
+    if(e && e.key && e.key === "ArrowDown") return browseDown();
+    if(e && e.deltaY && e.deltaY >0) return browseDown();
+    if(e && e.deltaY && e.deltaY <0) return browseUp();
     window.browseIndex = parseInt(document.querySelector('.currentIndex input').value);
     displayBrowsePreview();
   }
   document.querySelector('.currentIndex input').addEventListener('keyup',updateBrowseIndex);
+  document.querySelector('.browse tbody').addEventListener('wheel',updateBrowseIndex);
 </script>
 </section>
diff --git a/src/pages/steps/1_pubKey.ejs b/src/pages/steps/1_pubKey.ejs
index 3c8d497612402afc6d61378bc4a3faecc44e1d43..e8b5c7680e376b3c14258835e7ba2050881099f7 100644
--- a/src/pages/steps/1_pubKey.ejs
+++ b/src/pages/steps/1_pubKey.ejs
@@ -11,4 +11,13 @@
     <a href="#page_2_inputSecrets" class="button">
         Suivant <i class="fas fa-long-arrow-alt-right"></i>
     </a>
+    <div class="notice">
+        Vous êtes juste là pour tester, sans compte à récupérer&nbsp;?
+        Voici une clef publique valide utilisable comme exemple&nbsp;:
+        <a href="#page_1_pubKey=BCHERdQbh3HhyDSoqHdXTyGQZLZvDA9b6DBABcMAPdDW">BCHERdQbh3HhyDSoqHdXTyGQZLZvDA9b6DBABcMAPdDW</a>
+        <!--
+        idSec : "Ça me serait pas venu à l'idée !"
+        mdp : " MOI NON PLUS "
+        -->
+    </div>
 </section>
diff --git a/src/pages/steps/2_inputSecrets.ejs b/src/pages/steps/2_inputSecrets.ejs
index 1bbd0a28e8caf47db254b7fd8c1b3e4c7d997b1c..e3306449b03633bc931adc641d8c5606e5773b87 100755
--- a/src/pages/steps/2_inputSecrets.ejs
+++ b/src/pages/steps/2_inputSecrets.ejs
@@ -15,11 +15,8 @@
             <a id="startSearch" class="button" href="#page_3_inProgress">Valider</a>
         </div>
         <div class="inputArea">
-        <textarea id="secrets">
-un exemple
-un autre
-le 3ème
-        </textarea>
+        <textarea id="secrets">ça (|ne )me serait pas venu[e]{0,1} à l'idée !
+ moi non plus[ ]</textarea>
         <%- include('../fragments/browse.ejs'); %>
         </div>
         <div class="tabs">
diff --git a/src/pages/steps/4_result.ejs b/src/pages/steps/4_result.ejs
index 341b7f73a95121def848a9773cd78e8489860df3..3593442ae1f9978b48903f654bf035468b32b9c0 100644
--- a/src/pages/steps/4_result.ejs
+++ b/src/pages/steps/4_result.ejs
@@ -18,7 +18,7 @@
         </div>
         <div class="stats">
             <div><span id="resTested"></span> combinaisons testées en <span id="resSpent"></span> (soit <span id="resSpeed"></span>/s)</div>
-            <div><span id="resDuplicate"></span> doublons évités soit <span id="resDupRatio"></span>% des combinaisons testées</div>
+            <div id="resDup"><span id="resDuplicate"></span> doublons évités soit <span id="resDupRatio"></span>% des combinaisons testées</div>
         </div>
         <div class="success">
             <div class="gift notice">