Showing 1 changed files with 47 additions and 32 deletions
+47 -32
scripts/host_manager.pl
@@ -1165,35 +1165,39 @@ sub app_html {
1165 1165
       --login-form-width: calc((var(--otp-size) * 6) + (var(--otp-gap) * 5));
1166 1166
       background: #fff;
1167 1167
       border-radius: 16px;
1168
-      padding: 48px 36px 44px;
1168
+      padding: 56px;
1169 1169
       width: 100%;
1170
-      max-width: 424px;
1170
+      max-width: 760px;
1171
+      min-height: 420px;
1171 1172
       display: grid;
1172
-      gap: 26px;
1173
+      grid-template-columns: minmax(220px, 1fr) minmax(0, var(--login-form-width));
1174
+      align-items: center;
1175
+      gap: 56px;
1173 1176
       box-shadow: 0 8px 40px rgba(0,0,0,.28);
1174 1177
     }
1175
-    .login-card .brand { text-align: center; display: grid; gap: 6px; }
1178
+    .login-card .brand { text-align: left; display: grid; gap: 8px; }
1176 1179
     .login-card .brand .icon {
1177
-      margin: 0 auto 4px;
1178
-      width: 52px; height: 52px; border-radius: 14px;
1180
+      margin: 0 0 12px;
1181
+      width: 64px; height: 64px; border-radius: 18px;
1179 1182
       background: #e8f0fe; display: flex; align-items: center; justify-content: center;
1180 1183
     }
