JustPaste
HomeCategoriesAboutDonateContactTerms of UsePrivacy Policy
JustPaste

Free online notepad — write and share instantly

Navigate

  • Home
  • Timeline
  • Categories

Info

  • About
  • Donate
  • Contact

Legal

  • Terms of Use
  • Privacy Policy

© 2026 JustPaste.app. All rights reserved.

Made with ♥ by JustPaste

Untitled Page | JustPaste.app
17 days ago1 views
👨‍💻Programming

Untitled Page

<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Office Web — Word & Excel</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%;overflow:hidden}
body{font-family:"Segoe UI",system-ui,-apple-system,Arial,sans-serif;font-size:13px;color:#323130;background:#fff;-webkit-font-smoothing:antialiased}
.hidden{display:none!important}
button,input,select{font-family:inherit;font-size:inherit;color:inherit}
#app{display:flex;flex-direction:column;height:100vh}
#app[data-app="word"]{--accent:#2b579a;--accent-d:#1e3f73;--accent-l:#d2e0f5}
#app[data-app="excel"]{--accent:#217346;--accent-d:#17532f;--accent-l:#d3e5db}
#titlebar{height:46px;flex-shrink:0;display:flex;align-items:center;gap:10px;padding:0 10px;background:var(--accent);color:#fff}
.app-launcher{display:flex;gap:4px}
.app-btn{width:34px;height:34px;border:none;border-radius:4px;cursor:pointer;background:rgba(255,255,255,.12);display:flex;align-items:center;justify-content:center;font-weight:800;font-size:14px;color:#fff;transition:background .15s}
.app-btn:hover{background:rgba(255,255,255,.25)}
.app-btn.active{background:#fff;color:var(--accent)}
.doc-title-wrap{flex:1;display:flex;justify-content:center}
#docTitle{background:transparent;border:1px solid transparent;border-radius:3px;color:#fff;font-size:13px;text-align:center;padding:4px 12px;min-width:180px;max-width:400px;outline:none}
#docTitle:hover{background:rgba(255,255,255,.12)}
#docTitle:focus{background:rgba(255,255,255,.2);border-color:rgba(255,255,255,.5)}
.titlebar-actions{display:flex;gap:4px;align-items:center}
.tb-btn{border:1px solid rgba(255,255,255,.35);background:rgba(255,255,255,.08);color:#fff;border-radius:3px;padding:5px 11px;cursor:pointer;font-size:12px}
.tb-btn:hover{background:rgba(255,255,255,.22)}
.tb-btn.primary{background:#fff;color:var(--accent);font-weight:600;border-color:#fff}
.tb-btn.primary:hover{background:#f0f0f0}
#ribbon{flex-shrink:0;background:#f3f2f1;border-bottom:1px solid #e1dfdd;overflow-x:auto;overflow-y:hidden}
#ribbon::-webkit-scrollbar{height:6px}
#ribbon::-webkit-scrollbar-thumb{background:#c8c6c4;border-radius:3px}
.ribbon-content{display:flex;align-items:stretch;padding:5px 8px;min-height:88px}
.rgroup{display:flex;flex-direction:column;align-items:center;justify-content:space-between;padding:0 8px;border-right:1px solid #e1dfdd;flex-shrink:0}
.rgroup:last-child{border-right:none}
.rgroup-items{display:flex;align-items:center;gap:2px;flex:1;padding-top:2px;flex-wrap:wrap}
.rgroup-label{font-size:10px;color:#605e5c;padding-top:3px;white-space:nowrap}
.rbtn{min-width:28px;height:28px;padding:0 6px;border:1px solid transparent;background:transparent;border-radius:3px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-size:13px;color:#323130;user-select:none}
.rbtn:hover{background:#e1dfdd;border-color:#c8c6c4}
.rbtn.active{background:var(--accent-l);border-color:var(--accent);color:var(--accent-d)}
.rselect{height:28px;border:1px solid #c8c6c4;border-radius:3px;background:#fff;padding:0 4px;cursor:pointer;font-size:12px;outline:none}
.rselect.small{width:58px}
.rcolor{position:relative;width:28px;height:28px;border:1px solid transparent;border-radius:3px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-size:13px;font-weight:600;user-select:none}
.rcolor input[type=color]{position:absolute;inset:0;opacity:0;cursor:pointer;width:100%;height:100%;border:none;padding:0}
.rcolor .bar{position:absolute;bottom:2px;left:4px;right:4px;height:3px;border-radius:2px}
.rsep{width:1px;height:24px;background:#e1dfdd;margin:0 3px;flex-shrink:0}
#formulaBar{flex-shrink:0;height:30px;display:flex;align-items:stretch;border-bottom:1px solid #e1dfdd;background:#fff}
.cellref{width:92px;flex-shrink:0;display:flex;align-items:center;justify-content:center;border-right:1px solid #e1dfdd;font-family:Consolas,monospace;font-size:12px;background:#faf9f8;user-select:none}
.fx-btn{width:34px;flex-shrink:0;display:flex;align-items:center;justify-content:center;border-right:1px solid #e1dfdd;font-style:italic;font-weight:600;color:#217346;font-size:13px;background:#faf9f8;user-select:none}
#formulaInput{flex:1;border:none;outline:none;padding:0 10px;font-family:Consolas,monospace;font-size:12px;background:#fff}
#workspace{flex:1;overflow:hidden;display:flex;background:#e6e6e6}
.view{flex:1;overflow:hidden;display:flex;flex-direction:column;min-width:0}
.word-scroll{flex:1;overflow:auto;padding:24px 16px;background:#e6e6e6;display:flex;justify-content:center;align-items:flex-start}
.word-page{width:816px;min-height:1056px;background:#fff;flex-shrink:0;box-shadow:0 2px 14px rgba(0,0,0,.18);padding:80px 88px}
#wordEditor{outline:none;font-family:Calibri,"Segoe UI",sans-serif;font-size:11pt;line-height:1.5;color:#000;min-height:880px;word-wrap:break-word}
#wordEditor p{margin:0 0 8pt 0}
#wordEditor h1{font-size:24pt;margin:12pt 0 6pt}
#wordEditor h2{font-size:18pt;margin:10pt 0 6pt}
#wordEditor h3{font-size:14pt;margin:8pt 0 4pt}
#wordEditor ul,#wordEditor ol{margin:6pt 0;padding-left:36pt}
#wordEditor blockquote{border-left:4px solid #2b579a;margin:8pt 0;padding:4pt 12pt;color:#444;background:#f7f9fc}
#wordEditor table{border-collapse:collapse;margin:8pt 0}
#wordEditor table td,#wordEditor table th{border:1px solid #999;padding:4pt 8pt}
#wordEditor hr{border:none;border-top:1px solid #999;margin:10pt 0}
.grid-container{flex:1;overflow:hidden;position:relative;background:#fff}
#gridScroll{position:absolute;inset:0;overflow:auto;outline:none;background:#fff}
#gridScroll::-webkit-scrollbar{width:14px;height:14px}
#gridScroll::-webkit-scrollbar-track{background:#f3f2f1}
#gridScroll::-webkit-scrollbar-thumb{background:#c8c6c4;border-radius:7px;border:3px solid #f3f2f1}
#grid{border-collapse:separate;border-spacing:0;table-layout:fixed;font-size:12px;font-family:Calibri,"Segoe UI",sans-serif}
#grid th,#grid td{height:24px;padding:0 5px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;border-right:1px solid #d4d4d4;border-bottom:1px solid #d4d4d4;vertical-align:middle}
#grid thead th{position:sticky;top:0;z-index:2;background:#f5f5f5;font-weight:400;text-align:center;color:#444;font-size:11px;user-select:none;cursor:pointer;border-bottom:1px solid #b0b0b0;width:72px;min-width:72px;max-width:72px}
#grid thead th.sel{background:var(--accent-l);color:var(--accent-d);font-weight:600}
#grid .rowhead{position:sticky;left:0;z-index:1;background:#f5f5f5;font-weight:400;text-align:center;color:#444;font-size:11px;user-select:none;cursor:pointer;border-right:1px solid #b0b0b0;width:48px;min-width:48px;max-width:48px}
#grid .rowhead.sel{background:var(--accent-l);color:var(--accent-d);font-weight:600}
#grid .corner{position:sticky;top:0;left:0;z-index:4;background:#e8e8e8;width:48px;min-width:48px;max-width:48px;border-right:1px solid #b0b0b0}
#grid td.cell{background:#fff;cursor:cell;outline:none;user-select:none;width:72px;min-width:72px;max-width:72px}
#grid td.cell.active{outline:2px solid var(--accent);outline-offset:-2px;z-index:1;position:relative}
#grid td.cell.editing{outline:2px solid var(--accent);outline-offset:-2px;z-index:5;position:relative;background:#fff!important;user-select:text;cursor:text;white-space:pre;overflow:visible;padding:0 4px}
#grid td.cell.error{color:#c00;font-weight:600}
#grid td.cell.num{text-align:right}
.sheet-tabs{flex-shrink:0;height:30px;display:flex;align-items:stretch;gap:1px;background:#f3f2f1;border-top:1px solid #e1dfdd;padding:0 6px;overflow-x:auto}
.sheet-tab{border:none;background:transparent;padding:0 16px;cursor:pointer;font-size:12px;border-bottom:2px solid transparent;color:#444;white-space:nowrap}
.sheet-tab.active{background:#fff;border-bottom-color:var(--accent);color:var(--accent);font-weight:600}
.sheet-add{border:none;background:transparent;width:30px;cursor:pointer;font-size:16px;color:#666;border-radius:3px}
#statusbar{flex-shrink:0;height:26px;display:flex;align-items:center;justify-content:space-between;padding:0 12px;background:var(--accent);color:#fff;font-size:11px}
.status-right{display:flex;align-items:center;gap:4px}
.zoom-btn{width:20px;height:20px;border:none;border-radius:3px;background:rgba(255,255,255,.15);color:#fff;cursor:pointer;font-size:13px;line-height:1}
#zoomLabel{min-width:38px;text-align:center}
#toast{position:fixed;bottom:46px;left:50%;transform:translateX(-50%) translateY(20px);background:#323130;color:#fff;padding:10px 20px;border-radius:4px;font-size:12px;opacity:0;pointer-events:none;transition:opacity .22s,transform .22s;z-index:1000;box-shadow:0 4px 16px rgba(0,0,0,.3)}
#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
@media(max-width:900px){.word-page{width:100%;padding:40px 24px;min-height:auto}}
</style>
</head>
<body>
<div id="app" data-app="word">

<header id="titlebar">
  <div class="app-launcher">
    <button class="app-btn active" data-goto="word" title="Word">W</button>
    <button class="app-btn" data-goto="excel" title="Excel">X</button>
  </div>
  <div class="doc-title-wrap">
    <input id="docTitle" value="Documento1" spellcheck="false" autocomplete="off">
  </div>
  <div class="titlebar-actions">
    <button class="tb-btn" id="btnNew">Nuovo</button>
    <button class="tb-btn" id="btnOpen">Apri</button>
    <button class="tb-btn" id="btnDownload">Scarica</button>
    <button class="tb-btn primary" id="btnSave">Salva</button>
    <input type="file" id="fileInput" accept=".json,application/json" style="display:none">
  </div>
</header>

<div id="ribbon">
  <div class="ribbon-content" id="ribbonWord">
    <div class="rgroup">
      <div class="rgroup-items">
        <button class="rbtn" data-wcmd="undo" title="Annulla">↶</button>
        <button class="rbtn" data-wcmd="redo" title="Ripeti">↷</button>
        <button class="rbtn" id="wClearFmt" title="Cancella formattazione">⌫</button>
      </div>
      <div class="rgroup-label">Annulla</div>
    </div>
    <div class="rgroup">
      <div class="rgroup-items">
        <select class="rselect" id="wFont" style="width:126px">
          <option value="Calibri">Calibri</option>
          <option value="Arial">Arial</option>
          <option value="Times New Roman">Times New Roman</option>
          <option value="Georgia">Georgia</option>
          <option value="Verdana">Verdana</option>
          <option value="Tahoma">Tahoma</option>
          <option value="Courier New">Courier New</option>
          <option value="Consolas">Consolas</option>
        </select>
        <select class="rselect small" id="wSize">
          <option>9</option><option>10</option><option selected>11</option><option>12</option>
          <option>14</option><option>16</option><option>18</option><option>20</option>
          <option>24</option><option>28</option><option>36</option><option>48</option>
        </select>
      </div>
      <div class="rgroup-label">Carattere</div>
    </div>
    <div class="rgroup">
      <div class="rgroup-items">
        <button class="rbtn" data-wcmd="bold" style="font-weight:800">G</button>
        <button class="rbtn" data-wcmd="italic" style="font-style:italic">C</button>
        <button class="rbtn" data-wcmd="underline" style="text-decoration:underline">S</button>
        <button class="rbtn" data-wcmd="strikeThrough" style="text-decoration:line-through">B</button>
        <button class="rbtn" data-wcmd="superscript">x²</button>
        <button class="rbtn" data-wcmd="subscript">x₂</button>
        <label class="rcolor">A<span class="bar" id="wForeBar" style="background:#c00000"></span><input type="color" id="wForeColor" value="#c00000"></label>
        <label class="rcolor">▨<span class="bar" id="wHiliteBar" style="background:#ffff00"></span><input type="color" id="wHilite" value="#ffff00"></label>
      </div>
      <div class="rgroup-label">Stili</div>
    </div>
    <div class="rgroup">
      <div class="rgroup-items">
        <button class="rbtn" data-wcmd="justifyLeft">≡</button>
        <button class="rbtn" data-wcmd="justifyCenter">≡</button>
        <button class="rbtn" data-wcmd="justifyRight">≡</button>
        <button class="rbtn" data-wcmd="justifyFull">≡</button>
      </div>
      <div class="rgroup-label">Paragrafo</div>
    </div>
    <div class="rgroup">
      <div class="rgroup-items">
        <button class="rbtn" data-wcmd="insertUnorderedList">• —</button>
        <button class="rbtn" data-wcmd="insertOrderedList">1. —</button>
        <button class="rbtn" data-wcmd="outdent">◄</button>
        <button class="rbtn" data-wcmd="indent">►</button>
      </div>
      <div class="rgroup-label">Elenchi</div>
    </div>
    <div class="rgroup">
      <div class="rgroup-items">
        <button class="rbtn" id="wBlock">❝</button>
        <button class="rbtn" id="wLink">🔗</button>
        <button class="rbtn" id="wImage">🖼</button>
        <button class="rbtn" id="wTable">▦</button>
        <button class="rbtn" id="wHr">—</button>
      </div>
      <div class="rgroup-label">Inserisci</div>
    </div>
  </div>

  <div class="ribbon-content hidden" id="ribbonExcel">
    <div class="rgroup">
      <div class="rgroup-items">
        <button class="rbtn" id="xUndo">↶</button>
        <button class="rbtn" id="xRedo">↷</button>
        <button class="rbtn" id="xClearFmt">⌫</button>
      </div>
      <div class="rgroup-label">Annulla</div>
    </div>
    <div class="rgroup">
      <div class="rgroup-items">
        <select class="rselect" id="xFont" style="width:112px">
          <option value="Calibri">Calibri</option>
          <option value="Arial">Arial</option>
          <option value="Times New Roman">Times New Roman</option>
          <option value="Verdana">Verdana</option>
          <option value="Consolas">Consolas</option>
        </select>
        <select class="rselect small" id="xSize">
          <option>9</option><option>10</option><option selected>11</option><option>12</option>
          <option>14</option><option>16</option><option>18</option><option>20</option><option>24</option>
        </select>
      </div>
      <div class="rgroup-label">Carattere</div>
    </div>
    <div class="rgroup">
      <div class="rgroup-items">
        <button class="rbtn" data-xstyle="bold" style="font-weight:800">G</button>
        <button class="rbtn" data-xstyle="italic" style="font-style:italic">C</button>
        <button class="rbtn" data-xstyle="underline" style="text-decoration:underline">S</button>
        <label class="rcolor">A<span class="bar" id="xForeBar" style="background:#c00000"></span><input type="color" id="xForeColor" value="#c00000"></label>
        <label class="rcolor">▨<span class="bar" id="xFillBar" style="background:#ffe699"></span><input type="color" id="xFill" value="#ffe699"></label>
      </div>
      <div class="rgroup-label">Stili</div>
    </div>
    <div class="rgroup">
      <div class="rgroup-items">
        <button class="rbtn" data-xstyle="alignLeft">≡</button>
        <button class="rbtn" data-xstyle="alignCenter">≡</button>
        <button class="rbtn" data-xstyle="alignRight">≡</button>
        <button class="rbtn" data-xstyle="wrap">↵</button>
      </div>
      <div class="rgroup-label">Allineamento</div>
    </div>
  </div>
</div>

<div id="formulaBar" class="hidden">
  <div class="cellref" id="cellRef">A1</div>
  <div class="fx-btn">fx</div>
  <input id="formulaInput" spellcheck="false" autocomplete="off" placeholder="Valore o formula (es. =A1+B1, =SUM(A1:A5))">
</div>

<div id="workspace">
  <div id="wordView" class="view">
    <div class="word-scroll">
      <div class="word-page">
        <div id="wordEditor" contenteditable="true" spellcheck="false"></div>
      </div>
    </div>
  </div>
  <div id="excelView" class="view hidden">
    <div class="grid-container">
      <div id="gridScroll" tabindex="0">
        <table id="grid"></table>
      </div>
    </div>
    <div class="sheet-tabs" id="sheetTabs"></div>
  </div>
</div>

<div id="statusbar">
  <span id="statusInfo">Pronto</span>
  <div class="status-right">
    <button class="zoom-btn" id="zoomOut">−</button>
    <span id="zoomLabel">100%</span>
    <button class="zoom-btn" id="zoomIn">+</button>
  </div>
</div>
</div>

<div id="toast"></div>

<script>
(function(){
'use strict';

function $(s){ return document.querySelector(s); }
function $$(s){ return Array.prototype.slice.call(document.querySelectorAll(s)); }
function byId(id){ return document.getElementById(id); }

/* ============ UTILS ============ */
function colLetter(i){
  var s=''; i++;
  while(i>0){ i--; s=String.fromCharCode(65+(i%26))+s; i=Math.floor(i/26); }
  return s;
}
function colIndex(letter){
  var n=0;
  for(var i=0;i<letter.length;i++) n=n*26+(letter.charCodeAt(i)-64);
  return n-1;
}
function parseRef(ref){
  var m=/^([A-Z]+)(\d+)$/.exec(ref);
  if(!m) return null;
  return { col: colIndex(m[1]), row: parseInt(m[2],10) };
}
function toast(msg){
  var el=byId('toast');
  if(!el){ el=document.createElement('div'); el.id='toast'; document.body.appendChild(el); }
  el.textContent=msg;
  el.classList.add('show');
  clearTimeout(toast._t);
  toast._t=setTimeout(function(){ el.classList.remove('show'); },1700);
}
function escXml(s){
  return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&apos;');
}
function rgbToHex(c){
  if(!c) return null;
  if(c.charAt(0)==='#') return c.replace('#','').toUpperCase();
  var m=/rgba?\((\d+)[,\s]+(\d+)[,\s]+(\d+)/.exec(c);
  if(m){
    var h=function(n){ n=parseInt(n,10); return (n<16?'0':'')+n.toString(16); };
    return (h(m[1])+h(m[2])+h(m[3])).toUpperCase();
  }
  return null;
}
function downloadBytes(bytes,filename,mime){
  var blob=new Blob([bytes],{type:mime});
  var url=URL.createObjectURL(blob);
  var a=document.createElement('a');
  a.href=url; a.download=filename;
  document.body.appendChild(a); a.click(); document.body.removeChild(a);
  setTimeout(function(){ URL.revokeObjectURL(url); },800);
}
function setHidden(el,hidden){
  if(!el) return;
  if(hidden) el.classList.add('hidden');
  else el.classList.remove('hidden');
}

/* ============ ZIP WRITER ============ */
var crcTable=(function(){
  var t=new Uint32Array(256);
  for(var n=0;n<256;n++){
    var c=n;
    for(var k=0;k<8;k++) c=(c&1)?(0xEDB88320^(c>>>1)):(c>>>1);
    t[n]=c>>>0;
  }
  return t;
})();
function crc32(b){
  var c=0xFFFFFFFF;
  for(var i=0;i<b.length;i++) c=crcTable[(c^b[i])&0xFF]^(c>>>8);
  return (c^0xFFFFFFFF)>>>0;
}
function makeZip(files){
  var parts=[], cd=[], offset=0;
  var now=new Date();
  var dTime=((now.getHours()<<11)|(now.getMinutes()<<5)|(now.getSeconds()>>1))&0xFFFF;
  var dDate=(((now.getFullYear()-1980)<<9)|((now.getMonth()+1)<<5)|now.getDate())&0xFFFF;
  files.forEach(function(f){
    var nb=new TextEncoder().encode(f.name);
    var crc=crc32(f.data);
    var size=f.data.length;
    var lh=new Uint8Array(30+nb.length);
    var dv=new DataView(lh.buffer);
    dv.setUint32(0,0x04034b50,true);
    dv.setUint16(4,20,true);
    dv.setUint16(6,0x0800,true);
    dv.setUint16(8,0,true);
    dv.setUint16(10,dTime,true);
    dv.setUint16(12,dDate,true);
    dv.setUint32(14,crc,true);
    dv.setUint32(18,size,true);
    dv.setUint32(22,size,true);
    dv.setUint16(26,nb.length,true);
    dv.setUint16(28,0,true);
    lh.set(nb,30);
    parts.push(lh,f.data);
    var cdh=new Uint8Array(46+nb.length);
    var dv2=new DataView(cdh.buffer);
    dv2.setUint32(0,0x02014b50,true);
    dv2.setUint16(4,20,true);
    dv2.setUint16(6,20,true);
    dv2.setUint16(8,0x0800,true);
    dv2.setUint16(10,0,true);
    dv2.setUint16(12,dTime,true);
    dv2.setUint16(14,dDate,true);
    dv2.setUint32(16,crc,true);
    dv2.setUint32(20,size,true);
    dv2.setUint32(24,size,true);
    dv2.setUint16(28,nb.length,true);
    dv2.setUint16(30,0,true);
    dv2.setUint16(32,0,true);
    dv2.setUint16(34,0,true);
    dv2.setUint16(36,0,true);
    dv2.setUint32(38,0,true);
    dv2.setUint32(42,offset,true);
    cdh.set(nb,46);
    cd.push(cdh);
    offset+=lh.length+size;
  });
  var cdSize=cd.reduce(function(a,b){ return a+b.length; },0);
  var cdStart=offset;
  var eocd=new Uint8Array(22);
  var dv3=new DataView(eocd.buffer);
  dv3.setUint32(0,0x06054b50,true);
  dv3.setUint16(4,0,true);
  dv3.setUint16(6,0,true);
  dv3.setUint16(8,files.length,true);
  dv3.setUint16(10,files.length,true);
  dv3.setUint32(12,cdSize,true);
  dv3.setUint32(16,cdStart,true);
  dv3.setUint16(20,0,true);
  var all=parts.concat(cd,[eocd]);
  var total=all.reduce(function(a,b){ return a+b.length; },0);
  var out=new Uint8Array(total);
  var pos=0;
  all.forEach(function(p){ out.set(p,pos); pos+=p.length; });
  return out;
}

/* ============ STATE ============ */
var state={
  app:'word',
  title:'Documento1',
  sheets:[{ name:'Foglio1', cells:{} }],
  activeSheet:0,
  activeRef:'A1',
  zoom:100
};
var editing=false;
var COLS=18, ROWS=60;

/* ============ APP SWITCH ============ */
function switchApp(app){
  state.app=app;
  var root=byId('app');
  if(root) root.setAttribute('data-app',app);
  $$('.app-btn').forEach(function(b){ b.classList.toggle('active', b.getAttribute('data-goto')===app); });
  setHidden(byId('ribbonWord'), app!=='word');
  setHidden(byId('ribbonExcel'), app!=='excel');
  setHidden(byId('formulaBar'), app!=='excel');
  setHidden(byId('wordView'), app!=='word');
  setHidden(byId('excelView'), app!=='excel');
  if(app==='excel'){
    setTimeout(function(){ var gs=byId('gridScroll'); if(gs&&gs.focus) gs.focus(); },0);
  }
}
$$('.app-btn').forEach(function(b){
  b.addEventListener('click',function(){ switchApp(b.getAttribute('data-goto')); });
});

/* ============ TITLE ============ */
var docTitle=byId('docTitle');
if(docTitle){
  docTitle.addEventListener('input',function(){ state.title=docTitle.value; scheduleSave(); });
}

/* ============ WORD ============ */
var wordEditor=byId('wordEditor');

$$('[data-wcmd]').forEach(function(btn){
  btn.addEventListener('mousedown',function(e){
    e.preventDefault();
    try{ document.execCommand(btn.getAttribute('data-wcmd'),false,null); }catch(err){}
    if(wordEditor) wordEditor.focus();
    syncWordToolbar();
  });
});
var wFont=byId('wFont');
if(wFont) wFont.addEventListener('change',function(e){
  document.execCommand('fontName',false,e.target.value);
  if(wordEditor) wordEditor.focus();
});
var wSize=byId('wSize');
if(wSize) wSize.addEventListener('change',function(e){
  var size=e.target.value;
  document.execCommand('fontSize',false,'7');
  if(!wordEditor) return;
  var fonts=wordEditor.querySelectorAll('font[size="7"]');
  for(var i=0;i<fonts.length;i++){
    var span=document.createElement('span');
    span.style.fontSize=size+'pt';
    while(fonts[i].firstChild) span.appendChild(fonts[i].firstChild);
    fonts[i].parentNode.replaceChild(span,fonts[i]);
  }
  wordEditor.focus();
});
var wForeColor=byId('wForeColor');
if(wForeColor) wForeColor.addEventListener('input',function(e){
  document.execCommand('foreColor',false,e.target.value);
  var b=byId('wForeBar'); if(b) b.style.background=e.target.value;
  if(wordEditor) wordEditor.focus();
});
var wHilite=byId('wHilite');
if(wHilite) wHilite.addEventListener('input',function(e){
  try{ document.execCommand('hiliteColor',false,e.target.value); }
  catch(err){ document.execCommand('backColor',false,e.target.value); }
  var b=byId('wHiliteBar'); if(b) b.style.background=e.target.value;
  if(wordEditor) wordEditor.focus();
});
var wBlock=byId('wBlock');
if(wBlock) wBlock.addEventListener('click',function(){ document.execCommand('formatBlock',false,'blockquote'); if(wordEditor) wordEditor.focus(); });
var wLink=byId('wLink');
if(wLink) wLink.addEventListener('click',function(){ var u=prompt('URL del link:','https://'); if(u) document.execCommand('createLink',false,u); if(wordEditor) wordEditor.focus(); });
var wImage=byId('wImage');
if(wImage) wImage.addEventListener('click',function(){ var u=prompt('URL immagine:','https://'); if(u) document.execCommand('insertImage',false,u); if(wordEditor) wordEditor.focus(); });
var wTable=byId('wTable');
if(wTable) wTable.addEventListener('click',function(){
  var r=parseInt(prompt('Righe:','3'),10)||3;
  var c=parseInt(prompt('Colonne:','3'),10)||3;
  var html='<table>';
  for(var i=0;i<r;i++){ html+='<tr>'; for(var j=0;j<c;j++) html+='<td>&nbsp;</td>'; html+='</tr>'; }
  html+='</table><p><br></p>';
  document.execCommand('insertHTML',false,html);
  if(wordEditor) wordEditor.focus();
});
var wHr=byId('wHr');
if(wHr) wHr.addEventListener('click',function(){ document.execCommand('insertHorizontalRule'); if(wordEditor) wordEditor.focus(); });
var wClearFmt=byId('wClearFmt');
if(wClearFmt) wClearFmt.addEventListener('click',function(){ document.execCommand('removeFormat'); if(wordEditor) wordEditor.focus(); });

function syncWordToolbar(){
  ['bold','italic','underline','strikeThrough','justifyLeft','justifyCenter','justifyRight','justifyFull','insertUnorderedList','insertOrderedList'].forEach(function(cmd){
    var btn=document.querySelector('[data-wcmd="'+cmd+'"]');
    if(!btn) return;
    try{ btn.classList.toggle('active', document.queryCommandState(cmd)); }catch(e){}
  });
}
if(wordEditor){
  wordEditor.addEventListener('keyup',syncWordToolbar);
  wordEditor.addEventListener('mouseup',syncWordToolbar);
  wordEditor.addEventListener('input',scheduleSave);
  wordEditor.addEventListener('keydown',function(e){
    if(e.ctrlKey||e.metaKey){
      var k=e.key.toLowerCase();
      if(k==='b'){ e.preventDefault(); document.execCommand('bold'); syncWordToolbar(); }
      if(k==='i'){ e.preventDefault(); document.execCommand('italic'); syncWordToolbar(); }
      if(k==='u'){ e.preventDefault(); document.execCommand('underline'); syncWordToolbar(); }
    }
  });
}

/* ============ EXCEL GRID ============ */
var gridScroll=byId('gridScroll');
var grid=byId('grid');

function buildGrid(){
  if(!grid) return;
  var html='<thead><tr><th class="corner"></th>';
  for(var c=0;c<COLS;c++) html+='<th data-col="'+c+'">'+colLetter(c)+'</th>';
  html+='</tr></thead><tbody>';
  for(var r=1;r<=ROWS;r++){
    html+='<tr><th class="rowhead" data-row="'+r+'">'+r+'</th>';
    for(var cc=0;cc<COLS;cc++) html+='<td class="cell" data-ref="'+colLetter(cc)+r+'" data-row="'+r+'" data-col="'+cc+'"></td>';
    html+='</tr>';
  }
  html+='</tbody>';
  grid.innerHTML=html;
}
buildGrid();

function getSheet(){ return state.sheets[state.activeSheet]; }
function getCells(){ return getSheet().cells; }
function getTd(ref){ return grid?grid.querySelector('td[data-ref="'+ref+'"]'):null; }

/* ============ FORMULA ENGINE ============ */
function evaluate(ref,seen){
  seen=seen||{};
  if(seen[ref]) return 0;
  seen[ref]=true;
  var cell=getCells()[ref];
  if(!cell||cell.v==null||cell.v==='') return 0;
  var v=cell.v;
  if(typeof v==='string'&&v.charAt(0)==='='){
    try{ return evalFormula(v.slice(1),seen); }
    catch(e){ return '#ERR!'; }
  }
  var n=Number(v);
  return isNaN(n)?v:n;
}
function getRange(a,b){
  var pa=parseRef(a), pb=parseRef(b);
  if(!pa||!pb) return [];
  var res=[];
  for(var r=Math.min(pa.row,pb.row); r<=Math.max(pa.row,pb.row); r++)
    for(var c=Math.min(pa.col,pb.col); c<=Math.max(pa.col,pb.col); c++)
      res.push(colLetter(c)+r);
  return res;
}
function evalFormula(expr,seen){
  expr=expr.trim();
  if(expr==='') return '';
  var single=/^([A-Z]+\d+)$/i.exec(expr);
  if(single) return evaluate(single[1].toUpperCase(),seen);
  expr=expr.replace(/(SUM|AVERAGE|AVG|MIN|MAX|COUNT)\(([^)]*)\)/gi,function(m,fn,args){
    var nums=[];
    args.split(',').forEach(function(part){
      part=part.trim();
      if(!part) return;
      if(part.indexOf(':')>=0){
        var ab=part.split(':');
        getRange(ab[0].trim(),ab[1].trim()).forEach(function(r){
          var v=evaluate(r,Object.assign({},seen));
          if(typeof v==='number') nums.push(v);
        });
      } else if(/^[A-Z]+\d+$/i.test(part)){
        var v2=evaluate(part.toUpperCase(),Object.assign({},seen));
        if(typeof v2==='number') nums.push(v2);
      } else {
        var n=Number(part);
        if(!isNaN(n)) nums.push(n);
      }
    });
    fn=fn.toUpperCase();
    if(fn==='SUM') return nums.reduce(function(a,b){ return a+b; },0);
    if(fn==='AVERAGE'||fn==='AVG') return nums.length?nums.reduce(function(a,b){return a+b;},0)/nums.length:0;
    if(fn==='MIN') return nums.length?Math.min.apply(null,nums):0;
    if(fn==='MAX') return nums.length?Math.max.apply(null,nums):0;
    if(fn==='COUNT') return nums.length;
    return 0;
  });
  expr=expr.replace(/\b([A-Z]+\d+)\b/gi,function(m){
    var v=evaluate(m.toUpperCase(),Object.assign({},seen));
    return typeof v==='number'?String(v):'0';
  });
  if(!/^[\d\s+\-*/().,]+$/.test(expr)) return '#ERR!';
  try{
    var result=Function('"use strict"; return ('+expr+');')();
    return (typeof result==='number'&&isFinite(result))?result:'#ERR!';
  }catch(e){ return '#ERR!'; }
}

/* ============ RENDER GRID ============ */
function renderGrid(){
  if(!grid) return;
  var cells=getCells();
  var tds=grid.querySelectorAll('td.cell');
  for(var i=0;i<tds.length;i++){
    var td=tds[i];
    if(td.classList.contains('editing')) continue;
    var ref=td.getAttribute('data-ref');
    var cell=cells[ref];
    var display='', isNum=false, isErr=false;
    if(cell&&cell.v!=null&&cell.v!==''){
      if(typeof cell.v==='string'&&cell.v.charAt(0)==='='){
        var val=evaluate(ref);
        display=String(val);
        isNum=(typeof val==='number');
        isErr=(val==='#ERR!');
      } else {
        display=String(cell.v);
        var n=Number(cell.v);
        isNum=!isNaN(n)&&cell.v!=='';
      }
    }
    td.textContent=display;
    td.classList.toggle('num',isNum);
    td.classList.toggle('error',isErr);
    var s=(cell&&cell.s)||{};
    td.style.fontWeight=s.bold?'bold':'';
    td.style.fontStyle=s.italic?'italic':'';
    td.style.textDecoration=s.underline?'underline':'';
    td.style.color=s.color||'';
    td.style.background=s.bg||'';
    td.style.textAlign=s.align||(isNum?'right':'');
    td.style.fontFamily=s.font||'';
    td.style.fontSize=s.size?(parseInt(s.size,10)*(state.zoom/100))+'px':'';
    td.style.whiteSpace=s.wrap?'normal':'nowrap';
  }
  updateSelection();
}
function updateSelection(){
  if(grid){
    var prev=grid.querySelector('td.active');
    if(prev) prev.classList.remove('active');
    var td=getTd(state.activeRef);
    if(td) td.classList.add('active');
    var p=parseRef(state.activeRef);
    if(p){
      var old=grid.querySelectorAll('thead th.sel, .rowhead.sel');
      for(var i=0;i<old.length;i++) old[i].classList.remove('sel');
      var cth=grid.querySelector('thead th[data-col="'+p.col+'"]');
      if(cth) cth.classList.add('sel');
      var rth=grid.querySelector('.rowhead[data-row="'+p.row+'"]');
      if(rth) rth.classList.add('sel');
    }
  }
  /* Accesso difensivo: l'elemento potrebbe essere stato rimosso dal sanitizer */
  var el=byId('cellRef');
  if(el) el.textContent=state.activeRef;
  if(!editing){
    var fi=byId('formulaInput');
    if(fi){
      var cell=getCells()[state.activeRef];
      fi.value=(cell&&cell.v!=null)?cell.v:'';
    }
  }
}
function setActive(ref){
  if(!parseRef(ref)) return;
  state.activeRef=ref;
  updateSelection();
  var td=getTd(ref);
  if(td&&gridScroll){
    var r=td.getBoundingClientRect(), c=gridScroll.getBoundingClientRect();
    if(r.top<c.top+26) gridScroll.scrollTop+=r.top-c.top-26;
    else if(r.bottom>c.bottom) gridScroll.scrollTop+=r.bottom-c.bottom;
    if(r.left<c.left+50) gridScroll.scrollLeft+=r.left-c.left-50;
    else if(r.right>c.right) gridScroll.scrollLeft+=r.right-c.right;
  }
}
function moveBy(dr,dc){
  var p=parseRef(state.activeRef);
  if(!p) return;
  var r=Math.max(1,Math.min(ROWS,p.row+dr));
  var c=Math.max(0,Math.min(COLS-1,p.col+dc));
  setActive(colLetter(c)+r);
}
function startEdit(initialChar){
  if(editing) return;
  var td=getTd(state.activeRef);
  if(!td) return;
  editing=true;
  td.classList.add('editing');
  td.setAttribute('contenteditable','true');
  var cell=getCells()[state.activeRef];
  var cur=(cell&&cell.v!=null)?String(cell.v):'';
  td.textContent=(initialChar!=null)?initialChar:cur;
  td.focus();
  var sel=window.getSelection();
  var range=document.createRange();
  range.selectNodeContents(td);
  if(initialChar==null&&cur){ sel.removeAllRanges(); sel.addRange(range); }
  else { range.collapse(false); sel.removeAllRanges(); sel.addRange(range); }
}
function commitEdit(){
  if(!editing) return;
  editing=false;
  var td=getTd(state.activeRef);
  if(!td) return;
  var v=td.textContent;
  td.classList.remove('editing');
  td.removeAttribute('contenteditable');
  var cells=getCells();
  if(!cells[state.activeRef]) cells[state.activeRef]={};
  cells[state.activeRef].v=v;
  renderGrid();
  scheduleSave();
}
function cancelEdit(){
  if(!editing) return;
  editing=false;
  var td=getTd(state.activeRef);
  if(td){ td.classList.remove('editing'); td.removeAttribute('contenteditable'); }
  renderGrid();
}

if(grid){
  grid.addEventListener('mousedown',function(e){
    var td=e.target.closest?e.target.closest('td.cell'):null;
    if(!td) return;
    if(editing) commitEdit();
    setActive(td.getAttribute('data-ref'));
    if(gridScroll) gridScroll.focus();
    e.preventDefault();
  });
  grid.addEventListener('dblclick',function(e){
    var td=e.target.closest?e.target.closest('td.cell'):null;
    if(!td) return;
    setActive(td.getAttribute('data-ref'));
    startEdit();
  });
  grid.addEventListener('click',function(e){
    var th=e.target.closest?e.target.closest('thead th[data-col]'):null;
    if(th){ setActive(colLetter(parseInt(th.getAttribute('data-col'),10))+'1'); if(gridScroll) gridScroll.focus(); return; }
    var rh=e.target.closest?e.target.closest('.rowhead'):null;
    if(rh){ setActive('A'+rh.getAttribute('data-row')); if(gridScroll) gridScroll.focus(); }
  });
}
if(gridScroll){
  gridScroll.addEventListener('keydown',function(e){
    if(editing){
      if(e.key==='Enter'){ e.preventDefault(); commitEdit(); moveBy(1,0); gridScroll.focus(); }
      else if(e.key==='Tab'){ e.preventDefault(); commitEdit(); moveBy(0,e.shiftKey?-1:1); gridScroll.focus(); }
      else if(e.key==='Escape'){ e.preventDefault(); cancelEdit(); gridScroll.focus(); }
      return;
    }
    switch(e.key){
      case 'ArrowUp': e.preventDefault(); moveBy(-1,0); break;
      case 'ArrowDown': e.preventDefault(); moveBy(1,0); break;
      case 'ArrowLeft': e.preventDefault(); moveBy(0,-1); break;
      case 'ArrowRight': e.preventDefault(); moveBy(0,1); break;
      case 'Tab': e.preventDefault(); moveBy(0,e.shiftKey?-1:1); break;
      case 'Enter': case 'F2': e.preventDefault(); startEdit(); break;
      case 'Delete': case 'Backspace':
        e.preventDefault();
        var cells=getCells();
        if(!cells[state.activeRef]) cells[state.activeRef]={};
        cells[state.activeRef].v='';
        renderGrid(); scheduleSave(); break;
      default:
        if(e.key.length===1&&!e.ctrlKey&&!e.metaKey&&!e.altKey){
          e.preventDefault();
          startEdit(e.key);
        }
    }
  });
}
var formulaInput=byId('formulaInput');
if(formulaInput){
  formulaInput.addEventListener('keydown',function(e){
    if(e.key==='Enter'){
      e.preventDefault();
      var cells=getCells();
      if(!cells[state.activeRef]) cells[state.activeRef]={};
      cells[state.activeRef].v=formulaInput.value;
      renderGrid(); scheduleSave();
      if(gridScroll) gridScroll.focus();
      moveBy(1,0);
    } else if(e.key==='Escape'){ formulaInput.value=''; if(gridScroll) gridScroll.focus(); }
  });
  formulaInput.addEventListener('blur',function(){
    var cells=getCells();
    var cell=cells[state.activeRef];
    var cur=cell?(cell.v||''):'';
    if(formulaInput.value!==cur){
      if(!cell) cell=cells[state.activeRef]={};
      cell.v=formulaInput.value;
      renderGrid(); scheduleSave();
    }
  });
}

$$('[data-xstyle]').forEach(function(btn){
  btn.addEventListener('click',function(){
    var st=btn.getAttribute('data-xstyle');
    var cells=getCells();
    if(!cells[state.activeRef]) cells[state.activeRef]={};
    var cell=cells[state.activeRef];
    if(!cell.s) cell.s={};
    if(st==='bold') cell.s.bold=!cell.s.bold;
    else if(st==='italic') cell.s.italic=!cell.s.italic;
    else if(st==='underline') cell.s.underline=!cell.s.underline;
    else if(st==='alignLeft') cell.s.align='left';
    else if(st==='alignCenter') cell.s.align='center';
    else if(st==='alignRight') cell.s.align='right';
    else if(st==='wrap') cell.s.wrap=!cell.s.wrap;
    btn.classList.toggle('active');
    renderGrid(); scheduleSave();
    if(gridScroll) gridScroll.focus();
  });
});
var xFont=byId('xFont');
if(xFont) xFont.addEventListener('change',function(e){
  var cells=getCells();
  if(!cells[state.activeRef]) cells[state.activeRef]={};
  if(!cells[state.activeRef].s) cells[state.activeRef].s={};
  cells[state.activeRef].s.font=e.target.value;
  renderGrid(); scheduleSave();
});
var xSize=byId('xSize');
if(xSize) xSize.addEventListener('change',function(e){
  var cells=getCells();
  if(!cells[state.activeRef]) cells[state.activeRef]={};
  if(!cells[state.activeRef].s) cells[state.activeRef].s={};
  cells[state.activeRef].s.size=e.target.value;
  renderGrid(); scheduleSave();
});
var xForeColor=byId('xForeColor');
if(xForeColor) xForeColor.addEventListener('input',function(e){
  var cells=getCells();
  if(!cells[state.activeRef]) cells[state.activeRef]={};
  if(!cells[state.activeRef].s) cells[state.activeRef].s={};
  cells[state.activeRef].s.color=e.target.value;
  var b=byId('xForeBar'); if(b) b.style.background=e.target.value;
  renderGrid(); scheduleSave();
});
var xFill=byId('xFill');
if(xFill) xFill.addEventListener('input',function(e){
  var cells=getCells();
  if(!cells[state.activeRef]) cells[state.activeRef]={};
  if(!cells[state.activeRef].s) cells[state.activeRef].s={};
  cells[state.activeRef].s.bg=e.target.value;
  var b=byId('xFillBar'); if(b) b.style.background=e.target.value;
  renderGrid(); scheduleSave();
});
var xClearFmt=byId('xClearFmt');
if(xClearFmt) xClearFmt.addEventListener('click',function(){
  var cells=getCells();
  if(cells[state.activeRef]) cells[state.activeRef].s={};
  renderGrid(); scheduleSave();
});
var xUndo=byId('xUndo'); if(xUndo) xUndo.addEventListener('click',function(){ toast('Annulla non disponibile'); });
var xRedo=byId('xRedo'); if(xRedo) xRedo.addEventListener('click',function(){ toast('Ripeti non disponibile'); });

/* ============ SHEET TABS ============ */
function renderSheetTabs(){
  var cont=byId('sheetTabs');
  if(!cont) return;
  var html='';
  state.sheets.forEach(function(s,i){
    html+='<button class="sheet-tab'+(i===state.activeSheet?' active':'')+'" data-sheet="'+i+'">'+s.name+'</button>';
  });
  html+='<button class="sheet-add" title="Nuovo foglio">+</button>';
  cont.innerHTML=html;
}
var sheetTabsEl=byId('sheetTabs');
if(sheetTabsEl){
  sheetTabsEl.addEventListener('click',function(e){
    var tab=e.target.closest?e.target.closest('.sheet-tab'):null;
    if(tab){
      state.activeSheet=parseInt(tab.getAttribute('data-sheet'),10);
      state.activeRef='A1';
      renderSheetTabs(); renderGrid(); setActive('A1');
      return;
    }
    if(e.target.classList.contains('sheet-add')){
      state.sheets.push({ name:'Foglio'+(state.sheets.length+1), cells:{} });
      state.activeSheet=state.sheets.length-1;
      state.activeRef='A1';
      renderSheetTabs(); renderGrid(); setActive('A1');
      scheduleSave();
    }
  });
}

/* ============ SAVE / LOAD ============ */
function serialize(){
  return {
    version:1, app:state.app, title:state.title,
    wordHTML:wordEditor?wordEditor.innerHTML:'',
    sheets:state.sheets, activeSheet:state.activeSheet
  };
}
function deserialize(data){
  if(!data||typeof data!=='object') return;
  if(typeof data.title==='string'){ state.title=data.title; if(docTitle) docTitle.value=data.title; }
  if(typeof data.wordHTML==='string'&&wordEditor){ wordEditor.innerHTML=data.wordHTML; }
  if(Array.isArray(data.sheets)&&data.sheets.length){
    state.sheets=data.sheets.map(function(s){ return { name:s.name||'Foglio1', cells:s.cells||{} }; });
    state.activeSheet=Math.min(data.activeSheet||0,state.sheets.length-1);
  }
  state.activeRef='A1';
  renderSheetTabs(); renderGrid(); setActive('A1');
  if(data.app==='word'||data.app==='excel') switchApp(data.app);
}
var saveTimer;
function scheduleSave(){ clearTimeout(saveTimer); saveTimer=setTimeout(saveToStorage,600); }
function saveToStorage(){
  try{
    localStorage.setItem('officeweb.v1',JSON.stringify(serialize()));
    toast('Salvato nel browser');
  }catch(e){ toast('Errore di salvataggio'); }
}
function loadFromStorage(){
  try{
    var s=localStorage.getItem('officeweb.v1');
    if(s){ deserialize(JSON.parse(s)); return true; }
  }catch(e){}
  return false;
}

/* ============ EXPORT .DOCX ============ */
function wordRunXml(text,st){
  var rPr='';
  if(st.b) rPr+='<w:b/>';
  if(st.i) rPr+='<w:i/>';
  if(st.u) rPr+='<w:u w:val="single"/>';
  if(st.strike) rPr+='<w:strike/>';
  if(st.vert) rPr+='<w:vertAlign w:val="'+st.vert+'"/>';
  if(st.sz) rPr+='<w:sz w:val="'+st.sz+'"/><w:szCs w:val="'+st.sz+'"/>';
  if(st.color) rPr+='<w:color w:val="'+st.color+'"/>';
  if(st.shd) rPr+='<w:shd w:val="clear" w:color="auto" w:fill="'+st.shd+'"/>';
  if(st.font) rPr+='<w:rFonts w:ascii="'+escXml(st.font)+'" w:hAnsi="'+escXml(st.font)+'"/>';
  return '<w:r>'+(rPr?'<w:rPr>'+rPr+'</w:rPr>':'')+'<w:t xml:space="preserve">'+escXml(text)+'</w:t></w:r>';
}
function wordInlineToXml(node,base){
  if(node.nodeType===3){
    var t=node.textContent;
    if(!t) return '';
    return wordRunXml(t,base);
  }
  if(node.nodeType!==1) return '';
  var tag=node.tagName.toLowerCase();
  var st=Object.assign({},base);
  if(tag==='b'||tag==='strong') st.b=true;
  else if(tag==='i'||tag==='em') st.i=true;
  else if(tag==='u') st.u=true;
  else if(tag==='s'||tag==='strike'||tag==='del') st.strike=true;
  else if(tag==='sup') st.vert='superscript';
  else if(tag==='sub') st.vert='subscript';
  else if(tag==='br') return '<w:r><w:br/></w:r>';
  if(node.style){
    var css=node.style;
    if(css.fontWeight==='bold'||css.fontWeight==='700'||parseInt(css.fontWeight,10)>=600) st.b=true;
    if(css.fontStyle==='italic') st.i=true;
    if(css.textDecoration&&css.textDecoration.indexOf('underline')>=0) st.u=true;
    if(css.textDecoration&&css.textDecoration.indexOf('line-through')>=0) st.strike=true;
    if(css.color){ var hc=rgbToHex(css.color); if(hc) st.color=hc; }
    if(css.backgroundColor){ var hb=rgbToHex(css.backgroundColor); if(hb) st.shd=hb; }
    if(css.fontSize){
      var pts=parseFloat(css.fontSize);
      if(css.fontSize.indexOf('pt')<0) pts=pts*0.75;
      if(pts>0) st.sz=Math.round(pts*2);
    }
    if(css.fontFamily){
      var ff=css.fontFamily.replace(/["']/g,'').split(',')[0].trim();
      if(ff) st.font=ff;
    }
  }
  var out='';
  Array.prototype.forEach.call(node.childNodes,function(c){ out+=wordInlineToXml(c,st); });
  return out;
}
function wordRunsToXml(node,base){
  var xml='';
  Array.prototype.forEach.call(node.childNodes,function(c){ xml+=wordInlineToXml(c,base); });
  return xml||'<w:r><w:t xml:space="preserve"> </w:t></w:r>';
}
function wordBlockToXml(node){
  if(node.nodeType===3){
    var t=node.textContent;
    if(!t.trim()) return '';
    return '<w:p>'+wordRunXml(t,{})+'</w:p>';
  }
  if(node.nodeType!==1) return '';
  var tag=node.tagName.toLowerCase();
  if(tag==='p'||tag==='div') return '<w:p>'+wordRunsToXml(node,{})+'</w:p>';
  if(/^h[1-6]$/.test(tag)){
    var szMap={h1:48,h2:36,h3:28,h4:24,h5:20,h6:16};
    var sz=szMap[tag]||24;
    return '<w:p><w:pPr><w:spacing w:before="240" w:after="120"/></w:pPr>'+wordRunsToXml(node,{b:true,sz:sz})+'</w:p>';
  }
  if(tag==='ul'||tag==='ol'){
    var out='', idx=1;
    Array.prototype.forEach.call(node.children,function(li){
      if(li.tagName&&li.tagName.toLowerCase()==='li'){
        var prefix=tag==='ul'?'• ':(idx+'. ');
        out+='<w:p><w:pPr><w:ind w:left="720" w:hanging="360"/></w:pPr>'+
             '<w:r><w:t xml:space="preserve">'+escXml(prefix)+'</w:t></w:r>'+
             wordRunsToXml(li,{})+'</w:p>';
        idx++;
      }
    });
    return out;
  }
  if(tag==='blockquote'){
    return '<w:p><w:pPr><w:ind w:left="720"/></w:pPr>'+wordRunsToXml(node,{i:true})+'</w:p>';
  }
  if(tag==='hr'){
    return '<w:p><w:pPr><w:pBdr><w:bottom w:val="single" w:sz="6" w:space="1" w:color="999999"/></w:pBdr></w:pPr></w:p>';
  }
  if(tag==='table'){
    var rowsXml='';
    Array.prototype.forEach.call(node.querySelectorAll('tr'),function(tr){
      var cellsXml='';
      Array.prototype.forEach.call(tr.children,function(td){
        cellsXml+='<w:tc><w:tcPr><w:tcW w:w="2400" w:type="dxa"/></w:tcPr><w:p>'+wordRunsToXml(td,{})+'</w:p></w:tc>';
      });
      rowsXml+='<w:tr>'+cellsXml+'</w:tr>';
    });
    return '<w:tbl><w:tblPr><w:tblBorders>'+
      '<w:top w:val="single" w:sz="4" w:color="999999"/>'+
      '<w:left w:val="single" w:sz="4" w:color="999999"/>'+
      '<w:bottom w:val="single" w:sz="4" w:color="999999"/>'+
      '<w:right w:val="single" w:sz="4" w:color="999999"/>'+
      '<w:insideH w:val="single" w:sz="4" w:color="999999"/>'+
      '<w:insideV w:val="single" w:sz="4" w:color="999999"/>'+
      '</w:tblBorders></w:tblPr>'+rowsXml+'</w:tbl><w:p/>';
  }
  return '<w:p>'+wordRunsToXml(node,{})+'</w:p>';
}
function buildDocx(html){
  var container=document.createElement('div');
  container.innerHTML=html;
  var bodyXml='';
  Array.prototype.forEach.call(container.childNodes,function(n){ bodyXml+=wordBlockToXml(n); });
  if(!bodyXml) bodyXml='<w:p/>';
  var documentXml='<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+
    '<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" '+
    'xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">'+
    '<w:body>'+bodyXml+
    '<w:sectPr><w:pgSz w:w="11906" w:h="16838"/>'+
    '<w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440" w:header="720" w:footer="720" w:gutter="0"/>'+
    '</w:sectPr></w:body></w:document>';
  var contentTypes='<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+
    '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'+
    '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>'+
    '<Default Extension="xml" ContentType="application/xml"/>'+
    '<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>'+
    '</Types>';
  var rels='<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+
    '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'+
    '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>'+
    '</Relationships>';
  var enc=new TextEncoder();
  return makeZip([
    { name:'[Content_Types].xml', data:enc.encode(contentTypes) },
    { name:'_rels/.rels', data:enc.encode(rels) },
    { name:'word/document.xml', data:enc.encode(documentXml) }
  ]);
}
function exportDocx(){
  var title=state.title||'Documento';
  var bytes=buildDocx(wordEditor?wordEditor.innerHTML:'');
  downloadBytes(bytes,title+'.docx','application/vnd.openxmlformats-officedocument.wordprocessingml.document');
  toast('Scaricato '+title+'.docx');
}

/* ============ EXPORT .XLSX ============ */
function buildXlsx(){
  var sheet=getSheet();
  var cells=sheet.cells;
  var maxRow=1, maxCol=0;
  Object.keys(cells).forEach(function(ref){
    var p=parseRef(ref);
    if(!p) return;
    if(p.row>maxRow) maxRow=p.row;
    if(p.col>maxCol) maxCol=p.col;
  });
  var rowsXml='';
  for(var r=1;r<=maxRow;r++){
    var cellsXml='';
    for(var c=0;c<=maxCol;c++){
      var ref=colLetter(c)+r;
      var cell=cells[ref];
      if(!cell||cell.v==null||cell.v==='') continue;
      var v=String(cell.v);
      var attrs=' r="'+ref+'"';
      if(v.charAt(0)==='='){
        var result=evaluate(ref);
        var formula=v.slice(1);
        if(typeof result==='number'&&isFinite(result)){
          cellsXml+='<c'+attrs+'><f>'+escXml(formula)+'</f><v>'+result+'</v></c>';
        } else {
          cellsXml+='<c'+attrs+' t="str"><f>'+escXml(formula)+'</f><v>'+escXml(String(result))+'</v></c>';
        }
      } else {
        var num=Number(v);
        if(!isNaN(num)&&v.trim()!==''&&/^-?\d+(\.\d+)?$/.test(v.trim())){
          cellsXml+='<c'+attrs+'><v>'+num+'</v></c>';
        } else {
          cellsXml+='<c'+attrs+' t="inlineStr"><is><t xml:space="preserve">'+escXml(v)+'</t></is></c>';
        }
      }
    }
    if(cellsXml) rowsXml+='<row r="'+r+'">'+cellsXml+'</row>';
  }
  if(!rowsXml) rowsXml='<row r="1"><c r="A1" t="inlineStr"><is><t></t></is></c></row>';
  var sheetXml='<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+
    '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">'+
    '<sheetData>'+rowsXml+'</sheetData></worksheet>';
  var workbookXml='<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+
    '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" '+
    'xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">'+
    '<sheets><sheet name="'+escXml(sheet.name)+'" sheetId="1" r:id="rId1"/></sheets></workbook>';
  var wbRels='<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+
    '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'+
    '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>'+
    '</Relationships>';
  var contentTypes='<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+
    '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'+
    '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>'+
    '<Default Extension="xml" ContentType="application/xml"/>'+
    '<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>'+
    '<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>'+
    '</Types>';
  var rels='<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+
    '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'+
    '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>'+
    '</Relationships>';
  var enc=new TextEncoder();
  return makeZip([
    { name:'[Content_Types].xml', data:enc.encode(contentTypes) },
    { name:'_rels/.rels', data:enc.encode(rels) },
    { name:'xl/workbook.xml', data:enc.encode(workbookXml) },
    { name:'xl/_rels/workbook.xml.rels', data:enc.encode(wbRels) },
    { name:'xl/worksheets/sheet1.xml', data:enc.encode(sheetXml) }
  ]);
}
function exportXlsx(){
  var title=state.title||'Cartella';
  var bytes=buildXlsx();
  downloadBytes(bytes,title+'.xlsx','application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
  toast('Scaricato '+title+'.xlsx');
}

/* ============ TITLEBAR ACTIONS ============ */
var btnSave=byId('btnSave');
if(btnSave) btnSave.addEventListener('click',function(e){
  if(e.shiftKey){
    var json=JSON.stringify(serialize(),null,2);
    downloadBytes(new TextEncoder().encode(json),(state.title||'backup')+'.json','application/json');
    toast('Backup JSON scaricato');
    return;
  }
  saveToStorage();
});
var btnDownload=byId('btnDownload');
if(btnDownload) btnDownload.addEventListener('click',function(){
  try{
    if(state.app==='word') exportDocx();
    else exportXlsx();
  }catch(err){
    alert('Errore durante l\'esportazione: '+err.message);
    console.error(err);
  }
});
var btnNew=byId('btnNew');
if(btnNew) btnNew.addEventListener('click',function(){
  if(!confirm('Creare un nuovo documento? Le modifiche non salvate andranno perse.')) return;
  state.title='Documento1';
  if(docTitle) docTitle.value=state.title;
  if(wordEditor) wordEditor.innerHTML='<p><br></p>';
  state.sheets=[{ name:'Foglio1', cells:{} }];
  state.activeSheet=0;
  state.activeRef='A1';
  renderSheetTabs(); renderGrid(); setActive('A1');
  toast('Nuovo documento');
});
var btnOpen=byId('btnOpen');
var fileInput=byId('fileInput');
if(btnOpen&&fileInput) btnOpen.addEventListener('click',function(){ fileInput.click(); });
if(fileInput) fileInput.addEventListener('change',function(e){
  var file=e.target.files[0];
  if(!file) return;
  var reader=new FileReader();
  reader.onload=function(ev){
    try{
      deserialize(JSON.parse(ev.target.result));
      toast('File aperto');
    }catch(err){
      alert('File non valido.');
    }
  };
  reader.readAsText(file);
  e.target.value='';
});

/* ============ ZOOM ============ */
function applyZoom(){
  var z=state.zoom/100;
  var wp=document.querySelector('.word-page');
  if(wp) wp.style.zoom=z;
  if(grid) grid.style.fontSize=(12*z)+'px';
  var zl=byId('zoomLabel'); if(zl) zl.textContent=state.zoom+'%';
}
var zoomIn=byId('zoomIn');
var zoomOut=byId('zoomOut');
if(zoomIn) zoomIn.addEventListener('click',function(){ state.zoom=Math.min(200,state.zoom+10); applyZoom(); });
if(zoomOut) zoomOut.addEventListener('click',function(){ state.zoom=Math.max(50,state.zoom-10); applyZoom(); });

/* ============ GLOBAL SHORTCUT ============ */
document.addEventListener('keydown',function(e){
  if((e.ctrlKey||e.metaKey)&&e.key.toLowerCase()==='s'){
    e.preventDefault();
    saveToStorage();
  }
});

/* ============ INIT ============ */
try {
  renderSheetTabs();
  if(!loadFromStorage() && wordEditor){
    wordEditor.innerHTML=
      '<h1>Benvenuto in Office Web</h1>'+
      '<p>Versione semplificata di Word ed Excel in un unico file HTML, CSS e JavaScript.</p>'+
      '<h2>Word</h2>'+
      '<ul>'+
      '<li>Formattazione: grassetto, corsivo, sottolineato, barrato, apice, pedice</li>'+
      '<li>Colori testo ed evidenziazione</li>'+
      '<li>Allineamento, elenchi, link, immagini, tabelle</li>'+
      '<li>Scorciatoie: Ctrl+B, Ctrl+I, Ctrl+U</li>'+
      '</ul>'+
      '<h2>Excel</h2>'+
      '<ul>'+
      '<li>Griglia con celle selezionabili e modificabili</li>'+
      '<li>Formule: <code>=A1+B1</code>, <code>=SUM(A1:A10)</code>, <code>=AVERAGE</code>, <code>=MIN</code>, <code>=MAX</code>, <code>=COUNT</code></li>'+
      '<li>Stili, colori, allineamento, testo a capo</li>'+
      '<li>Barra della formula editabile</li>'+
      '</ul>'+
      '<p><b>Scarica</b> esporta <b>.docx</b> in Word e <b>.xlsx</b> in Excel, apribili con Microsoft Office.</p>';
  }
  switchApp(state.app||'word');
  renderGrid();
  setActive('A1');
  syncWordToolbar();
  applyZoom();
} catch(err){
  console.error('Errore di inizializzazione:',err);
}

})();
</script>
</body>
</html>
← Back to timeline