{"id":2476,"date":"2025-08-19T07:24:02","date_gmt":"2025-08-19T07:24:02","guid":{"rendered":"https:\/\/fraumiau.ro\/?page_id=2476"},"modified":"2025-11-22T00:02:01","modified_gmt":"2025-11-22T00:02:01","slug":"client","status":"publish","type":"page","link":"https:\/\/fraumiau.ro\/en\/client\/","title":{"rendered":"client"},"content":{"rendered":"<div data-elementor-type=\"wp-page\" data-elementor-id=\"2476\" class=\"elementor elementor-2476\">\n\t\t\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-691488b elementor-section-boxed elementor-section-height-default elementor-section-height-default wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no\" data-id=\"691488b\" data-element_type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"aux-parallax-section elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-1bd0401\" data-id=\"1bd0401\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-b6e443f elementor-widget elementor-widget-html\" data-id=\"b6e443f\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<!DOCTYPE html>\n<html lang=\"ro\">\n<head>\n  <meta charset=\"utf-8\">\n  <title>Contul meu<\/title>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, viewport-fit=cover\">\n  <meta name=\"theme-color\" content=\"#111\">\n    <meta name=\"web-app-url\" content=\"https:\/\/script.google.com\/macros\/s\/AKfycbwLLcEISW-DPhwND3H_qVOhfSGo0q8AWf9Uhq8qlHp34Mxy2xhMk8shLptAJlTNk7h-qw\/exec\">\n  <script>\n  \/\/ Safe stub so Google can call us even if main script isn't ready yet\n  window.handleCredentialResponse = function (resp) {\n    (window.__gsiQueue = window.__gsiQueue || []).push(resp);\n  };\n<\/script>\n<script src=\"https:\/\/accounts.google.com\/gsi\/client\" async defer><\/script>\n  <style>\n    :root {\n      --gap: 12px;\n      --radius: 14px;\n      --pad: 16px;\n      --max: 1100px;\n    }\n    * {\n      box-sizing: border-box;\n    }\n    html, body {\n      height: 100%;\n    }\n    body {\n      font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;\n      margin: 0;\n      line-height: 1.35;\n      background: #fafafa;\n      color: #111;\n    }\n    .container {\n      max-width: var(--max);\n      margin: 0 auto;\n      padding: clamp(10px, 2vw, 20px);\n    }\n    h1 {\n      font-family: inherit; \/* exactly same font as body *\/\n      font-size: 1.1rem;\n      font-weight: 500; \/* lighter to match rest *\/\n      margin: 8px 0 12px;\n      padding: 0 4px;\n    }\n    \/* Ensure all headings inherit the body font (avoid serif fallbacks) *\/\n    h1, h2, h3, h4, h5, h6 { font-family: inherit !important; }\n    \/* Cats section heading tweaks (mobile-friendly) *\/\n    #catsSection h3 {\n      font-size: clamp(1.8rem, 7vw, 2.8rem) !important; \/* much larger, very prominent *\/\n      line-height: 1.2;\n      font-weight: 700;\n      white-space: normal; \/* allow wrapping when needed *\/\n      color: #333;\n      margin-bottom: 12px;\n    }\n    @media (max-width: 640px) {\n      .container { padding: 6px 8px; } \/* slightly reduce side padding on mobile *\/\n      .panel { padding: 12px; }\n      #catsSection h3 { font-size: clamp(1.6rem, 7.5vw, 2.2rem) !important; }\n    }\n    .panel {\n      border: 1px solid #e7e7e7;\n      border-radius: var(--radius);\n      padding: var(--pad);\n      background: #fff;\n      box-shadow: 0 1px 0 rgba(0,0,0,.02);\n    }\n    .row {\n      display: grid;\n      gap: var(--gap);\n      grid-template-columns: 1fr;\n    }\n    @media (min-width: 720px) {\n      .row {\n        grid-template-columns: repeat(2, minmax(0, 1fr));\n      }\n    }\n    label {\n      display: grid;\n      gap: 6px;\n      font-weight: 650;\n    }\n    textarea {\n      font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;\n      font-size: 14px;\n      padding: 14px;\n      border: 1px solid #d0d0d0;\n      border-radius: 12px;\n      width: 100%;\n      box-sizing: border-box;\n      resize: none; \/* auto-resize via JS; no manual handle *\/\n      height: auto;\n      overflow: hidden;\n      max-width: 100%;\n      background: transparent;\n      transition: border-color .15s ease;\n    }\n    textarea.editable {\n      animation: pulse-border 2s ease-in-out infinite;\n    }\n    @keyframes pulse-border {\n      0%, 100% {\n        border-color: #666;\n        box-shadow: 0 0 0 0 rgba(102, 102, 102, 0.4);\n      }\n      50% {\n        border-color: #888;\n        box-shadow: 0 0 0 4px rgba(102, 102, 102, 0.1);\n      }\n    }\n    textarea::placeholder {\n      font-family: inherit;\n      font-size: inherit;\n      color: #999;\n      white-space: pre-line; \/* show real newlines in placeholder *\/\n    }\n    #tab-rez {\n      width: 100%;\n      max-width: 100%;\n      overflow: hidden;\n      box-sizing: border-box;\n    }\n    #servicesSection {\n      width: 100%;\n      max-width: 100%;\n      overflow: hidden;\n      box-sizing: border-box;\n    }\n    #servicesSection .row {\n      width: 100% !important;\n      max-width: 100% !important;\n      overflow: hidden !important;\n      box-sizing: border-box !important;\n    }\n    #servicesSection textarea {\n      max-width: 100% !important;\n      width: 100% !important;\n      box-sizing: border-box !important;\n    }\n    .hidden-measure {\n      position: absolute;\n      visibility: hidden;\n      white-space: pre-line; \/* match placeholder rendering *\/\n      word-wrap: break-word;\n      pointer-events: none;\n      box-sizing: border-box;\n    }\n    input[type=\"date\"], input[type=\"text\"], input[type=\"email\"], select {\n      font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;\n      font-size: 14px;\n      padding: 14px;\n      border: 1px solid #d0d0d0;\n      border-radius: 12px;\n      width: 100%;\n      box-sizing: border-box;\n      background: #fff;\n      max-width: 90vw;\n      transition: border-color .15s ease;\n    }\n    .readonly {\n      background: transparent;\n      color: #555;\n    }\n    .sticky {\n      position: sticky;\n      bottom: 0;\n      z-index: 5;\n      margin-top: 10px;\n    }\n    .sticky .inner {\n      border: 1px solid #e7e7e7;\n      border-radius: 14px;\n      padding: 10px;\n      background: #fff;\n      display: flex;\n      gap: 10px;\n      flex-wrap: wrap;\n      align-items: flex-start;\n      justify-content: space-between;\n    }\n    .actions {\n      display: flex;\n      flex-direction: column;\n      gap: 8px;\n      width: min(360px, 100%);\n    }\n    .btn {\n      padding: 14px 16px;\n      border-radius: 12px;\n      border: 0;\n      background: #111;\n      color: #fff;\n      font-weight: 750;\n      cursor: pointer;\n      touch-action: manipulation;\n      width: 100%;\n    }\n    @keyframes pulseGlow {\n      0% { box-shadow: 0 0 0 0 rgba(10,122,50,.45); }\n      70% { box-shadow: 0 0 0 12px rgba(10,122,50,0); }\n      100% { box-shadow: 0 0 0 0 rgba(10,122,50,0); }\n    }\n    .btn.pulse { animation: pulseGlow 1.5s ease-out 0s 2; outline: 2px solid #0a7a32; }\n    .btn[disabled] {\n      opacity: .6;\n      cursor: default;\n    }\n    .btn-alt {\n      background: #f2f2f2;\n      color: #111;\n    }\n    .btn-alt:hover {\n      background: #ececec;\n    }\n    .hint {\n      font-size: .9rem;\n      line-height: 1.3;\n    }\n    .cats-list {\n      display: grid;\n      gap: 12px;\n    }\n    .cat-item {\n      border: 1px dashed #e0e0e0;\n      border-radius: 10px;\n      padding: 10px;\n      background: #fcfcfc;\n    }\n    .cat-head {\n      display: flex;\n      align-items: center;\n      justify-content: space-between;\n      margin-bottom: 6px;\n    }\n    .btn-light {\n      background: #f0f0f0;\n      color: #111;\n      border: 0;\n      border-radius: 10px;\n      padding: 8px 10px;\n      cursor: pointer;\n    }\n    .btn-light[disabled] {\n      opacity: .6;\n      cursor: default;\n    }\n    .charcount {\n      font-size: 12px;\n      text-align: right;\n      color: #666;\n      margin-top: 6px;\n    }\n    .btn-add {\n      display: inline-flex;\n      align-items: center;\n      gap: 8px;\n      padding: 12px 16px;\n      border-radius: 12px;\n      border: 2px dashed #0a7a32;\n      background: #fff;\n      color: #0a7a32;\n      font-weight: 750;\n      cursor: pointer;\n      transition: background .15s ease, border-color .15s ease;\n    }\n    .btn-add:hover {\n      background: #f0fff4;\n      border-color: #0b8a38;\n    }\n    .btn-add:active {\n      background: #e6ffef;\n    }\n    .btn-add:focus-visible {\n      outline: 3px solid rgba(10,122,50,.35);\n      outline-offset: 2px;\n    }\n    .tabcard {\n      padding: 0;\n    }\n    .tabcard .folder-tabs {\n      display: flex;\n      background: #f5f5f5;\n      border-radius: 4px;\n      padding: 4px;\n      margin-bottom: 24px;\n    }\n    .tabcard .folder-tabs::after { display: none; }\n    .tabcard .folder-tabs [role=\"tab\"] {\n      appearance: none;\n      -webkit-appearance: none;\n      background: transparent;\n      color: #666;\n      border: none;\n      border-radius: 20px;\n      padding: 8px 16px;\n      font-weight: 500;\n      font-size: 14px;\n      cursor: pointer;\n      user-select: none;\n      position: relative;\n      transition: all 0.2s ease;\n      flex: 1;\n      text-align: center;\n    }\n    .tabcard .folder-tabs [role=\"tab\"][aria-disabled=\"true\"] {\n      opacity: .5;\n      cursor: not-allowed;\n    }\n    .tabcard .folder-tabs [role=\"tab\"]:hover { \n      background: rgba(255,255,255,0.5);\n    }\n    .tabcard .folder-tabs [role=\"tab\"][aria-selected=\"true\"] {\n      background: #fff;\n      color: #333;\n      box-shadow: 0 1px 2px rgba(0,0,0,0.1);\n    }\n    .tabcard .tab-panels [role=\"tabpanel\"] {\n      background: #fff;\n      border: 1px solid #e5e5e5;\n      border-radius: 4px;\n      padding: 24px;\n    }\n    .tabcard .tab-panels [role=\"tabpanel\"][hidden] {\n      display: none;\n    }\n    .muted {\n      color: #666;\n    }\n    .ok {\n      color: #0a7a32;\n    }\n    .err {\n      color: #b00;\n    }\n    .profile-status {\n      padding: 12px 16px;\n      margin: 8px 0;\n      border-radius: 8px;\n      font-weight: 500;\n      text-align: center;\n      transition: all 0.3s ease;\n    }\n    .profile-status.muted {\n      background: #f5f5f5;\n      color: #666;\n      border: 1px solid #e0e0e0;\n    }\n    .profile-status.ok {\n      background: #e8f5e8;\n      color: #0a7a32;\n      border: 1px solid #c3e6c3;\n    }\n    .profile-status.err {\n      background: #ffeaea;\n      color: #b00;\n      border: 1px solid #ffcccc;\n    }\n    .hidden {\n      display: none !important;\n    }\n    table.table {\n      width: 100%;\n      border-collapse: separate;\n      border-spacing: 0;\n    }\n    .table th, .table td {\n      padding: 10px;\n      border-bottom: 1px solid #eee;\n      vertical-align: middle;\n    }\n    .table th {\n      text-align: left;\n      font-weight: 650;\n    }\n    .table tbody tr:last-child td {\n      border-bottom: 0;\n    }\n    .nowrap {\n      white-space: nowrap;\n    }\n    .datecell {\n      display: inline-flex;\n      align-items: center;\n      gap: 6px;\n    }\n    .delx {\n      appearance: none;\n      -webkit-appearance: none;\n      background: transparent;\n      border: 0;\n      color: #b00;\n      font-weight: 800;\n      cursor: pointer;\n      padding: 0 6px;\n      line-height: 1;\n    }\n    .delx:hover {\n      color: #e00;\n    }\n    @media (max-width: 640px) {\n      table.table, .table thead, .table tbody, .table th, .table td, .table tr {\n        display: block;\n      }\n      .table thead {\n        position: absolute;\n        left: -9999px;\n        top: -9999px;\n      }\n      .table tr {\n        border: 1px solid #eee;\n        border-radius: 12px;\n        padding: 8px;\n        margin-bottom: 8px;\n      }\n      .table td {\n        border: 0;\n        padding: 6px 0;\n        display: flex;\n        align-items: center;\n        justify-content: space-between;\n      }\n      .table td .col-lbl {\n        display: inline-block;\n        font-size: 12px;\n        color: #666;\n        margin-right: 10px;\n      }\n      .nowrap {\n        white-space: normal;\n      }\n    }\n  <\/style>\n<\/head>\n<body>\n  <div class=\"container\">\n    <h1>Contul meu<\/h1>\n    <div id=\"profileStatusTop\" class=\"profile-status\" style=\"display: none;\"><\/div>\n    <div id=\"authCard\" class=\"panel\">\n      <div id=\"gsiArea\" aria-label=\"Autentificare Google\">\n        <div id=\"g_id_onload\"\n             data-client_id=\"561911591877-5k5rpqqisut38ljjqvf5uudphduc4tco.apps.googleusercontent.com\"\n             data-context=\"signin\"\n             data-ux_mode=\"popup\"\n             data-callback=\"handleCredentialResponse\"\n             data-auto_select=\"false\"\n             data-auto_prompt=\"false\">\n        <\/div>\n        <div class=\"g_id_signin\"\n             data-type=\"standard\"\n             data-shape=\"rectangular\"\n             data-theme=\"outline\"\n             data-text=\"signin_with\"\n             data-size=\"large\"\n             data-logo_alignment=\"left\">\n        <\/div>\n      <\/div>\n      <div id=\"signedInAs\" class=\"muted\" role=\"status\" aria-live=\"polite\">\n        Te rog autentific\u0103-te pentru a continua.\n      <\/div>\n    <\/div>\n    <div id=\"app\" class=\"panel hidden\" style=\"margin-top:10px\">\n      <form id=\"profileForm\" novalidate action=\"\">\n        <input type=\"hidden\" name=\"SourceForm\" value=\"client_portal\">\n        <div class=\"row\">\n          <label for=\"p_fullName\">Name\n            <textarea id=\"p_fullName\" required autocomplete=\"name\"><\/textarea>\n          <\/label>\n          <label for=\"p_email\">Email <small class=\"hint\">(preluat din Google)<\/small>\n            <textarea id=\"p_email\" class=\"readonly\" required autocomplete=\"email\" readonly><\/textarea>\n          <\/label>\n        <\/div>\n        <div class=\"row\">\n          <label for=\"p_addressFull\">Adresa completa\n            <textarea id=\"p_addressFull\" required autocomplete=\"street-address\" placeholder=\"Strad\u0103, num\u0103r, scar\u0103, etaj, apartament, interfon...\"><\/textarea>\n          <\/label>\n        <\/div>\n        <div class=\"row\">\n          <label for=\"p_phone\">Phone number\n            <textarea id=\"p_phone\" required inputmode=\"tel\" autocomplete=\"tel\" placeholder=\"Ve\u0219tile de la vizite le trimitem pe WhatsApp\"><\/textarea>\n          <\/label>\n          <label for=\"p_contactBuch\">Contact de urgen\u021b\u0103 (obligatoriu)\n            <textarea id=\"p_contactBuch\" required placeholder=\"Nume \u0219i telefon, \u00een caz de urgen\u021b\u0103\"><\/textarea>\n          <\/label>\n        <\/div>\n        <div class=\"row\">\n          <label for=\"p_vetContact\">Date contact veterinar\n            <textarea id=\"p_vetContact\" required placeholder=\"Dac\u0103 nu ai un veterinar, las\u0103 adresa celui mai apropiat cabinet, \u00een caz de urgen\u021b\u0103\"><\/textarea>\n          <\/label>\n        <\/div>\n        <div class=\"row\">\n          <label for=\"p_water\">Apa\n            <textarea id=\"p_water\" required placeholder=\"de la robinet\/plat\u0103\/can\u0103 filtrant\u0103...\\nDac\u0103 ave\u021bi f\u00e2nt\u00e2n\u0103 electric\u0103, l\u0103sa\u021bi \u0219i un bol \u00een caz c\u0103 se ia curentul\"><\/textarea>\n          <\/label>\n        <\/div>\n        <div class=\"row\">\n          <label for=\"p_dryFood\">Hrana uscata\n            <textarea id=\"p_dryFood\" required placeholder=\"Unde e depozitat\u0103 \u0219i dac\u0103 e la discre\u021bie \/ cantitatea zilnic\u0103\"><\/textarea>\n          <\/label>\n          <label for=\"p_wetFood\">Hrana umeda\n            <textarea id=\"p_wetFood\" required placeholder=\"Unde e depozitat\u0103 \u0219i cantitatea zilnic\u0103\"><\/textarea>\n          <\/label>\n        <\/div>\n        <div class=\"row\">\n          <label for=\"p_treats\">Treats\/suplimente alimentare\n            <textarea id=\"p_treats\" required placeholder=\"Unde sunt depozitate \u0219i cantitatea zilnic\u0103\"><\/textarea>\n          <\/label>\n          <label for=\"p_litter\">Litiera\n            <textarea id=\"p_litter\" required placeholder=\"Tipul de nisip \u0219i unde se arunc\u0103\"><\/textarea>\n          <\/label>\n        <\/div>\n        <div class=\"row\">\n          <label for=\"p_safeWindow\">Ce fereastra poate fi deschisa in siguranta pentru aerisire pe durata vizitei\n            <textarea id=\"p_safeWindow\" required placeholder=\"Ai grij\u0103 ca \u00eenainte de plecare s\u0103 \u00eenchizi u\u015file \u015fi ferestrele pentru siguran\u0163a casei \u015fi a pisicilor\"><\/textarea>\n          <\/label>\n          <label for=\"p_initialVisit\">C\u00e2nd preferi s\u0103 stabilim vizita ini\u0163ial\u0103 gratuit\u0103, pentru a cunoa\u015fte pisica \u015fi a prelua cheile?\n            <textarea id=\"p_initialVisit\" required placeholder=\"Variante de date \u0219i interval orar\"><\/textarea>\n          <\/label>\n        <\/div>\n        <!-- Servicii moved to Rezervare flow as its own section -->\n        <div class=\"row\">\n          <label for=\"p_otherInfo\">Alte informatii\n            <textarea id=\"p_otherInfo\"><\/textarea>\n          <\/label>\n        <\/div>\n        <section id=\"catsSection\" style=\"margin-top:12px\">\n          <h3 style=\"margin:0 0 4px 0\">DETALII DESPRE PISICI<\/h3>\n          <p class=\"muted\" style=\"margin:0 0 8px 0; font-size: 0.9rem;\">(se completeaz\u0103 pentru fiecare pisic\u0103 \u00een parte)<\/p>\n          <div class=\"muted\" style=\"white-space:pre-line;margin:4px 0 12px\">\n            Numele pisicii, sex, culoare, data na\u015fterii, sterilizat\u0103\/castrat\n            Grad de socializare (Prietenoas\u0103, vrea afec\u021biune \/ indiferent\u0103, vrea s\u0103 fie l\u0103sat\u0103 \u00een pace \/ Fricoas\u0103 cu str\u0103inii, se ascunde sau atac\u0103)\n            Probleme de s\u0103n\u0103tate (dac\u0103 e cazul, schema de tratament)\n            Alte particularit\u0103\u021bi sau tabieturi\n          <\/div>\n          <div id=\"catsList\" class=\"cats-list\"><\/div>\n          <div style=\"margin-top:10px\">\n            <button id=\"btnAddCat\" type=\"button\" class=\"btn-add\">\u2795 Adaug\u0103 un c\u00e2mp nou (alt\u0103 pisic\u0103)<\/button>\n          <\/div>\n        <\/section>\n        <div class=\"sticky\">\n          <div class=\"inner\">\n            <div class=\"actions\">\n              <button id=\"btnSave\" class=\"btn\" type=\"submit\" disabled>Salveaz\u0103 profil<\/button>              \n            <\/div>\n            <div style=\"min-width:220px;flex:1\">\n              <div id=\"profileStatus\" class=\"muted\" role=\"status\" aria-live=\"polite\">\n                \n              <\/div>\n              <div id=\"profileResult\" class=\"muted\" role=\"status\" aria-live=\"polite\"><\/div>\n            <\/div>\n          <\/div>\n        <\/div>\n      <input type=\"hidden\" name=\"trp-form-language\" value=\"en\"\/><\/form>\n    <\/div>\n  <\/div>\n  <script>\n    \/* Version: v1.1.9 \u2014 updated webapp URL for profile saving *\/\n    \/\/ Get web app URL directly\n    const WEB_APP_URL = (document.querySelector('meta[name=\"web-app-url\"]')?.content || '').trim();\n\n    let ID_TOKEN = null, TOKEN = null, DIRTY = false;\n    let RES_TMP = {\n      period: { start: null, end: null },\n      settings: { frequency: 'daily', perDay: 1, dayPart: 'any', duration: 30 },\n      advanced: { diffDays: false, fixedHour: false, fixedHourTime: '10:00', fixedHourDetails: '' },\n      days: [],\n      excluded: [],\n      keys: { method: 'custody', pickupHome: false, returnHome: false, notes: '' },\n      services: { freeEnabled: false, freeNotes: '', paidEnabled: false, paidNotes: '' }\n    };\n    const $ = id => document.getElementById(id);\n\n    function dbg(label, data) {\n      try {\n        const pre = $('debugPre'), box = $('debugBox');\n        if (pre && box) {\n          pre.textContent += `\\n=== ${label} ===\\n${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\\n`;\n          box.classList.remove('hidden');\n        }\n      } catch {}\n    }\n\n    function decodeJwtPayload(idToken) {\n      try {\n        const seg = idToken.split('.')[1] || '';\n        const b64 = seg.replace(\/-\/g, '+').replace(\/_\/g, '\/');\n        const pad = b64 + '==='.slice((b64.length + 3) % 4);\n        const json = decodeURIComponent(Array.prototype.map.call(atob(pad), c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)).join(''));\n        return JSON.parse(json);\n      } catch {\n        return null;\n      }\n    }\n\n    function sanitizePhone(raw) {\n  const s = String(raw || '').trim();\n  const hasPlus = s.startsWith('+');\n  \/\/ keep only digits, then re-add one leading + if it was present\n  let v = s.replace(\/[^\\d]\/g, '');\n  return hasPlus ? ('+' + v) : v;\n}\n\n    function localISODate(d) {\n      return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;\n    }\n\n    function parseYMD(s) {\n      if (!s) return null;\n      const [y, m, d] = s.split('-').map(Number);\n      return (y && m && d) ? new Date(y, m - 1, d) : null;\n    }\n\n    function daysInclusive(a, b) {\n      const MS = 86400000;\n      const da = parseYMD(a), db = parseYMD(b);\n      return (da && db) ? Math.floor((db.getTime() - da.getTime()) \/ MS) + 1 : 0;\n    }\n\n    function fmtDateRO(d) {\n      return d ? d.toLocaleDateString('ro-RO', { day: 'numeric', month: 'long', year: 'numeric' }) : '';\n    }\n\n    function fmtDateShort(d) {\n      return d ? `${String(d.getDate()).padStart(2, '0')}.${String(d.getMonth() + 1).padStart(2, '0')}.${d.getFullYear()}` : '';\n    }\n\n\tfunction getForm(){\n\t  \/\/ Cats as compact text only\n\t  const cleaned = (CATS || []).map(c => ({\n\t\tname: clampLen(String(c?.name || ''), CAT_NAME_MAX).trim(),\n\t\tdetails: clampLen(String(c?.details || ''), CAT_DETAILS_MAX).trim()\n\t  }));\n\t  const catsPayload = {};\n\t  cleaned.forEach((c, i) => {\n\t\tconst txt = combinedCatText(c); \/\/ \"Name \u2014 details\" or just one of them\n\t\tif (txt) catsPayload[i + 1] = txt; \/\/ text only \u2192 smaller payload\n\t  });\n\t  console.log('getForm: cleaned cats =', cleaned);\n\t  console.log('getForm: catsPayload =', catsPayload);\n\n\t  const base = {\n\t\tfullName: $('p_fullName')?.value.trim() || '',\n\t\temail:    $('p_email')?.value.trim() || '',\n\t\taddressFull: $('p_addressFull')?.value.trim() || '',\n\t\tphone: sanitizePhone($('p_phone')?.value.trim() || ''),\n\t\tcontactBucharest: $('p_contactBuch')?.value.trim() || '',\n\t\tvetContact: $('p_vetContact')?.value.trim() || '',\n\t\twater: $('p_water')?.value.trim() || '',\n\t\tdryFood: $('p_dryFood')?.value.trim() || '',\n\t\twetFood: $('p_wetFood')?.value.trim() || '',\n\t\ttreats: $('p_treats')?.value.trim() || '',\n\t\tlitter: $('p_litter')?.value.trim() || '',\n\t\tsafeWindow: $('p_safeWindow')?.value.trim() || '',\n\t\tinitialVisitPref: $('p_initialVisit')?.value.trim() || '',\n\t\tservicesFree: '',\n\t\tservicesPaid: '',\n\t\t\/\/ keyReturn removed from profile UI\n\t\totherInfo: $('p_otherInfo')?.value.trim() || ''\n\t  };\n\t  const out = compact(base);\n\t  if (Object.keys(catsPayload).length) out.cats = catsPayload;\n\t  return out;\n\t}\n\n\tfunction setForm(p){\n\t  const set = (id, v) => { $('p_' + id) && ($('p_' + id).value = v || ''); };\n\t  set('fullName', p.fullName);\n\t  set('email', p.email);\n\t  set('addressFull', p.addressFull);\n\t  set('phone', p.phone);\n\t  set('contactBuch', p.contactBucharest);\n\t  set('vetContact', p.vetContact);\n\t  set('water', p.water);\n\t  set('dryFood', p.dryFood);\n\t  set('wetFood', p.wetFood);\n\t  set('treats', p.treats);\n\t  set('litter', p.litter);\n\t  set('safeWindow', p.safeWindow);\n\t  set('initialVisit', p.initialVisitPref);\n\t  \/\/ Servicii moved to Rezervare; ignore legacy keyReturn\n\t  set('otherInfo', p.otherInfo);\n\n\t  \/\/ Accept old shapes (array of strings) or new objects\n\t  const incomingCats = Array.isArray(p.cats)\n\t\t? p.cats\n\t\t: (p.cats && typeof p.cats === 'object') ? p.cats : [];\n\n\t  renderCats(incomingCats);\n\t}\n\n\t\tfunction fmtTs(ts) {\n\t\t  try {\n\t\t\treturn new Date(ts).toLocaleString('ro-RO');\n\t\t  } catch {\n\t\t\treturn '';\n\t\t  }\n\t\t}\n\t\t\n\t\tconst CAT_NAME_MAX = 60;\n\t\tconst CAT_DETAILS_MAX = 400;\n\t\tlet CATS = [];\n\t\t\n\t\tfunction splitNameDetails(raw){\n\t  const s = clampLen(String(raw || '').trim(), CAT_NAME_MAX + 5 + CAT_DETAILS_MAX);\n\t  if (!s) return { name:'', details:'' };\n\n\t  \/\/ If multiple lines: first = name, rest = details\n\t  const lines = s.split(\/\\r?\\n\/).map(x => x.trim()).filter(Boolean);\n\t  if (lines.length > 1) {\n\t\tconst first = clampLen(lines.shift() || '', CAT_NAME_MAX);\n\t\tconst rest  = clampLen(lines.join('\\n'), CAT_DETAILS_MAX);\n\t\tif (first && rest) return { name:first, details:rest };\n\t  }\n\n\t  \/\/ Delimiters with spaces preferred: \u2014, \u2013, -, :\n\t  const m = s.match(\/^\\s*(.{1,80}?)\\s*(?:\u2014|\u2013|:|-)\\s+(.+)$\/);\n\t  if (m) {\n\t\tconst name    = clampLen((m[1] || '').trim(), CAT_NAME_MAX);\n\t\tconst details = clampLen((m[2] || '').trim(), CAT_DETAILS_MAX);\n\t\tif (name && details) return { name, details };\n\t  }\n\n\t  \/\/ Fallback: treat whole thing as details\n\t  return { name:'', details: clampLen(s, CAT_DETAILS_MAX) };\n\t}\n\n\t\tfunction clampLen(s, max){ return String(s || '').slice(0, max); }\n\n\nfunction normalizeCatsIncoming(input){\n  const toPair = (v) => {\n    if (v && typeof v === 'object') {\n      let name    = clampLen(v.name || '', CAT_NAME_MAX);\n      let details = clampLen(v.details || '', CAT_DETAILS_MAX);\n      const txt   = v.text || v.desc || v.description || v.label || '';\n\n      \/\/ Backfill from combined text if one of the fields is missing\n      if ((!name || !details) && txt) {\n        const nd = splitNameDetails(txt);\n        if (!name)    name = nd.name;\n        if (!details) details = nd.details;\n      }\n      return { name, details };\n    }\n    \/\/ Legacy plain string\n    return splitNameDetails(v);\n  };\n\n  if (Array.isArray(input)) {\n    const out = (input.length ? input : ['']).map(toPair);\n    return out.length ? out : [{ name:'', details:'' }];\n  }\n\n  if (input && typeof input === 'object') {\n    const out = [];\n    Object.keys(input)\n      .sort((a,b) => Number(a) - Number(b))\n      .forEach(k => out.push(toPair(input[k])));\n    return out.length ? out : [{ name:'', details:'' }];\n  }\n\n  return [{ name:'', details:'' }];\n}\n\nfunction combinedCatText(c){\n  const nm = String(c?.name || '').trim();\n  const dt = String(c?.details || '').trim();\n  if (nm && dt) return `${nm} \u2014 ${dt}`;\n  return nm || dt;\n}\n\n    function escapeHtml(s) {\n      return String(s || '').replace(\/[&<>\"']\/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', '\\'': '&#39;' })[c]);\n    }\n\n    function clampCatText(s) {\n      return String(s || '').slice(0, 400);\n    }\n\nfunction renderCats(arr){\n  CATS = normalizeCatsIncoming(arr);\n  if (!CATS.length) CATS = [{ name:'', details:'' }];\n\n  const list = $('catsList');\n  if (!list) return;\n  list.innerHTML = '';\n\n  CATS.forEach((cat, idx) => {\n    const nameId = `cat_${idx}_name`;\n    const detId  = `cat_${idx}_details`;\n    const nameCntId = `cat_${idx}_name_cnt`;\n    const detCntId  = `cat_${idx}_details_cnt`;\n\n    const wrap = document.createElement('div');\n    wrap.className = 'cat-item';\n    wrap.innerHTML = `\n      <div class=\"cat-head\">\n        <strong>Pisica ${idx + 1}<\/strong>\n        <button type=\"button\" class=\"btn-light\" data-remove-idx=\"${idx}\" ${idx === 0 ? 'disabled' : ''}>\u0218terge<\/button>\n      <\/div>\n\n      <label for=\"${nameId}\" style=\"display:grid;gap:6px;margin:6px 0\">\n        <span>Nume<\/span>\n        <textarea id=\"${nameId}\" maxlength=\"${CAT_NAME_MAX}\" placeholder=\"\">${escapeHtml(cat.name)}<\/textarea>\n        <div class=\"charcount\"><span id=\"${nameCntId}\">${cat.name.length}<\/span>\/${CAT_NAME_MAX}<\/div>\n      <\/label>\n\n      <label for=\"${detId}\" style=\"display:grid;gap:6px;margin:6px 0\">\n        <span>Detalii<\/span>\n        <textarea id=\"${detId}\" maxlength=\"${CAT_DETAILS_MAX}\" placeholder=\"Detalii (max ${CAT_DETAILS_MAX} caractere)\">${escapeHtml(cat.details)}<\/textarea>\n        <div class=\"charcount\"><span id=\"${detCntId}\">${cat.details.length}<\/span>\/${CAT_DETAILS_MAX}<\/div>\n      <\/label>\n    `;\n    list.appendChild(wrap);\n\n    \/\/ Wire inputs\n    const nameEl = $(nameId);\n    const detEl  = $(detId);\n    const nameCnt = $(nameCntId);\n    const detCnt  = $(detCntId);\n\n    if (nameEl) {\n      nameEl.addEventListener('input', () => {\n        nameEl.value = clampLen(nameEl.value, CAT_NAME_MAX);\n        CATS[idx].name = nameEl.value;\n        if (nameCnt) nameCnt.textContent = String(nameEl.value.length);\n      });\n    }\n    if (detEl) {\n      detEl.addEventListener('input', () => {\n        detEl.value = clampLen(detEl.value, CAT_DETAILS_MAX);\n        CATS[idx].details = detEl.value;\n        if (detCnt) detCnt.textContent = String(detEl.value.length);\n      });\n    }\n  });\n\n  \/\/ Remove buttons\n  list.querySelectorAll('[data-remove-idx]').forEach(btn => {\n    btn.addEventListener('click', () => {\n      const i = Number(btn.getAttribute('data-remove-idx'));\n      if (i > 0) {\n        CATS.splice(i, 1);\n        renderCats(CATS);\n      }\n    });\n  });\n}\n\nfunction addEmptyCat(){\n  CATS.push({ name:'', details:'' });\n  renderCats(CATS);\n  setTimeout(() => {\n    const last = document.querySelector('#catsList textarea:last-of-type');\n    last?.focus();\n  }, 0);\n}\n\n    const LS = {\n      base: 'fm_profile',\n      key(email) { return `${this.base}:${(email || '').toLowerCase()}`; },\n      read(email) {\n        try {\n          const t = localStorage.getItem(this.key(email));\n          return t ? JSON.parse(t) : null;\n        } catch {\n          return null;\n        }\n      },\n      write(email, data) {\n        try {\n          const o = { data, ts: Date.now() };\n          localStorage.setItem(this.key(email), JSON.stringify(o));\n          return o;\n        } catch (e) {\n          dbg('LS_WRITE_ERR', String(e));\n          return null;\n        }\n      }\n    };\n\n    function saveLocal(msg) {\n      const email = TOKEN?.email || $('p_email')?.value;\n      if (!email) return;\n      const o = LS.write(email, getForm());\n      DIRTY = false;\n      const el = $('profileResult');\n      if (el && msg) {\n        el.className = 'muted';\n        el.textContent = `${msg} la ${fmtTs(o?.ts || Date.now())}`;\n      }\n\t  updateRezTabAccess();\n    }\n\n    function restoreLocal() {\n      const email = TOKEN?.email || $('p_email')?.value;\n      if (!email) return false;\n      const d = LS.read(email);\n      if (d?.data) {\n        setForm(d.data);\n        DIRTY = false;\n        const el = $('profileResult');\n        if (el) {\n          el.className = 'ok';\n          el.textContent = `Preluat din stocarea local\u0103 (${fmtTs(d.ts)})`;\n        }\n        return true;\n      }\n      return false;\n    }\n\n    const RLS = {\n      base: 'fm_reservari',\n      key(email) { return `${this.base}:${(email || 'anon').toLowerCase()}`; },\n      read(email) {\n        try {\n          const t = localStorage.getItem(this.key(email));\n          return t ? JSON.parse(t) : null;\n        } catch {\n          return null;\n        }\n      },\n      write(email, data) {\n        try {\n          const o = { data, ts: Date.now() };\n          localStorage.setItem(this.key(email), JSON.stringify(o));\n          return o;\n        } catch (e) {\n          dbg('RLS_WRITE_ERR', String(e));\n          return null;\n        }\n      }\n    };\n\n    function saveResLocal() {\n      const email = TOKEN?.email || $('p_email')?.value || 'anon';\n      RLS.write(email, RES_TMP);\n    }\n\n    function restoreResLocal() {\n      const email = TOKEN?.email || $('p_email')?.value || 'anon';\n      const d = RLS.read(email);\n      if (d?.data) {\n        RES_TMP = { ...RES_TMP, ...d.data };\n        return true;\n      }\n      dbg('RES_LOAD_ERR', 'No data');\n      return false;\n    }\n\n\tasync function api(action, data = {}, opts = {}) {\n\t  console.log('\ud83c\udf10 api: Starting request', { action, dataKeys: Object.keys(data), hasSignal: !!opts.signal });\n\t  \n\t  if (!WEB_APP_URL) throw new Error('WEB_APP_URL missing');\n\t  if (!ID_TOKEN) throw new Error('Missing ID token');\n\n\t  let body;\n\t  \/\/ For reservation submissions, send data directly without envelope\n\t  if (action === 'submitReservation') {\n\t\tbody = new URLSearchParams();\n\t\tbody.set('action', action);\n\t\tbody.set('payload', JSON.stringify({ ...data, SourceForm: 'client_portal' }));\n\t\tbody.set('idToken', ID_TOKEN);\n\t  } else {\n\t\t\/\/ For other actions, use the envelope structure\n\t\tconst envelope = { idToken: ID_TOKEN, action, payload: { ...data, SourceForm: 'client_portal' } };\n\t\tbody = new URLSearchParams();\n\t\tbody.set('action', action);\n\t\tbody.set('payload', JSON.stringify(envelope));\n\t  }\n\n\t  const makeReq = (keep) => {\n\t\tconsole.log('\ud83c\udf10 api: Making fetch request', { \n\t\t  url: WEB_APP_URL, \n\t\t  action, \n\t\t  hasSignal: !!opts.signal,\n\t\t  keepalive: !!keep,\n\t\t  timestamp: new Date().toISOString()\n\t\t});\n\t\t\n\t\treturn fetch(WEB_APP_URL, {\n\t\t  method: 'POST',\n\t\t  headers: { 'Content-Type': 'application\/x-www-form-urlencoded;charset=utf-8' },\n\t\t  body,\n\t\t  keepalive: !!keep,\n\t\t  mode: 'cors',\n\t\t  redirect: 'follow',\n\t\t  cache: 'no-store',\n\t\t  signal: opts.signal || undefined\n\t\t});\n\t  };\n\n\t  let r;\n\t  try {\n\t\tconsole.log('\ud83c\udf10 api: Attempting request with keepalive');\n\t\tr = await makeReq(true);\n\t\tconsole.log('\ud83c\udf10 api: Request with keepalive successful', { status: r.status, statusText: r.statusText });\n\t  } catch (e1) {\n\t\tconsole.log('\ud83c\udf10 api: Request with keepalive failed, retrying without keepalive', e1);\n\t\t\/\/ Retry without keepalive; some devices block cross-origin keepalive\n\t\tr = await makeReq(false);\n\t\tconsole.log('\ud83c\udf10 api: Request without keepalive completed', { status: r.status, statusText: r.statusText });\n\t  }\n\n\t  console.log('\ud83c\udf10 api: Processing response', { status: r.status, statusText: r.statusText, ok: r.ok });\n\t  const t = await r.text();\n\t  console.log('\ud83c\udf10 api: Response text received', { length: t.length, preview: t.substring(0, 200) });\n\t  \n\t  let j;\n\t  try { \n\t\tj = JSON.parse(t); \n\t\tconsole.log('\ud83c\udf10 api: JSON parsed successfully', { ok: j?.ok, hasResult: !!j?.result, hasError: !!j?.error });\n\t  } catch (parseErr) { \n\t\tconsole.log('\u274c api: JSON parse failed', { error: parseErr, text: t });\n\t\tthrow new Error(`Bad JSON: ${t}`); \n\t  }\n\t  \n\t  if (!r.ok || !j?.ok) {\n\t\tconsole.log('\u274c api: Request failed', { httpOk: r.ok, responseOk: j?.ok, error: j?.error, status: r.status });\n\t\tthrow new Error(j?.error || `HTTP ${r.status} ${t}`);\n\t  }\n\t  \n\t  console.log('\u2705 api: Request successful');\n\t  return j;\n\t}\n\n\t\/\/ NEW: Background processing function\n\tasync function triggerBackgroundProcessing(submissionId) {\n\t  if (!WEB_APP_URL) {\n\t\tconsole.warn('Web app URL not configured');\n\t\treturn;\n\t  }\n\t  \n\t  try {\n\t\tconst response = await fetch(WEB_APP_URL, {\n\t\t  method: 'POST',\n\t\t  headers: { 'Content-Type': 'application\/x-www-form-urlencoded;charset=utf-8' },\n\t\t  body: 'action=backgroundProcessing&submissionId=' + encodeURIComponent(submissionId),\n\t\t  mode: 'cors',\n\t\t  cache: 'no-store'\n\t\t});\n\t\t\n\t\tif (response.ok) {\n\t\t  const result = await response.json();\n\t\t  console.log('Background processing completed:', result);\n\t\t} else {\n\t\t  console.warn('Background processing failed:', response.status);\n\t\t}\n\t  } catch (error) {\n\t\tconsole.warn('Background processing error:', error);\n\t\t\/\/ Don't throw - background processing is optional\n\t  }\n\t}\n\t\n\tfunction formURLEncoded(action, data) {\n  const envelope = { idToken: ID_TOKEN, action, payload: { ...data, SourceForm: 'client_portal' } };\n  return 'action=' + encodeURIComponent(action) +\n         '&payload=' + encodeURIComponent(JSON.stringify(envelope));\n}\n\n\/** Try to send without waiting; returns boolean \"queued\" status *\/\nfunction sendFast(action, data) {\n  const payload = formURLEncoded(action, data);\n  \/\/ 1) Try sendBeacon (most reliable on unload, ~64 KB cap)\n  if (navigator.sendBeacon) {\n    try {\n      return navigator.sendBeacon(\n        WEB_APP_URL,\n        new Blob([payload], { type: 'application\/x-www-form-urlencoded;charset=utf-8' })\n      );\n    } catch {}\n  }\n  \/\/ 2) Fallback to a fire-and-forget fetch\n  try {\n    fetch(WEB_APP_URL, {\n      method: 'POST',\n      headers: { 'Content-Type': 'application\/x-www-form-urlencoded;charset=utf-8' },\n      body: payload,\n      keepalive: true\n    }).catch(()=>{});\n    return true;\n  } catch { return false; }\n}\n\n\/** Background confirmation with timeout (does not block UI) *\/\nfunction confirmInBackground(action, data, ms = 10000) {\n  const ctrl = new AbortController();\n  const t = setTimeout(() => ctrl.abort('timeout'), ms);\n  return api(action, data, { signal: ctrl.signal }).finally(() => clearTimeout(t));\n}\n\n\/** Keep only non-empty string-ish values *\/\nfunction compact(obj) {\n  const out = {};\n  Object.entries(obj || {}).forEach(([k, v]) => {\n    if (v == null) return;\n    const s = typeof v === 'string' ? v.trim() : v;\n    if (typeof s === 'string') { if (s) out[k] = s; }\n    else out[k] = s;\n  });\n  return out;\n}\n\n\n\n    function handleCredentialResponse(resp) {\n  ID_TOKEN = resp?.credential || null;\n  TOKEN = ID_TOKEN ? decodeJwtPayload(ID_TOKEN) : null;\n  if (ID_TOKEN) {\n    const email = TOKEN?.email || 'utilizator';\n    $('p_email') && ($('p_email').value = email);\n    $('signedInAs') && ( $('signedInAs').textContent = `Autentificat: ${email}` );\n    $('gsiArea') && ( $('gsiArea').style.display = 'none' );\n    $('app') && $('app').classList.remove('hidden');\n    $('btnSave') && ($('btnSave').disabled = false);\n    restoreLocal();\n    autoLoadProfile();\n    updateRezTabAccess();\n  }\n}\nwindow.handleCredentialResponse = handleCredentialResponse;\n\n\/\/ \u2b07\ufe0f Drain anything queued by the head stub (in case Google fired early)\nif (Array.isArray(window.__gsiQueue)) {\n  window.__gsiQueue.splice(0).forEach(handleCredentialResponse);\n}\n    window.handleCredentialResponse = handleCredentialResponse;\n\n    async function autoLoadProfile() {\n      const resEl = $('profileResult');\n      const topEl = $('profileStatusTop');\n      if (!ID_TOKEN) {\n        console.log('\ud83d\udd0d autoLoadProfile: No ID_TOKEN available');\n        return;\n      }\n      \n      console.log('\ud83d\udd0d autoLoadProfile: Starting profile load', {\n        email: TOKEN?.email,\n        google_sub: TOKEN?.sub,\n        timestamp: new Date().toISOString()\n      });\n      \n      \/\/ Show loading message in both places\n      resEl.className = 'muted';\n      resEl.textContent = 'Profilul t\u0103u se \u00eencarc\u0103 de pe server\u2026';\n      if (topEl) {\n        topEl.className = 'profile-status muted';\n        topEl.textContent = 'Profilul t\u0103u se \u00eencarc\u0103 de pe server\u2026';\n        topEl.style.display = 'block';\n      }\n      try {\n        \/\/ Add timeout to prevent hanging\n        const controller = new AbortController();\n        const timeoutId = setTimeout(() => {\n          console.log('\u23f0 autoLoadProfile: 30 second timeout reached, aborting request');\n          controller.abort();\n        }, 30000); \/\/ 30 second timeout\n        \n        console.log('\ud83d\udd0d autoLoadProfile: Making API call to getProfile');\n        const j = await api('getProfile', { \n          email: TOKEN?.email || '', \n          google_sub: TOKEN?.sub || '', \n          __debug: true \n        }, { signal: controller.signal });\n        \n        clearTimeout(timeoutId);\n        console.log('\ud83d\udd0d autoLoadProfile: API call completed successfully', j);\n        const data = j.result || {};\n        console.log('\ud83d\udd0d autoLoadProfile: Processing response data', { \n          hasResult: !!j.result, \n          dataKeys: Object.keys(data),\n          catsCount: Array.isArray(data.cats) ? data.cats.length : 'not array'\n        });\n        \n        const hasAny = ['fullName', 'nume_si_prenume', 'email', 'addressFull', 'adresa_completa', 'telefon', 'vetContact', 'contact_veterinar', 'water', 'apa', 'dryFood', 'hrana_uscata', 'wetFood', 'hrana_umeda', 'treats', 'treats_suplimente', 'litter', 'litiera', 'safeWindow', 'fereastra_sigura', 'initialVisitPref', 'vizita_initiala', 'servicesFree', 'servicii_gratuite', 'servicesPaid', 'servicii_contra_cost', 'keyReturnMethod', 'returnare_chei', 'otherInfo'].some(k => (data[k] && String(data[k]).trim()));\n        const remoteExists = hasAny || (Array.isArray(data.cats) && data.cats.length > 0);\n        \n        console.log('\ud83d\udd0d autoLoadProfile: Profile data analysis', { \n          hasAny, \n          remoteExists, \n          catsArray: Array.isArray(data.cats),\n          catsLength: Array.isArray(data.cats) ? data.cats.length : 'N\/A'\n        });\n        \n        if (!remoteExists) {\n          console.log('\ud83d\udd0d autoLoadProfile: No profile data found on server');\n          resEl.className = 'muted';\n          resEl.textContent = 'Nu s-au g\u0103sit date pe server pentru acest cont \u00eenc\u0103.';\n          if (topEl) {\n            topEl.className = 'profile-status muted';\n            topEl.textContent = 'Nu s-au g\u0103sit date pe server pentru acest cont \u00eenc\u0103.';\n            topEl.style.display = 'block';\n          }\n          return;\n        }\n        try {\n          const email = TOKEN?.email || $('p_email')?.value || '';\n          const existingLocal = LS.read(email);\n          if (existingLocal) localStorage.setItem(LS.key(email) + ':backup', JSON.stringify(existingLocal));\n        } catch {}\n        setForm({\n          fullName: data.fullName || data.nume_si_prenume || '',\n          email: data.email || '',\n          addressFull: data.addressFull || data.adresa_completa || '',\n          phone: data.phone || data.telefon || '',\n          contactBucharest: data.contactBucharest || data.persoana_contact_bucuresti || '',\n          vetContact: data.vetContact || data.contact_veterinar || '',\n          water: data.water || data.apa || '',\n          dryFood: data.dryFood || data.hrana_uscata || '',\n          wetFood: data.wetFood || data.hrana_umeda || '',\n          treats: data.treats || data.treats_suplimente || '',\n          litter: data.litter || data.litiera || '',\n          safeWindow: data.safeWindow || data.fereastra_sigura || '',\n          initialVisitPref: data.initialVisitPref || data.vizita_initiala || '',\n          servicesFree: data.servicesFree || data.servicii_gratuite || '',\n          servicesPaid: data.servicesPaid || data.servicii_contra_cost || '',\n          keyReturnMethod: data.keyReturnMethod || data.returnare_chei || '',\n          otherInfo: data.otherInfo || data.alte_informatii || '',\n          cats: Array.isArray(data.cats) ? data.cats : (data.cats || [])\n        });\n        DIRTY = false;\n        saveLocal('Salvat local');\n        console.log('\ud83d\udd0d autoLoadProfile: Profile data found, showing success message');\n        resEl.className = 'ok';\n        resEl.textContent = 'Profil \u00eenc\u0103rcat automat de pe server.';\n        if (topEl) {\n          topEl.className = 'profile-status ok';\n          topEl.textContent = 'Profil \u00eenc\u0103rcat automat de pe server.';\n          topEl.style.display = 'block';\n          \/\/ Hide the top notification after 5 seconds\n          setTimeout(() => {\n            if (topEl) {\n              topEl.style.display = 'none';\n            }\n          }, 5000);\n        }\n      } catch (err) {\n        console.log('\u274c autoLoadProfile: Error occurred', {\n          name: err.name,\n          message: err.message,\n          stack: err.stack,\n          isAbortError: err.name === 'AbortError',\n          containsTimeout: err.message?.includes('timeout')\n        });\n        \n        \/\/ Handle timeout specifically\n        if (err.name === 'AbortError' || err.message?.includes('timeout')) {\n          console.log('\u23f0 autoLoadProfile: Timeout error - showing timeout message');\n          resEl.className = 'muted';\n          resEl.textContent = 'Timeout la \u00eenc\u0103rcarea profilului. \u00cencearc\u0103 din nou.';\n          if (topEl) {\n            topEl.className = 'profile-status muted';\n            topEl.textContent = 'Timeout la \u00eenc\u0103rcarea profilului. \u00cencearc\u0103 din nou.';\n            topEl.style.display = 'block';\n          }\n        } else {\n          console.log('\u274c autoLoadProfile: Other error - showing error message');\n          resEl.className = 'err';\n          resEl.textContent = String(err.message || err);\n          if (topEl) {\n            topEl.className = 'profile-status err';\n            topEl.textContent = String(err.message || err);\n            topEl.style.display = 'block';\n          }\n        }\n        dbg('AUTOLOAD_ERR', err?.stack || String(err));\n      }\n    }\n\n    \/\/ Singleton measure div to avoid creating\/destroying it repeatedly\n    let measureDiv = null;\n    let textareaEventListenersAdded = false;\n    \n    function adjustAllTextareas() {\n      \/\/ Create measure div only once\n      if (!measureDiv) {\n        measureDiv = document.createElement('div');\n        measureDiv.className = 'hidden-measure';\n        document.body.appendChild(measureDiv);\n      }\n\n      function autoSize(el) {\n        const cs = window.getComputedStyle(el);\n        measureDiv.style.font = cs.font;\n        measureDiv.style.fontSize = cs.fontSize;\n        measureDiv.style.lineHeight = cs.lineHeight;\n        measureDiv.style.padding = cs.padding;\n        \/\/ Use actual content box width to match wrapping\n        const w = (el.clientWidth && el.clientWidth > 0) ? (el.clientWidth + 'px') : cs.width;\n        measureDiv.style.width = w;\n        \/\/ Prefer content; fall back to placeholder (rendering newlines)\n        const content = String(el.value || '').trim();\n        if (content) {\n          measureDiv.textContent = content;\n        } else {\n          measureDiv.textContent = el.placeholder || '';\n        }\n        \/\/ Use measure height and also ensure scrollHeight fits\n        const measured = measureDiv.offsetHeight + 10;\n        \/\/ Compute scrollHeight for content or for placeholder when empty\n        el.style.height = 'auto';\n        let sc = el.scrollHeight;\n        if (!content) {\n          const saved = el.value;\n          el.value = (el.getAttribute('placeholder') || '').replace(\/\\\\n\/g, '\\n');\n          sc = Math.max(sc, el.scrollHeight);\n          el.value = saved;\n        }\n        let h = Math.max(38, measured, sc + 2);\n        \/\/ For long placeholders that still clip, compute line-based minimum for key fields\n        const id = el.id || '';\n        if (!content && (id === 'p_vetContact' || id === 'p_safeWindow' || id === 'p_servicesPaid' || id === 'p_servicesFree' || id === 'svFreeNotes' || id === 'svPaidNotes' || id === 'advFixedHourNotes')) {\n          const ph = (el.getAttribute('placeholder') || '').replace(\/\\\\n\/g, '\\n');\n          const lines = Math.max(1, ph.split('\\n').length);\n          const lineH = parseFloat(cs.lineHeight) || 18;\n          const padV = (parseFloat(cs.paddingTop) || 0) + (parseFloat(cs.paddingBottom) || 0);\n          const minByLines = Math.ceil(lines * lineH + padV + 10);\n          h = Math.max(h, minByLines);\n        }\n        el.style.height = h + 'px';\n        \/\/ Final safety pass: if still scrolls, expand to scrollHeight\n        if (el.scrollHeight > el.clientHeight) {\n          el.style.height = (el.scrollHeight + 2) + 'px';\n        }\n      }\n\n      const all = document.querySelectorAll('textarea');\n      all.forEach(t => {\n        autoSize(t);\n        \/\/ Only add event listeners once to prevent duplicates\n        if (!t._autoSizeListenerAdded) {\n          t.addEventListener('input', () => autoSize(t));\n          t._autoSizeListenerAdded = true;\n        }\n      });\n    }\n\n    \/\/ Singleton ResizeObserver to prevent multiple instances\n    let textareaResizeObserver = null;\n    \n    function observeTextareas() {\n      if (typeof ResizeObserver !== 'function') return;\n      \n      \/\/ Only create one ResizeObserver instance\n      if (!textareaResizeObserver) {\n        textareaResizeObserver = new ResizeObserver(entries => {\n          entries.forEach(entry => {\n            const el = entry.target;\n            \/\/ Recompute height when width\/layout changes\n            if (el && el.tagName === 'TEXTAREA') {\n              \/\/ Use a tiny async tick so layout settles\n              setTimeout(() => {\n                \/\/ Reuse adjustAllTextareas' autosizer via input trigger\n                const ev = new Event('input', { bubbles: false });\n                el.dispatchEvent(ev);\n              }, 0);\n            }\n          });\n        });\n      }\n      \n      \/\/ Only observe textareas that aren't already being observed\n      document.querySelectorAll('textarea').forEach(t => {\n        if (!t._resizeObserved) {\n          textareaResizeObserver.observe(t);\n          t._resizeObserved = true;\n        }\n      });\n    }\n\n    \/\/ Replace literal \"\\n\" sequences in placeholders with real newlines\n    function normalizePlaceholders() {\n      document.querySelectorAll('textarea[placeholder]').forEach(el => {\n        const ph = el.getAttribute('placeholder');\n        if (ph && ph.indexOf('\\\\n') !== -1) {\n          el.setAttribute('placeholder', ph.replace(\/\\\\n\/g, '\\n'));\n        }\n      });\n    }\n\n    \/\/ Fit cats section heading to largest size without wrapping\n    function fitCatsHeading() {\n      const h = document.querySelector('#catsSection h3');\n      if (!h) return;\n      \/\/ DISABLED: Let CSS handle the sizing instead of JavaScript override\n      \/\/ The CSS clamp() function should handle responsive sizing properly\n      return;\n    }\n\t\n\t\/* === Access gate helpers === *\/\nfunction requiredProfileIds() {\n  try {\n    const els = document.querySelectorAll('#profileForm textarea[required], #profileForm input[required]');\n    const ids = Array.from(els).map(el => el.id).filter(Boolean);\n    if (ids && ids.length) return ids;\n  } catch {}\n  \/\/ Fallback: keep in sync with DOM required fields; exclude optional p_otherInfo\n  return [\n    'p_fullName','p_email','p_addressFull','p_phone','p_contactBuch','p_vetContact',\n    'p_water','p_dryFood','p_wetFood','p_treats','p_litter','p_safeWindow',\n    'p_initialVisit','p_keyReturn'\n  ];\n}\nfunction profileIsComplete() {\n  if (!ID_TOKEN) return false; \/\/ must be signed in\n  for (const id of requiredProfileIds()) {\n    const el = document.getElementById(id);\n    if (!el || !String(el.value || '').trim()) return false;\n  }\n  return true;\n}\nfunction firstMissingProfileId() {\n  for (const id of requiredProfileIds()) {\n    const el = document.getElementById(id);\n    if (!el || !String(el.value || '').trim()) return id;\n  }\n  return null;\n}\nfunction showProfileIncompleteMessage() {\n  const s = document.getElementById('profileStatus');\n  if (s) {\n    s.className = 'err';\n    s.textContent = 'Te rug\u0103m s\u0103 completezi \u0219i s\u0103 salvezi profilul \u00eenainte de a accesa Rezerv\u0103ri.';\n  }\n  const saveBtn = document.getElementById('btnSave');\n  if (saveBtn) {\n    saveBtn.classList.add('pulse');\n    try { saveBtn.scrollIntoView({ behavior: 'smooth', block: 'center' }); } catch {}\n    setTimeout(() => saveBtn.classList.remove('pulse'), 2200);\n  }\n  const miss = firstMissingProfileId();\n  if (miss) {\n    try { const el = document.getElementById(miss); el && el.focus(); } catch {}\n  }\n}\nfunction updateRezTabAccess() {\n  const app = document.getElementById('app');\n  if (!app) return;\n  const rezTab = app.querySelector('.folder-tabs [role=\"tab\"][data-tab=\"rez\"]');\n  if (!rezTab) return;\n  const allowed = profileIsComplete();\n  rezTab.setAttribute('aria-disabled', allowed ? 'false' : 'true');\n  rezTab.title = allowed ? '' : 'Finalizeaz\u0103 profilul pentru a accesa Rezerv\u0103ri';\n}\n\n    function initTopTabs() {\n\tconst app = $('app');\n\tif (!app || app.dataset.tabsBuilt === '1') return;\n\n\tapp.classList.add('tabcard');\n\n\t  \/\/ Tablist\n\t  const tablist = document.createElement('div');\n\t  tablist.className = 'folder-tabs';\n\t  tablist.setAttribute('role', 'tablist');\n\t  tablist.setAttribute('aria-label', 'Sec\u021biuni portal');\n\n\t  const mkTab = (id, label, selected) => {\n\t\tconst b = document.createElement('button');\n\t\tb.type = 'button';\n\t\tb.setAttribute('role', 'tab');\n\t\tb.dataset.tab = id;\n\t\tb.setAttribute('aria-selected', selected ? 'true' : 'false');\n\t\tb.textContent = label;\n\t\treturn b;\n\t};\n\n  \/\/ Only two tabs now\n  const tabs = [\n    mkTab('profil', 'Profil', true),\n    mkTab('rez', 'Rezervare', false)\n  ];\n  tabs.forEach(b => tablist.appendChild(b));\n\n  \/\/ Panels\n  const panels = document.createElement('div');\n  panels.className = 'tab-panels';\n\n  const pProfil = document.createElement('section');\n  pProfil.id = 'tab-profil';\n  pProfil.setAttribute('role', 'tabpanel');\n\n  \/\/ move existing content into Profil panel\n  while (app.firstChild) pProfil.appendChild(app.firstChild);\n\n  const pRez = document.createElement('section');\n  pRez.id = 'tab-rez';\n  pRez.setAttribute('role', 'tabpanel');\n  pRez.hidden = true;\n\n  app.appendChild(tablist);\n  panels.append(pProfil, pRez);\n  app.appendChild(panels);\n\n  app.dataset.tabsBuilt = '1';\n  updateRezTabAccess();\n\n  function setActive(id) {\n    if (id === 'rez' && !profileIsComplete()) {\n      showProfileIncompleteMessage();\n      id = 'profil';\n    }\n    app.querySelectorAll('[role=\"tab\"]').forEach(btn => {\n      btn.setAttribute('aria-selected', btn.dataset.tab === id ? 'true' : 'false');\n    });\n    app.querySelectorAll('[role=\"tabpanel\"]').forEach(p => {\n      p.hidden = (p.id !== 'tab-' + id);\n    });\n    try { localStorage.setItem('fm_active_tab', id); } catch {}\n  }\n\n  \/\/ Click navigation + gate Rezervare (even when visually disabled)\n  tablist.addEventListener('click', e => {\n    const b = e.target.closest('[role=\"tab\"]');\n    if (!b) return;\n    if (b.dataset.tab === 'rez' && !profileIsComplete()) {\n      e.preventDefault();\n      showProfileIncompleteMessage();\n      updateRezTabAccess();\n      setActive('profil');\n      return;\n    }\n    setActive(b.dataset.tab);\n  });\n\n  \/\/ Also gate via keyboard when disabled\n  tablist.addEventListener('keydown', e => {\n    const targetBtn = e.target.closest('[role=\"tab\"]');\n    if (!targetBtn) return;\n    const isRez = targetBtn.dataset.tab === 'rez';\n    if ((e.key === 'Enter' || e.key === ' ') && isRez && !profileIsComplete()) {\n      e.preventDefault();\n      showProfileIncompleteMessage();\n      updateRezTabAccess();\n      setActive('profil');\n    }\n  });\n\n  \/\/ Arrow-key navigation\n  tablist.addEventListener('keydown', e => {\n    if (e.key !== 'ArrowRight' && e.key !== 'ArrowLeft') return;\n    const ts = Array.from(tablist.querySelectorAll('[role=\"tab\"]'));\n    const i = ts.indexOf(document.activeElement);\n    const dir = e.key === 'ArrowRight' ? 1 : -1;\n    const next = ts[(i + dir + ts.length) % ts.length];\n    e.preventDefault();\n    next.focus();\n    const target = (next.dataset.tab === 'rez' && !profileIsComplete()) ? 'profil' : next.dataset.tab;\n    setActive(target);\n  });\n\n  \/\/ Remember last tab; sanitize old \"istoric\"\n  let remembered = null;\n  try { remembered = localStorage.getItem('fm_active_tab'); } catch {}\n  if (remembered === 'istoric') {\n    try { localStorage.removeItem('fm_active_tab'); } catch {}\n    remembered = null;\n  }\n\n  if (remembered === 'rez') {\n    setActive(profileIsComplete() ? 'rez' : 'profil');\n  } else if (remembered === 'profil') {\n    setActive('profil');\n  } else {\n    setActive('profil');\n  }\n}\n\n\n    function setupRezervari() {\n      const container = $('tab-rez');\n      if (!container) return dbg('NAV_ERR', 'tab-rez not found');\n      container.innerHTML = `\n        <section id=\"rezPeriod\">\n          <h3 style=\"margin:0 0 6px 0\">Perioada vizitelor<\/h3>\n          <form id=\"resForm\" novalidate>\n            <div class=\"row\">\n              <label for=\"resStart\">Data de \u00eenceput\n                <input id=\"resStart\" type=\"date\" required aria-describedby=\"rezHint\">\n              <\/label>\n              <label for=\"resEnd\">Data de sf\u00e2r\u0219it\n                <input id=\"resEnd\" type=\"date\" required aria-describedby=\"rezHint\">\n              <\/label>\n            <\/div>\n            <p id=\"rezHint\" class=\"muted hint\" style=\"margin:6px 0 0\">Zilele de s\u0103rb\u0103toare din aceast\u0103 perioad\u0103 au tarif suplimentar.<\/p>\n            <div class=\"sticky\" style=\"margin-top:12px\">\n              <div class=\"inner\">\n                <div class=\"actions\">\n                  <button id=\"resContinue\" class=\"btn\" type=\"submit\" disabled>Continu\u0103<\/button>\n                <\/div>\n                <div style=\"min-width:220px;flex:1\">\n                  <div id=\"resMsg\" class=\"muted\" role=\"status\" aria-live=\"polite\"><\/div>\n                <\/div>\n              <\/div>\n            <\/div>\n          <\/form>\n        <\/section>`;\n      const startEl = $('resStart'), endEl = $('resEnd'), btn = $('resContinue'), msg = $('resMsg');\n      if (!startEl || !endEl || !btn || !msg) return dbg('NAV_ERR', `setupRezervari: startEl=${!!startEl}, endEl=${!!endEl}, btn=${!!btn}, msg=${!!msg}`);\n      const today = new Date(), min = localISODate(today);\n      startEl.min = min;\n      endEl.min = min;\n      startEl.value = RES_TMP.period?.start || min;\n      endEl.value = RES_TMP.period?.end || localISODate(new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1));\n      function validate() {\n        const s = startEl.value, e = endEl.value;\n        endEl.min = s || min;\n        const ok = s && e && !(parseYMD(e) < parseYMD(s));\n        btn.disabled = !ok;\n        msg.textContent = ok ? `Interval selectat: ${fmtDateRO(parseYMD(s))} \u2013 ${fmtDateRO(parseYMD(e))} (${daysInclusive(s, e)} ${daysInclusive(s, e) === 1 ? 'zi' : 'zile'}).` : (s && e ? 'Data de sf\u00e2r\u0219it trebuie s\u0103 fie dup\u0103 sau egal\u0103 cu data de \u00eenceput.' : 'Alege ambele date.');\n      }\n      startEl.addEventListener('change', validate);\n      endEl.addEventListener('change', validate);\n      validate();\n      const form = $('resForm');\n      if (!form) return dbg('NAV_ERR', 'resForm not found');\n      form.addEventListener('submit', e => {\n        e.preventDefault();\n        if (!btn.disabled) {\n          RES_TMP.period = { start: startEl.value, end: endEl.value };\n          saveResLocal();\n          renderVisitSettings();\n        }\n      });\n    }\n\n   function renderVisitSettings() {\n  const container = $('tab-rez');\n  if (!container) return dbg('NAV_ERR', 'tab-rez not found');\n\n  container.innerHTML = `\n    <section id=\"visitSettings\">\n      <h3 style=\"margin:0 0 6px 0\">Set\u0103ri vizite<\/h3>\n      <form id=\"vsForm\" novalidate>\n        <div class=\"row\">\n          <label for=\"vsPerDay\">Num\u0103r vizite\/zi\n            <select id=\"vsPerDay\">\n              <option value=\"1\" selected>1<\/option>\n              <option value=\"2\">2<\/option>\n            <\/select>\n          <\/label>\n\n          <label for=\"vsPart\">Partea zilei\n            <select id=\"vsPart\" aria-describedby=\"vsHint\">\n              <option value=\"any\" selected>Flexibil<\/option>\n              <option value=\"am\">AM (10\u201316)<\/option>\n              <option value=\"pm\">PM (16\u201322)<\/option>\n            <\/select>\n          <\/label>\n        <\/div>\n\n        <div class=\"row\">\n          <label for=\"vsDur\">Durata vizit\u0103\n            <select id=\"vsDur\">\n              <option value=\"30\" selected>30 min<\/option>\n              <option value=\"60\">60 min<\/option>\n              <option value=\"90\">90 min<\/option>\n            <\/select>\n          <\/label>\n        <\/div>\n\n        <p id=\"vsHint\" class=\"muted hint\" style=\"margin:6px 0 0\">\n          Dac\u0103 alegi 2 vizite\/zi, op\u021biunile AM\/PM sunt dezactivate.\n        <\/p>\n\n        <div class=\"sticky\" style=\"margin-top:12px\">\n          <div class=\"inner\">\n            <div class=\"actions\">\n              <button id=\"vsBack\" class=\"btn btn-alt\" type=\"button\" aria-label=\"\u00cenapoi la selec\u021bia perioadei\">\u00cenapoi<\/button>\n              <button id=\"vsContinue\" class=\"btn\" type=\"submit\">Continu\u0103<\/button>\n            <\/div>\n            <div style=\"min-width:220px;flex:1\">\n              <div id=\"vsMsg\" class=\"muted\" role=\"status\" aria-live=\"polite\"><\/div>\n            <\/div>\n          <\/div>\n        <\/div>\n      <\/form>\n    <\/section>`;\n\n  const selPerDay = $('vsPerDay');\n  const selPart   = $('vsPart');\n  const selDur    = $('vsDur');\n  const vsForm    = $('vsForm');\n  const vsBack    = $('vsBack');\n\n  if (!selPerDay || !selPart || !selDur || !vsForm || !vsBack) {\n    return dbg('NAV_ERR', `renderVisitSettings: perDay=${!!selPerDay}, part=${!!selPart}, dur=${!!selDur}, form=${!!vsForm}, back=${!!vsBack}`);\n  }\n\n  \/\/ Set current values (with sensible defaults)\n  selPerDay.value = String(RES_TMP.settings?.perDay ?? 1);\n  selPart.value   = RES_TMP.settings?.dayPart || 'any';\n  selDur.value    = String(RES_TMP.settings?.duration || 30);\n\n  function applyRules() {\n    \/\/ When 2 visits\/day, part-of-day is forced to ANY\n    const perDay = Number(selPerDay.value || 1);\n    const amOpt = selPart.querySelector('option[value=\"am\"]');\n    const pmOpt = selPart.querySelector('option[value=\"pm\"]');\n    if (perDay === 2) {\n      if (amOpt) amOpt.disabled = true;\n      if (pmOpt) pmOpt.disabled = true;\n      selPart.value = 'any';\n      RES_TMP.settings.dayPart = 'any';\n    } else {\n      if (amOpt) amOpt.disabled = false;\n      if (pmOpt) pmOpt.disabled = false;\n    }\n  }\n\n  function sync() {\n    RES_TMP.settings = {\n      \/\/ frequency removed \u2014 always daily internally\n      perDay: Number(selPerDay.value || 1),\n      dayPart: selPart.value,\n      duration: Number(selDur.value || 30)\n    };\n    saveResLocal();\n  }\n\n  selPerDay.addEventListener('change', () => { applyRules(); sync(); });\n  selPart.addEventListener('change', sync);\n  selDur.addEventListener('change', sync);\n\n  applyRules();\n  sync();\n\n  vsForm.addEventListener('submit', e => {\n    e.preventDefault();\n    sync();\n    renderAdvancedOptions();\n  });\n  vsBack.addEventListener('click', setupRezervari);\n}\n\n\nfunction renderAdvancedOptions() {\n  const container = $('tab-rez');\n  if (!container) return;\n\n  \/\/ Safe defaults for older local data\n  const fixedOn  = !!RES_TMP.advanced?.fixedHour;\n  const fixedVal = String(RES_TMP.advanced?.fixedHourTime || '10:00');\n  const fixedNotes = String(RES_TMP.advanced?.fixedHourDetails || '');\n\n  container.innerHTML = `\n    <section id=\"advOptions\">\n      <h3 style=\"margin:0 0 6px 0\">Op\u021biuni avansate<\/h3>\n      <form id=\"advForm\" novalidate>\n        <div class=\"row\" style=\"grid-template-columns:1fr;gap:10px\">\n          <label style=\"display:flex;align-items:center;gap:10px\">\n            <input id=\"advDiffDays\" type=\"checkbox\">\n            <span>Am nevoie ca unele zile s\u0103 fie diferite<\/span>\n          <\/label>\n\n          <!-- Fixed hour toggle -->\n          <label style=\"display:flex;align-items:center;gap:10px\">\n            <input id=\"advFixedHour\" type=\"checkbox\">\n            <span>Am nevoie de tratament la or\u0103 fix\u0103 (interval 1h)<\/span>\n          <\/label>\n\n          <!-- Mobile-friendly \"wheel\/spinner\" time input (shown only if fixed hour is ON) -->\n          <div id=\"advFixedHourWrap\" class=\"row\" style=\"grid-template-columns:1fr;gap:8px;${fixedOn ? '' : 'display:none'}\">\n            <label for=\"advFixedHourNotes\" style=\"display:grid;gap:6px\">\n              <span>Detalii tratament la or\u0103 fix\u0103<\/span>\n              <textarea id=\"advFixedHourNotes\" placeholder=\"Ex.: insulin\u0103 la 18:00; pastile diminea\u021ba\">${(fixedNotes || '').replace(\/<\/g,'&lt;')}<\/textarea>\n            <\/label>\n            <div class=\"muted\">Introduce\u021bi detaliile dorite \u00een c\u00e2mpul de mai sus.<\/div>\n          <\/div>\n        <\/div>\n\n        <div class=\"sticky\" style=\"margin-top:12px\">\n          <div class=\"inner\">\n            <div class=\"actions\">\n              <button id=\"advBack\" class=\"btn btn-alt\" type=\"button\" aria-label=\"\u00cenapoi la set\u0103ri vizite\">\u00cenapoi<\/button>\n              <button id=\"advContinue\" class=\"btn\" type=\"submit\">Continu\u0103<\/button>\n            <\/div>\n            <div style=\"min-width:220px;flex:1\"><div id=\"advMsg\" class=\"muted\" role=\"status\" aria-live=\"polite\"><\/div><\/div>\n          <\/div>\n        <\/div>\n      <\/form>\n    <\/section>\n  `;\n\n  const diffCb   = $('advDiffDays');\n  const fixedCb  = $('advFixedHour');\n  const wrapTime = $('advFixedHourWrap');\n  const timeInp  = $('advFixedHourTime');\n  const notesInp = $('advFixedHourNotes');\n  const advForm  = $('advForm');\n  const advBack  = $('advBack');\n  const advCont  = $('advContinue');\n\n  if (!diffCb || !fixedCb || !advForm || !advBack || !advCont) return;\n\n  \/\/ Initial state\n  diffCb.checked  = !!RES_TMP.advanced?.diffDays;\n  fixedCb.checked = fixedOn;\n\n  \/\/ Utilities\n  const clampHour = (v) => {\n    const [h] = String(v || '10:00').split(':').map(Number);\n    const hh = Math.min(22, Math.max(10, isFinite(h) ? h : 10));\n    return `${String(hh).padStart(2,'0')}:00`;\n  };\n\n  function syncAdvanced(save = true) {\n    const next = {\n      diffDays: !!diffCb.checked,\n      fixedHour: !!fixedCb.checked,\n      fixedHourTime: clampHour(timeInp?.value || fixedVal),\n      fixedHourDetails: String(notesInp?.value || '')\n    };\n    RES_TMP.advanced = next;\n\n    \/\/ If fixed hour is ON, keep rows aligned to the global hour\n    if (next.fixedHour) {\n      \/\/ Make sure days exist only if user already went through the table once;\n      \/\/ if not, the hour will be applied when building the table.\n      if (Array.isArray(RES_TMP.days) && RES_TMP.days.length) {\n        RES_TMP.days.forEach(d => { d.hour = next.fixedHourTime; });\n      }\n    }\n\n    if (save) saveResLocal();\n  }\n\n  function routeNext() {\n    \/\/ Require details if fixed-hour is enabled\n    if (fixedCb.checked) {\n      const details = String(notesInp?.value || '').trim();\n      if (!details) {\n        const msg = $('advMsg');\n        if (msg) { msg.className = 'err'; msg.textContent = 'Te rug\u0103m s\u0103 completezi detaliile pentru tratamentul la or\u0103 fix\u0103.'; }\n        try { notesInp?.focus(); } catch {}\n        return;\n      }\n    }\n    syncAdvanced(true);\n    RES_TMP.advanced.diffDays\n      ? renderPerDayTable()\n      : renderKeyHandling();\n  }\n\n  \/\/ Wire events\n  fixedCb.addEventListener('change', () => {\n    const on = !!fixedCb.checked;\n    if (wrapTime) wrapTime.style.display = on ? '' : 'none';\n    \/\/ If turning ON, ensure a valid time exists and apply immediately\n    if (on && timeInp) {\n      timeInp.value = clampHour(timeInp.value || '10:00');\n    }\n    syncAdvanced(true);\n    try { adjustAllTextareas(); setTimeout(adjustAllTextareas, 30); } catch {}\n  });\n\n  timeInp?.addEventListener('change', () => {\n    if (!fixedCb.checked) return;\n    timeInp.value = clampHour(timeInp.value);\n    syncAdvanced(true);\n  });\n  notesInp?.addEventListener('input', () => {\n    if (!fixedCb.checked) return;\n    syncAdvanced(true);\n  });\n\n  advForm.addEventListener('submit', (e) => { e.preventDefault(); routeNext(); });\n  advCont.addEventListener('click', (e) => { e.preventDefault(); routeNext(); });\n  advBack.addEventListener('click', renderVisitSettings);\n}\n\n\n\tfunction buildDaysFromRange() {\n\t  const s = RES_TMP.period?.start, e = RES_TMP.period?.end;\n\t  if (!s || !e) return [];\n\n\t  const out = [];\n\t  let d = parseYMD(s), end = parseYMD(e);\n\t  if (!d || !end) return out;\n\n\t  const byDate = new Map((RES_TMP.days || []).map(x => [x.date, x]));\n\t  const excluded = new Set(RES_TMP.excluded || []);\n\t  const g = currentGlobalVisitDefaults();\n\n\t  while (d <= end) {\n\t\tconst iso = localISODate(d);\n\t\tif (!excluded.has(iso)) {\n\t\t  const existing = byDate.get(iso);\n\t\t  if (existing) {\n\t\t\t\/\/ Backfill missing fields from older local data\n\t\t\tif (!existing.hour) existing.hour = RES_TMP.advanced?.fixedHourTime || '10:00';\n\t\t\tif (existing.duration == null || existing.duration === '') existing.duration = g.duration;\n\t\t\tif (!existing.__custom) existing.__custom = { perDay:false, dayPart:false, duration:false };\n\t\t\tout.push(existing);\n\t\t  } else {\n\t\t\tout.push({\n\t\t\t  date: iso,\n\t\t\t  perDay: g.perDay,\n\t\t\t  dayPart: g.perDay === 2 ? 'any' : g.dayPart,\n\t\t\t  hour: RES_TMP.advanced?.fixedHourTime || '10:00',\n\t\t\t  duration: g.duration,\n\t\t\t  __custom: { perDay:false, dayPart:false, duration:false }\n\t\t\t});\n\t\t  }\n\t\t}\n\t\td = new Date(d.getFullYear(), d.getMonth(), d.getDate() + 1);\n\t  }\n\t  return out;\n\t}\n\n\t\n\tfunction currentGlobalVisitDefaults() {\n  return {\n    perDay: Number(RES_TMP.settings?.perDay || 1),\n    dayPart: RES_TMP.settings?.dayPart || 'any',\n    duration: Number(RES_TMP.settings?.duration || 30)\n  };\n}\n\nfunction syncDaysWithSettings(opts) {\n  const force = !!(opts && opts.force);\n  const g = currentGlobalVisitDefaults();\n\n  RES_TMP.days = (RES_TMP.days || []).map(d => {\n    const c = d.__custom || {};\n    const perDay = (force || !c.perDay) ? g.perDay : Number(d.perDay || g.perDay);\n\n    \/\/ If perDay is 2, dayPart must be \"any\"\n    const dayPartInherited = (perDay === 2) ? 'any' : g.dayPart;\n    const dayPart = (force || !c.dayPart) ? dayPartInherited : (d.dayPart || 'any');\n\n    const duration = (force || !c.duration)\n      ? g.duration\n      : Number(d.duration || g.duration);\n\n    return {\n      ...d,\n      perDay,\n      dayPart,\n      duration,\n      __custom: c\n    };\n  });\n\n  saveResLocal();\n}\n\nfunction baselineKeyForState() {\n  const g = currentGlobalVisitDefaults();\n  return [\n    RES_TMP.period?.start || '',\n    RES_TMP.period?.end || '',\n    g.perDay, g.dayPart, g.duration,\n    !!RES_TMP.advanced?.fixedHour\n  ].join('|');\n}\n\nfunction computeBaselineDays() {\n  const s = RES_TMP.period?.start, e = RES_TMP.period?.end;\n  if (!s || !e) return [];\n  const g = currentGlobalVisitDefaults();\n  const out = [];\n  let d = parseYMD(s), end = parseYMD(e);\n  while (d && end && d <= end) {\n    const iso = localISODate(d);\n    out.push({\n      date: iso,\n      perDay: g.perDay,\n      dayPart: g.perDay === 2 ? 'any' : g.dayPart,\n      hour: RES_TMP.advanced?.fixedHourTime || '10:00', \n      duration: g.duration,\n      __custom: { perDay:false, dayPart:false, duration:false }\n    });\n    d = new Date(d.getFullYear(), d.getMonth(), d.getDate() + 1);\n  }\n  return out;\n}\n\nfunction ensureBaselineDays() {\n  const key = baselineKeyForState();\n  if (RES_TMP.__baselineKey !== key) {\n    RES_TMP.__baselineDays = computeBaselineDays();\n    RES_TMP.__baselineKey = key;\n    saveResLocal();\n  } else if (!Array.isArray(RES_TMP.__baselineDays)) {\n    RES_TMP.__baselineDays = computeBaselineDays();\n    saveResLocal();\n  }\n}\n\nfunction deepCloneDays(arr) {\n  try { return JSON.parse(JSON.stringify(arr || [])); } catch { return (arr || []).slice(); }\n}\n\n\tfunction renderPerDayTable() {\n\t  const container = $('tab-rez');\n\t  if (!container) return dbg('NAV_ERR', 'tab-rez not found');\n\n\t  \/\/ Baseline for this period + settings (used by Reset)\n\t  ensureBaselineDays();\n\n\t  \/\/ Build rows from current state (keeps deletions), then sync non-overridden fields\n\t  RES_TMP.days = buildDaysFromRange();\n\t  syncDaysWithSettings();\n\t  const rows = RES_TMP.days;\n\n\t  const thead = `\n\t\t<tr>\n\t\t  <th>Data<\/th>\n\t\t  <th>Num\u0103r vizite\/zi<\/th>\n\t\t  <th>Partea zilei<\/th>\n\t\t  <th>Durata vizit\u0103<\/th>\n\t\t<\/tr>`;\n\n\t  const cells = rows.map((r, i) => `\n\t\t<tr data-date=\"${r.date}\">\n\t\t  <td class=\"nowrap\">\n\t\t\t<span class=\"datecell\">\n\t\t\t  <button type=\"button\" class=\"delx\" data-del-date=\"${r.date}\" aria-label=\"Elimin\u0103 ziua\">\u00d7<\/button>\n\t\t\t  <span>${fmtDateShort(parseYMD(r.date))}<\/span>\n\t\t\t<\/span>\n\t\t  <\/td>\n\n\t\t  <td>\n\t\t\t<span class=\"col-lbl\">Num\u0103r vizite\/zi<\/span>\n\t\t\t<select id=\"d_${i}_per\" data-i=\"${i}\">\n\t\t\t  <option value=\"1\" ${r.perDay === 1 ? 'selected' : ''}>1<\/option>\n\t\t\t  <option value=\"2\" ${r.perDay === 2 ? 'selected' : ''}>2<\/option>\n\t\t\t<\/select>\n\t\t  <\/td>\n\n\t\t  <td>\n\t\t\t<span class=\"col-lbl\">Partea zilei<\/span>\n\t\t\t<select id=\"d_${i}_part\" data-i=\"${i}\">\n\t\t\t  <option value=\"any\" ${r.dayPart === 'any' ? 'selected' : ''}>Flexibil<\/option>\n\t\t\t  <option value=\"am\" ${r.dayPart === 'am' ? 'selected' : ''}>AM (10\u201316)<\/option>\n\t\t\t  <option value=\"pm\" ${r.dayPart === 'pm' ? 'selected' : ''}>PM (16\u201322)<\/option>\n\t\t\t<\/select>\n\t\t  <\/td>\n\n\t\t  <td>\n\t\t\t<span class=\"col-lbl\">Durata vizit\u0103<\/span>\n\t\t\t<select id=\"d_${i}_dur\" data-i=\"${i}\">\n\t\t\t  <option value=\"30\" ${Number(r.duration) === 30 ? 'selected' : ''}>30 min<\/option>\n\t\t\t  <option value=\"60\" ${Number(r.duration) === 60 ? 'selected' : ''}>60 min<\/option>\n\t\t\t  <option value=\"90\" ${Number(r.duration) === 90 ? 'selected' : ''}>90 min<\/option>\n\t\t\t<\/select>\n\t\t  <\/td>\n\t\t<\/tr>`).join('');\n\n\t  container.innerHTML = `\n\t\t<section id=\"perDayTable\">\n\t\t  <h3 style=\"margin:0 0 6px 0\">Preferin\u021be pe zile<\/h3>\n\t\t  <div class=\"muted\" style=\"margin:0 0 8px 0\">\n\t\t\tInterval: ${fmtDateShort(parseYMD(RES_TMP.period.start))} \u2013 ${fmtDateShort(parseYMD(RES_TMP.period.end))}\n\t\t  <\/div>\n\t\t  <div>\n\t\t\t<table class=\"table\" aria-label=\"Preferin\u021be pe zile\">\n\t\t\t  <thead>${thead}<\/thead>\n\t\t\t  <tbody>${cells}<\/tbody>\n\t\t\t<\/table>\n\t\t  <\/div>\n\t\t  <div class=\"sticky\" style=\"margin-top:12px\">\n\t\t\t<div class=\"inner\">\n\t\t\t  <div class=\"actions\">\n\t\t\t\t<button id=\"pdBack\" class=\"btn btn-alt\" type=\"button\" aria-label=\"\u00cenapoi la op\u021biuni avansate\">\u00cenapoi<\/button>\n\t\t\t\t<!-- NEW: Reset table to original baseline -->\n\t\t\t\t<button id=\"pdReset\" class=\"btn btn-alt\" type=\"button\" title=\"Reseteaz\u0103 tabelul la starea ini\u021bial\u0103 pentru perioada curent\u0103\">\n\t\t\t\t  Reseteaz\u0103 tabelul\n\t\t\t\t<\/button>\n\t\t\t\t<button id=\"pdContinue\" class=\"btn\" type=\"button\">Continu\u0103<\/button>\n\t\t\t  <\/div>\n\t\t\t  <div style=\"min-width:220px;flex:1\"><div id=\"pdMsg\" class=\"muted\" role=\"status\" aria-live=\"polite\"><\/div><\/div>\n\t\t\t<\/div>\n\t\t  <\/div>\n\t\t<\/section>`;\n\n\t  function applyRowRules(i) {\n\t\tconst perSel = $(`d_${i}_per`);\n\t\tconst partSel = $(`d_${i}_part`);\n\t\tif (Number(perSel?.value || 1) === 2) {\n\t\t  \/\/ Auto-rule: 2 vizite\/zi -> 'any'\n\t\t  if (partSel) {\n\t\t\tpartSel.value = 'any';\n\t\t\tpartSel.disabled = true;\n\t\t  }\n\t\t  RES_TMP.days[i].dayPart = 'any';\n\t\t} else if (partSel) {\n\t\t  partSel.disabled = false;\n\t\t}\n\t  }\n\n\tfunction syncRow(i, mark = {}) {\n\t  const perSel = $(`d_${i}_per`);\n\t  const partSel = $(`d_${i}_part`);\n\t  const durSel  = $(`d_${i}_dur`);\n\n\t  const row = RES_TMP.days[i];\n\n\t  row.perDay  = Number(perSel?.value || 1);\n\t  row.dayPart = partSel?.value || 'any';\n\t  row.duration = Number(durSel?.value || currentGlobalVisitDefaults().duration);\n\n\t  \/\/ mark explicit overrides\n\t  row.__custom = row.__custom || { perDay:false, dayPart:false, duration:false };\n\t  if (mark.perDay === true)   row.__custom.perDay = true;\n\t  if (mark.dayPart === true)  row.__custom.dayPart = true;\n\t  if (mark.duration === true) row.__custom.duration = true;\n\t}\n\n\n\t  rows.forEach((_, i) => {\n\t\tconst perSel = $(`d_${i}_per`);\n\t\tconst partSel = $(`d_${i}_part`);\n\t\tconst durSel = $(`d_${i}_dur`);\n\n\t\tif (!perSel || !partSel || !durSel) {\n\t\t  return dbg('NAV_ERR', `renderPerDayTable row ${i}: perSel=${!!perSel}, partSel=${!!partSel}, durSel=${!!durSel}`);\n\t\t}\n\n\t\tapplyRowRules(i);\n\n\t\tperSel.addEventListener('change', () => {\n\t\t  applyRowRules(i);\n\t\t  syncRow(i, { perDay: true });\n\t\t  saveResLocal();\n\t\t});\n\n\t\tpartSel.addEventListener('change', () => {\n\t\t  syncRow(i, { dayPart: true });\n\t\t  saveResLocal();\n\t\t});\n\n\t\tdurSel.addEventListener('change', () => {\n\t\t  syncRow(i, { duration: true });\n\t\t  saveResLocal();\n\t\t});\n\t  });\n\n\t  \/\/ Delete day\n\t  container.querySelectorAll('[data-del-date]').forEach(btn => {\n\t\tbtn.addEventListener('click', () => {\n\t\t  const date = btn.getAttribute('data-del-date');\n\t\t  if (date) {\n\t\t\tRES_TMP.excluded = RES_TMP.excluded || [];\n\t\t\tif (!RES_TMP.excluded.includes(date)) RES_TMP.excluded.push(date);\n\t\t\tRES_TMP.days = RES_TMP.days.filter(x => x.date !== date);\n\t\t\tsaveResLocal();\n\t\t\trenderPerDayTable();\n\t\t  }\n\t\t});\n\t  });\n\n\t  \/\/ Buttons (Back \/ Reset \/ Continue)\n\t  const pdBack = $('pdBack');\n\t  const pdContinue = $('pdContinue');\n\t  const pdReset = $('pdReset');\n\t  if (!pdBack || !pdContinue || !pdReset) {\n\t\treturn dbg('NAV_ERR', `renderPerDayTable: pdBack=${!!pdBack}, pdContinue=${!!pdContinue}, pdReset=${!!pdReset}`);\n\t  }\n\n\t  \/\/ NEW: Reset to original baseline for this period + settings\n\t  pdReset.addEventListener('click', () => {\n\t\tensureBaselineDays(); \/\/ in case settings changed just now\n\t\tRES_TMP.excluded = [];                           \/\/ restore all days in range\n\t\tRES_TMP.days = deepCloneDays(RES_TMP.__baselineDays); \/\/ clear overrides\/deletions\n\t\tsaveResLocal();\n\t\trenderPerDayTable();\n\t  });\n\n\t  pdContinue.addEventListener('click', () => {\n\t\trows.forEach((_, i) => syncRow(i));\n\t\tsaveResLocal();\n\t\trenderKeyHandling();\n\t  });\n\n\t  pdBack.addEventListener('click', renderAdvancedOptions);\n\t}\n\n\n\n\n\t\tfunction renderServicesSection() {\n\t  const container = $('tab-rez');\n\t  if (!container) return dbg('NAV_ERR', 'tab-rez not found');\n\n\t  const prev = RES_TMP.services || {};\n\t  const freeChecked = !!prev.freeEnabled;\n\t  const paidChecked = !!prev.paidEnabled;\n\t  const freeNotesPh = '\u2022 administrat tratamente usoare (pastile, suplimente alimentare etc.)\\n\u2022 hr\u0103nit alte animale din cas\u0103 (papagali\/iepuri\/testoase s.a.)\\n\u2022 udat plante';\n\t  const paidNotesPh = '\u2022 cump\u0103r\u0103turi pentru pisici: 20 lei + costul produselor\\n\u2022 cump\u0103r\u0103turi supermarket pe c\u00e2nd te \u00eentorci: 35 lei + costul produselor\\n\u2022 vizit\u0103 la veterinar - durat\u0103 deplasare + consulta\u021bie: 50 lei \/30 min, 75 lei \/60 min, 100 lei \/90 min + tarif Bolt Pets';\n\n\t  container.innerHTML = `\n\t\t<section id=\"servicesSection\" style=\"width:100%;overflow:hidden\">\n\t\t  <h3 style=\"margin:0 0 6px 0\">Servicii<\/h3>\n\t\t  <form id=\"svForm\" novalidate style=\"width:100%;overflow:hidden\">\n\t\t\t<div class=\"row\" style=\"grid-template-columns:1fr;gap:10px;width:100%;max-width:100%;overflow:hidden;box-sizing:border-box\">\n\t\t\t  <label style=\"display:grid;gap:6px;width:100%;box-sizing:border-box\">\n\t\t\t\t<span style=\"display:flex;align-items:center;gap:10px\"><input id=\"svFree\" type=\"checkbox\" ${freeChecked ? 'checked' : ''}> <span>Servicii gratuite<\/span><\/span>\n\t\t\t\t<textarea id=\"svFreeNotes\" placeholder=\"${freeNotesPh}\" ${freeChecked ? '' : 'disabled'} style=\"width:100%;max-width:100%;box-sizing:border-box\">${prev.freeNotes ? prev.freeNotes : ''}<\/textarea>\n\t\t\t  <\/label>\n\t\t\t  <label style=\"display:grid;gap:6px;width:100%;box-sizing:border-box\">\n\t\t\t\t<span style=\"display:flex;align-items:center;gap:10px\"><input id=\"svPaid\" type=\"checkbox\" ${paidChecked ? 'checked' : ''}> <span>Servicii contra-cost<\/span><\/span>\n\t\t\t\t<textarea id=\"svPaidNotes\" placeholder=\"${paidNotesPh}\" ${paidChecked ? '' : 'disabled'} style=\"width:100%;max-width:100%;box-sizing:border-box\">${prev.paidNotes ? prev.paidNotes : ''}<\/textarea>\n\t\t\t  <\/label>\n\t\t\t<\/div>\n\n\t\t\t<div class=\"sticky\" style=\"margin-top:12px\">\n\t\t\t  <div class=\"inner\">\n\t\t\t\t<div class=\"actions\">\n\t\t\t\t  <button id=\"svBack\" class=\"btn btn-alt\" type=\"button\" aria-label=\"\u00cenapoi la chei\">\u00cenapoi<\/button>\n\t\t\t\t  <button id=\"svContinue\" class=\"btn\" type=\"submit\">Continu\u0103<\/button>\n\t\t\t\t<\/div>\n\t\t\t\t<div style=\"min-width:220px;flex:1\"><div id=\"svMsg\" class=\"muted\" role=\"status\" aria-live=\"polite\"><\/div><\/div>\n\t\t\t  <\/div>\n\t\t\t<\/div>\n\t\t  <\/form>\n\t\t<\/section>`;\n\n\t  const form = $('svForm');\n\t  const svBack = $('svBack');\n\t  const svContinue = $('svContinue');\n\t  const cbFree = $('svFree');\n\t  const cbPaid = $('svPaid');\n\t  const taFree = $('svFreeNotes');\n\t  const taPaid = $('svPaidNotes');\n\t  if (!form || !svBack || !svContinue || !cbFree || !cbPaid || !taFree || !taPaid) return;\n\n\t  function applyEnableStates() {\n\t\ttaFree.disabled = !cbFree.checked;\n\t\ttaPaid.disabled = !cbPaid.checked;\n\t\ttry {\n\t\t  const labF = taFree.closest && taFree.closest('label');\n\t\t  const labP = taPaid.closest && taPaid.closest('label');\n\t\t  if (labF) { if (taFree.disabled) labF.classList.add('readonly'); else labF.classList.remove('readonly'); }\n\t\t  if (labP) { if (taPaid.disabled) labP.classList.add('readonly'); else labP.classList.remove('readonly'); }\n\t\t} catch {}\n\t\t\/\/ Add\/remove editable class for animation\n\t\tif (taFree.disabled) taFree.classList.remove('editable'); else taFree.classList.add('editable');\n\t\tif (taPaid.disabled) taPaid.classList.remove('editable'); else taPaid.classList.add('editable');\n\t\ttry { adjustAllTextareas(); } catch {}\n\t  }\n\n\t  function syncServices() {\n\t\tRES_TMP.services = {\n\t\t  freeEnabled: !!cbFree.checked,\n\t\t  freeNotes: String(taFree.value || '').trim(),\n\t\t  paidEnabled: !!cbPaid.checked,\n\t\t  paidNotes: String(taPaid.value || '').trim()\n\t\t};\n\t\tsaveResLocal();\n\t  }\n\n\t  cbFree.addEventListener('change', () => { applyEnableStates(); syncServices(); });\n\t  cbPaid.addEventListener('change', () => { applyEnableStates(); syncServices(); });\n\t  taFree.addEventListener('input', syncServices);\n\t  taPaid.addEventListener('input', syncServices);\n\n\t  applyEnableStates();\n\n\t  \/\/ Trigger autosize for placeholders right after render\n\t  try {\n\t\tadjustAllTextareas();\n\t\tsetTimeout(adjustAllTextareas, 50);\n\t  } catch {}\n\n\t  form.addEventListener('submit', e => {\n\t\te.preventDefault();\n\t\tsyncServices();\n\t\trenderReservationSummary();\n\t  });\n\n\t  svBack.addEventListener('click', renderKeyHandling);\n\t}\n\n\n\t\tfunction renderKeyHandling() {\n\t  const container = $('tab-rez');\n\t  if (!container) return dbg('NAV_ERR', 'tab-rez not found');\n\n\t  \/\/ Start unchecked for clarity; preserve existing notes only\n\t  const prev = RES_TMP.keys || {};\n\t  RES_TMP.keys = {\n\t\tcustody: false,\n\t\tmeet: false,\n\t\texternal: false,\n\t\tpickupHome: false,\n\t\treturnHome: false,\n\t\tnotes: prev.notes || ''\n\t  };\n\n\t  container.innerHTML = `\n\t\t<section id=\"keysSection\">\n\t\t  <h3 style=\"margin:0 0 6px 0\">Cum proced\u0103m cu cheile?<\/h3>\n\t\t  <form id=\"keysForm\" novalidate>\n\t\t\t<div class=\"row\" style=\"grid-template-columns:1fr;gap:10px\">\n\t\t\t  <label for=\"km_custody\" style=\"display:flex;align-items:center;gap:10px\">\n\t\t\t\t<input id=\"km_custody\" type=\"checkbox\" ${RES_TMP.keys.custody ? 'checked' : ''}>\n\t\t\t\t<span>r\u0103m\u00e2n \u00een custodia Frau Miau<\/span>\n\t\t\t  <\/label>\n\t\t\t  <label for=\"km_external\" style=\"display:flex;align-items:center;gap:10px\">\n\t\t\t\t<input id=\"km_external\" type=\"checkbox\" ${RES_TMP.keys.external ? 'checked' : ''}>\n\t\t\t\t<span>cutie po\u0219tal\u0103 \/ vecin \/ portar <span class=\"muted\">(detalii obligatorii)<\/span><\/span>\n\t\t\t  <\/label>\n\t\t\t  <label for=\"km_meet\" style=\"display:flex;align-items:center;gap:10px\">\n\t\t\t\t<input id=\"km_meet\" type=\"checkbox\" ${RES_TMP.keys.meet ? 'checked' : ''}>\n\t\t\t\t<span>stabilim \u00eent\u00e2lnire de comun acord cu catsitterul<\/span>\n\t\t\t  <\/label>\n\t\t\t<\/div>\n\n\t\t\t<div class=\"row\" style=\"grid-template-columns:1fr;gap:10px\">\n\t\t\t  <label for=\"keysPickupHome\" style=\"display:flex;align-items:center;gap:10px\">\n\t\t\t\t<input id=\"keysPickupHome\" type=\"checkbox\" ${RES_TMP.keys.pickupHome ? 'checked' : ''}>\n\t\t\t\t<span>preluat chei de acas\u0103 (50 lei)<\/span>\n\t\t\t  <\/label>\n\t\t\t  <label for=\"keysReturnHome\" style=\"display:flex;align-items:center;gap:10px\">\n\t\t\t\t<input id=\"keysReturnHome\" type=\"checkbox\" ${RES_TMP.keys.returnHome ? 'checked' : ''}>\n\t\t\t\t<span>returnat chei acas\u0103 (50 lei)<\/span>\n\t\t\t  <\/label>\n\t\t\t<\/div>\n\n\t\t\t<div class=\"row\">\n\t\t\t  <label for=\"keysNotes\">Note speciale <span class=\"muted\">(op\u021bional)<\/span>\n\t\t\t\t<textarea id=\"keysNotes\" placeholder=\"Detalii cutie po\u0219tal\u0103 \/ vecin \/ portar, instruc\u021biuni chei etc.\">${RES_TMP.keys.notes || ''}<\/textarea>\n\t\t\t  <\/label>\n\t\t\t<\/div>\n\n\t\t\t<div class=\"sticky\" style=\"margin-top:12px\">\n\t\t\t  <div class=\"inner\">\n\t\t\t\t<div class=\"actions\">\n\t\t\t\t  <button id=\"keysBack\" class=\"btn btn-alt\" type=\"button\" aria-label=\"\u00cenapoi la chei\">\u00cenapoi<\/button>\n\t\t\t\t  <button id=\"keysContinue\" class=\"btn\" type=\"submit\">Continu\u0103<\/button>\n\t\t\t\t<\/div>\n\t\t\t\t<div style=\"min-width:220px;flex:1\">\n\t\t\t\t  <div id=\"keysMsg\" class=\"muted\" role=\"status\" aria-live=\"polite\"><\/div>\n\t\t\t\t<\/div>\n\t\t\t  <\/div>\n\t\t\t<\/div>\n\t\t  <\/form>\n\t\t<\/section>`;\n\n\t  const form = $('keysForm');\n\t  const notesEl = $('keysNotes');\n\t  const keysBack = $('keysBack');\n\t  const keysContinue = $('keysContinue');\n\t  const kmCust = $('km_custody');\n\t  const kmMeet = $('km_meet');\n\t  const kmExt  = $('km_external');\n\t  const pickEl = $('keysPickupHome');\n\t  const retEl  = $('keysReturnHome');\n\t  if (!form || !notesEl || !keysBack || !keysContinue || !pickEl || !retEl) {\n\t\treturn dbg('NAV_ERR', `renderKeyHandling: form=${!!form}, notesEl=${!!notesEl}, keysBack=${!!keysBack}, keysContinue=${!!keysContinue}, pickEl=${!!pickEl}, retEl=${!!retEl}`);\n\t  }\n\n\t  function computeMethodFromTicks() {\n\t\t\/\/ Backward compatibility: prefer custody, then meet, then external\n\t\tif (kmCust && kmCust.checked) return 'custody';\n\t\tif (kmMeet && kmMeet.checked) return 'meet';\n\t\tif (kmExt && kmExt.checked)  return 'external';\n\t\treturn 'custody';\n\t  }\n\n\t  function setDisabled(el, disabled) {\n\t\tif (!el) return;\n\t\tel.disabled = !!disabled;\n\t\tconst lab = el.closest && el.closest('label');\n\t\tif (lab) { if (disabled) lab.classList.add('readonly'); else lab.classList.remove('readonly'); }\n\t  }\n\n\t  function applyKeysInterlocks() {\n\t\t\/\/ Allow all checkboxes to be selected independently - no restrictions\n\t\tsetDisabled(kmCust, false);\n\t\tsetDisabled(kmExt, false);\n\t\tsetDisabled(kmMeet, false);\n\t\tsetDisabled(pickEl, false);\n\t\tsetDisabled(retEl, false);\n\t  }\n\n\t  function sync() {\n\t\t\/\/ Re-find elements to ensure they're current\n\t\tconst pickupEl = $('keysPickupHome');\n\t\tconst returnEl = $('keysReturnHome');\n\t\tconst custodyEl = $('km_custody');\n\t\tconst meetEl = $('km_meet');\n\t\tconst externalEl = $('km_external');\n\t\tconst notesEl = $('keysNotes');\n\t\t\n\t\tconsole.log('sync: pickupEl found =', !!pickupEl);\n\t\tconsole.log('sync: returnEl found =', !!returnEl);\n\t\tconsole.log('sync: pickupHome checked =', pickupEl && pickupEl.checked);\n\t\tconsole.log('sync: returnHome checked =', returnEl && returnEl.checked);\n\t\t\n\t\tRES_TMP.keys = {\n\t\t  method: computeMethodFromTicks(),\n\t\t  custody: !!(custodyEl && custodyEl.checked),\n\t\t  meet: !!(meetEl && meetEl.checked),\n\t\t  external: !!(externalEl && externalEl.checked),\n\t\t  pickupHome: !!(pickupEl && pickupEl.checked),\n\t\t  returnHome: !!(returnEl && returnEl.checked),\n\t\t  notes: (notesEl && notesEl.value) ? notesEl.value : ''\n\t\t};\n\t\t\n\t\tconsole.log('sync: RES_TMP.keys after update =', RES_TMP.keys);\n\t\tsaveResLocal();\n\t\tapplyKeysInterlocks();\n\t  }\n\n\t  function validateKeys() {\n\t\tconst msg = $('keysMsg');\n\t\tconst k = RES_TMP.keys || {};\n\t\t\n\t\t\/\/ Debug logging\n\t\tconsole.log('validateKeys: RES_TMP.keys =', k);\n\t\tconsole.log('validateKeys: custody =', k.custody);\n\t\tconsole.log('validateKeys: external =', k.external);\n\t\tconsole.log('validateKeys: meet =', k.meet);\n\t\tconsole.log('validateKeys: pickupHome =', k.pickupHome);\n\t\tconsole.log('validateKeys: returnHome =', k.returnHome);\n\t\t\n\t\t\/\/ Check if at least one key method is selected\n\t\tconst hasKeyMethod = k.custody || k.external || k.meet || k.pickupHome || k.returnHome;\n\t\tconsole.log('validateKeys: hasKeyMethod =', hasKeyMethod);\n\t\t\n\t\tif (!hasKeyMethod) {\n\t\t  if (msg) { msg.className = 'err'; msg.textContent = 'Te rug\u0103m s\u0103 alegi cel pu\u021bin o op\u021biune pentru chei.'; }\n\t\t  return false;\n\t\t}\n\t\t\n\t\tif (k.external && !String(k.notes || '').trim()) {\n\t\t  if (msg) { msg.className = 'err'; msg.textContent = 'Te rug\u0103m completeaz\u0103 detaliile pentru op\u021biunea cutie po\u0219tal\u0103 \/ vecin \/ portar.'; }\n\t\t  try {\n\t\t\tconst lab = (notesEl && notesEl.closest) ? notesEl.closest('label') : null;\n\t\t\tif (notesEl && notesEl.setAttribute) notesEl.setAttribute('aria-invalid', 'true');\n\t\t\tif (lab && lab.classList) lab.classList.add('error');\n\t\t  } catch {}\n\t\t  return false;\n\t\t}\n\t\tif (msg) { msg.className = 'muted'; msg.textContent = ''; }\n\t\treturn true;\n\t  }\n\n\t  \/\/ Live sync\n\t  form.addEventListener('change', sync);\n\t  form.addEventListener('input', sync);\n\t  sync();\n\n\t  keysBack.addEventListener('click', () =>\n\t\t(RES_TMP.advanced?.diffDays) ? renderPerDayTable() : renderAdvancedOptions()\n\t  );\n\n\t  form.addEventListener('submit', e => {\n\t\te.preventDefault();\n\t\t\/\/ Force sync with a small delay to ensure DOM is updated\n\t\tsetTimeout(() => {\n\t\t  sync();\n\t\t  if (!validateKeys()) return;\n\t\t  renderServicesSection();\n\t\t}, 10);\n\t  });\n\t}\n\t\n\t\/* ===== Reservation Summary ===== *\/\n\nfunction roDayPartLabel(v) {\n  return v === 'am' ? 'AM (10\u201316)' : v === 'pm' ? 'PM (16\u201322)' : 'Flexibil';\n}\n\nfunction roKeysMethodLabel(m) {\n  if (m === 'meet') return '\u00cent\u00e2lnire cu catsitterul';\n  if (m === 'external') return 'Cutie po\u0219tal\u0103 \/ vecin \/ portar';\n  return 'Custodia Frau Miau';\n}\n\n\/** Build the effective day list for summary (dates kept + their settings) *\/\nfunction computeSummaryDays() {\n  const s = RES_TMP.period?.start, e = RES_TMP.period?.end;\n  if (!s || !e) return [];\n  const start = parseYMD(s), end = parseYMD(e);\n  if (!start || !end) return [];\n\n  const g = currentGlobalVisitDefaults(); \/\/ { perDay, dayPart, duration }\n  const excluded = new Set(RES_TMP.excluded || []);\n  const useAdvanced = !!(RES_TMP.advanced?.diffDays || RES_TMP.advanced?.fixedHour);\n\n  \/\/ If advanced flow\/table was used, rely on table state merged with defaults\n  if (useAdvanced || (RES_TMP.days && RES_TMP.days.length)) {\n    return buildDaysFromRange().map(r => ({\n      date: r.date,\n      perDay: Number(r.perDay || g.perDay || 1),\n      dayPart: (Number(r.perDay || g.perDay) === 2) ? 'any' : (r.dayPart || g.dayPart || 'any'),\n      hour: undefined,\n      duration: Number(r.duration || g.duration || 30)\n    }));\n  }\n\n  \/\/ Simple flow (no table): include every day in range (no \"frequency\")\n  const days = [];\n  let d = new Date(start);\n  while (d <= end) {\n    const iso = localISODate(d);\n    if (!excluded.has(iso)) {\n      days.push({\n        date: iso,\n        perDay: g.perDay,\n        dayPart: (g.perDay === 2) ? 'any' : g.dayPart,\n        hour: undefined,\n        duration: g.duration\n      });\n    }\n    d = new Date(d.getFullYear(), d.getMonth(), d.getDate() + 1);\n  }\n  return days;\n}\n\n\nfunction estimateTotals(effDays) {\n  const daysCount = effDays.length;\n  let visitsCount = 0;\n  effDays.forEach(d => { visitsCount += Number(d.perDay || 1); });\n  return { daysCount, visitsCount };\n}\n\nfunction buildReservationPayload() {\n  const effDays = computeSummaryDays();\n  return {\n    email: TOKEN?.email || $('p_email')?.value || '',\n    period: { ...RES_TMP.period },\n    \/\/ settings without frequency:\n    settings: {\n      perDay: RES_TMP.settings?.perDay || 1,\n      dayPart: RES_TMP.settings?.dayPart || 'any',\n      duration: RES_TMP.settings?.duration || 30\n    },\n    advanced: { ...RES_TMP.advanced },\n    excluded: [...(RES_TMP.excluded || [])],\n    days: effDays,\n    keys: {\n      method: RES_TMP.keys?.method || 'custody',\n      pickupHome: !!RES_TMP.keys?.pickupHome,\n      returnHome: !!RES_TMP.keys?.returnHome,\n      notes: RES_TMP.keys?.notes || ''\n    },\n    services: {\n      freeEnabled: !!RES_TMP.services?.freeEnabled,\n      freeNotes: RES_TMP.services?.freeNotes || '',\n      paidEnabled: !!RES_TMP.services?.paidEnabled,\n      paidNotes: RES_TMP.services?.paidNotes || ''\n    },\n    clientProfile: getForm()\n  };\n}\n\n\nasync function submitReservation() {\n  const payload = buildReservationPayload();\n  \n  try {\n    \/\/ Step 1: Submit reservation (fast - gets immediate response)\n    const result = await api('submitReservation', payload);\n    const finalResult = result && result.result ? result.result : result;\n    \n    \/\/ Step 2: Automatically trigger background processing (don't wait for it)\n    if (finalResult && finalResult.submissionId) {\n      \/\/ Trigger background processing asynchronously\n      triggerBackgroundProcessing(finalResult.submissionId).catch(error => {\n        console.warn('Background processing failed:', error);\n        \/\/ Don't show error to user - background processing is optional\n      });\n    }\n    \n    return finalResult;\n  } catch (error) {\n    console.error('Reservation submission failed:', error);\n    throw error;\n  }\n}\n\nfunction renderReservationSummary() {\n  const container = $('tab-rez');\n  if (!container) return dbg('NAV_ERR', 'tab-rez not found');\n\n  const effDays = computeSummaryDays();\n  const totals = estimateTotals(effDays);\n\n  const s = parseYMD(RES_TMP.period?.start);\n  const e = parseYMD(RES_TMP.period?.end);\n  const perDay = Number(RES_TMP.settings?.perDay || 1);\n  const dayPartLbl = roDayPartLabel(perDay === 2 ? 'any' : (RES_TMP.settings?.dayPart || 'any'));\n  const dur = Number(RES_TMP.settings?.duration || 30);\n  const fixedHour = !!RES_TMP.advanced?.fixedHour;\n\n  const keysMethod = roKeysMethodLabel(RES_TMP.keys?.method || 'custody');\n  const keysExtras = [\n    RES_TMP.keys?.pickupHome ? 'Preluat chei de acas\u0103 (50 lei)' : null,\n    RES_TMP.keys?.returnHome ? 'Returnat chei acas\u0103 (50 lei)' : null\n  ].filter(Boolean);\n  const svFreeList = RES_TMP.services?.freeEnabled && (RES_TMP.services?.freeNotes || '').trim()\n    ? `<div style=\"margin-top:6px\"><strong>Servicii gratuite:<\/strong><div class=\\\"muted\\\" style=\\\"white-space:pre-line;margin-top:4px\\\">${(RES_TMP.services.freeNotes || '').replace(\/<\/g,'&lt;')}<\/div><\/div>` : '';\n  const svPaidList = RES_TMP.services?.paidEnabled && (RES_TMP.services?.paidNotes || '').trim()\n    ? `<div style=\"margin-top:6px\"><strong>Servicii contra-cost:<\/strong><div class=\\\"muted\\\" style=\\\"white-space:pre-line;margin-top:4px\\\">${(RES_TMP.services.paidNotes || '').replace(\/<\/g,'&lt;')}<\/div><\/div>` : '';\n  const keysNotes = (RES_TMP.keys?.method === 'external' && (RES_TMP.keys?.notes || '').trim())\n    ? RES_TMP.keys.notes.trim()\n    : ((RES_TMP.keys?.notes || '').trim());\n\n  \/\/ Table rows\n  const showHourCol = false;\n  const thead = `\n    <tr>\n      <th>Data<\/th>\n      <th>Vizite\/zi<\/th>\n      <th>Partea zilei<\/th>\n      ${showHourCol ? '<th>Ora tratament<\/th>' : ''}\n      <th>Durata<\/th>\n    <\/tr>`;\n  const tbody = effDays.map(d => `\n    <tr>\n      <td class=\"nowrap\">${fmtDateShort(parseYMD(d.date))}<\/td>\n      <td>Num\u0103r vizite\/zi: ${Number(d.perDay || 1)}<\/td>\n      <td>${roDayPartLabel(d.dayPart || 'any')}<\/td>\n      ${showHourCol ? `<td>${d.hour || '\u2014'}<\/td>` : ''}\n      <td>${Number(d.duration || 30)} min<\/td>\n    <\/tr>\n  `).join('');\n\n  container.innerHTML = `\n    <section id=\"rezSummary\">\n      <h3 style=\"margin:0 0 6px 0\">Rezumat rezervare<\/h3>\n\n      <div class=\"panel\" style=\"margin:0 0 10px 0\">\n        <div class=\"row\">\n          <div>\n            <div><strong>Perioad\u0103<\/strong><\/div>\n            <div class=\"muted\">${fmtDateRO(s)} \u2013 ${fmtDateRO(e)}<\/div>\n          <\/div>\n          <div>\n            <div><strong>Total estimat<\/strong><\/div>\n            <div class=\"muted\">${totals.daysCount} zile \u00b7 ${totals.visitsCount} vizite<\/div>\n          <\/div>\n        <\/div>\n        <div class=\"row\" style=\"margin-top:8px\">\n          <div>\n            <div><strong>Set\u0103ri vizite<\/strong><\/div>\n            <div class=\"muted\">\n              ${perDay} vizit\u0103\/zi${perDay===2?' (AM+PM)':''} \u00b7 ${dayPartLbl} \u00b7 ${dur} min${fixedHour ? ' \u00b7 tratament la or\u0103 fix\u0103' : ''}\n            <\/div>\n            ${fixedHour && (RES_TMP.advanced?.fixedHourDetails || '').trim() ? \n              `<div style=\"margin-top:6px\"><strong>Detalii tratament la or\u0103 fix\u0103:<\/strong><div class=\"muted\" style=\"white-space:pre-line;margin-top:4px\">${(RES_TMP.advanced.fixedHourDetails || '').replace(\/<\/g,'&lt;')}<\/div><\/div>` : ''}\n          <\/div>\n        <\/div>\n      <\/div>\n\n      <div class=\"panel\" style=\"margin:0 0 10px 0\">\n        <div><strong>Chei<\/strong><\/div>\n        <div class=\"muted\">${keysMethod}<\/div>\n        ${keysExtras.length ? `<ul style=\"margin:6px 0 0 18px\">${keysExtras.map(x=>`<li>${x}<\/li>`).join('')}<\/ul>` : ''}\n        ${keysNotes ? `<div style=\"margin-top:6px\"><em>Note:<\/em> ${keysNotes.replace(\/<\/g,'&lt;')}<\/div>` : ''}\n        ${svFreeList}\n        ${svPaidList}\n      <\/div>\n\n      <div>\n        <table class=\"table\" aria-label=\"Program vizite\">\n          <thead>${thead}<\/thead>\n          <tbody>${tbody}<\/tbody>\n        <\/table>\n      <\/div>\n\n      <div class=\"sticky\" style=\"margin-top:12px\">\n        <div class=\"inner\">\n          <div class=\"actions\">\n            <button id=\"sumBack\" class=\"btn btn-alt\" type=\"button\" aria-label=\"\u00cenapoi la chei\">\u00cenapoi<\/button>\n            <button id=\"sumSubmit\" class=\"btn\" type=\"button\">Trimite rezervarea<\/button>\n          <\/div>\n          <div style=\"min-width:220px;flex:1\">\n            <div id=\"sumMsg\" class=\"muted\" role=\"status\" aria-live=\"polite\"><\/div>\n          <\/div>\n        <\/div>\n      <\/div>\n    <\/section>\n  `;\n\n  const sumBack = $('sumBack');\n  const sumSubmit = $('sumSubmit');\n  const sumMsg = $('sumMsg');\n\n  if (sumBack) {\n    sumBack.addEventListener('click', () => {\n      renderServicesSection();\n    });\n  }\n\n  if (sumSubmit) {\n    sumSubmit.addEventListener('click', async () => {\n      if (!ID_TOKEN) {\n        sumMsg.className = 'err';\n        sumMsg.textContent = 'Te rog autentific\u0103-te.';\n        return;\n      }\n      sumMsg.className = 'muted';\n      sumMsg.textContent = 'Se trimite rezervarea\u2026';\n      sumSubmit.disabled = true;\n\n      try {\n        const result = await submitReservation();\n        \/\/ New async path: accepted + jobId\n        if (result && result.accepted && result.jobId) {\n          RES_TMP.__lastJobId = result.jobId;\n          saveResLocal();\n          sumMsg.className = 'ok';\n          sumMsg.textContent = `Mul\u021bumim! Rezervarea a fost primit\u0103 \u0219i este \u00een curs de procesare (Job: ${result.jobId}).`;\n          \/\/ Convert button into \"F\u0103 o rezervare nou\u0103\" and go to first section on click\n          sumSubmit.textContent = 'F\u0103 o rezervare nou\u0103';\n          sumSubmit.disabled = false;\n          sumSubmit.addEventListener('click', () => {\n            \/\/ Reset flow to first section (Perioada vizitelor)\n            RES_TMP = { ...RES_TMP, period: { start: null, end: null }, days: [], excluded: [] };\n            saveResLocal();\n            setupRezervari();\n          }, { once: true });\n          return; \/\/ prevent re-enabling below\n        } else {\n          \/\/ Legacy direct path: have a submissionId\n          \/\/ Fix: Handle both direct result and wrapped result structures\n          let sid = '\u2014';\n          if (result) {\n            if (result.submissionId) {\n              sid = result.submissionId;\n            } else if (result.result && result.result.submissionId) {\n              sid = result.result.submissionId;\n            }\n          }\n          RES_TMP.__lastSubmission = sid;\n          saveResLocal();\n          sumMsg.className = 'ok';\n          sumMsg.textContent = `Mul\u021bumim! Rezervarea a fost \u00eenregistrat\u0103 (ID: ${sid})`;\n          sumSubmit.textContent = 'F\u0103 o rezervare nou\u0103';\n          sumSubmit.disabled = false;\n          sumSubmit.addEventListener('click', () => {\n            RES_TMP = { ...RES_TMP, period: { start: null, end: null }, days: [], excluded: [] };\n            saveResLocal();\n            setupRezervari();\n          }, { once: true });\n          return;\n        }\n      } catch (err) {\n        sumMsg.className = 'err';\n        sumMsg.textContent = `Eroare la trimitere: ${err && err.message ? err.message : String(err)}`;\n        try { dbg('SUBMIT_REZ_ERR', err && err.stack ? err.stack : String(err)); } catch {}\n      } finally {\n        sumSubmit.disabled = false;\n      }\n    });\n  }\n}\n\n\n\t\n\n    window.addEventListener('DOMContentLoaded', () => {\n      initTopTabs();\n      normalizePlaceholders();\n      renderCats([]);\n      adjustAllTextareas();\n      setTimeout(adjustAllTextareas, 200);\n      setTimeout(adjustAllTextareas, 600);\n      observeTextareas();\n      \/\/ Fit cats heading after layout\n      setTimeout(fitCatsHeading, 50);\n      $('btnAddCat')?.addEventListener('click', addEmptyCat);\n      let t = null;\n      $('profileForm')?.addEventListener('input', () => {\n        DIRTY = true;\n        clearTimeout(t);\n        t = setTimeout(() => saveLocal('Salvat local'), 500);\n      });\n      restoreResLocal();\n\t  setupRezervari();\n      \n      \n      $('profileForm')?.addEventListener('submit', async (e) => {\n  e.preventDefault();\n  const resEl = $('profileResult');\n  if (!ID_TOKEN) {\n    resEl.className = 'err';\n    resEl.textContent = 'Te rog autentific\u0103-te.';\n    return;\n  }\n  if (!e.currentTarget.reportValidity()) return;\n\n  const data = getForm();\n\n  \/\/ Optimistic local save\n  DIRTY = false;\n  saveLocal('Salvat local');\n  resEl.className = 'muted';\n  resEl.textContent = 'Se salveaz\u0103\u2026';\n\n  try {\n    const response = await api('saveProfile', data);\n    console.log('Profile save response:', response);\n    resEl.className = 'ok';\n    resEl.textContent = 'Salvat pe server.';\n    try { updateRezTabAccess(); } catch {}\n  } catch (err) {\n    \/\/ No retry UI; keep UX simple and non-blocking\n    if (!navigator.onLine) {\n      resEl.className = 'muted';\n      resEl.textContent = 'E\u0219ti offline. Am salvat local; sincroniz\u0103m automat c\u00e2nd revii online.';\n    } else {\n      resEl.className = 'muted';\n      resEl.textContent = 'Salvat local. Se sincronizeaz\u0103 cu serverul \u00een fundal.';\n    }\n  }\n});\n    window.addEventListener('load', () => {\n      adjustAllTextareas();\n      setTimeout(adjustAllTextareas, 100);\n      observeTextareas();\n      fitCatsHeading();\n    });\n\n    \/\/ Test function to verify treatment details show in summary\n    window.testTreatmentSummary = function() {\n      console.log('=== TESTING TREATMENT SUMMARY ===');\n      \n      \/\/ Set up test data with treatment details\n      RES_TMP = {\n        period: { start: '2025-01-20', end: '2025-01-22' },\n        settings: { perDay: 1, dayPart: 'any', duration: 30 },\n        advanced: {\n          fixedHour: true,\n          fixedHourTime: '10:00',\n          fixedHourDetails: 'insulin\u0103 la 18:00; pastile diminea\u021ba'\n        },\n        days: [\n          { date: '2025-01-20', perDay: 1, dayPart: 'any', duration: 30 },\n          { date: '2025-01-21', perDay: 1, dayPart: 'any', duration: 30 }\n        ],\n        keys: { method: 'custody', pickupHome: false, returnHome: false, notes: '' },\n        services: { freeEnabled: false, freeNotes: '', paidEnabled: false, paidNotes: '' }\n      };\n      \n      \/\/ Render the summary\n      renderReservationSummary();\n      \n      console.log('Treatment summary test completed. Check the Rezumat rezervare section.');\n      return 'SUCCESS: Treatment summary test completed';\n    };\n});\n  <\/script>\n<\/body>\n<\/html>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<\/div>","protected":false},"excerpt":{"rendered":"<p>Contul meu Contul meu Te rog autentific\u0103-te pentru a continua. Nume Email (preluat din Google) Adresa completa Telefon Contact de urgen\u021b\u0103 (obligatoriu) Date contact veterinar Apa Hrana uscata Hrana umeda Treats\/suplimente alimentare Litiera Ce fereastra poate fi deschisa in siguranta pentru aerisire pe durata vizitei C\u00e2nd preferi s\u0103 stabilim vizita ini\u0163ial\u0103 gratuit\u0103, pentru a cunoa\u015fte pisica \u015fi a prelua cheile? Alte informatii DETALII DESPRE PISICI (se completeaz\u0103 pentru fiecare pisic\u0103 \u00een parte) Numele pisicii, sex, culoare, data na\u015fterii, sterilizat\u0103\/castrat Grad de socializare (Prietenoas\u0103, vrea afec\u021biune \/ indiferent\u0103, vrea s\u0103 fie l\u0103sat\u0103 \u00een pace \/ Fricoas\u0103 cu str\u0103inii, se ascunde sau atac\u0103) Probleme de s\u0103n\u0103tate (dac\u0103 e cazul, schema de tratament) Alte particularit\u0103\u021bi sau tabieturi \u2795 Adaug\u0103 un c\u00e2mp nou (alt\u0103 pisic\u0103) Salveaz\u0103 profil<\/p>","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-2476","page","type-page","status-publish","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v25.7 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>client - Frau Miau<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/fraumiau.ro\/en\/client\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"client - Frau Miau\" \/>\n<meta property=\"og:description\" content=\"Contul meu Contul meu Te rog autentific\u0103-te pentru a continua. Nume Email (preluat din Google) Adresa completa Telefon Contact de urgen\u021b\u0103 (obligatoriu) Date contact veterinar Apa Hrana uscata Hrana umeda Treats\/suplimente alimentare Litiera Ce fereastra poate fi deschisa in siguranta pentru aerisire pe durata vizitei C\u00e2nd preferi s\u0103 stabilim vizita ini\u0163ial\u0103 gratuit\u0103, pentru a cunoa\u015fte pisica \u015fi a prelua cheile? Alte informatii DETALII DESPRE PISICI (se completeaz\u0103 pentru fiecare pisic\u0103 \u00een parte) Numele pisicii, sex, culoare, data na\u015fterii, sterilizat\u0103\/castrat Grad de socializare (Prietenoas\u0103, vrea afec\u021biune \/ indiferent\u0103, vrea s\u0103 fie l\u0103sat\u0103 \u00een pace \/ Fricoas\u0103 cu str\u0103inii, se ascunde sau atac\u0103) Probleme de s\u0103n\u0103tate (dac\u0103 e cazul, schema de tratament) Alte particularit\u0103\u021bi sau tabieturi \u2795 Adaug\u0103 un c\u00e2mp nou (alt\u0103 pisic\u0103) Salveaz\u0103 profil\" \/>\n<meta property=\"og:url\" content=\"https:\/\/fraumiau.ro\/en\/client\/\" \/>\n<meta property=\"og:site_name\" content=\"Frau Miau\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/FrauMiauCatsitting\" \/>\n<meta property=\"article:modified_time\" content=\"2025-11-22T00:02:01+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"1 minute\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/fraumiau.ro\/client\/\",\"url\":\"https:\/\/fraumiau.ro\/client\/\",\"name\":\"client - Frau Miau\",\"isPartOf\":{\"@id\":\"https:\/\/fraumiau.ro\/#website\"},\"datePublished\":\"2025-08-19T07:24:02+00:00\",\"dateModified\":\"2025-11-22T00:02:01+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/fraumiau.ro\/client\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/fraumiau.ro\/client\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/fraumiau.ro\/client\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/fraumiau.ro\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"client\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/fraumiau.ro\/#website\",\"url\":\"https:\/\/fraumiau.ro\/\",\"name\":\"Frau Miau\",\"description\":\"Frau Miau\",\"publisher\":{\"@id\":\"https:\/\/fraumiau.ro\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/fraumiau.ro\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/fraumiau.ro\/#organization\",\"name\":\"Frau Miau\",\"url\":\"https:\/\/fraumiau.ro\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fraumiau.ro\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/fraumiau.ro\/wp-content\/uploads\/2020\/04\/frau_miau_logo_svg_1.svg\",\"contentUrl\":\"https:\/\/fraumiau.ro\/wp-content\/uploads\/2020\/04\/frau_miau_logo_svg_1.svg\",\"width\":250,\"height\":250,\"caption\":\"Frau Miau\"},\"image\":{\"@id\":\"https:\/\/fraumiau.ro\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/FrauMiauCatsitting\",\"https:\/\/www.instagram.com\/frau.miau.catsitting\/\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"client - Frau Miau","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/fraumiau.ro\/en\/client\/","og_locale":"en_US","og_type":"article","og_title":"client - Frau Miau","og_description":"Contul meu Contul meu Te rog autentific\u0103-te pentru a continua. Nume Email (preluat din Google) Adresa completa Telefon Contact de urgen\u021b\u0103 (obligatoriu) Date contact veterinar Apa Hrana uscata Hrana umeda Treats\/suplimente alimentare Litiera Ce fereastra poate fi deschisa in siguranta pentru aerisire pe durata vizitei C\u00e2nd preferi s\u0103 stabilim vizita ini\u0163ial\u0103 gratuit\u0103, pentru a cunoa\u015fte pisica \u015fi a prelua cheile? Alte informatii DETALII DESPRE PISICI (se completeaz\u0103 pentru fiecare pisic\u0103 \u00een parte) Numele pisicii, sex, culoare, data na\u015fterii, sterilizat\u0103\/castrat Grad de socializare (Prietenoas\u0103, vrea afec\u021biune \/ indiferent\u0103, vrea s\u0103 fie l\u0103sat\u0103 \u00een pace \/ Fricoas\u0103 cu str\u0103inii, se ascunde sau atac\u0103) Probleme de s\u0103n\u0103tate (dac\u0103 e cazul, schema de tratament) Alte particularit\u0103\u021bi sau tabieturi \u2795 Adaug\u0103 un c\u00e2mp nou (alt\u0103 pisic\u0103) Salveaz\u0103 profil","og_url":"https:\/\/fraumiau.ro\/en\/client\/","og_site_name":"Frau Miau","article_publisher":"https:\/\/www.facebook.com\/FrauMiauCatsitting","article_modified_time":"2025-11-22T00:02:01+00:00","twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"1 minute"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/fraumiau.ro\/client\/","url":"https:\/\/fraumiau.ro\/client\/","name":"client - Frau Miau","isPartOf":{"@id":"https:\/\/fraumiau.ro\/#website"},"datePublished":"2025-08-19T07:24:02+00:00","dateModified":"2025-11-22T00:02:01+00:00","breadcrumb":{"@id":"https:\/\/fraumiau.ro\/client\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/fraumiau.ro\/client\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/fraumiau.ro\/client\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/fraumiau.ro\/"},{"@type":"ListItem","position":2,"name":"client"}]},{"@type":"WebSite","@id":"https:\/\/fraumiau.ro\/#website","url":"https:\/\/fraumiau.ro\/","name":"Frau Miau","description":"Frau Miau","publisher":{"@id":"https:\/\/fraumiau.ro\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/fraumiau.ro\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/fraumiau.ro\/#organization","name":"Frau Miau","url":"https:\/\/fraumiau.ro\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fraumiau.ro\/#\/schema\/logo\/image\/","url":"https:\/\/fraumiau.ro\/wp-content\/uploads\/2020\/04\/frau_miau_logo_svg_1.svg","contentUrl":"https:\/\/fraumiau.ro\/wp-content\/uploads\/2020\/04\/frau_miau_logo_svg_1.svg","width":250,"height":250,"caption":"Frau Miau"},"image":{"@id":"https:\/\/fraumiau.ro\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/FrauMiauCatsitting","https:\/\/www.instagram.com\/frau.miau.catsitting\/"]}]}},"_links":{"self":[{"href":"https:\/\/fraumiau.ro\/en\/wp-json\/wp\/v2\/pages\/2476","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fraumiau.ro\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/fraumiau.ro\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/fraumiau.ro\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/fraumiau.ro\/en\/wp-json\/wp\/v2\/comments?post=2476"}],"version-history":[{"count":0,"href":"https:\/\/fraumiau.ro\/en\/wp-json\/wp\/v2\/pages\/2476\/revisions"}],"wp:attachment":[{"href":"https:\/\/fraumiau.ro\/en\/wp-json\/wp\/v2\/media?parent=2476"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}