1181
-    .login-card .brand .icon svg { width: 26px; height: 26px; fill: var(--accent); }
1182
-    .login-card .brand h1 { margin: 0; font-size: 22px; font-weight: 700; color: var(--ink); }
1183
-    .login-card .brand p { margin: 0; color: var(--muted); font-size: 13px; }
1184
+    .login-card .brand .icon svg { width: 38px; height: 38px; fill: none; stroke: var(--accent); stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
1185
+    .login-card .brand h1 { margin: 0; font-size: 32px; line-height: 1.05; font-weight: 750; color: var(--ink); }
1186
+    .login-card .brand p { margin: 0; color: var(--muted); font-size: 16px; }
1184 1187
     .login-card form {
1185 1188
       display: grid;
1186 1189
       gap: 16px;
1187 1190
       width: min(100%, var(--login-form-width));
1188
-      justify-self: center;
1191
+      justify-self: end;
1192
+      padding-bottom: clamp(92px, 12vh, 132px);
1189 1193
     }
1194
+    .login-card form.busy { opacity: .72; pointer-events: none; }
1190 1195
     .login-card .field-label { font-size: 13px; font-weight: 600; color: var(--ink); }
1191 1196
     /* 6 separate OTP digit boxes */
1192 1197
     .otp-row {
1193 1198
       display: flex;
1194 1199
       gap: var(--otp-gap);
1195 1200
       justify-content: center;
1196
-      margin-bottom: clamp(92px, 13vh, 132px);
1197 1201
     }
1198 1202
     .otp-row input {
1199 1203
       width: var(--otp-size); height: 56px; border: 1.5px solid #dde2ec; border-radius: 10px;
@@ -1203,17 +1207,24 @@ sub app_html {
1203 1207
     }
1204 1208
     .otp-row input:focus { border-color: var(--accent); background: #fff; }
1205 1209
     .otp-row input.filled { border-color: #b3c6f0; background: #fff; }
1206
-    .login-card button.primary {
1207
-      width: 100%; border: none; background: var(--accent); color: #fff;
1208
-      border-radius: 10px; padding: 13px; font: inherit; font-size: 15px;
1209
-      font-weight: 600; cursor: pointer; min-height: 48px;
1210
-      display: flex; align-items: center; justify-content: center; gap: 8px;
1211
-    }
1212
-    .login-card button.primary:hover { background: #0f52b8; }
1213
-    .login-card button.primary:disabled { opacity: .55; cursor: not-allowed; }
1214 1210
     #login-error {
1215 1211
       color: var(--bad); font-size: 13px; text-align: center;
1216
-      min-height: 18px; margin-top: -8px;
1212
+      min-height: 18px; margin-top: -88px; grid-column: 2;
1213
+    }
1214
+    @media (max-width: 760px) {
1215
+      .login-card {
1216
+        max-width: 424px;
1217
+        min-height: 0;
1218
+        padding: 48px 36px 44px;
1219
+        grid-template-columns: 1fr;
1220
+        gap: 26px;
1221
+      }
1222
+      .login-card .brand { text-align: center; }
1223
+      .login-card .brand .icon { margin: 0 auto 8px; }
1224
+      .login-card .brand h1 { font-size: 24px; }
1225
+      .login-card .brand p { font-size: 14px; }
1226
+      .login-card form { justify-self: center; padding-bottom: 96px; }
1227
+      #login-error { grid-column: 1; margin-top: -82px; }
1217 1228
     }
1218 1229
     @media (max-width: 430px) {
1219 1230
       #login-screen { padding: 24px 16px 120px; }
@@ -1223,12 +1234,13 @@ sub app_html {
1223 1234
         padding: 36px 22px 34px;
1224 1235
       }
1225 1236
       .otp-row input { height: 52px; }
1226
-      .otp-row { margin-bottom: 88px; }
1237
+      .login-card form { padding-bottom: 88px; }
1227 1238
     }
1228 1239
     @media (max-height: 720px) {
1229 1240
       #login-screen { padding-top: 28px; padding-bottom: 96px; }
1230 1241
       .login-card { padding-top: 34px; padding-bottom: 34px; gap: 20px; }
1231
-      .otp-row { margin-bottom: 72px; }
1242
+      .login-card form { padding-bottom: 72px; }
1243
+      #login-error { margin-top: -58px; }
1232 1244
     }
1233 1245
 
1234 1246
     /* ── App shell (hidden until authenticated) ── */
@@ -1280,10 +1292,13 @@ sub app_html {
1280 1292
     <div class="login-card">
1281 1293
       <div class="brand">
1282 1294
         <div class="icon">
1283
-          <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
1284
-            <path d="M20 3H4a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1zm-1 5H5V5h14v3zm1 5H4a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1v-5a1 1 0 0 0-1-1zm-1 5H5v-3h14v3z"/>
1285
-            <circle cx="17" cy="7" r="1"/><circle cx="19.5" cy="7" r="1"/>
1286
-            <circle cx="17" cy="15.5" r="1"/><circle cx="19.5" cy="15.5" r="1"/>
1295
+          <svg viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
1296
+            <rect x="16" y="10" width="32" height="44" rx="4"/>
1297
+            <rect x="21" y="16" width="22" height="8" rx="2"/>
1298
+            <rect x="21" y="28" width="22" height="8" rx="2"/>
1299
+            <rect x="21" y="40" width="22" height="8" rx="2"/>
1300
+            <path d="M26 20h8M26 32h8M26 44h8"/>
1301
+            <path d="M40 20h.01M40 32h.01M40 44h.01"/>
1287 1302
           </svg>
1288 1303
         </div>
1289 1304
         <h1>Host Manager</h1>
@@ -1299,7 +1314,6 @@ sub app_html {
1299 1314
           <input type="text" inputmode="numeric" maxlength="1" pattern="[0-9]" class="otp-digit">
1300 1315
           <input type="text" inputmode="numeric" maxlength="1" pattern="[0-9]" class="otp-digit">
1301 1316
         </div>
1302
-        <button class="primary" type="submit" id="login-btn">Autentifică-te</button>
1303 1317
       </form>
1304 1318
       <div id="login-error"></div>
1305 1319
     </div>
@@ -1603,7 +1617,7 @@ sub app_html {
1603 1617
         input.value = val;
1604 1618
         input.classList.toggle('filled', !!val);
1605 1619
         if (val && idx < otpDigits.length - 1) otpDigits[idx + 1].focus();
1606
-        if (val && idx === otpDigits.length - 1) $('login-form').requestSubmit();
1620
+        if (val && idx === otpDigits.length - 1 && otpReady()) $('login-form').requestSubmit();
1607 1621
       });
1608 1622
       input.addEventListener('paste', (e) => {
1609 1623
         const text = (e.clipboardData || window.clipboardData).getData('text').replace(/\D/g, '');
@@ -1614,17 +1628,18 @@ sub app_html {
1614 1628
         });
1615 1629
         const next = Math.min(text.length, otpDigits.length - 1);
1616 1630
         otpDigits[next].focus();
1617
-        if (text.length >= otpDigits.length) $('login-form').requestSubmit();
1631
+        if (otpReady()) $('login-form').requestSubmit();
1618 1632
       });
1619 1633
     });
1620 1634
 
1621 1635
     function getOtp() { return otpDigits.map(i => i.value).join(''); }
1636
+    function otpReady() { return otpDigits.every(i => /^\d$/.test(i.value)); }
1622 1637
     function clearOtp() { otpDigits.forEach(i => { i.value = ''; i.classList.remove('filled'); }); otpDigits[0].focus(); }
1623 1638
 
1624 1639
     $('login-form').addEventListener('submit', async (event) => {
1625 1640
       event.preventDefault();
1626
-      const btn = $('login-btn');
1627
-      btn.disabled = true;
1641
+      if (!otpReady()) return;
1642
+      $('login-form').classList.add('busy');
1628 1643
       $('login-error').textContent = '';
1629 1644
       try {
1630 1645
         await api('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ otp: getOtp() }) });
@@ -1632,7 +1647,7 @@ sub app_html {
1632 1647
       } catch (e) {
1633 1648
         showLogin(e.message === 'invalid_otp' ? 'Cod incorect.' : e.message);
1634 1649
       } finally {
1635
-        btn.disabled = false;
1650
+        $('login-form').classList.remove('busy');
1636 1651
       }
1637 1652
     });
1638 1653