` tag.
-
-```
-.icon-account-login {
- fill: #f00;
-}
-```
-
-To learn more about SVG Sprites, read [Chris Coyier's guide](http://css-tricks.com/svg-sprites-use-better-icon-fonts/).
-
-#### Using Open Iconic's Icon Font...
-
-
-##### …with Bootstrap
-
-You can find our Bootstrap stylesheets in `font/css/open-iconic-bootstrap.{css, less, scss, styl}`
-
-
-```
-
-```
-
-
-```
-
-```
-
-##### …with Foundation
-
-You can find our Foundation stylesheets in `font/css/open-iconic-foundation.{css, less, scss, styl}`
-
-```
-
-```
-
-
-```
-
-```
-
-##### …on its own
-
-You can find our default stylesheets in `font/css/open-iconic.{css, less, scss, styl}`
-
-```
-
-```
-
-```
-
-```
-
-
-## License
-
-### Icons
-
-All code (including SVG markup) is under the [MIT License](http://opensource.org/licenses/MIT).
-
-### Fonts
-
-All fonts are under the [SIL Licensed](http://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web).
diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css b/EnvelopeGenerator.ReceiverUI/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css
deleted file mode 100644
index 4664f2e8..00000000
--- a/EnvelopeGenerator.ReceiverUI/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css
+++ /dev/null
@@ -1 +0,0 @@
-@font-face{font-family:Icons;src:url(../fonts/open-iconic.eot);src:url(../fonts/open-iconic.eot?#iconic-sm) format('embedded-opentype'),url(../fonts/open-iconic.woff) format('woff'),url(../fonts/open-iconic.ttf) format('truetype'),url(../fonts/open-iconic.otf) format('opentype'),url(../fonts/open-iconic.svg#iconic-sm) format('svg');font-weight:400;font-style:normal}.oi{position:relative;top:1px;display:inline-block;speak:none;font-family:Icons;font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.oi:empty:before{width:1em;text-align:center;box-sizing:content-box}.oi.oi-align-center:before{text-align:center}.oi.oi-align-left:before{text-align:left}.oi.oi-align-right:before{text-align:right}.oi.oi-flip-horizontal:before{-webkit-transform:scale(-1,1);-ms-transform:scale(-1,1);transform:scale(-1,1)}.oi.oi-flip-vertical:before{-webkit-transform:scale(1,-1);-ms-transform:scale(-1,1);transform:scale(1,-1)}.oi.oi-flip-horizontal-vertical:before{-webkit-transform:scale(-1,-1);-ms-transform:scale(-1,1);transform:scale(-1,-1)}.oi-account-login:before{content:'\e000'}.oi-account-logout:before{content:'\e001'}.oi-action-redo:before{content:'\e002'}.oi-action-undo:before{content:'\e003'}.oi-align-center:before{content:'\e004'}.oi-align-left:before{content:'\e005'}.oi-align-right:before{content:'\e006'}.oi-aperture:before{content:'\e007'}.oi-arrow-bottom:before{content:'\e008'}.oi-arrow-circle-bottom:before{content:'\e009'}.oi-arrow-circle-left:before{content:'\e00a'}.oi-arrow-circle-right:before{content:'\e00b'}.oi-arrow-circle-top:before{content:'\e00c'}.oi-arrow-left:before{content:'\e00d'}.oi-arrow-right:before{content:'\e00e'}.oi-arrow-thick-bottom:before{content:'\e00f'}.oi-arrow-thick-left:before{content:'\e010'}.oi-arrow-thick-right:before{content:'\e011'}.oi-arrow-thick-top:before{content:'\e012'}.oi-arrow-top:before{content:'\e013'}.oi-audio-spectrum:before{content:'\e014'}.oi-audio:before{content:'\e015'}.oi-badge:before{content:'\e016'}.oi-ban:before{content:'\e017'}.oi-bar-chart:before{content:'\e018'}.oi-basket:before{content:'\e019'}.oi-battery-empty:before{content:'\e01a'}.oi-battery-full:before{content:'\e01b'}.oi-beaker:before{content:'\e01c'}.oi-bell:before{content:'\e01d'}.oi-bluetooth:before{content:'\e01e'}.oi-bold:before{content:'\e01f'}.oi-bolt:before{content:'\e020'}.oi-book:before{content:'\e021'}.oi-bookmark:before{content:'\e022'}.oi-box:before{content:'\e023'}.oi-briefcase:before{content:'\e024'}.oi-british-pound:before{content:'\e025'}.oi-browser:before{content:'\e026'}.oi-brush:before{content:'\e027'}.oi-bug:before{content:'\e028'}.oi-bullhorn:before{content:'\e029'}.oi-calculator:before{content:'\e02a'}.oi-calendar:before{content:'\e02b'}.oi-camera-slr:before{content:'\e02c'}.oi-caret-bottom:before{content:'\e02d'}.oi-caret-left:before{content:'\e02e'}.oi-caret-right:before{content:'\e02f'}.oi-caret-top:before{content:'\e030'}.oi-cart:before{content:'\e031'}.oi-chat:before{content:'\e032'}.oi-check:before{content:'\e033'}.oi-chevron-bottom:before{content:'\e034'}.oi-chevron-left:before{content:'\e035'}.oi-chevron-right:before{content:'\e036'}.oi-chevron-top:before{content:'\e037'}.oi-circle-check:before{content:'\e038'}.oi-circle-x:before{content:'\e039'}.oi-clipboard:before{content:'\e03a'}.oi-clock:before{content:'\e03b'}.oi-cloud-download:before{content:'\e03c'}.oi-cloud-upload:before{content:'\e03d'}.oi-cloud:before{content:'\e03e'}.oi-cloudy:before{content:'\e03f'}.oi-code:before{content:'\e040'}.oi-cog:before{content:'\e041'}.oi-collapse-down:before{content:'\e042'}.oi-collapse-left:before{content:'\e043'}.oi-collapse-right:before{content:'\e044'}.oi-collapse-up:before{content:'\e045'}.oi-command:before{content:'\e046'}.oi-comment-square:before{content:'\e047'}.oi-compass:before{content:'\e048'}.oi-contrast:before{content:'\e049'}.oi-copywriting:before{content:'\e04a'}.oi-credit-card:before{content:'\e04b'}.oi-crop:before{content:'\e04c'}.oi-dashboard:before{content:'\e04d'}.oi-data-transfer-download:before{content:'\e04e'}.oi-data-transfer-upload:before{content:'\e04f'}.oi-delete:before{content:'\e050'}.oi-dial:before{content:'\e051'}.oi-document:before{content:'\e052'}.oi-dollar:before{content:'\e053'}.oi-double-quote-sans-left:before{content:'\e054'}.oi-double-quote-sans-right:before{content:'\e055'}.oi-double-quote-serif-left:before{content:'\e056'}.oi-double-quote-serif-right:before{content:'\e057'}.oi-droplet:before{content:'\e058'}.oi-eject:before{content:'\e059'}.oi-elevator:before{content:'\e05a'}.oi-ellipses:before{content:'\e05b'}.oi-envelope-closed:before{content:'\e05c'}.oi-envelope-open:before{content:'\e05d'}.oi-euro:before{content:'\e05e'}.oi-excerpt:before{content:'\e05f'}.oi-expand-down:before{content:'\e060'}.oi-expand-left:before{content:'\e061'}.oi-expand-right:before{content:'\e062'}.oi-expand-up:before{content:'\e063'}.oi-external-link:before{content:'\e064'}.oi-eye:before{content:'\e065'}.oi-eyedropper:before{content:'\e066'}.oi-file:before{content:'\e067'}.oi-fire:before{content:'\e068'}.oi-flag:before{content:'\e069'}.oi-flash:before{content:'\e06a'}.oi-folder:before{content:'\e06b'}.oi-fork:before{content:'\e06c'}.oi-fullscreen-enter:before{content:'\e06d'}.oi-fullscreen-exit:before{content:'\e06e'}.oi-globe:before{content:'\e06f'}.oi-graph:before{content:'\e070'}.oi-grid-four-up:before{content:'\e071'}.oi-grid-three-up:before{content:'\e072'}.oi-grid-two-up:before{content:'\e073'}.oi-hard-drive:before{content:'\e074'}.oi-header:before{content:'\e075'}.oi-headphones:before{content:'\e076'}.oi-heart:before{content:'\e077'}.oi-home:before{content:'\e078'}.oi-image:before{content:'\e079'}.oi-inbox:before{content:'\e07a'}.oi-infinity:before{content:'\e07b'}.oi-info:before{content:'\e07c'}.oi-italic:before{content:'\e07d'}.oi-justify-center:before{content:'\e07e'}.oi-justify-left:before{content:'\e07f'}.oi-justify-right:before{content:'\e080'}.oi-key:before{content:'\e081'}.oi-laptop:before{content:'\e082'}.oi-layers:before{content:'\e083'}.oi-lightbulb:before{content:'\e084'}.oi-link-broken:before{content:'\e085'}.oi-link-intact:before{content:'\e086'}.oi-list-rich:before{content:'\e087'}.oi-list:before{content:'\e088'}.oi-location:before{content:'\e089'}.oi-lock-locked:before{content:'\e08a'}.oi-lock-unlocked:before{content:'\e08b'}.oi-loop-circular:before{content:'\e08c'}.oi-loop-square:before{content:'\e08d'}.oi-loop:before{content:'\e08e'}.oi-magnifying-glass:before{content:'\e08f'}.oi-map-marker:before{content:'\e090'}.oi-map:before{content:'\e091'}.oi-media-pause:before{content:'\e092'}.oi-media-play:before{content:'\e093'}.oi-media-record:before{content:'\e094'}.oi-media-skip-backward:before{content:'\e095'}.oi-media-skip-forward:before{content:'\e096'}.oi-media-step-backward:before{content:'\e097'}.oi-media-step-forward:before{content:'\e098'}.oi-media-stop:before{content:'\e099'}.oi-medical-cross:before{content:'\e09a'}.oi-menu:before{content:'\e09b'}.oi-microphone:before{content:'\e09c'}.oi-minus:before{content:'\e09d'}.oi-monitor:before{content:'\e09e'}.oi-moon:before{content:'\e09f'}.oi-move:before{content:'\e0a0'}.oi-musical-note:before{content:'\e0a1'}.oi-paperclip:before{content:'\e0a2'}.oi-pencil:before{content:'\e0a3'}.oi-people:before{content:'\e0a4'}.oi-person:before{content:'\e0a5'}.oi-phone:before{content:'\e0a6'}.oi-pie-chart:before{content:'\e0a7'}.oi-pin:before{content:'\e0a8'}.oi-play-circle:before{content:'\e0a9'}.oi-plus:before{content:'\e0aa'}.oi-power-standby:before{content:'\e0ab'}.oi-print:before{content:'\e0ac'}.oi-project:before{content:'\e0ad'}.oi-pulse:before{content:'\e0ae'}.oi-puzzle-piece:before{content:'\e0af'}.oi-question-mark:before{content:'\e0b0'}.oi-rain:before{content:'\e0b1'}.oi-random:before{content:'\e0b2'}.oi-reload:before{content:'\e0b3'}.oi-resize-both:before{content:'\e0b4'}.oi-resize-height:before{content:'\e0b5'}.oi-resize-width:before{content:'\e0b6'}.oi-rss-alt:before{content:'\e0b7'}.oi-rss:before{content:'\e0b8'}.oi-script:before{content:'\e0b9'}.oi-share-boxed:before{content:'\e0ba'}.oi-share:before{content:'\e0bb'}.oi-shield:before{content:'\e0bc'}.oi-signal:before{content:'\e0bd'}.oi-signpost:before{content:'\e0be'}.oi-sort-ascending:before{content:'\e0bf'}.oi-sort-descending:before{content:'\e0c0'}.oi-spreadsheet:before{content:'\e0c1'}.oi-star:before{content:'\e0c2'}.oi-sun:before{content:'\e0c3'}.oi-tablet:before{content:'\e0c4'}.oi-tag:before{content:'\e0c5'}.oi-tags:before{content:'\e0c6'}.oi-target:before{content:'\e0c7'}.oi-task:before{content:'\e0c8'}.oi-terminal:before{content:'\e0c9'}.oi-text:before{content:'\e0ca'}.oi-thumb-down:before{content:'\e0cb'}.oi-thumb-up:before{content:'\e0cc'}.oi-timer:before{content:'\e0cd'}.oi-transfer:before{content:'\e0ce'}.oi-trash:before{content:'\e0cf'}.oi-underline:before{content:'\e0d0'}.oi-vertical-align-bottom:before{content:'\e0d1'}.oi-vertical-align-center:before{content:'\e0d2'}.oi-vertical-align-top:before{content:'\e0d3'}.oi-video:before{content:'\e0d4'}.oi-volume-high:before{content:'\e0d5'}.oi-volume-low:before{content:'\e0d6'}.oi-volume-off:before{content:'\e0d7'}.oi-warning:before{content:'\e0d8'}.oi-wifi:before{content:'\e0d9'}.oi-wrench:before{content:'\e0da'}.oi-x:before{content:'\e0db'}.oi-yen:before{content:'\e0dc'}.oi-zoom-in:before{content:'\e0dd'}.oi-zoom-out:before{content:'\e0de'}
\ No newline at end of file
diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/css/open-iconic/font/fonts/open-iconic.eot b/EnvelopeGenerator.ReceiverUI/wwwroot/css/open-iconic/font/fonts/open-iconic.eot
deleted file mode 100644
index f98177db..00000000
Binary files a/EnvelopeGenerator.ReceiverUI/wwwroot/css/open-iconic/font/fonts/open-iconic.eot and /dev/null differ
diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/css/open-iconic/font/fonts/open-iconic.otf b/EnvelopeGenerator.ReceiverUI/wwwroot/css/open-iconic/font/fonts/open-iconic.otf
deleted file mode 100644
index f6bd6846..00000000
Binary files a/EnvelopeGenerator.ReceiverUI/wwwroot/css/open-iconic/font/fonts/open-iconic.otf and /dev/null differ
diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/css/open-iconic/font/fonts/open-iconic.svg b/EnvelopeGenerator.ReceiverUI/wwwroot/css/open-iconic/font/fonts/open-iconic.svg
deleted file mode 100644
index 32b2c4e9..00000000
--- a/EnvelopeGenerator.ReceiverUI/wwwroot/css/open-iconic/font/fonts/open-iconic.svg
+++ /dev/null
@@ -1,543 +0,0 @@
-
-
-
-
-
-Created by FontForge 20120731 at Tue Jul 1 20:39:22 2014
- By P.J. Onori
-Created by P.J. Onori with FontForge 2.0 (http://fontforge.sf.net)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf b/EnvelopeGenerator.ReceiverUI/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf
deleted file mode 100644
index fab60486..00000000
Binary files a/EnvelopeGenerator.ReceiverUI/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf and /dev/null differ
diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/css/open-iconic/font/fonts/open-iconic.woff b/EnvelopeGenerator.ReceiverUI/wwwroot/css/open-iconic/font/fonts/open-iconic.woff
deleted file mode 100644
index f9309988..00000000
Binary files a/EnvelopeGenerator.ReceiverUI/wwwroot/css/open-iconic/font/fonts/open-iconic.woff and /dev/null differ
diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/css/privacy-policy.css b/EnvelopeGenerator.ReceiverUI/wwwroot/css/privacy-policy.css
deleted file mode 100644
index 12d2fc42..00000000
--- a/EnvelopeGenerator.ReceiverUI/wwwroot/css/privacy-policy.css
+++ /dev/null
@@ -1,41 +0,0 @@
-body {
- font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
- line-height: 1.6;
- margin: 1.25rem;
- background-color: #f4f4f4;
-}
-
-header {
- text-align: center;
- margin: 0rem 10rem 3rem 10rem;
- box-shadow: 0 .25rem .5rem rgba(0, 0, 0, 0.1);
-}
-
-h1 {
- color: #333;
-}
-
-h2 {
- color: #0056b3;
-}
-
-section {
- background-color: white;
- padding: 1.25rem;
- border-radius: 0.5rem;
- box-shadow: 0 .25rem .5rem rgba(0, 0, 0, 0.1);
- margin: 0rem 10rem 3rem 10rem
-}
-
-ul {
- list-style: disc inside;
-}
-
-a {
- color: #0056b3;
- text-decoration: none;
-}
-
-a:hover {
- text-decoration: underline;
-}
\ No newline at end of file
diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/css/privacy-policy.min.css b/EnvelopeGenerator.ReceiverUI/wwwroot/css/privacy-policy.min.css
deleted file mode 100644
index eb1528de..00000000
--- a/EnvelopeGenerator.ReceiverUI/wwwroot/css/privacy-policy.min.css
+++ /dev/null
@@ -1 +0,0 @@
-body{font-family:system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.6;margin:1.25rem;background-color:#f4f4f4}header{text-align:center;margin:0 10rem 3rem 10rem;box-shadow:0 .25rem .5rem rgba(0,0,0,.1)}h1{color:#333}h2{color:#0056b3}section{background-color:#fff;padding:1.25rem;border-radius:.5rem;box-shadow:0 .25rem .5rem rgba(0,0,0,.1);margin:0 10rem 3rem 10rem}ul{list-style:disc inside}a{color:#0056b3;text-decoration:none}a:hover{text-decoration:underline}
\ No newline at end of file
diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/css/sender-page.css b/EnvelopeGenerator.ReceiverUI/wwwroot/css/sender-page.css
deleted file mode 100644
index 9195af48..00000000
--- a/EnvelopeGenerator.ReceiverUI/wwwroot/css/sender-page.css
+++ /dev/null
@@ -1,297 +0,0 @@
-.sender-dashboard-layout {
- display: flex;
- flex-direction: column;
- height: 100vh;
- overflow: hidden;
- background: linear-gradient(135deg, #1e3c72 0%, #2a5298 50%, #7e22ce 100%);
-}
-
-.sender-action-bar {
- background: rgba(255, 255, 255, 0.95);
- backdrop-filter: blur(20px);
- border-bottom: 3px solid rgba(126, 34, 206, 0.3);
- padding: 1rem 2rem;
- flex-shrink: 0;
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
-}
-
-.sender-action-bar__inner {
- max-width: 1600px;
- margin: 0 auto;
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 1.5rem;
-}
-
-.sender-title-section {
- display: flex;
- align-items: center;
- gap: 1rem;
-}
-
-.sender-logo svg {
- filter: drop-shadow(0 2px 4px rgba(126, 34, 206, 0.3));
- color: #7e22ce;
-}
-
-.sender-title {
- font-size: 1.25rem;
- font-weight: 700;
- color: #1e293b;
- letter-spacing: -0.025em;
-}
-
-.sender-toolbar {
- display: flex;
- align-items: center;
- gap: 0.75rem;
- flex-wrap: wrap;
-}
-
-.sender-btn {
- display: flex;
- align-items: center;
- gap: 0.5rem;
- padding: 0.625rem 1.125rem;
- background: linear-gradient(135deg, rgba(126, 34, 206, 0.05) 0%, rgba(42, 82, 152, 0.05) 100%);
- border: 1px solid rgba(126, 34, 206, 0.2);
- border-radius: 8px;
- font-size: 0.875rem;
- font-weight: 600;
- color: #1e293b;
- cursor: pointer;
- transition: all 0.2s ease;
- white-space: nowrap;
-}
-
- .sender-btn:hover:not(:disabled) {
- background: linear-gradient(135deg, rgba(126, 34, 206, 0.1) 0%, rgba(42, 82, 152, 0.1) 100%);
- border-color: rgba(126, 34, 206, 0.4);
- transform: translateY(-1px);
- box-shadow: 0 4px 12px rgba(126, 34, 206, 0.2);
- }
-
- .sender-btn:disabled {
- opacity: 0.4;
- cursor: not-allowed;
- background: rgba(0, 0, 0, 0.02);
- border-color: rgba(0, 0, 0, 0.1);
- }
-
-.sender-btn--primary {
- background: linear-gradient(135deg, #7e22ce 0%, #2a5298 100%);
- border-color: transparent;
- color: white;
-}
-
- .sender-btn--primary:hover:not(:disabled) {
- background: linear-gradient(135deg, #6b1cb0 0%, #1e3a72 100%);
- transform: translateY(-1px);
- box-shadow: 0 4px 16px rgba(126, 34, 206, 0.3);
- }
-
-.sender-btn--danger {
- background: linear-gradient(135deg, rgba(239, 68, 68, 0.08) 0%, rgba(220, 38, 38, 0.08) 100%);
- border-color: rgba(239, 68, 68, 0.3);
- color: #dc2626;
-}
-
- .sender-btn--danger:hover:not(:disabled) {
- background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
- border-color: transparent;
- color: white;
- }
-
-.sender-btn--logout {
- padding: 0.5rem;
- min-width: 38px;
-}
-
-.sender-content {
- flex: 1;
- min-height: 0;
- padding: 1.5rem;
- position: relative;
- overflow: auto;
-}
-
-.sender-grid-container {
- background: rgba(255, 255, 255, 0.98);
- backdrop-filter: blur(20px);
- border-radius: 16px;
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(255, 255, 255, 0.1);
- overflow: hidden;
- position: relative;
- max-width: 1600px;
- margin: 0 auto;
-}
-
- .sender-grid-container::before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- height: 4px;
- background: linear-gradient(90deg, #7e22ce 0%, #2a5298 100%);
- z-index: 1;
- border-radius: 16px 16px 0 0;
- }
-
-.sender-tabs {
- display: flex;
- border-bottom: 2px solid rgba(126, 34, 206, 0.1);
- padding: 0 2rem;
- background: rgba(126, 34, 206, 0.02);
-}
-
-.sender-tab {
- padding: 1rem 1.5rem;
- font-size: 0.875rem;
- font-weight: 600;
- color: #6b7280;
- background: transparent;
- border: none;
- border-bottom: 3px solid transparent;
- cursor: pointer;
- transition: all 0.2s ease;
- white-space: nowrap;
-}
-
- .sender-tab:hover {
- color: #7e22ce;
- background: rgba(126, 34, 206, 0.05);
- }
-
-.sender-tab--active {
- color: #7e22ce;
- border-bottom-color: #7e22ce;
- background: white;
-}
-
-.sender-grid-wrapper {
- padding: 1.5rem 2rem 2rem;
-}
-
-/* Hide DevExpress empty cells */
-.dxbl-grid-empty-cell {
- display: none !important;
-}
-
-.status-badge {
- display: inline-flex;
- align-items: center;
- gap: 0.375rem;
- padding: 0.25rem 0.625rem;
- border-radius: 6px;
- font-size: 0.75rem;
- font-weight: 600;
- white-space: nowrap;
-}
-
-.status-badge--partly-signed,
-.status-badge--completed {
- background: rgba(129, 199, 132, 0.15);
- color: #2e7d32;
-}
-
-.status-badge--queued,
-.status-badge--sent {
- background: rgba(255, 183, 77, 0.15);
- color: #e65100;
-}
-
-.status-badge--deleted,
-.status-badge--rejected,
-.status-badge--withdrawn {
- background: rgba(229, 115, 115, 0.15);
- color: #c62828;
-}
-
-.status-badge--created,
-.status-badge--saved {
- background: rgba(100, 181, 246, 0.15);
- color: #1565c0;
-}
-
-.status-dot {
- width: 6px;
- height: 6px;
- border-radius: 50%;
-}
-
-.status-dot--green {
- background: #81c784;
-}
-
-.status-dot--orange {
- background: #ffb74d;
-}
-
-.status-dot--red {
- background: #e57373;
-}
-
-.status-dot--blue {
- background: #64b5f6;
-}
-
-.receiver-badge {
- display: inline-flex;
- align-items: center;
- gap: 0.25rem;
- padding: 0.125rem 0.5rem;
- background: #f3f4f6;
- border-radius: 4px;
- font-size: 0.75rem;
- color: #374151;
- white-space: nowrap;
-}
-
-.receiver-badge--signed {
- background: rgba(129, 199, 132, 0.15);
- color: #2e7d32;
-}
-
-.receiver-badge--unsigned {
- background: rgba(229, 115, 115, 0.15);
- color: #c62828;
-}
-
-@@media (max-width: 768px) {
- .sender-action-bar {
- padding: 1rem 1.25rem;
- }
-
- .sender-action-bar__inner {
- flex-wrap: wrap;
- }
-
- .sender-toolbar {
- width: 100%;
- justify-content: flex-start;
- }
-
- .sender-title {
- font-size: 1.125rem;
- }
-
- .sender-content {
- padding: 0.75rem;
- }
-
- .sender-grid-wrapper {
- padding: 1rem;
- }
-
- .sender-tabs {
- padding: 0 1rem;
- overflow-x: auto;
- }
-
- .sender-tab {
- padding: 0.875rem 1rem;
- font-size: 0.813rem;
- }
-}
diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/docs/Document.pdf b/EnvelopeGenerator.ReceiverUI/wwwroot/docs/Document.pdf
deleted file mode 100644
index e36adb6c..00000000
Binary files a/EnvelopeGenerator.ReceiverUI/wwwroot/docs/Document.pdf and /dev/null differ
diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/docs/privacy-policy.de-DE.html b/EnvelopeGenerator.ReceiverUI/wwwroot/docs/privacy-policy.de-DE.html
deleted file mode 100644
index 6b1bc8d4..00000000
--- a/EnvelopeGenerator.ReceiverUI/wwwroot/docs/privacy-policy.de-DE.html
+++ /dev/null
@@ -1,167 +0,0 @@
-
-
-
-
-
-
- Datenschutzinformation für das Fernsignatursystem: signFLOW
-
-
-
-
-
- Datenschutzinformation für das Fernsignatursystem signFLOW
- Stand: 18.11.2025
-
-
-
- 1. Allgemeine Informationen
- In der heutigen schnelllebigen und zunehmend digitalen Welt, sind personenbezogene Daten eine wichtige
- Ressource. Ihre Daten sind wertvoll und müssen daher mit der durch diverse Gesetze und Vorschriften (DSGVO,
- TDDDG, ...) gebotenen Sorgfalt behandelt werden.
- Als Anbieter von lokalen Lösungen (OnPremise) setzt der Hersteller des signFLOWs, die Digital Data GmbH,
- einen klaren Schwerpunkt auf Datenschutz und Datensicherheit. Für Sie bedeutet dies, dass nur die nötigsten
- Daten erhoben und gespeichert werden (Datensparsamkeit bzw. Datenminimierung). Außerdem kommen bei der
- Verarbeitung aktuelle und als sicher geltende Technologien zum Einsatz.
- Kontaktdaten des Herstellers:
-
- Digital Data GmbH
- Ludwig-Rinn-Straße 16
- 35452 Heuchelheim
- https://digitaldata.works
- info-flow@digitaldata.works
- Telefon: 0049 641 202360
-
- Kontakt zum Datenschutzbeauftragten: privacy-flow@digitaldata.works
-
-
-
-
-
- 3. Datenerhebung
- 3.1 Die folgenden Kategorien personenbezogener Daten werden verarbeitet
-
- Namen: Benutzername, Vor- und Zunamen sowie Ihre digitale Unterschrift
- Kontaktdaten: Telefonnummer, Mobilfunknummer und E-Mail-Adresse
- Technische Daten: IP-Adresse, Zeitpunkt des Zugriffs oder Zugriffsversuchs
-
-
- 3.2 Ursprung der personenbezogenen Daten
- Sie haben die unter 3.1 stehenden Daten vorab an Ihren Geschäftspartner (die verantwortliche Stelle)
- übermittelt. Diese Übermittlung kann mündlich per Telefon, im persönlichen Kontakt, per E-Mail oder per
- Kontaktformular geschehen sein.
- Ihre digitale Signatur übermitteln Sie eigenständig, bei der Unterzeichnung eines Dokuments.
-
- 3.3 Aufbewahrungsfristen / Speicherungsdauer
-
- Die automatische E-Mail Korrespondenz wird für 6 Jahre aufbewahrt.
- Unterzeichnete Verträge werden für die Dauer ihrer Laufzeit + 10 Jahre aufbewahrt.
- Der technische Vorgang wird in der signFLOW Softwarelösung je nach Dokumentart bzw. Vertragsart
- unbegrenzt aufbewahrt.
-
- Ihre personenbezogenen Daten werden aber grundsätzlich anonymisiert, wenn:
-
- Der Vertrag ausgelaufen ist und die gesetzliche Aufbewahrungszeit vorbei ist.
- Der Vertrag von Ihnen abgelehnt wurde bzw. nie unterschrieben wurde.
-
- Die gesetzlichen Grundlagen dieser Aufbewahrungsfristen bieten unter anderen:
-
- Handelsgesetzbuch (HGB)
- Abgabenordnung (AO)
- Grundsätze zur ordnungsgemäßen Führung und Aufbewahrung von Büchern, Aufzeichnungen und Unterlagen in
- elektronischer Form sowie zum Datenzugriff (GoBD)
-
-
- Abhängig von der spezifischen Dokumentart, kann die Aufbewahrungsfrist variieren. Außerdem können sich
- Zeiten
- verlängern, falls Unregelmäßigkeiten auftreten. Z.B.: eine rechtliche Auseinandersetzung droht oder aktuell
- läuft.
-
-
- 3.4 Verarbeitungszweck
- Die unter 3.1 definierten personenbezogenen Daten werden verarbeitet um:
-
- Den technisch notwendigen Prozess abzubilden bzw. anbieten zu können.
- Damit Sie als Endbenutzer in der Lage sind, ein zu unterzeichnendes Dokument, digital zu unterschreiben,
- ist die Feststellung der Identität des Antragstellers, Antragsprüfung und -abwicklung, Abrechnung sowie
- die Wahrung der Dokumentationspflichten notwendig.
-
- In Einzelfällen werden Daten zur Fehlerbehebung insbesondere bei Supportanfragen von der IT-Abteilung
- gesondert behandelt oder ggf. an den Hersteller weitergegeben.
- Im Zuge der Maßnahmen zur Sicherstellung der Informationssicherheit, insbesondere zur Identifizierung und
- Abwehr von Angriffen, sowie zur Durchführung interner und externer Audits, der Exportkontrolle und der
- Prüfung von Sanktionslisten, erfolgt ebenfalls eine Datenverarbeitung. Bei Anfragen gemäß §8 Abs. 2 VDG
- werden die entsprechenden Informationen an die zuständigen Stellen übermittelt.
-
- 3.5 Rechtmäßigkeit der Verarbeitung
- Ihre Daten werden auf Grundlage einer sich anbahnenden oder bereits vorhandenen Geschäftsbeziehung erhoben.
-
- Die rechtliche Grundlage für die Übermittlung an zuständige Stellen bildet §8 Abs. 2 VDG. Anfragen von
- Betroffenen werden gemäß den Artikeln 12 bis 23 der DSGVO sowie den Paragraphen 32 bis 37 des BDSG
- bearbeitet.
-
- 3.6 Berechtigte Interessen
- Ein berechtigtes Interesse der verantwortlichen Stelle gemäß Art. 6 Abs. 1 lit. f DSGVO besteht in den
- folgenden Fällen:
- Es werden Maßnahmen zur Informationssicherheit ergriffen, die sowohl präventive technische als auch
- organisatorische Maßnahmen sowie die Behandlung von Vorfällen umfassen. Ziel ist es, potenzielle Schäden für
- das Unternehmen, die von der Datenverarbeitung betroffenen Personen und die Nutzer der Vertrauensdienste zu
- bewerten und zu vermeiden.
-
- 3.7 Erforderlichkeit der Daten
- Die erhobenen Daten stellen das Minimum der nötigen Daten zwecks digitaler Unterschrift dar. Ohne die unter
- 3.1 genannten Daten, kann der Dienst nicht betrieben werden.
- Besonders wichtig ist die Angabe einer Mobilfunknummer oder einer deutschen Festnetznummer, da diese für die
- Authentifizierung und die Signaturauslösung als zweiter Faktor verwendet wird. Ohne diesen
- Sicherheitsmechanismus kann der Dienst leider nicht bereitgestellt werden.
-
- 3.8 Weitergabe von Daten
- Eine systemische Übermittlung von Daten findet nicht statt.
- Daten werden nur in Ausnahmefällen, zwecks Supportleistungen, an den Hersteller weitergegeben. Mit der
- Hersteller besteht ein gültiger Auftragsdatenverarbeitungsvertrag (AVV), welcher die Sicherheit und
- Integrität des Umgangs mit Ihren Daten gewährleistet.
-
-
-
- 4. Verwendung von Cookies
-
- Beim Besuch bestimmter Seiten kommen temporäre Cookies zum Einsatz, die für die technische Bereitstellung
- der Dienste notwendig sind. Diese sogenannten Session-Cookies enthalten keine personenbezogenen Daten und
- werden nach Beendigung der Sitzung automatisch gelöscht. Methoden wie Java-Applets oder Active-X-Controls,
- die das Nutzerverhalten nachvollziehbar machen könnten, werden nicht verwendet.
-
-
-
-
- 5. Betroffenenrechte
-
- Wenn Sie Fragen zu Ihren Daten haben oder eine Berichtigung, Löschung oder Einschränkung der Verarbeitung
- wünschen, senden Sie bitte Ihre Anfrage per Post oder E-Mail an die oben angegebene Adresse. Dies gilt auch,
- wenn Sie gemäß Art. 21 DSGVO Widerspruch gegen die Verarbeitung einlegen möchten oder eine Anfrage zur
- Datenübertragbarkeit haben.
-
-
- Bei Fragen oder Beschwerden zu einem Verfahren können Sie sich ebenfalls über die genannten
- Kontaktmöglichkeiten an uns wenden. Sollten Sie darüber hinaus einen Grund zur Beschwerde haben, haben Sie
- die Möglichkeit, sich an unsere Aufsichtsbehörde zu wenden. Welche Aufsichtsbehörde für Sie zuständig ist,
- erfahren Sie hier:
- Laender-node.html
-
-
-
-
-
\ No newline at end of file
diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/docs/privacy-policy.en-US.html b/EnvelopeGenerator.ReceiverUI/wwwroot/docs/privacy-policy.en-US.html
deleted file mode 100644
index 8117bfe5..00000000
--- a/EnvelopeGenerator.ReceiverUI/wwwroot/docs/privacy-policy.en-US.html
+++ /dev/null
@@ -1,158 +0,0 @@
-
-
-
-
-
-
- Data Protection Information for the Remote Signature System signFLOW
-
-
-
-
-
- Data Protection Information for the Remote Signature System: signFLOW
- As of: 18.11.2025
-
-
- 1. General Information
- In today's fast-paced and increasingly digital world, personal data is an important resource. Your data is
- valuable and must therefore be handled with the care required by various laws and regulations (GDPR, TDDDG,
- ...).
- As a provider of local solutions (OnPremise), the manufacturer of signFLOW, Digital Data GmbH, places a clear
- focus on data protection and data security. For you, this means that only the necessary data is collected
- and stored (data minimization). Furthermore, current and secure technologies are used in processing.
- Contact details of the manufacturer:
-
- Digital Data GmbH
- Ludwig-Rinn-Straße 16
- 35452 Heuchelheim
- https://digitaldata.works
- info-flow@digitaldata.works
- Phone: 0049 641 202360
-
- Contact the Data Protection Officer: privacy-flow@digitaldata.works
-
-
-
-
-
- 3. Data Collection
- 3.1 The following categories of personal data are processed
-
- Names: Username, first and last names as well as your digital signature
- Contact details: Phone number, mobile phone number, and email address
- Technical data: IP address, time of access, or access attempts
-
-
- 3.2 Source of the personal data
- You have previously provided the data mentioned under 3.1 to your business partner (the responsible entity).
- This transmission may have occurred verbally over the phone, in personal contact, via email, or via a
- contact form.
- You transmit your digital signature independently when signing a document.
-
- 3.3 Retention periods / Storage duration
-
- Automatic email correspondence is stored for 6 years.
- Signed contracts are retained for the duration of their term + 10 years.
- The technical process is stored in the signFLOW software solution indefinitely, depending on the
- document or contract type.
-
- Your personal data will generally be anonymized when:
-
- The contract has expired, and the statutory retention period is over.
- The contract was rejected by you or never signed.
-
- The legal basis for these retention periods includes:
-
- Commercial Code (HGB)
- Tax Code (AO)
- Principles for the Proper Keeping and Retention of Books, Records, and Documents in Electronic Form and
- for Data Access (GoBD)
-
-
- Depending on the specific type of document, the retention period may vary. Additionally, the periods may be
- extended in case of irregularities, such as a pending or ongoing legal dispute.
-
-
- 3.4 Purpose of processing
- The personal data defined under 3.1 is processed to:
-
- Support or provide the technically necessary process.
- Enable you, as the end user, to sign a document digitally. This requires the identification of the
- applicant, application verification and processing, billing, and compliance with documentation
- requirements.
-
- In individual cases, data is processed separately by the IT department, particularly in response to support
- requests, or possibly forwarded to the manufacturer for further processing.
- Data processing also occurs to ensure information security, especially for the identification and prevention
- of attacks, and for conducting internal and external audits, export controls, and sanctions list checks.
- Information may also be transmitted to the relevant authorities in accordance with Section 8 (2) VDG.
-
- 3.5 Legality of processing
- Your data is collected based on an impending or already existing business relationship.
- The legal basis for the transmission to competent authorities is Section 8 (2) VDG. Requests from data
- subjects are processed in accordance with Articles 12 to 23 of the GDPR and Sections 32 to 37 of the Federal
- Data Protection Act (BDSG).
-
- 3.6 Legitimate interests
- A legitimate interest of the responsible entity in accordance with Article 6 (1) (f) GDPR exists in the
- following cases:
- Measures are taken for information security, which include both preventive technical and organizational
- measures as well as incident handling. The aim is to assess and avoid potential harm to the company, the
- individuals affected by data processing, and the users of trust services.
-
- 3.7 Necessity of data
- The collected data represents the minimum necessary for the digital signature. Without the data mentioned
- under 3.1, the service cannot be operated.
- It is particularly important to provide a mobile number or a German landline number, as this is used for
- authentication and signature triggering as a second factor. Without this security mechanism, the service
- cannot be provided.
-
- 3.8 Data transfer
- Systematic data transmission does not take place.
- Data is only forwarded to the manufacturer for support services in exceptional cases. A valid data processing
- agreement (DPA) exists with the manufacturer, which ensures the security and integrity of the handling of
- your data.
-
-
-
- 4. Use of Cookies
-
- When visiting certain pages, temporary cookies are used, which are necessary for the technical provision of
- the services. These so-called session cookies do not contain any personal data and are automatically deleted
- after the session ends. Methods such as Java applets or Active-X controls that could track user behavior are
- not used.
-
-
-
-
- 5. Rights of Affected Persons
-
- If you have questions about your data or wish to request correction, deletion, or restriction of processing,
- please send your request by mail or email to the address provided above. This also applies if you wish to
- object to the processing in accordance with Article 21 GDPR or request data portability.
-
-
- If you have questions or complaints about a procedure, you can also contact us using the contact details
- provided. If you have further grounds for complaint, you can contact our supervisory authority. You can find
- out which supervisory authority is responsible for you here:
- Laender-node.html
-
-
-
-
-
\ No newline at end of file
diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/docs/privacy-policy.fr-FR.html b/EnvelopeGenerator.ReceiverUI/wwwroot/docs/privacy-policy.fr-FR.html
deleted file mode 100644
index 9299b72d..00000000
--- a/EnvelopeGenerator.ReceiverUI/wwwroot/docs/privacy-policy.fr-FR.html
+++ /dev/null
@@ -1,126 +0,0 @@
-
-
-
-
-
-
- Informations sur la protection des données pour le système de signature à distance signFLOW
-
-
-
-
-
- Informations sur la protection des données pour le système de signature à distance : signFLOW
- À jour au : 18.11.2025
-
-
-
- 1. Informations générales
- Dans le monde actuel, rapide et de plus en plus numérique, les données personnelles sont une ressource précieuse. Vos données sont importantes et doivent donc être traitées avec le soin requis par les différentes lois et réglementations (RGPD, TDDDG, ...).
- En tant que fournisseur de solutions locales (OnPremise), le fabricant de signFLOW, Digital Data GmbH, accorde une attention particulière à la protection et à la sécurité des données. Pour vous, cela signifie que seules les données nécessaires sont collectées et stockées (minimisation des données). De plus, des technologies actuelles et sécurisées sont utilisées lors du traitement.
- Coordonnées du fabricant :
-
- Digital Data GmbH
- Ludwig-Rinn-Straße 16
- 35452 Heuchelheim
- https://digitaldata.works
- info-flow@digitaldata.works
- Téléphone : 0049 641 202360
-
- Contactez le délégué à la protection des données : privacy-flow@digitaldata.works
-
-
-
-
-
- 3. Collecte des données
- 3.1 Catégories de données personnelles traitées
-
- Noms : nom d’utilisateur, prénom et nom ainsi que votre signature numérique
- Coordonnées : numéro de téléphone, numéro de mobile et adresse e-mail
- Données techniques : adresse IP, heure d’accès ou tentatives d’accès
-
-
- 3.2 Source des données personnelles
- Vous avez précédemment fourni les données mentionnées en 3.1 à votre partenaire commercial (le responsable du traitement). Cette transmission a pu se faire verbalement par téléphone, lors d’un contact personnel, par e-mail ou via un formulaire de contact.
- Vous transmettez votre signature numérique de manière autonome lors de la signature d’un document.
-
- 3.3 Durée de conservation / stockage
-
- La correspondance par e-mail automatique est conservée pendant 6 ans.
- Les contrats signés sont conservés pendant leur durée + 10 ans.
- Le processus technique est stocké dans la solution logicielle signFLOW indéfiniment, selon le type de document ou de contrat.
-
- Vos données personnelles seront généralement anonymisées lorsque :
-
- Le contrat a expiré et la période légale de conservation est terminée.
- Le contrat a été refusé par vous ou jamais signé.
-
- La base légale de ces périodes de conservation comprend :
-
- Code de commerce (HGB)
- Code fiscal (AO)
- Principes de tenue correcte et de conservation des livres, registres et documents sous forme électronique et d’accès aux données (GoBD)
-
-
- Selon le type spécifique de document, la période de conservation peut varier. De plus, ces périodes peuvent être prolongées en cas d’irrégularités, telles qu’un litige en cours ou imminent.
-
-
- 3.4 Finalité du traitement
- Les données personnelles définies en 3.1 sont traitées pour :
-
- Supporter ou fournir le processus techniquement nécessaire.
- Permettre à l’utilisateur final de signer un document numériquement. Cela nécessite l’identification du demandeur, la vérification et le traitement de la demande, la facturation et le respect des obligations documentaires.
-
- Dans certains cas, les données sont traitées séparément par le département informatique, notamment en réponse à des demandes de support, ou éventuellement transmises au fabricant pour un traitement supplémentaire.
- Le traitement des données intervient également pour garantir la sécurité de l’information, notamment pour identifier et prévenir les attaques, et pour effectuer des audits internes et externes, le contrôle des exportations et les vérifications des listes de sanctions. Les informations peuvent également être transmises aux autorités compétentes conformément à l’article 8 (2) VDG.
-
- 3.5 Licéité du traitement
- Vos données sont collectées sur la base d’une relation commerciale existante ou imminente.
- La base légale pour la transmission aux autorités compétentes est l’article 8 (2) VDG. Les demandes des personnes concernées sont traitées conformément aux articles 12 à 23 du RGPD et aux articles 32 à 37 de la loi fédérale sur la protection des données (BDSG).
-
- 3.6 Intérêts légitimes
- Un intérêt légitime du responsable du traitement conformément à l’article 6 (1) (f) RGPD existe dans les cas suivants :
- Des mesures sont prises pour la sécurité de l’information, incluant à la fois des mesures techniques et organisationnelles préventives ainsi que la gestion des incidents. L’objectif est d’évaluer et d’éviter les dommages potentiels pour l’entreprise, les personnes concernées par le traitement des données et les utilisateurs des services de confiance.
-
- 3.7 Nécessité des données
- Les données collectées représentent le minimum nécessaire pour la signature numérique. Sans les données mentionnées en 3.1, le service ne peut pas être opéré.
- Il est particulièrement important de fournir un numéro de mobile ou un numéro fixe allemand, car cela est utilisé pour l’authentification et le déclenchement de la signature comme second facteur. Sans ce mécanisme de sécurité, le service ne peut être fourni.
-
- 3.8 Transfert de données
- Aucun transfert systématique de données n’a lieu.
- Les données ne sont transmises au fabricant pour le support que dans des cas exceptionnels. Un accord de traitement des données (DPA) valide existe avec le fabricant, garantissant la sécurité et l’intégrité de la gestion de vos données.
-
-
-
- 4. Utilisation des cookies
-
- Lors de la visite de certaines pages, des cookies temporaires sont utilisés, nécessaires à la fourniture technique des services. Ces cookies de session ne contiennent aucune donnée personnelle et sont automatiquement supprimés à la fin de la session. Aucune méthode telle que les applets Java ou les contrôles Active-X, pouvant suivre le comportement de l’utilisateur, n’est utilisée.
-
-
-
-
- 5. Droits des personnes concernées
-
- Si vous avez des questions concernant vos données ou souhaitez demander une correction, suppression ou limitation du traitement, veuillez envoyer votre demande par courrier ou e-mail à l’adresse indiquée ci-dessus. Cela s’applique également si vous souhaitez vous opposer au traitement conformément à l’article 21 RGPD ou demander la portabilité des données.
-
-
- Si vous avez des questions ou des plaintes concernant une procédure, vous pouvez également nous contacter via les coordonnées fournies. Si vous avez d’autres motifs de plainte, vous pouvez contacter notre autorité de contrôle. Vous pouvez vérifier quelle autorité de contrôle est compétente pour vous ici :
- Laender-node.html
-
-
-
-
-
\ No newline at end of file
diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/fake-data/annotations.json b/EnvelopeGenerator.ReceiverUI/wwwroot/fake-data/annotations.json
deleted file mode 100644
index 333c83b2..00000000
--- a/EnvelopeGenerator.ReceiverUI/wwwroot/fake-data/annotations.json
+++ /dev/null
@@ -1,20 +0,0 @@
-[
- {
- "id": 1,
- "page": 1,
- "x": 390.0,
- "y": 380.0
- },
- {
- "id": 2,
- "page": 2,
- "x": 390.0,
- "y": 680.0
- },
- {
- "id": 3,
- "page": 3,
- "x": 390.0,
- "y": 980.0
- }
-]
diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/fonts/opensans.ttf b/EnvelopeGenerator.ReceiverUI/wwwroot/fonts/opensans.ttf
deleted file mode 100644
index ac587b48..00000000
Binary files a/EnvelopeGenerator.ReceiverUI/wwwroot/fonts/opensans.ttf and /dev/null differ
diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/images/banner.png b/EnvelopeGenerator.ReceiverUI/wwwroot/images/banner.png
deleted file mode 100644
index 2da0ee1d..00000000
Binary files a/EnvelopeGenerator.ReceiverUI/wwwroot/images/banner.png and /dev/null differ
diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/images/sad.svg b/EnvelopeGenerator.ReceiverUI/wwwroot/images/sad.svg
deleted file mode 100644
index aab2fdda..00000000
--- a/EnvelopeGenerator.ReceiverUI/wwwroot/images/sad.svg
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/index.html b/EnvelopeGenerator.ReceiverUI/wwwroot/index.html
deleted file mode 100644
index ec48b8fa..00000000
--- a/EnvelopeGenerator.ReceiverUI/wwwroot/index.html
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
-
-
-
-
-
-
-
- EnvelopeGenerator.ReceiverUI
-
-
-
-
-
-
-
-
-
-
-
-
-
-
EnvelopeGenerator.ReceiverUI
-
Loading...
-
-
-
-
- An unhandled error has occurred.
-
Reload
-
X
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/js/pdf-viewer.js b/EnvelopeGenerator.ReceiverUI/wwwroot/js/pdf-viewer.js
deleted file mode 100644
index 20998519..00000000
--- a/EnvelopeGenerator.ReceiverUI/wwwroot/js/pdf-viewer.js
+++ /dev/null
@@ -1,1138 +0,0 @@
-// PDF.js Viewer for Blazor WASM
-window.pdfViewer = {
- pdfDoc: null,
- pageNum: 1,
- pageRendering: false,
- pageNumPending: null,
- scale: 1.5,
- canvas: null,
- ctx: null,
- totalPages: 0,
- currentRenderTask: null,
- dotNetReference: null,
- wheelEventAttached: false,
- _renderLock: false, // Lock to prevent concurrent renders
-
- // Quality options (configurable from appsettings.json)
- qualityOptions: {
- thumbnailBaseScale: 0.75,
- thumbnailEnableHiDPI: true,
- thumbnailMaxDPR: 2.0,
- mainCanvasEnableHiDPI: true,
- mainCanvasMaxDPR: 2.0,
- enableSmoothZoom: true,
- zoomTransitionDuration: 150,
- renderingOpacity: 0.85,
- zoomStepPercentage: 5
- },
-
- setQualityOptions(options) {
- this.qualityOptions = { ...this.qualityOptions, ...options };
-
- // Apply CSS variables for dynamic styling
- document.documentElement.style.setProperty('--zoom-transition-duration', `${options.zoomTransitionDuration}ms`);
- document.documentElement.style.setProperty('--rendering-opacity', options.renderingOpacity);
- },
-
- async initialize(canvasId, pdfDataUrl, dotNetRef) {
- try {
- this.dotNetReference = dotNetRef;
-
- if (typeof window.pdfjsLib === 'undefined') {
- await this.waitForPdfJs();
- }
-
- const pdfjsLib = window.pdfjsLib;
- pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
-
- this.canvas = document.getElementById(canvasId);
- if (!this.canvas) {
- return false;
- }
-
- this.ctx = this.canvas.getContext('2d');
- this.attachWheelEvent();
-
- const uint8Array = this.base64ToUint8Array(pdfDataUrl);
- const loadingTask = pdfjsLib.getDocument({ data: uint8Array });
- this.pdfDoc = await loadingTask.promise;
- this.totalPages = this.pdfDoc.numPages;
-
- await this.renderPage(this.pageNum);
- return true;
- } catch (error) {
- console.error('PDF viewer initialization failed:', error);
- return false;
- }
- },
-
- attachWheelEvent() {
- if (this.wheelEventAttached) return;
-
- // Attach to the entire document body for global zoom control
- document.body.addEventListener('wheel', (e) => {
- // Check if Ctrl key is pressed (zoom gesture)
- if (e.ctrlKey || e.metaKey) {
- e.preventDefault();
-
- const step = this.qualityOptions.zoomStepPercentage / 100; // Convert to decimal
-
- if (e.deltaY < 0) {
- // Scroll up = Zoom In
- this.scale = Math.min(this.scale + step, 3.0);
- this.queueRenderPage(this.pageNum);
- if (this.dotNetReference) {
- this.dotNetReference.invokeMethodAsync('OnZoomChanged', this.scale);
- }
- } else {
- // Scroll down = Zoom Out
- this.scale = Math.max(this.scale - step, 0.5);
- this.queueRenderPage(this.pageNum);
- if (this.dotNetReference) {
- this.dotNetReference.invokeMethodAsync('OnZoomChanged', this.scale);
- }
- }
- }
- }, { passive: false });
-
- this.wheelEventAttached = true;
- },
-
- async waitForPdfJs() {
- for (let i = 0; i < 50; i++) {
- if (typeof window.pdfjsLib !== 'undefined') {
- return;
- }
- await new Promise(resolve => setTimeout(resolve, 100));
- }
- throw new Error('PDF.js failed to load after 5 seconds');
- },
-
- base64ToUint8Array(base64) {
- // Remove data URL prefix if present
- const base64String = base64.includes(',') ? base64.split(',')[1] : base64;
- const raw = atob(base64String);
- const uint8Array = new Uint8Array(raw.length);
- for (let i = 0; i < raw.length; i++) {
- uint8Array[i] = raw.charCodeAt(i);
- }
- return uint8Array;
- },
-
- async renderPage(num) {
- // CRITICAL: Single render at a time - use a lock
- if (this._renderLock) {
- // Another render is in progress, queue it
- this.pageNumPending = num;
- return;
- }
-
- this._renderLock = true;
-
- // Cancel any existing render task
- if (this.currentRenderTask) {
- try {
- this.currentRenderTask.cancel();
- await new Promise(resolve => setTimeout(resolve, 100));
- } catch (e) {
- // Ignore cancellation errors
- }
- this.currentRenderTask = null;
- }
-
- this.pageRendering = true;
-
- // Add rendering class for smooth transition (if enabled)
- if (this.qualityOptions.enableSmoothZoom) {
- this.canvas.classList.add('rendering');
- }
-
- try {
- // Get scroll container
- const container = this.canvas.closest('.pdf-canvas-wrapper');
-
- // Store scroll position and viewport center BEFORE rendering
- let scrollLeft = 0, scrollTop = 0;
- let centerX = 0, centerY = 0;
- let oldWidth = this.canvas.width;
- let oldHeight = this.canvas.height;
-
- if (container) {
- scrollLeft = container.scrollLeft;
- scrollTop = container.scrollTop;
- centerX = scrollLeft + container.clientWidth / 2;
- centerY = scrollTop + container.clientHeight / 2;
- }
-
- const page = await this.pdfDoc.getPage(num);
-
- // HiDPI support for main canvas (configurable)
- const dpr = this.qualityOptions.mainCanvasEnableHiDPI
- ? Math.min(window.devicePixelRatio || 1, this.qualityOptions.mainCanvasMaxDPR)
- : 1.0;
- const viewport = page.getViewport({ scale: this.scale * dpr });
-
- // Set internal canvas resolution (high quality)
- this.canvas.width = viewport.width;
- this.canvas.height = viewport.height;
-
- // Set CSS display size (visual size)
- this.canvas.style.width = `${viewport.width / dpr}px`;
- this.canvas.style.height = `${viewport.height / dpr}px`;
-
- const renderContext = {
- canvasContext: this.ctx,
- viewport: viewport
- };
-
- // Enable high-quality rendering
- this.ctx.imageSmoothingEnabled = true;
- this.ctx.imageSmoothingQuality = 'high';
-
- // Start new render task
- this.currentRenderTask = page.render(renderContext);
- await this.currentRenderTask.promise;
-
- // Render text layer for copy-paste functionality
- await this.renderTextLayer(page, viewport, dpr);
-
- // Restore viewport center position AFTER rendering
- if (container && oldWidth > 0 && oldHeight > 0) {
- const scaleX = this.canvas.width / oldWidth;
- const scaleY = this.canvas.height / oldHeight;
-
- const newCenterX = centerX * scaleX;
- const newCenterY = centerY * scaleY;
-
- container.scrollLeft = newCenterX - container.clientWidth / 2;
- container.scrollTop = newCenterY - container.clientHeight / 2;
- }
-
- // Remove rendering class after completion
- if (this.qualityOptions.enableSmoothZoom) {
- this.canvas.classList.remove('rendering');
- }
-
- this.currentRenderTask = null;
- this.pageRendering = false;
-
- } catch (error) {
- if (error.name !== 'RenderingCancelledException') {
- console.error('Render error:', error);
- }
- this.canvas.classList.remove('rendering');
- this.currentRenderTask = null;
- this.pageRendering = false;
- } finally {
- // Always release lock
- this._renderLock = false;
-
- // Process pending render
- if (this.pageNumPending !== null) {
- const pendingPage = this.pageNumPending;
- this.pageNumPending = null;
- this.renderPage(pendingPage);
- }
- }
- },
-
- async renderTextLayer(page, viewport, dpr) {
- try {
- const textLayerDiv = document.getElementById('pdf-text-layer');
- if (!textLayerDiv) {
- return;
- }
-
- // Clear previous text layer
- textLayerDiv.innerHTML = '';
-
- // Set text layer dimensions to match canvas display size
- textLayerDiv.style.width = `${viewport.width / dpr}px`;
- textLayerDiv.style.height = `${viewport.height / dpr}px`;
-
- // Set --scale-factor CSS variable required by PDF.js
- textLayerDiv.style.setProperty('--scale-factor', this.scale);
-
- // Get text content from PDF
- const textContent = await page.getTextContent();
-
- // Create viewport for text layer (without DPR scaling for positioning)
- const textViewport = page.getViewport({ scale: this.scale });
-
- // Render text layer using PDF.js built-in function
- const pdfjsLib = window.pdfjsLib;
- await pdfjsLib.renderTextLayer({
- textContentSource: textContent,
- container: textLayerDiv,
- viewport: textViewport,
- textDivs: []
- }).promise;
-
- } catch (error) {
- // Silently handle text layer errors (non-critical feature)
- }
- },
-
- queueRenderPage(num) {
- // Always use pending mechanism to avoid race conditions
- if (this.pageRendering || this.currentRenderTask) {
- this.pageNumPending = num;
- } else {
- this.renderPage(num);
- }
- },
-
- nextPage() {
- if (this.pageNum >= this.totalPages) {
- return false;
- }
- this.pageNum++;
- this.queueRenderPage(this.pageNum);
- return true;
- },
-
- previousPage() {
- if (this.pageNum <= 1) {
- return false;
- }
- this.pageNum--;
- this.queueRenderPage(this.pageNum);
- return true;
- },
-
- goToPage(num) {
- if (num < 1 || num > this.totalPages) {
- return false;
- }
- this.pageNum = num;
- this.queueRenderPage(this.pageNum);
- return true;
- },
-
- async zoomIn() {
- const step = this.qualityOptions.zoomStepPercentage / 100;
- this.scale = Math.min(this.scale + step, 3.0);
- await this.renderPage(this.pageNum);
- },
-
- async zoomOut() {
- const step = this.qualityOptions.zoomStepPercentage / 100;
- this.scale = Math.max(this.scale - step, 0.5);
- await this.renderPage(this.pageNum);
- },
-
- setScale(scale) {
- if (scale >= 0.5 && scale <= 3.0) {
- this.scale = scale;
- this.queueRenderPage(this.pageNum);
- }
- },
-
- async fitToWidth() {
- const container = this.canvas.closest('.pdf-frame');
- if (!container || !this.pdfDoc) return;
-
- try {
- const page = await this.pdfDoc.getPage(this.pageNum);
- const viewport = page.getViewport({ scale: 1.0 });
-
- const containerWidth = container.clientWidth - 80;
- const optimalScale = containerWidth / viewport.width;
-
- this.scale = Math.min(Math.max(optimalScale, 0.5), 3.0);
- this.queueRenderPage(this.pageNum);
- } catch (error) {
- console.error('Error fitting to width:', error);
- }
- },
-
- async renderThumbnail(pageNum, canvasId) {
- if (!this.pdfDoc) {
- return;
- }
-
- try {
- // Wait for canvas to be in DOM
- let canvas = document.getElementById(canvasId);
- let retries = 0;
- while (!canvas && retries < 10) {
- await new Promise(resolve => setTimeout(resolve, 100));
- canvas = document.getElementById(canvasId);
- retries++;
- }
-
- if (!canvas) {
- return;
- }
-
- // Check if canvas is already being used by a render task
- if (canvas._renderTask) {
- try {
- await canvas._renderTask;
- } catch (e) {
- // Ignore cancellation errors
- }
- }
-
- const page = await this.pdfDoc.getPage(pageNum);
-
- // High-quality rendering with HiDPI support (configurable)
- const dpr = this.qualityOptions.thumbnailEnableHiDPI
- ? Math.min(window.devicePixelRatio || 1, this.qualityOptions.thumbnailMaxDPR)
- : 1.0;
- const baseScale = this.qualityOptions.thumbnailBaseScale;
- const scale = baseScale * dpr;
-
- const viewport = page.getViewport({ scale: scale });
-
- // Set actual canvas pixel dimensions (internal resolution)
- canvas.width = viewport.width;
- canvas.height = viewport.height;
-
- // Remove any inline styles - let CSS handle display size
- canvas.style.width = '';
- canvas.style.height = '';
-
- const ctx = canvas.getContext('2d');
-
- // Enable maximum quality rendering
- ctx.imageSmoothingEnabled = true;
- ctx.imageSmoothingQuality = 'high';
-
- const renderContext = {
- canvasContext: ctx,
- viewport: viewport
- };
-
- // Store render task on canvas to track active renders
- const renderTask = page.render(renderContext);
- canvas._renderTask = renderTask.promise;
-
- await renderTask.promise;
-
- // Clear the task when done
- delete canvas._renderTask;
- } catch (error) {
- // Silently handle thumbnail errors (non-critical)
- }
- },
-
- getCurrentPage() {
- return this.pageNum;
- },
-
- getTotalPages() {
- return this.totalPages;
- },
-
- getScale() {
- return this.scale;
- },
-
- dispose() {
- if (this.wheelEventAttached) {
- this.wheelEventAttached = false;
- this.dotNetReference = null;
- }
- this.detachResizeListeners();
- },
-
- // Resizable splitter functionality
- isResizing: false,
- resizeMouseMoveHandler: null,
- resizeMouseUpHandler: null,
-
- attachResizeListeners(dotNetRef) {
- this.dotNetReference = dotNetRef;
-
- this.resizeMouseMoveHandler = (e) => {
- if (this.isResizing && this.dotNetReference) {
- this.dotNetReference.invokeMethodAsync('OnSplitterMouseMove', e.clientX);
- }
- };
-
- this.resizeMouseUpHandler = () => {
- if (this.isResizing && this.dotNetReference) {
- this.isResizing = false;
- this.dotNetReference.invokeMethodAsync('OnSplitterMouseUp');
- }
- };
-
- document.addEventListener('mousemove', this.resizeMouseMoveHandler);
- document.addEventListener('mouseup', this.resizeMouseUpHandler);
- },
-
- detachResizeListeners() {
- if (this.resizeMouseMoveHandler) {
- document.removeEventListener('mousemove', this.resizeMouseMoveHandler);
- this.resizeMouseMoveHandler = null;
- }
- if (this.resizeMouseUpHandler) {
- document.removeEventListener('mouseup', this.resizeMouseUpHandler);
- this.resizeMouseUpHandler = null;
- }
- this.isResizing = false;
- },
-
- startResize() {
- this.isResizing = true;
- },
-
- // Signature button functionality
- signatureButtons: [],
- appliedSignatures: [], // Track which signatures have been applied (ID list)
- appliedSignatureElements: [], // ✅ NEW: Track applied signature DOM elements
- _lastViewedSignatureId: null, // Track last viewed signature for navigation
-
- /**
- * Gets signature navigation state (for toolbar display)
- * @returns {object} { total, signed, unsigned, currentIndex, canGoPrev, canGoNext }
- */
- getSignatureNavState() {
- // Return empty state if global signature list is empty
- if (!this._allSignatures || this._allSignatures.length === 0) {
- return {
- total: 0,
- signed: 0,
- unsigned: 0,
- currentIndex: -1,
- canGoPrev: false,
- canGoNext: false
- };
- }
-
-
- // Count signatures across ALL pages (from database global list)
- const total = this._allSignatures.length; // Global: Total signature count
- const signed = this.appliedSignatures.length; // Signed signatures
- const unsigned = total - signed; // Calculated: Unsigned signatures
-
- // Find the current viewed signature index
- let currentIndex = 0;
- if (this._lastViewedSignatureId) {
- const index = this._allSignatures.findIndex(s => s.id === this._lastViewedSignatureId);
- currentIndex = index !== -1 ? index + 1 : 0; // 1-based index (for user display)
- }
-
- return {
- total: total,
- signed: signed,
- unsigned: unsigned,
- currentIndex: currentIndex, // Current signature index (1-5 range)
- canGoPrev: total > 0, // Always active (if signatures exist)
- canGoNext: total > 0 // Always active (if signatures exist)
- };
- },
-
- /**
- * Navigates to the next unsigned signature button.
- * Scrolls to button position and changes page if necessary.
- * Cross-page navigation: searches ALL pages for next unsigned signature.
- */
- async goToNextSignature(dotNetRef) {
- // Exit if no global signature list
- if (!this._allSignatures || this._allSignatures.length === 0) {
- return false;
- }
-
- // Find current displayed signature's index
- let currentIndex = -1;
- if (this._lastViewedSignatureId) {
- currentIndex = this._allSignatures.findIndex(s => s.id === this._lastViewedSignatureId);
- }
-
- // Get next signature (regardless of signed status)
- let nextIndex = currentIndex + 1;
-
- // Infinite loop: If at last signature, return to first
- if (nextIndex >= this._allSignatures.length) {
- nextIndex = 0; // Return to first signature
- }
-
- const nextSignature = this._allSignatures[nextIndex];
-
- // Change page if signature is on different page
- if (nextSignature.page !== this.pageNum) {
- // Change page
- this.pageNum = nextSignature.page;
- this.queueRenderPage(this.pageNum);
-
- // Wait until render completes
- let waitCount = 0;
- while (this.pageRendering && waitCount < 20) {
- await new Promise(resolve => setTimeout(resolve, 100));
- waitCount++;
- }
-
- // Notify Blazor - re-render signature buttons
- if (dotNetRef) {
- await dotNetRef.invokeMethodAsync('OnPageChangedBySignatureNav', this.pageNum);
- }
-
- // Wait for buttons to be added to DOM
- await new Promise(resolve => setTimeout(resolve, 150));
- }
-
-
- // Save last viewed signature
- this._lastViewedSignatureId = nextSignature.id;
-
- // Check if signature is signed
- const isApplied = this.appliedSignatures.some(s => s.id === nextSignature.id);
-
- if (isApplied) {
- // Signed - find overlay container and scroll
- const container = document.querySelector(`.applied-signature[data-signature-id="${nextSignature.id}"]`);
- if (container) {
- this.scrollToElement(container);
- }
- } else {
- // Unsigned - find button and scroll
- const button = this.signatureButtons.find(btn =>
- parseInt(btn.getAttribute('data-signature-id')) === nextSignature.id
- );
- if (button) {
- this.scrollToButton(button);
- }
- }
-
- // Update counter (notify Blazor)
- if (dotNetRef) {
- dotNetRef.invokeMethodAsync('OnSignatureNavChanged');
- }
-
- return true;
- },
-
- /**
- * Navigates to the previous signature (last applied one).
- * Scrolls to signature position and changes page if necessary.
- */
- async goToPreviousSignature(dotNetRef) {
- if (!this._allSignatures || this._allSignatures.length === 0) {
- return false;
- }
-
- // Find current displayed signature's index
- let currentIndex = this._allSignatures.length; // Default: after last signature
- if (this._lastViewedSignatureId) {
- currentIndex = this._allSignatures.findIndex(s => s.id === this._lastViewedSignatureId);
- }
-
- // Get previous signature
- let prevIndex = currentIndex - 1;
-
- // Infinite loop: If at first signature, go to last
- if (prevIndex < 0) {
- prevIndex = this._allSignatures.length - 1; // Go to last signature
- }
-
- const prevSignature = this._allSignatures[prevIndex];
-
- // Change page if needed
- if (prevSignature.page !== this.pageNum) {
- // Change page
- this.pageNum = prevSignature.page;
- this.queueRenderPage(this.pageNum);
-
- // Wait until render completes
- let waitCount = 0;
- while (this.pageRendering && waitCount < 20) {
- await new Promise(resolve => setTimeout(resolve, 100));
- waitCount++;
- }
-
- // Notify Blazor - re-render signature buttons
- if (dotNetRef) {
- await dotNetRef.invokeMethodAsync('OnPageChangedBySignatureNav', this.pageNum);
- }
-
- // Wait for DOM update
- await new Promise(resolve => setTimeout(resolve, 150));
- }
-
- // Save last viewed signature
- this._lastViewedSignatureId = prevSignature.id;
-
- // Check if signature is signed
- const isApplied = this.appliedSignatures.some(s => s.id === prevSignature.id);
-
- if (isApplied) {
- // Signed - find overlay container and scroll
- const container = document.querySelector(`.applied-signature[data-signature-id="${prevSignature.id}"]`);
- if (container) {
- this.scrollToElement(container);
- }
- } else {
- // Unsigned - find button and scroll
- const button = this.signatureButtons.find(btn =>
- parseInt(btn.getAttribute('data-signature-id')) === prevSignature.id
- );
- if (button) {
- this.scrollToButton(button);
- }
- }
-
- // Notify Blazor
- if (dotNetRef) {
- dotNetRef.invokeMethodAsync('OnSignatureNavChanged');
- }
-
- return true;
- },
-
- /**
- * Scrolls to center a button in the viewport
- */
- scrollToButton(button) {
- const wrapper = this.canvas.closest('.pdf-canvas-wrapper');
- if (!wrapper) return;
-
- const buttonRect = button.getBoundingClientRect();
- const wrapperRect = wrapper.getBoundingClientRect();
-
- // Calculate scroll to center button
- const scrollLeft = wrapper.scrollLeft + buttonRect.left - wrapperRect.left - (wrapperRect.width / 2) + (buttonRect.width / 2);
- const scrollTop = wrapper.scrollTop + buttonRect.top - wrapperRect.top - (wrapperRect.height / 2) + (buttonRect.height / 2);
-
- wrapper.scrollTo({
- left: scrollLeft,
- top: scrollTop,
- behavior: 'smooth'
- });
- },
-
- /**
- * Scrolls to center an element in the viewport
- */
- scrollToElement(element) {
- const wrapper = this.canvas.closest('.pdf-canvas-wrapper');
- if (!wrapper) return;
-
- const elemRect = element.getBoundingClientRect();
- const wrapperRect = wrapper.getBoundingClientRect();
-
- const scrollLeft = wrapper.scrollLeft + elemRect.left - wrapperRect.left - (wrapperRect.width / 2) + (elemRect.width / 2);
- const scrollTop = wrapper.scrollTop + elemRect.top - wrapperRect.top - (wrapperRect.height / 2) + (elemRect.height / 2);
-
- wrapper.scrollTo({
- left: scrollLeft,
- top: scrollTop,
- behavior: 'smooth'
- });
- },
-
- /**
- * Renders clickable signature buttons on the PDF canvas.
- * @param {Array} signatures - Array of SignatureDto objects with x, y coordinates in PDF POINTS
- * @param {number} currentPageNum - Current page number (1-based)
- * @param {object} dotNetRef - .NET reference for callbacks
- */
- async renderSignatureButtons(signatures, currentPageNum, dotNetRef) {
- // Clear existing buttons (NOT applied signatures!)
- this.clearSignatureButtons();
-
- if (!this.pdfDoc || !signatures || signatures.length === 0) {
- return;
- }
-
- this.dotNetReference = dotNetRef;
- this._allSignatures = signatures; // Store for navigation
-
- try {
- // CRITICAL: Filter OUT already applied signatures!
- const appliedIds = new Set(this.appliedSignatures.map(s => s.id));
- const pageSignatures = signatures.filter(sig =>
- sig.page === currentPageNum && !appliedIds.has(sig.id) // ? Skip applied ones!
- );
-
- if (pageSignatures.length === 0) {
- return;
- }
-
- // Get current page and viewport
- const page = await this.pdfDoc.getPage(currentPageNum);
- const dpr = this.qualityOptions.mainCanvasEnableHiDPI
- ? Math.min(window.devicePixelRatio || 1, this.qualityOptions.mainCanvasMaxDPR)
- : 1.0;
- const viewport = page.getViewport({ scale: this.scale * dpr });
-
- // Get signature layer container
- const signatureLayer = document.getElementById('pdf-signature-layer');
- if (!signatureLayer) {
- console.warn('Signature layer not found');
- return;
- }
-
- // Set signature layer dimensions to match canvas display size
- signatureLayer.style.width = `${viewport.width / dpr}px`;
- signatureLayer.style.height = `${viewport.height / dpr}px`;
-
- // Update applied signature coordinates for current zoom level
- this.updateAppliedSignaturePositions(signatureLayer, currentPageNum);
-
- // Create button for each UNSIGNED signature
- pageSignatures.forEach(sig => {
- // Coordinates are in PDF POINTS - convert to display pixels
- const xPx = (sig.x * this.scale);
- const yPx = (sig.y * this.scale);
-
- // ✅ FIXED: Scale button size proportionally with zoom
- const baseScale = 1.5; // Reference scale (initial load)
- const scaleFactor = this.scale / baseScale;
- const baseWidth = 150;
- const baseHeight = 60;
- const baseFontSize = 18;
- const baseIconSize = 24;
-
- const scaledWidth = baseWidth * scaleFactor;
- const scaledHeight = baseHeight * scaleFactor;
- const scaledFontSize = Math.max(baseFontSize * scaleFactor, 10); // Min 10px
- const scaledIconSize = baseIconSize * scaleFactor;
-
- // Create button element
- const button = document.createElement('button');
- button.className = 'signature-button';
- button.setAttribute('data-signature-id', sig.id);
- button.setAttribute('type', 'button');
- button.setAttribute('tabindex', '0');
- button.style.position = 'absolute';
- button.style.left = `${xPx}px`;
- button.style.top = `${yPx}px`;
- button.style.width = `${scaledWidth}px`; // ✅ Scaled
- button.style.height = `${scaledHeight}px`; // ✅ Scaled
- button.style.backgroundColor = '#4F46E5';
- button.style.color = 'white';
- button.style.border = 'none';
- button.style.borderRadius = '8px';
- button.style.cursor = 'pointer';
- button.style.fontSize = '16px';
- button.style.fontWeight = '600';
- button.style.display = 'flex';
- button.style.flexDirection = 'column';
- button.style.alignItems = 'center';
- button.style.justifyContent = 'center';
- button.style.gap = '4px';
- button.style.boxShadow = '0 2px 8px rgba(0,0,0,0.15)';
- button.style.transition = 'all 0.2s ease';
- button.style.zIndex = '100';
-
- // Add text
- const textDiv = document.createElement('div');
- textDiv.textContent = 'Unterschreiben';
- textDiv.style.fontSize = `${scaledFontSize}px`; // ✅ Scaled
- textDiv.style.fontWeight = '700';
-
- // Add SVG icon
- const svgNS = 'http://www.w3.org/2000/svg';
- const svg = document.createElementNS(svgNS, 'svg');
- svg.setAttribute('width', scaledIconSize); // ✅ Scaled
- svg.setAttribute('height', scaledIconSize); // ✅ Scaled
- svg.setAttribute('viewBox', '0 8 32 36');
- svg.setAttribute('fill', 'none');
- svg.style.filter = 'drop-shadow(0 1px 2px rgba(0,0,0,0.2))';
-
- const path = document.createElementNS(svgNS, 'path');
- path.setAttribute('fill-rule', 'evenodd');
- path.setAttribute('clip-rule', 'evenodd');
- path.setAttribute('d', 'M25.061 6.90625L23.7115 8.25503C23.2861 8.05188 22.8241 7.9503 22.3621 7.9503C21.5605 7.9503 20.7589 8.25613 20.1483 8.86778L8.18147 20.8336L6.70565 26.7379H6.70557V27.7817H26.5372V26.7379H6.70671L12.6102 25.2623L24.576 13.2955C25.5404 12.3318 25.7445 10.8952 25.1882 9.73146L26.5369 8.38214L25.061 6.90625ZM23.174 10.27C22.9569 10.0539 22.6688 9.93388 22.362 9.93388C22.0551 9.93388 21.767 10.0539 21.5499 10.27L13.5323 18.2876L15.1564 19.9117L23.174 11.8941C23.6218 11.4463 23.6218 10.7177 23.174 10.27ZM14.4922 20.5759L12.868 18.9518L9.97241 21.8475L9.43069 24.0133L11.5965 23.4716L14.4922 20.5759Z');
- path.setAttribute('fill', 'white');
-
- svg.appendChild(path);
- button.appendChild(textDiv);
- button.appendChild(svg);
-
- // Add hover effect
- button.addEventListener('mouseenter', () => {
- button.style.backgroundColor = '#4338CA';
- button.style.transform = 'scale(1.05)';
- button.style.boxShadow = '0 4px 12px rgba(79, 70, 229, 0.4)';
- });
- button.addEventListener('mouseleave', () => {
- button.style.backgroundColor = '#4F46E5';
- button.style.transform = 'scale(1)';
- button.style.boxShadow = '0 2px 8px rgba(0,0,0,0.15)';
- });
-
- // Add click handler
- button.addEventListener('click', () => {
- if (this.dotNetReference) {
- this.dotNetReference.invokeMethodAsync('OnSignatureButtonClick', sig.id);
- }
- });
-
- signatureLayer.appendChild(button);
- this.signatureButtons.push(button);
- });
-
- } catch (error) {
- console.error('Error rendering signature buttons:', error);
- }
- },
-
- /**
- * Scales an applied signature container based on current zoom level
- * @param {HTMLElement} container - Applied signature container
- * @param {number} currentScale - Current PDF zoom scale
- */
- scaleAppliedSignature(container, currentScale) {
- const baseScale = 1.5; // Reference scale (initial load)
- const scaleFactor = currentScale / baseScale;
-
- // Scale width
- const baseWidth = parseInt(container.getAttribute('data-base-width') || 230);
- container.style.width = `${baseWidth * scaleFactor}px`;
-
- // Scale padding
- const basePadding = parseInt(container.getAttribute('data-base-padding') || 12);
- container.style.padding = `${basePadding * scaleFactor}px`;
-
- // Scale border radius (subtle detail)
- const baseBorderRadius = parseInt(container.getAttribute('data-base-border-radius') || 6);
- container.style.borderRadius = `${baseBorderRadius * scaleFactor}px`;
-
- // Scale font size (with min 6px for readability)
- const infoContainer = container.querySelector('.signature-info-text');
- if (infoContainer) {
- const baseFontSize = parseInt(infoContainer.getAttribute('data-base-font-size') || 9);
- const scaledFontSize = Math.max(baseFontSize * scaleFactor, 6);
- infoContainer.style.fontSize = `${scaledFontSize}px`;
- }
-
- // Scale image max height
- const baseImgHeight = parseInt(container.getAttribute('data-base-img-height') || 70);
- const img = container.querySelector('img');
- if (img) {
- img.style.maxHeight = `${baseImgHeight * scaleFactor}px`;
- }
-
- // Scale separator line margin
- const baseSeparatorMargin = 6;
- const separators = container.querySelectorAll('div[style*="border-top"]');
- separators.forEach(sep => {
- sep.style.marginTop = `${baseSeparatorMargin * scaleFactor}px`;
- sep.style.marginBottom = `${(baseSeparatorMargin + 2) * scaleFactor}px`;
- });
- },
-
- /**
- * Updates applied signature positions based on current zoom level
- * @param {HTMLElement} signatureLayer - Signature layer container
- * @param {number} currentPageNum - Current page number
- */
- updateAppliedSignaturePositions(signatureLayer, currentPageNum) {
- if (!signatureLayer || !this._allSignatures) return;
-
- const appliedContainers = signatureLayer.querySelectorAll('.applied-signature');
- appliedContainers.forEach(container => {
- const signatureId = parseInt(container.getAttribute('data-signature-id'));
- const signature = this._allSignatures.find(s => s.id === signatureId);
-
- if (signature) {
- // ✅ Position calculation (same as renderSignatureButtons)
- const xPx = signature.x * this.scale;
- const yPx = signature.y * this.scale;
-
- container.style.left = `${xPx}px`;
- container.style.top = `${yPx}px`;
-
- // ✅ FIXED: Apply comprehensive scaling using helper method
- this.scaleAppliedSignature(container, this.scale);
-
- // Show/hide based on current page
- container.style.display = (signature.page === currentPageNum) ? '' : 'none';
- }
- });
- },
-
- /**
- * Clears all signature buttons from the canvas.
- * Also hides applied signatures that don't belong to current page.
- */
- clearSignatureButtons() {
- // Remove unsigned signature buttons
- this.signatureButtons.forEach(button => {
- if (button.parentNode) {
- button.parentNode.removeChild(button);
- }
- });
- this.signatureButtons = [];
-
- // ✅ FIXED: Update applied signatures (position + scaling)
- this.appliedSignatureElements.forEach(container => {
- const signatureId = parseInt(container.getAttribute('data-signature-id'));
- const signature = this._allSignatures?.find(s => s.id === signatureId);
-
- if (signature) {
- // Update position
- const xPx = signature.x * this.scale;
- const yPx = signature.y * this.scale;
- container.style.left = `${xPx}px`;
- container.style.top = `${yPx}px`;
-
- // Update scaling
- this.scaleAppliedSignature(container, this.scale);
-
- // Show/hide based on current page
- container.style.display = (signature.page === this.pageNum) ? '' : 'none';
- }
- });
- },
-
- /**
- * Applies a signature to a specific signature field, removing the button and rendering the signature.
- * German standard: Signature image + Name, Position, Place, Date
- * @param {number} signatureId - ID of the signature field
- * @param {string} signatureDataUrl - Base64 PNG data URL of signature
- * @param {string} fullName - Signer's full name
- * @param {string} position - Signer's position (optional, can be empty)
- * @param {string} place - Signing place
- */
- async applySignature(signatureId, signatureDataUrl, fullName, position, place) {
- try {
- // Find and remove the button
- const buttonIndex = this.signatureButtons.findIndex(btn => {
- return btn.getAttribute('data-signature-id') == signatureId;
- });
-
- if (buttonIndex === -1) {
- console.warn(`Signature button #${signatureId} not found`);
- return;
- }
-
- const button = this.signatureButtons[buttonIndex];
- const signatureLayer = document.getElementById('pdf-signature-layer');
-
- if (!signatureLayer) {
- console.error('Signature layer not found');
- return;
- }
-
- // Get button position before removing it
- const left = button.style.left;
- const top = button.style.top;
-
- // Find signature data for tracking
- const signature = this._allSignatures?.find(s => s.id === signatureId);
-
- // Remove button
- if (button.parentNode) {
- button.parentNode.removeChild(button);
- }
- this.signatureButtons.splice(buttonIndex, 1);
-
- // Track applied signature
- if (signature) {
- this.appliedSignatures.push({
- id: signatureId,
- page: signature.page
- });
- }
-
- // Create signature container
- const signatureContainer = document.createElement('div');
- signatureContainer.className = 'applied-signature';
- signatureContainer.setAttribute('data-signature-id', signatureId);
-
- // ✅ FIXED: Store base values for scaling
- signatureContainer.setAttribute('data-base-width', '230');
- signatureContainer.setAttribute('data-base-padding', '12');
- signatureContainer.setAttribute('data-base-font-size', '9');
- signatureContainer.setAttribute('data-base-img-height', '70');
- signatureContainer.setAttribute('data-base-border-radius', '6');
-
- signatureContainer.style.position = 'absolute';
- signatureContainer.style.left = left;
- signatureContainer.style.top = top;
- signatureContainer.style.width = '230px';
- signatureContainer.style.backgroundColor = '#f8f9fa';
- signatureContainer.style.border = '1px solid #dee2e6';
- signatureContainer.style.borderRadius = '6px';
- signatureContainer.style.padding = '12px';
- signatureContainer.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)';
- signatureContainer.style.fontFamily = "'Open Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
-
- // Signature image
- const signatureImg = document.createElement('img');
- signatureImg.src = signatureDataUrl;
- signatureImg.alt = 'Unterschrift';
- signatureImg.style.width = '100%';
- signatureImg.style.height = 'auto';
- signatureImg.style.maxHeight = '70px';
- signatureImg.style.display = 'block';
- signatureImg.style.objectFit = 'contain';
- signatureImg.style.marginBottom = '6px';
-
- // Separator line (German standard)
- const separator = document.createElement('div');
- separator.style.width = '100%';
- separator.style.height = '1px';
- separator.style.backgroundColor = '#495057';
- separator.style.marginBottom = '8px';
-
- // Text information container
- const infoContainer = document.createElement('div');
- infoContainer.className = 'signature-info-text'; // ✅ Add class (for querySelector)
- infoContainer.setAttribute('data-base-font-size', '9'); // ✅ Store base font size
- infoContainer.style.fontSize = '9px';
- infoContainer.style.lineHeight = '1.4';
- infoContainer.style.color = '#495057';
- infoContainer.style.fontWeight = '400';
-
- // Format date (German style: dd.MM.yyyy)
- const today = new Date();
- const dateStr = today.toLocaleDateString('de-DE', {
- day: '2-digit',
- month: '2-digit',
- year: 'numeric'
- });
-
- // Build text lines (German standard format)
- const lines = [];
- lines.push(`${this.escapeHtml(fullName)} `);
-
- if (position && position.trim() !== '') {
- lines.push(`${this.escapeHtml(position)}`);
- }
-
- lines.push(`${this.escapeHtml(place)}, ${dateStr}`);
-
- infoContainer.innerHTML = lines.join(' ');
-
- // Assemble container
- signatureContainer.appendChild(signatureImg);
- signatureContainer.appendChild(separator);
- signatureContainer.appendChild(infoContainer);
-
- // Add to signature layer
- signatureLayer.appendChild(signatureContainer);
-
- // ✅ FIXED: Track applied signature element for zoom updates
- this.appliedSignatureElements.push(signatureContainer);
-
- // ✅ FIXED: Apply initial scaling based on current zoom
- this.scaleAppliedSignature(signatureContainer, this.scale);
-
- console.log(`Signature #${signatureId} applied successfully`);
-
- } catch (error) {
- console.error('Error applying signature:', error);
- }
- },
-
- /**
- * Escapes HTML to prevent XSS attacks
- */
- escapeHtml(text) {
- const div = document.createElement('div');
- div.textContent = text;
- return div.innerHTML;
- }
-};
-
-
-
-
-
-
-
diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/js/receiver-signature.js b/EnvelopeGenerator.ReceiverUI/wwwroot/js/receiver-signature.js
deleted file mode 100644
index 11bc80fc..00000000
--- a/EnvelopeGenerator.ReceiverUI/wwwroot/js/receiver-signature.js
+++ /dev/null
@@ -1,353 +0,0 @@
-window.receiverSignature = (() => {
-
- // ?? State ???????????????????????????????????????????????????????????????
- const pads = new Map();
- const typedSignatures = new Map();
- const imageSignatures = new Map();
- const overlayButtons = new Map(); // annotationId -> { btn, signed }
- let _dotNetRef = null;
-
- // DevExpress Blazor Report Viewer selectors (confirmed via debugDumpViewerDom)
- const PAGE_IMG_SEL = '.dxbrv-report-preview-content-img';
- const SCROLL_CONTAINER_SEL = '.dxbrv-surface-wrapper';
- const VIEWER_WRAPPER_SEL = '.receiver-viewer-wrapper';
-
- // DX report coordinate space (1/100 inch, A4)
- const DX_PAGE_WIDTH = 827.0;
- const DX_PAGE_HEIGHT = 1169.0;
-
- // Signature field size in DX units
- const SIG_WIDTH_DX = 230.0;
- const SIG_HEIGHT_DX = 154.0;
-
- // ?? Annotation Checkboxes ????????????????????????????????????????????????
-
- // Active install context — holds everything needed to reposition on resize/scroll
- let _installCtx = null;
-
- function installAnnotationCheckboxes(annotations, checkedIds, dotNetRef) {
- _dotNetRef = dotNetRef;
-
- // Tear down previous install completely
- _teardownCheckboxes();
-
- if (!annotations || annotations.length === 0) return;
-
- const ctx = {
- annotations,
- checkedIds: new Set(Array.isArray(checkedIds) ? checkedIds : []),
- scrollEl: null,
- pageEls: [],
- resizeObs: null,
- onScroll: null,
- onResize: null,
- resizeTimer: null,
- };
- _installCtx = ctx;
-
- _waitForCheckboxPages(ctx);
- }
-
- function _teardownCheckboxes() {
- document.querySelectorAll('.annot-sig-cb-wrapper').forEach(el => el.remove());
- overlayButtons.clear();
-
- if (!_installCtx) return;
- const ctx = _installCtx;
-
- if (ctx.resizeObs) { ctx.resizeObs.disconnect(); ctx.resizeObs = null; }
- if (ctx.scrollEl && ctx.onScroll) ctx.scrollEl.removeEventListener('scroll', ctx.onScroll);
- if (ctx.onResize) window.removeEventListener('resize', ctx.onResize);
- if (ctx.resizeTimer) clearTimeout(ctx.resizeTimer);
-
- _installCtx = null;
- }
-
- function _waitForCheckboxPages(ctx) {
- const wrapper = document.querySelector(VIEWER_WRAPPER_SEL);
- if (!wrapper) return;
-
- if (!_tryInstallCheckboxes(ctx)) {
- const observer = new MutationObserver(() => {
- if (_tryInstallCheckboxes(ctx)) observer.disconnect();
- });
- observer.observe(wrapper, { childList: true, subtree: true });
- setTimeout(() => observer.disconnect(), 15000);
- }
- }
-
- function _tryInstallCheckboxes(ctx) {
- const scrollEl = document.querySelector(SCROLL_CONTAINER_SEL);
- if (!scrollEl) return false;
-
- const pageEls = Array.from(document.querySelectorAll(PAGE_IMG_SEL));
- if (pageEls.length === 0) return false;
-
- if (getComputedStyle(scrollEl).position === 'static')
- scrollEl.style.position = 'relative';
-
- ctx.scrollEl = scrollEl;
- ctx.pageEls = pageEls;
-
- // Initial render
- _renderAllCheckboxes(ctx);
-
- // ResizeObserver — watches every page image for size changes (zoom in/out)
- const ro = new ResizeObserver(() => _repositionAll(ctx));
- pageEls.forEach(el => ro.observe(el));
- ctx.resizeObs = ro;
-
- // Scroll — reposition when user scrolls the viewer
- ctx.onScroll = () => _repositionAll(ctx);
- scrollEl.addEventListener('scroll', ctx.onScroll, { passive: true });
-
- // Window resize — debounced 60 ms
- ctx.onResize = () => {
- if (ctx.resizeTimer) clearTimeout(ctx.resizeTimer);
- ctx.resizeTimer = setTimeout(() => _repositionAll(ctx), 60);
- };
- window.addEventListener('resize', ctx.onResize, { passive: true });
-
- return true;
- }
-
- function _repositionAll(ctx) {
- if (!ctx || !ctx.scrollEl) return;
-
- const scrollEl = ctx.scrollEl;
- const pageEls = Array.from(document.querySelectorAll(PAGE_IMG_SEL)); // re-query in case DOM changed
- if (pageEls.length === 0) return;
-
- const scrollRect = scrollEl.getBoundingClientRect();
-
- ctx.annotations.forEach(ann => {
- const wrapper = document.querySelector(`.annot-sig-cb-wrapper[data-annot-id="${ann.id}"]`);
- if (!wrapper) return;
-
- const pageEl = pageEls[(ann.page || 1) - 1] ?? pageEls[pageEls.length - 1];
- if (!pageEl) return;
-
- const pageRect = pageEl.getBoundingClientRect();
- if (pageRect.width === 0 || pageRect.height === 0) return;
-
- const scaleX = pageRect.width / DX_PAGE_WIDTH;
- const scaleY = pageRect.height / DX_PAGE_HEIGHT;
-
- const absLeft = pageRect.left - scrollRect.left + scrollEl.scrollLeft + (ann.x || 0) * scaleX;
- const absTop = pageRect.top - scrollRect.top + scrollEl.scrollTop + (ann.y || 0) * scaleY;
- const boxW = SIG_WIDTH_DX * scaleX;
- const boxH = SIG_HEIGHT_DX * scaleY;
-
- wrapper.style.left = Math.round(absLeft) + 'px';
- wrapper.style.top = Math.round(absTop) + 'px';
- wrapper.style.width = Math.round(boxW) + 'px';
- wrapper.style.height = Math.round(boxH) + 'px';
- });
- }
-
- function _renderAllCheckboxes(ctx) {
- const scrollEl = ctx.scrollEl;
- const pageEls = ctx.pageEls;
- const scrollRect = scrollEl.getBoundingClientRect();
-
- ctx.annotations.forEach(ann => {
- const pageEl = pageEls[(ann.page || 1) - 1] ?? pageEls[pageEls.length - 1];
- if (!pageEl) return;
-
- const pageRect = pageEl.getBoundingClientRect();
- if (pageRect.width === 0 || pageRect.height === 0) return;
-
- const scaleX = pageRect.width / DX_PAGE_WIDTH;
- const scaleY = pageRect.height / DX_PAGE_HEIGHT;
-
- const absLeft = pageRect.left - scrollRect.left + scrollEl.scrollLeft + (ann.x || 0) * scaleX;
- const absTop = pageRect.top - scrollRect.top + scrollEl.scrollTop + (ann.y || 0) * scaleY;
- const boxW = SIG_WIDTH_DX * scaleX;
- const boxH = SIG_HEIGHT_DX * scaleY;
-
- const isChecked = ctx.checkedIds.has(ann.id);
- _createCheckboxOverlay(ann.id, scrollEl, absLeft, absTop, boxW, boxH, isChecked);
- });
- }
-
- function _createCheckboxOverlay(annotationId, container, left, top, width, height, isChecked) {
- const wrapper = document.createElement('div');
- wrapper.className = 'annot-sig-cb-wrapper' + (isChecked ? ' annot-sig-cb-wrapper--checked' : '');
- wrapper.setAttribute('data-annot-id', String(annotationId));
-
- Object.assign(wrapper.style, {
- position: 'absolute',
- left: Math.round(left) + 'px',
- top: Math.round(top) + 'px',
- width: Math.round(width) + 'px',
- height: Math.round(height) + 'px',
- zIndex: '9999',
- cursor: 'pointer',
- boxSizing: 'border-box',
- });
-
- const cb = document.createElement('input');
- cb.type = 'checkbox';
- cb.checked = isChecked;
- cb.className = 'annot-sig-cb';
- cb.setAttribute('aria-label', 'Unterschriftsfeld bestaetigen');
-
- const label = document.createElement('span');
- label.className = 'annot-sig-cb__label';
- label.textContent = isChecked ? '\u2713 Bestaetigt' : '\u270e Hier unterschreiben';
-
- wrapper.appendChild(cb);
- wrapper.appendChild(label);
-
- wrapper.addEventListener('click', (e) => {
- if (e.target !== cb) cb.checked = !cb.checked;
- const checked = cb.checked;
- wrapper.classList.toggle('annot-sig-cb-wrapper--checked', checked);
- label.textContent = checked ? '\u2713 Bestaetigt' : '\u270e Hier unterschreiben';
- if (_installCtx) {
- if (checked) _installCtx.checkedIds.add(annotationId);
- else _installCtx.checkedIds.delete(annotationId);
- }
- if (_dotNetRef)
- _dotNetRef.invokeMethodAsync('OnAnnotationToggled', annotationId, checked);
- });
-
- container.appendChild(wrapper);
- overlayButtons.set(annotationId, { btn: wrapper, signed: isChecked });
- }
-
- // ?? Signature Pad ???????????????????????????????????????????????????????
-
- function _pos(canvas, event) {
- const r = canvas.getBoundingClientRect();
- const s = (event.touches && event.touches.length) ? event.touches[0] : event;
- return { x: (s.clientX - r.left) * (canvas.width / r.width), y: (s.clientY - r.top) * (canvas.height / r.height) };
- }
-
- function _clear(canvas) { canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height); }
-
- function initialize(canvasId) {
- const canvas = document.getElementById(canvasId);
- if (!canvas || pads.has(canvasId)) return;
- const ctx = canvas.getContext('2d');
- ctx.lineWidth = 2.5; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; ctx.strokeStyle = '#111';
- const state = { drawing: false, hasSignature: false };
- pads.set(canvasId, state);
- const start = e => { e.preventDefault(); const p = _pos(canvas, e); state.drawing = true; ctx.beginPath(); ctx.moveTo(p.x, p.y); };
- const move = e => { if (!state.drawing) return; e.preventDefault(); const p = _pos(canvas, e); ctx.lineTo(p.x, p.y); ctx.stroke(); state.hasSignature = true; };
- const end = e => { if (!state.drawing) return; e.preventDefault(); state.drawing = false; };
- canvas.addEventListener('mousedown', start);
- canvas.addEventListener('mousemove', move);
- window.addEventListener('mouseup', end);
- canvas.addEventListener('touchstart', start, { passive: false });
- canvas.addEventListener('touchmove', move, { passive: false });
- canvas.addEventListener('touchend', end, { passive: false });
- }
-
- function initializeTyped(canvasId) {
- const canvas = document.getElementById(canvasId);
- if (!canvas || typedSignatures.has(canvasId)) return;
- typedSignatures.set(canvasId, { hasSignature: false });
- }
-
- function initializeImage(inputId, canvasId) {
- const input = document.getElementById(inputId);
- const canvas = document.getElementById(canvasId);
- if (!input || !canvas || imageSignatures.has(canvasId)) return;
- const state = { hasSignature: false };
- imageSignatures.set(canvasId, state);
- input.addEventListener('change', () => {
- const file = input.files && input.files[0];
- if (!file || !file.type.startsWith('image/')) { _clear(canvas); state.hasSignature = false; return; }
- const reader = new FileReader();
- reader.onload = () => {
- const img = new Image();
- img.onload = () => {
- const ctx = canvas.getContext('2d'); _clear(canvas);
- const p = 10, mw = canvas.width - p * 2, mh = canvas.height - p * 2;
- const s = Math.min(mw / img.width, mh / img.height, 1);
- ctx.drawImage(img, (canvas.width - img.width * s) / 2, (canvas.height - img.height * s) / 2, img.width * s, img.height * s);
- state.hasSignature = true;
- };
- img.src = reader.result;
- };
- reader.readAsDataURL(file);
- });
- }
-
- function clear(canvasId) {
- const c = document.getElementById(canvasId); const s = pads.get(canvasId);
- if (c && s) { _clear(c); s.hasSignature = false; }
- }
-
- function clearTyped(canvasId) {
- const c = document.getElementById(canvasId); const s = typedSignatures.get(canvasId);
- if (c && s) { _clear(c); s.hasSignature = false; }
- }
-
- function clearImage(inputId, canvasId) {
- const inp = document.getElementById(inputId); const c = document.getElementById(canvasId); const s = imageSignatures.get(canvasId);
- if (c && s) { if (inp) inp.value = ''; _clear(c); s.hasSignature = false; }
- }
-
- function renderTypedSignature(canvasId, text, fontFamily) {
- const canvas = document.getElementById(canvasId); const state = typedSignatures.get(canvasId);
- if (!canvas || !state) return;
- const value = (text || '').trim(); _clear(canvas);
- if (!value) { state.hasSignature = false; return; }
- const ctx = canvas.getContext('2d'); let fs = 54;
- do { ctx.font = 'italic ' + fs + 'px ' + (fontFamily || 'cursive'); fs -= 2; }
- while (ctx.measureText(value).width > canvas.width - 30 && fs > 24);
- ctx.fillStyle = '#111'; ctx.textBaseline = 'middle'; ctx.textAlign = 'center';
- ctx.fillText(value, canvas.width / 2, canvas.height / 2);
- state.hasSignature = true;
- }
-
- function startTyped(elementId, text, typeSpeed) {
- if (typeof Typed === 'undefined') return;
- new Typed('#' + elementId, {
- strings: [text],
- typeSpeed: typeSpeed || 15,
- showCursor: false
- });
- }
-
- function getDataUrl(id) { const c = document.getElementById(id); const s = pads.get(id); return (c && s && s.hasSignature) ? c.toDataURL('image/png') : null; }
- function getTypedDataUrl(id) { const c = document.getElementById(id); const s = typedSignatures.get(id); return (c && s && s.hasSignature) ? c.toDataURL('image/png') : null; }
- function getImageDataUrl(id) { const c = document.getElementById(id); const s = imageSignatures.get(id); return (c && s && s.hasSignature) ? c.toDataURL('image/png') : null; }
-
- function loadExistingSignature(canvasId, dataUrl) {
- const canvas = document.getElementById(canvasId);
- const state = pads.get(canvasId);
- if (!canvas || !state || !dataUrl) return;
-
- const ctx = canvas.getContext('2d');
- const img = new Image();
-
- img.onload = () => {
- _clear(canvas);
- ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
- state.hasSignature = true;
- };
-
- img.src = dataUrl;
- }
-
- // ?? Public API ??????????????????????????????????????????????????????????
- return {
- startTyped: startTyped,
- installAnnotationCheckboxes: installAnnotationCheckboxes,
- initialize: initialize,
- initializeTyped: initializeTyped,
- initializeImage: initializeImage,
- clear: clear,
- clearTyped: clearTyped,
- clearImage: clearImage,
- renderTypedSignature: renderTypedSignature,
- getDataUrl: getDataUrl,
- getTypedDataUrl: getTypedDataUrl,
- getImageDataUrl: getImageDataUrl,
- loadExistingSignature: loadExistingSignature
- };
-})();
-
diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/js/typed.umd.js b/EnvelopeGenerator.ReceiverUI/wwwroot/js/typed.umd.js
deleted file mode 100644
index 0a4e41d6..00000000
--- a/EnvelopeGenerator.ReceiverUI/wwwroot/js/typed.umd.js
+++ /dev/null
@@ -1,3 +0,0 @@
-!function(t,s){"object"==typeof exports&&"undefined"!=typeof module?module.exports=s():"function"==typeof define&&define.amd?define(s):(t||self).Typed=s()}(this,function(){function t(){return t=Object.assign?Object.assign.bind():function(t){for(var s=1;s0&&(e.strPos=e.currentElContent.length-1,e.strings.unshift(e.currentElContent)),e.sequence=[],e.strings)e.sequence[u]=u;e.arrayPos=0,e.stopNum=0,e.loop=e.options.loop,e.loopCount=e.options.loopCount,e.curLoop=0,e.shuffle=e.options.shuffle,e.pause={status:!1,typewrite:!0,curString:"",curStrPos:0},e.typingComplete=!1,e.autoInsertCss=e.options.autoInsertCss,e.autoInsertCss&&(this.appendCursorAnimationCss(e),this.appendFadeOutAnimationCss(e))},n.getCurrentElContent=function(t){return t.attr?t.el.getAttribute(t.attr):t.isInput?t.el.value:"html"===t.contentType?t.el.innerHTML:t.el.textContent},n.appendCursorAnimationCss=function(t){var s="data-typed-js-cursor-css";if(t.showCursor&&!document.querySelector("["+s+"]")){var e=document.createElement("style");e.setAttribute(s,"true"),e.innerHTML="\n .typed-cursor{\n opacity: 1;\n }\n .typed-cursor.typed-cursor--blink{\n animation: typedjsBlink 0.7s infinite;\n -webkit-animation: typedjsBlink 0.7s infinite;\n animation: typedjsBlink 0.7s infinite;\n }\n @keyframes typedjsBlink{\n 50% { opacity: 0.0; }\n }\n @-webkit-keyframes typedjsBlink{\n 0% { opacity: 1; }\n 50% { opacity: 0.0; }\n 100% { opacity: 1; }\n }\n ",document.body.appendChild(e)}},n.appendFadeOutAnimationCss=function(t){var s="data-typed-fadeout-js-css";if(t.fadeOut&&!document.querySelector("["+s+"]")){var e=document.createElement("style");e.setAttribute(s,"true"),e.innerHTML="\n .typed-fade-out{\n opacity: 0;\n transition: opacity .25s;\n }\n .typed-cursor.typed-cursor--blink.typed-fade-out{\n -webkit-animation: 0;\n animation: 0;\n }\n ",document.body.appendChild(e)}},e}()),n=new(/*#__PURE__*/function(){function t(){}var s=t.prototype;return s.typeHtmlChars=function(t,s,e){if("html"!==e.contentType)return s;var n=t.substring(s).charAt(0);if("<"===n||"&"===n){var i;for(i="<"===n?">":";";t.substring(s+1).charAt(0)!==i&&!(1+ ++s>t.length););s++}return s},s.backSpaceHtmlChars=function(t,s,e){if("html"!==e.contentType)return s;var n=t.substring(s).charAt(0);if(">"===n||";"===n){var i;for(i=">"===n?"<":"&";t.substring(s-1).charAt(0)!==i&&!(--s<0););s--}return s},t}());/*#__PURE__*/
-return function(){function t(t,s){e.load(this,s,t),this.begin()}var s=t.prototype;return s.toggle=function(){this.pause.status?this.start():this.stop()},s.stop=function(){this.typingComplete||this.pause.status||(this.toggleBlinking(!0),this.pause.status=!0,this.options.onStop(this.arrayPos,this))},s.start=function(){this.typingComplete||this.pause.status&&(this.pause.status=!1,this.pause.typewrite?this.typewrite(this.pause.curString,this.pause.curStrPos):this.backspace(this.pause.curString,this.pause.curStrPos),this.options.onStart(this.arrayPos,this))},s.destroy=function(){this.reset(!1),this.options.onDestroy(this)},s.reset=function(t){void 0===t&&(t=!0),clearInterval(this.timeout),this.replaceText(""),this.cursor&&this.cursor.parentNode&&(this.cursor.parentNode.removeChild(this.cursor),this.cursor=null),this.strPos=0,this.arrayPos=0,this.curLoop=0,t&&(this.insertCursor(),this.options.onReset(this),this.begin())},s.begin=function(){var t=this;this.options.onBegin(this),this.typingComplete=!1,this.shuffleStringsIfNeeded(this),this.insertCursor(),this.bindInputFocusEvents&&this.bindFocusEvents(),this.timeout=setTimeout(function(){0===t.strPos?t.typewrite(t.strings[t.sequence[t.arrayPos]],t.strPos):t.backspace(t.strings[t.sequence[t.arrayPos]],t.strPos)},this.startDelay)},s.typewrite=function(t,s){var e=this;this.fadeOut&&this.el.classList.contains(this.fadeOutClass)&&(this.el.classList.remove(this.fadeOutClass),this.cursor&&this.cursor.classList.remove(this.fadeOutClass));var i=this.humanizer(this.typeSpeed),r=1;!0!==this.pause.status?this.timeout=setTimeout(function(){s=n.typeHtmlChars(t,s,e);var i=0,o=t.substring(s);if("^"===o.charAt(0)&&/^\^\d+/.test(o)){var a=1;a+=(o=/\d+/.exec(o)[0]).length,i=parseInt(o),e.temporaryPause=!0,e.options.onTypingPaused(e.arrayPos,e),t=t.substring(0,s)+t.substring(s+a),e.toggleBlinking(!0)}if("`"===o.charAt(0)){for(;"`"!==t.substring(s+r).charAt(0)&&(r++,!(s+r>t.length)););var u=t.substring(0,s),p=t.substring(u.length+1,s+r),c=t.substring(s+r+1);t=u+p+c,r--}e.timeout=setTimeout(function(){e.toggleBlinking(!1),s>=t.length?e.doneTyping(t,s):e.keepTyping(t,s,r),e.temporaryPause&&(e.temporaryPause=!1,e.options.onTypingResumed(e.arrayPos,e))},i)},i):this.setPauseStatus(t,s,!0)},s.keepTyping=function(t,s,e){0===s&&(this.toggleBlinking(!1),this.options.preStringTyped(this.arrayPos,this));var n=t.substring(0,s+=e);this.replaceText(n),this.typewrite(t,s)},s.doneTyping=function(t,s){var e=this;this.options.onStringTyped(this.arrayPos,this),this.toggleBlinking(!0),this.arrayPos===this.strings.length-1&&(this.complete(),!1===this.loop||this.curLoop===this.loopCount)||(this.timeout=setTimeout(function(){e.backspace(t,s)},this.backDelay))},s.backspace=function(t,s){var e=this;if(!0!==this.pause.status){if(this.fadeOut)return this.initFadeOut();this.toggleBlinking(!1);var i=this.humanizer(this.backSpeed);this.timeout=setTimeout(function(){s=n.backSpaceHtmlChars(t,s,e);var i=t.substring(0,s);if(e.replaceText(i),e.smartBackspace){var r=e.strings[e.arrayPos+1];e.stopNum=r&&i===r.substring(0,s)?s:0}s>e.stopNum?(s--,e.backspace(t,s)):s<=e.stopNum&&(e.arrayPos++,e.arrayPos===e.strings.length?(e.arrayPos=0,e.options.onLastStringBackspaced(),e.shuffleStringsIfNeeded(),e.begin()):e.typewrite(e.strings[e.sequence[e.arrayPos]],s))},i)}else this.setPauseStatus(t,s,!1)},s.complete=function(){this.options.onComplete(this),this.loop?this.curLoop++:this.typingComplete=!0},s.setPauseStatus=function(t,s,e){this.pause.typewrite=e,this.pause.curString=t,this.pause.curStrPos=s},s.toggleBlinking=function(t){this.cursor&&(this.pause.status||this.cursorBlinking!==t&&(this.cursorBlinking=t,t?this.cursor.classList.add("typed-cursor--blink"):this.cursor.classList.remove("typed-cursor--blink")))},s.humanizer=function(t){return Math.round(Math.random()*t/2)+t},s.shuffleStringsIfNeeded=function(){this.shuffle&&(this.sequence=this.sequence.sort(function(){return Math.random()-.5}))},s.initFadeOut=function(){var t=this;return this.el.className+=" "+this.fadeOutClass,this.cursor&&(this.cursor.className+=" "+this.fadeOutClass),setTimeout(function(){t.arrayPos++,t.replaceText(""),t.strings.length>t.arrayPos?t.typewrite(t.strings[t.sequence[t.arrayPos]],0):(t.typewrite(t.strings[0],0),t.arrayPos=0)},this.fadeOutDelay)},s.replaceText=function(t){this.attr?this.el.setAttribute(this.attr,t):this.isInput?this.el.value=t:"html"===this.contentType?this.el.innerHTML=t:this.el.textContent=t},s.bindFocusEvents=function(){var t=this;this.isInput&&(this.el.addEventListener("focus",function(s){t.stop()}),this.el.addEventListener("blur",function(s){t.el.value&&0!==t.el.value.length||t.start()}))},s.insertCursor=function(){this.showCursor&&(this.cursor||(this.cursor=document.createElement("span"),this.cursor.className="typed-cursor",this.cursor.setAttribute("aria-hidden",!0),this.cursor.innerHTML=this.cursorChar,this.el.parentNode&&this.el.parentNode.insertBefore(this.cursor,this.el.nextSibling)))},t}()});
-//# sourceMappingURL=typed.umd.js.map
diff --git a/EnvelopeGenerator.sln b/EnvelopeGenerator.sln
index 7508a491..d769a5cc 100644
--- a/EnvelopeGenerator.sln
+++ b/EnvelopeGenerator.sln
@@ -42,8 +42,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.API", "En
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.DependencyInjection", "EnvelopeGenerator.DependencyInjection\EnvelopeGenerator.DependencyInjection.csproj", "{5DCCF9A1-C03F-90E6-87D3-E96DB25250C2}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.ReceiverUI", "EnvelopeGenerator.ReceiverUI\EnvelopeGenerator.ReceiverUI.csproj", "{FB2D306B-1042-4A70-31ED-F991A1599371}"
-EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "EnvelopeGenerator.Server", "EnvelopeGenerator.Server", "{BF1700D5-592E-4FFA-84E8-5480E289A1F0}"
ProjectSection(SolutionItems) = preProject
EnvelopeGenerator.Server\publish.bat = EnvelopeGenerator.Server\publish.bat
@@ -108,10 +106,6 @@ Global
{5DCCF9A1-C03F-90E6-87D3-E96DB25250C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5DCCF9A1-C03F-90E6-87D3-E96DB25250C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5DCCF9A1-C03F-90E6-87D3-E96DB25250C2}.Release|Any CPU.Build.0 = Release|Any CPU
- {FB2D306B-1042-4A70-31ED-F991A1599371}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {FB2D306B-1042-4A70-31ED-F991A1599371}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {FB2D306B-1042-4A70-31ED-F991A1599371}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {FB2D306B-1042-4A70-31ED-F991A1599371}.Release|Any CPU.Build.0 = Release|Any CPU
{4E6C54DA-576D-0955-2564-9EC890BB8279}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4E6C54DA-576D-0955-2564-9EC890BB8279}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4E6C54DA-576D-0955-2564-9EC890BB8279}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -147,7 +141,6 @@ Global
{224C4845-1CDE-22B7-F3A9-1FF9297F70E8} = {0CBC2432-A561-4440-89BC-671B66A24146}
{EC768913-6270-14F4-1DD3-69C87A659462} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB}
{5DCCF9A1-C03F-90E6-87D3-E96DB25250C2} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB}
- {FB2D306B-1042-4A70-31ED-F991A1599371} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB}
{BF1700D5-592E-4FFA-84E8-5480E289A1F0} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB}
{4E6C54DA-576D-0955-2564-9EC890BB8279} = {BF1700D5-592E-4FFA-84E8-5480E289A1F0}
{9C41A0B1-6AFE-AFC5-CE33-08052F45DAEF} = {BF1700D5-592E-4FFA-84E8-5480E289A1F0}