Plugin Directory

Changeset 3713964


Ignore:
Timestamp:
09/26/2026 07:45:24 AM (3 weeks ago)
Author:
readystack
Message:

Release 1.0.2

Location:
readystack-payroll-tax-spreadsheet
Files:
14 edited
1 copied

Legend:

Unmodified
Added
Removed
  • readystack-payroll-tax-spreadsheet/tags/1.0.2/assets/tool-1.js

    r3712579 r3713964  
    1 (function(){var s=document.createElement("script");s.type="application/json";s.id="calcdata";s.textContent="{\"spec\": {\"sheets\": [{\"name\": \"Inputs - Your Payroll Numbers\", \"purpose\": \"Type your own eight numbers here; both calc sheets recalculate. A free online paycheck calculator cannot carry year-to-date wages from one run to the next, so it never knows that Social Security has already stopped for this employee — that is the whole reason this file exists. Rates you must confirm yourself are inputs, not guesses: the Social Security wage base is set by the SSA each October and the default shown is the 2025 figure of 176,100, so replace it with the figure for the year you are running. The $7,000 FUTA base is statutory. Your state UI rate and state UI taxable wage base come from your state agency's annual notice and differ in every state. Excel: open the file. Google Sheets: File, Import, Upload, Replace spreadsheet.\", \"inputs\": [{\"label\": \"Gross wages this pay period, one employee\", \"key\": \"gross_wages_this_period\", \"value\": 3000, \"format\": \"currency\"}, {\"label\": \"Wages already paid to this employee this year, before this period\", \"key\": \"ytd_wages_before_this_period\", \"value\": 6000, \"format\": \"currency\"}, {\"label\": \"Federal income tax withheld this period (from IRS Publication 15-T)\", \"key\": \"federal_income_tax_withheld\", \"value\": 316, \"format\": \"currency\"}, {\"label\": \"Social Security wage base for your tax year (SSA notice; 2025 figure shown)\", \"key\": \"social_security_wage_base\", \"value\": 176100, \"format\": \"currency\"}, {\"label\": \"FUTA wage base per employee per year (statutory)\", \"key\": \"futa_wage_base\", \"value\": 7000, \"format\": \"currency\"}, {\"label\": \"FUTA net rate after the 5.4% state credit (use 6.0% in a credit-reduction state)\", \"key\": \"futa_net_rate\", \"value\": 0.006, \"format\": \"percent\"}, {\"label\": \"Your state UI taxable wage base (from your state agency notice)\", \"key\": \"state_ui_wage_base\", \"value\": 7000, \"format\": \"currency\"}, {\"label\": \"Your assigned state UI rate for this year (from your state agency notice)\", \"key\": \"state_ui_rate\", \"value\": 0.034, \"format\": \"percent\"}]}, {\"name\": \"1. This Paycheck (Employee)\", \"purpose\": \"One question, answered completely: what comes out of this paycheck. Social Security stops at the wage base, Medicare never stops, and the extra 0.9% starts only after $200,000 of wages in the calendar year — employers withhold that 0.9% at $200,000 regardless of the employee's filing status, and do not match it (IRS Publication 15). Charge 6.2% on wages above the base and you over-withhold: the employee recovers it on their 1040, but your Form 941 no longer ties out and the correction lands on you. Net pay here is before state income tax and any benefit deductions.\", \"calc_rows\": [{\"label\": \"Wages subject to Social Security this period (capped at the wage base)\", \"key\": \"ss_taxable_wages\", \"formula\": \"=MIN({gross_wages_this_period},MAX(0,{social_security_wage_base}-{ytd_wages_before_this_period}))\"}, {\"label\": \"Social Security withheld from the employee at 6.2%\", \"key\": \"ss_withheld_employee\", \"formula\": \"=ROUND({ss_taxable_wages}*0.062,2)\"}, {\"label\": \"Medicare withheld from the employee at 1.45% (no wage cap)\", \"key\": \"medicare_withheld_employee\", \"formula\": \"=ROUND({gross_wages_this_period}*0.0145,2)\"}, {\"label\": \"Additional Medicare withheld at 0.9% on wages over $200,000 year to date\", \"key\": \"additional_medicare_withheld\", \"formula\": \"=ROUND((MAX(0,{ytd_wages_before_this_period}+{gross_wages_this_period}-200000)-MAX(0,{ytd_wages_before_this_period}-200000))*0.009,2)\"}, {\"label\": \"Total withheld from this paycheck, income tax plus FICA\", \"key\": \"employee_taxes_withheld_total\", \"formula\": \"=ROUND({federal_income_tax_withheld}+{ss_withheld_employee}+{medicare_withheld_employee}+{additional_medicare_withheld},2)\"}, {\"label\": \"Net pay before state income tax and benefit deductions\", \"key\": \"net_pay_this_period\", \"formula\": \"=ROUND({gross_wages_this_period}-{employee_taxes_withheld_total},2)\"}]}]}, \"lang\": \"en\", \"locale\": \"en-US\", \"currency\": \"USD\", \"paid\": \"\", \"slug\": \"payroll-tax-spreadsheet\", \"ui\": {\"inputs\": \"Your numbers\", \"results\": \"Your result\", \"recalc\": \"Everything updates as you type.\", \"get\": \"Get the full calculator\", \"email\": \"Email me the full version\", \"placeholder\": \"you@example.com\", \"sent\": \"Sent. Check your inbox.\", \"share\": \"Save result image\", \"copy\": \"Copy link\", \"copied\": \"Link copied\", \"more\": \"Full version: all tabs, your own file, works offline.\", \"free\": \"Free calculator\", \"note\": \"Nothing is uploaded. This runs in your browser.\", \"dl\": \"Download my numbers (CSV)\", \"dl_need\": \"Enter your email above and we will unlock the download.\", \"dl_done\": \"Downloaded.\"}, \"title\": \"Payroll Tax Spreadsheet for US Small Employers — FICA, FUTA and State UI Wage-Base Caps per Paycheck\"}";document.body.appendChild(s);})();
     1(function () {
     2  var s = document.createElement("script");
     3  s.type = "application/json";
     4  s.id = "calcdata";
     5  s.textContent = JSON.stringify({
     6    spec: {
     7      sheets: [
     8        {
     9          name: "Inputs - Your Payroll Numbers",
     10          purpose:
     11            "Type your own eight numbers here; both calc sheets recalculate. A free online paycheck calculator cannot carry year-to-date wages from one run to the next, so it never knows that Social Security has already stopped for this employee — that is the whole reason this file exists. Rates you must confirm yourself are inputs, not guesses: the Social Security wage base is set by the SSA each October and the default shown is the 2025 figure of 176,100, so replace it with the figure for the year you are running. The $7,000 FUTA base is statutory. Your state UI rate and state UI taxable wage base come from your state agency's annual notice and differ in every state. Excel: open the file. Google Sheets: File, Import, Upload, Replace spreadsheet.",
     12          inputs: [
     13            {
     14              label: "Gross wages this pay period, one employee",
     15              key: "gross_wages_this_period",
     16              value: 3000,
     17              format: "currency",
     18            },
     19            {
     20              label: "Wages already paid to this employee this year, before this period",
     21              key: "ytd_wages_before_this_period",
     22              value: 6000,
     23              format: "currency",
     24            },
     25            {
     26              label: "Federal income tax withheld this period (from IRS Publication 15-T)",
     27              key: "federal_income_tax_withheld",
     28              value: 316,
     29              format: "currency",
     30            },
     31            {
     32              label: "Social Security wage base for your tax year (SSA notice; 2025 figure shown)",
     33              key: "social_security_wage_base",
     34              value: 176100,
     35              format: "currency",
     36            },
     37            {
     38              label: "FUTA wage base per employee per year (statutory)",
     39              key: "futa_wage_base",
     40              value: 7000,
     41              format: "currency",
     42            },
     43            {
     44              label:
     45                "FUTA net rate after the 5.4% state credit (use 6.0% in a credit-reduction state)",
     46              key: "futa_net_rate",
     47              value: 0.006,
     48              format: "percent",
     49            },
     50            {
     51              label: "Your state UI taxable wage base (from your state agency notice)",
     52              key: "state_ui_wage_base",
     53              value: 7000,
     54              format: "currency",
     55            },
     56            {
     57              label: "Your assigned state UI rate for this year (from your state agency notice)",
     58              key: "state_ui_rate",
     59              value: 0.034,
     60              format: "percent",
     61            },
     62          ],
     63        },
     64        {
     65          name: "1. This Paycheck (Employee)",
     66          purpose:
     67            "One question, answered completely: what comes out of this paycheck. Social Security stops at the wage base, Medicare never stops, and the extra 0.9% starts only after $200,000 of wages in the calendar year — employers withhold that 0.9% at $200,000 regardless of the employee's filing status, and do not match it (IRS Publication 15). Charge 6.2% on wages above the base and you over-withhold: the employee recovers it on their 1040, but your Form 941 no longer ties out and the correction lands on you. Net pay here is before state income tax and any benefit deductions.",
     68          calc_rows: [
     69            {
     70              label: "Wages subject to Social Security this period (capped at the wage base)",
     71              key: "ss_taxable_wages",
     72              formula:
     73                "=MIN({gross_wages_this_period},MAX(0,{social_security_wage_base}-{ytd_wages_before_this_period}))",
     74            },
     75            {
     76              label: "Social Security withheld from the employee at 6.2%",
     77              key: "ss_withheld_employee",
     78              formula: "=ROUND({ss_taxable_wages}*0.062,2)",
     79            },
     80            {
     81              label: "Medicare withheld from the employee at 1.45% (no wage cap)",
     82              key: "medicare_withheld_employee",
     83              formula: "=ROUND({gross_wages_this_period}*0.0145,2)",
     84            },
     85            {
     86              label: "Additional Medicare withheld at 0.9% on wages over $200,000 year to date",
     87              key: "additional_medicare_withheld",
     88              formula:
     89                "=ROUND((MAX(0,{ytd_wages_before_this_period}+{gross_wages_this_period}-200000)-MAX(0,{ytd_wages_before_this_period}-200000))*0.009,2)",
     90            },
     91            {
     92              label: "Total withheld from this paycheck, income tax plus FICA",
     93              key: "employee_taxes_withheld_total",
     94              formula:
     95                "=ROUND({federal_income_tax_withheld}+{ss_withheld_employee}+{medicare_withheld_employee}+{additional_medicare_withheld},2)",
     96            },
     97            {
     98              label: "Net pay before state income tax and benefit deductions",
     99              key: "net_pay_this_period",
     100              formula: "=ROUND({gross_wages_this_period}-{employee_taxes_withheld_total},2)",
     101            },
     102          ],
     103        },
     104      ],
     105    },
     106    lang: "en",
     107    locale: "en-US",
     108    currency: "USD",
     109    paid: "",
     110    slug: "payroll-tax-spreadsheet",
     111    ui: {
     112      inputs: "Your numbers",
     113      results: "Your result",
     114      recalc: "Everything updates as you type.",
     115      get: "Get the full calculator",
     116      email: "Email me the full version",
     117      placeholder: "you@example.com",
     118      sent: "Sent. Check your inbox.",
     119      share: "Save result image",
     120      copy: "Copy link",
     121      copied: "Link copied",
     122      more: "Full version: all tabs, your own file, works offline.",
     123      free: "Free calculator",
     124      note: "Nothing is uploaded. This runs in your browser.",
     125      dl: "Download my numbers (CSV)",
     126      dl_need: "Enter your email above and we will unlock the download.",
     127      dl_done: "Downloaded.",
     128    },
     129    title:
     130      "Payroll Tax Spreadsheet for US Small Employers — FICA, FUTA and State UI Wage-Base Caps per Paycheck",
     131  });
     132  document.body.appendChild(s);
     133})();
  • readystack-payroll-tax-spreadsheet/tags/1.0.2/assets/tool-2.js

    r3712579 r3713964  
    1 var PRE={"=MIN({gross_wages_this_period},MAX(0,{social_security_wage_base}-{ytd_wages_before_this_period}))":function(V,FN){return (FN.MIN( V["gross_wages_this_period"] ,FN.MAX(0, V["social_security_wage_base"] - V["ytd_wages_before_this_period"] )));},"=ROUND({ss_taxable_wages}*0.062,2)":function(V,FN){return (FN.ROUND( V["ss_taxable_wages"] *0.062,2));},"=ROUND({gross_wages_this_period}*0.0145,2)":function(V,FN){return (FN.ROUND( V["gross_wages_this_period"] *0.0145,2));},"=ROUND((MAX(0,{ytd_wages_before_this_period}+{gross_wages_this_period}-200000)-MAX(0,{ytd_wages_before_this_period}-200000))*0.009,2)":function(V,FN){return (FN.ROUND((FN.MAX(0, V["ytd_wages_before_this_period"] + V["gross_wages_this_period"] -200000)-FN.MAX(0, V["ytd_wages_before_this_period"] -200000))*0.009,2));},"=ROUND({federal_income_tax_withheld}+{ss_withheld_employee}+{medicare_withheld_employee}+{additional_medicare_withheld},2)":function(V,FN){return (FN.ROUND( V["federal_income_tax_withheld"] + V["ss_withheld_employee"] + V["medicare_withheld_employee"] + V["additional_medicare_withheld"] ,2));},"=ROUND({gross_wages_this_period}-{employee_taxes_withheld_total},2)":function(V,FN){return (FN.ROUND( V["gross_wages_this_period"] - V["employee_taxes_withheld_total"] ,2));},"=ROUND({ss_taxable_wages}*0.062+{gross_wages_this_period}*0.0145,2)":function(V,FN){return (FN.ROUND( V["ss_taxable_wages"] *0.062+ V["gross_wages_this_period"] *0.0145,2));},"=MIN({gross_wages_this_period},MAX(0,{futa_wage_base}-{ytd_wages_before_this_period}))":function(V,FN){return (FN.MIN( V["gross_wages_this_period"] ,FN.MAX(0, V["futa_wage_base"] - V["ytd_wages_before_this_period"] )));},"=ROUND({futa_taxable_wages}*{futa_net_rate},2)":function(V,FN){return (FN.ROUND( V["futa_taxable_wages"] * V["futa_net_rate"] ,2));},"=ROUND(MIN({gross_wages_this_period},MAX(0,{state_ui_wage_base}-{ytd_wages_before_this_period}))*{state_ui_rate},2)":function(V,FN){return (FN.ROUND(FN.MIN( V["gross_wages_this_period"] ,FN.MAX(0, V["state_ui_wage_base"] - V["ytd_wages_before_this_period"] ))* V["state_ui_rate"] ,2));},"=ROUND({federal_income_tax_withheld}+{ss_withheld_employee}+{medicare_withheld_employee}+{additional_medicare_withheld}+{employer_ss_and_medicare_match},2)":function(V,FN){return (FN.ROUND( V["federal_income_tax_withheld"] + V["ss_withheld_employee"] + V["medicare_withheld_employee"] + V["additional_medicare_withheld"] + V["employer_ss_and_medicare_match"] ,2));},"=ROUND({gross_wages_this_period}+{employer_ss_and_medicare_match}+{futa_tax_this_period}+{state_ui_tax_this_period},2)":function(V,FN){return (FN.ROUND( V["gross_wages_this_period"] + V["employer_ss_and_medicare_match"] + V["futa_tax_this_period"] + V["state_ui_tax_this_period"] ,2));}};
     1var PRE = {
     2  "=MIN({gross_wages_this_period},MAX(0,{social_security_wage_base}-{ytd_wages_before_this_period}))":
     3    function (V, FN) {
     4      return FN.MIN(
     5        V["gross_wages_this_period"],
     6        FN.MAX(0, V["social_security_wage_base"] - V["ytd_wages_before_this_period"]),
     7      );
     8    },
     9  "=ROUND({ss_taxable_wages}*0.062,2)": function (V, FN) {
     10    return FN.ROUND(V["ss_taxable_wages"] * 0.062, 2);
     11  },
     12  "=ROUND({gross_wages_this_period}*0.0145,2)": function (V, FN) {
     13    return FN.ROUND(V["gross_wages_this_period"] * 0.0145, 2);
     14  },
     15  "=ROUND((MAX(0,{ytd_wages_before_this_period}+{gross_wages_this_period}-200000)-MAX(0,{ytd_wages_before_this_period}-200000))*0.009,2)":
     16    function (V, FN) {
     17      return FN.ROUND(
     18        (FN.MAX(0, V["ytd_wages_before_this_period"] + V["gross_wages_this_period"] - 200000) -
     19          FN.MAX(0, V["ytd_wages_before_this_period"] - 200000)) *
     20          0.009,
     21        2,
     22      );
     23    },
     24  "=ROUND({federal_income_tax_withheld}+{ss_withheld_employee}+{medicare_withheld_employee}+{additional_medicare_withheld},2)":
     25    function (V, FN) {
     26      return FN.ROUND(
     27        V["federal_income_tax_withheld"] +
     28          V["ss_withheld_employee"] +
     29          V["medicare_withheld_employee"] +
     30          V["additional_medicare_withheld"],
     31        2,
     32      );
     33    },
     34  "=ROUND({gross_wages_this_period}-{employee_taxes_withheld_total},2)": function (V, FN) {
     35    return FN.ROUND(V["gross_wages_this_period"] - V["employee_taxes_withheld_total"], 2);
     36  },
     37  "=ROUND({ss_taxable_wages}*0.062+{gross_wages_this_period}*0.0145,2)": function (V, FN) {
     38    return FN.ROUND(V["ss_taxable_wages"] * 0.062 + V["gross_wages_this_period"] * 0.0145, 2);
     39  },
     40  "=MIN({gross_wages_this_period},MAX(0,{futa_wage_base}-{ytd_wages_before_this_period}))":
     41    function (V, FN) {
     42      return FN.MIN(
     43        V["gross_wages_this_period"],
     44        FN.MAX(0, V["futa_wage_base"] - V["ytd_wages_before_this_period"]),
     45      );
     46    },
     47  "=ROUND({futa_taxable_wages}*{futa_net_rate},2)": function (V, FN) {
     48    return FN.ROUND(V["futa_taxable_wages"] * V["futa_net_rate"], 2);
     49  },
     50  "=ROUND(MIN({gross_wages_this_period},MAX(0,{state_ui_wage_base}-{ytd_wages_before_this_period}))*{state_ui_rate},2)":
     51    function (V, FN) {
     52      return FN.ROUND(
     53        FN.MIN(
     54          V["gross_wages_this_period"],
     55          FN.MAX(0, V["state_ui_wage_base"] - V["ytd_wages_before_this_period"]),
     56        ) * V["state_ui_rate"],
     57        2,
     58      );
     59    },
     60  "=ROUND({federal_income_tax_withheld}+{ss_withheld_employee}+{medicare_withheld_employee}+{additional_medicare_withheld}+{employer_ss_and_medicare_match},2)":
     61    function (V, FN) {
     62      return FN.ROUND(
     63        V["federal_income_tax_withheld"] +
     64          V["ss_withheld_employee"] +
     65          V["medicare_withheld_employee"] +
     66          V["additional_medicare_withheld"] +
     67          V["employer_ss_and_medicare_match"],
     68        2,
     69      );
     70    },
     71  "=ROUND({gross_wages_this_period}+{employer_ss_and_medicare_match}+{futa_tax_this_period}+{state_ui_tax_this_period},2)":
     72    function (V, FN) {
     73      return FN.ROUND(
     74        V["gross_wages_this_period"] +
     75          V["employer_ss_and_medicare_match"] +
     76          V["futa_tax_this_period"] +
     77          V["state_ui_tax_this_period"],
     78        2,
     79      );
     80    },
     81};
  • readystack-payroll-tax-spreadsheet/tags/1.0.2/assets/tool-3.js

    r3712579 r3713964  
    11var CALC = (function () {
    2   function n(x){ x = Number(x); return isFinite(x) ? x : 0; }
    3   function ROUND(x, d){ d = n(d); var f = Math.pow(10, d); var v = n(x) * f;
    4     var r = (v >= 0) ? Math.floor(v + 0.5) : Math.ceil(v - 0.5); return r / f; }
    5   function POWER(a, b){ return Math.pow(n(a), n(b)); }
    6   function ABS(a){ return Math.abs(n(a)); }
    7   function MIN(){ var a=[].slice.call(arguments).map(n); return a.length?Math.min.apply(null,a):0; }
    8   function MAX(){ var a=[].slice.call(arguments).map(n); return a.length?Math.max.apply(null,a):0; }
    9   function SUM(){ var s=0; for(var i=0;i<arguments.length;i++) s+=n(arguments[i]); return s; }
    10   function IF(c, a, b){ return c ? a : b; }
    11   function FLOOR(x, sig){ sig = (arguments.length > 1) ? n(sig) : 1;
    12     if (sig === 0) return 0; return Math.floor(n(x) / sig) * sig; }
    13   function CEILING(x, sig){ sig = (arguments.length > 1) ? n(sig) : 1;
    14     if (sig === 0) return 0; return Math.ceil(n(x) / sig) * sig; }
    15   function PMT(rate, nper, pv, fv, type){
    16     rate = n(rate); nper = n(nper); pv = n(pv); fv = n(fv); type = n(type);
     2  function n(x) {
     3    x = Number(x);
     4    return isFinite(x) ? x : 0;
     5  }
     6  function ROUND(x, d) {
     7    d = n(d);
     8    var f = Math.pow(10, d);
     9    var v = n(x) * f;
     10    var r = v >= 0 ? Math.floor(v + 0.5) : Math.ceil(v - 0.5);
     11    return r / f;
     12  }
     13  function POWER(a, b) {
     14    return Math.pow(n(a), n(b));
     15  }
     16  function ABS(a) {
     17    return Math.abs(n(a));
     18  }
     19  function MIN() {
     20    var a = [].slice.call(arguments).map(n);
     21    return a.length ? Math.min.apply(null, a) : 0;
     22  }
     23  function MAX() {
     24    var a = [].slice.call(arguments).map(n);
     25    return a.length ? Math.max.apply(null, a) : 0;
     26  }
     27  function SUM() {
     28    var s = 0;
     29    for (var i = 0; i < arguments.length; i++) s += n(arguments[i]);
     30    return s;
     31  }
     32  function IF(c, a, b) {
     33    return c ? a : b;
     34  }
     35  function FLOOR(x, sig) {
     36    sig = arguments.length > 1 ? n(sig) : 1;
     37    if (sig === 0) return 0;
     38    return Math.floor(n(x) / sig) * sig;
     39  }
     40  function CEILING(x, sig) {
     41    sig = arguments.length > 1 ? n(sig) : 1;
     42    if (sig === 0) return 0;
     43    return Math.ceil(n(x) / sig) * sig;
     44  }
     45  function PMT(rate, nper, pv, fv, type) {
     46    rate = n(rate);
     47    nper = n(nper);
     48    pv = n(pv);
     49    fv = n(fv);
     50    type = n(type);
    1751    if (nper === 0) return 0;
    1852    if (rate === 0) return -(pv + fv) / nper;
    1953    var p = Math.pow(1 + rate, nper);
    20     return -(pv * p + fv) * rate / ((p - 1) * (1 + rate * type));
     54    return (-(pv * p + fv) * rate) / ((p - 1) * (1 + rate * type));
    2155  }
    22   var FN = { ROUND:ROUND, POWER:POWER, ABS:ABS, MIN:MIN, MAX:MAX, SUM:SUM, IF:IF, PMT:PMT,
    23              FLOOR:FLOOR, CEILING:CEILING };
     56  var FN = {
     57    ROUND: ROUND,
     58    POWER: POWER,
     59    ABS: ABS,
     60    MIN: MIN,
     61    MAX: MAX,
     62    SUM: SUM,
     63    IF: IF,
     64    PMT: PMT,
     65    FLOOR: FLOOR,
     66    CEILING: CEILING,
     67  };
    2468  var FNAMES = Object.keys(FN);
    2569
    … …  
    3074  //     하는 대신 ★주석을 바꾼다 (⛔검사기를 무디게 만드는 것이 s67 의 병이었다).
    3175  //   ⇒ ★빌드 때 toExpr 로 식을 뽑아 HTML 에 ★구워 넣고, 브라우저는 ★조회만 한다.
    32   function toExpr(formula){
    33     var s = String(formula || '').trim();
    34     if (s.charAt(0) === '=') s = s.slice(1);
     76  function toExpr(formula) {
     77    var s = String(formula || "").trim();
     78    if (s.charAt(0) === "=") s = s.slice(1);
    3579    if (!s) return null;
    36     s = s.replace(/\{([A-Za-z0-9_]+)\}/g, function(_, k){ return ' V["' + k + '"] '; });
    37     s = s.replace(/<>/g, '!==').replace(/>=/g, '@GE@').replace(/<=/g, '@LE@');
    38     s = s.replace(/=/g, '===').replace(/@GE@/g, '>=').replace(/@LE@/g, '<=');
    39     s = s.replace(/\^/g, '**');
    40     FNAMES.forEach(function(f){ s = s.replace(new RegExp('\\b' + f + '\\s*\\(', 'gi'), 'FN.' + f + '('); });
    41     var probe = s.replace(/V\["[A-Za-z0-9_]+"\]/g, '0').replace(/FN\.[A-Z]+\(/g, '(');
     80    s = s.replace(/\{([A-Za-z0-9_]+)\}/g, function (_, k) {
     81      return ' V["' + k + '"] ';
     82    });
     83    s = s.replace(/<>/g, "!==").replace(/>=/g, "@GE@").replace(/<=/g, "@LE@");
     84    s = s.replace(/=/g, "===").replace(/@GE@/g, ">=").replace(/@LE@/g, "<=");
     85    s = s.replace(/\^/g, "**");
     86    FNAMES.forEach(function (f) {
     87      s = s.replace(new RegExp("\\b" + f + "\\s*\\(", "gi"), "FN." + f + "(");
     88    });
     89    var probe = s.replace(/V\["[A-Za-z0-9_]+"\]/g, "0").replace(/FN\.[A-Z]+\(/g, "(");
    4290    if (!/^[0-9+\-*/%().,\s<>=!&|*]*$/.test(probe)) {
    43       throw new Error('unsupported token in formula: ' + formula);
     91      throw new Error("unsupported token in formula: " + formula);
    4492    }
    4593    return s;
    4694  }
    47   function compile(formula){
     95  function compile(formula) {
    4896    // ★브라우저: 빌드 때 구워넣은 PRE 를 ★조회만 한다 (eval 0).
    49     var raw = String(formula || '').trim();
     97    var raw = String(formula || "").trim();
    5098    if (!raw) return null;
    51     if (typeof PRE !== 'undefined' && PRE && PRE[raw]) return PRE[raw];
    52     throw new Error('not precompiled: ' + raw);
     99    if (typeof PRE !== "undefined" && PRE && PRE[raw]) return PRE[raw];
     100    throw new Error("not precompiled: " + raw);
    53101  }
    54102
    55103  // 명세 -> {values, order, rows}. ⛔없는 key 는 0 (xlsx_builder 와 같은 규칙).
    56   function run(spec, override){
     104  function run(spec, override) {
    57105    var sheets = (spec.sheets_content && spec.sheets_content.sheets) || spec.sheets || [];
    58106    var V = Object.create(null);
    59     var order = [], rows = [];
    60     var handler = { get: function(t, k){ return (k in t) ? t[k] : 0; } };
    61     var PV = (typeof Proxy !== 'undefined') ? new Proxy(V, handler) : V;
    62     sheets.forEach(function(sh){
     107    var order = [],
     108      rows = [];
     109    var handler = {
     110      get: function (t, k) {
     111        return k in t ? t[k] : 0;
     112      },
     113    };
     114    var PV = typeof Proxy !== "undefined" ? new Proxy(V, handler) : V;
     115    sheets.forEach(function (sh) {
    63116      if (sh && sh.inputs && sh.inputs.length) {
    64         sh.inputs.forEach(function(it){
     117        sh.inputs.forEach(function (it) {
    65118          if (!it || !it.key) return;
    66           var v = (override && (it.key in override)) ? override[it.key] : it.value;
     119          var v = override && it.key in override ? override[it.key] : it.value;
    67120          V[it.key] = n(v);
    68121        });
    69122      }
    70123    });
    71     sheets.forEach(function(sh){
     124    sheets.forEach(function (sh) {
    72125      if (!sh || !sh.calc_rows || !sh.calc_rows.length) return;
    73       sh.calc_rows.forEach(function(cr){
     126      sh.calc_rows.forEach(function (cr) {
    74127        if (!cr) return;
    75         var val = 0, err = null;
    76         try { var f = compile(cr.formula); val = f ? n(f(PV, FN)) : 0; }
    77         catch (e) { err = String(e && e.message || e); }
     128        var val = 0,
     129          err = null;
     130        try {
     131          var f = compile(cr.formula);
     132          val = f ? n(f(PV, FN)) : 0;
     133        } catch (e) {
     134          err = String((e && e.message) || e);
     135        }
    78136        if (cr.key) V[cr.key] = val;
    79         var rec = { sheet: sh.name, label: cr.label, key: cr.key || null,
    80                     format: cr.format || 'number', value: val, error: err };
    81         order.push(rec); rows.push(rec);
     137        var rec = {
     138          sheet: sh.name,
     139          label: cr.label,
     140          key: cr.key || null,
     141          format: cr.format || "number",
     142          value: val,
     143          error: err,
     144        };
     145        order.push(rec);
     146        rows.push(rec);
    82147      });
    83148    });
    … …  
    86151  return { run: run, compile: compile, toExpr: toExpr, FN: FN };
    87152})();
    88 if (typeof module !== 'undefined' && module.exports) module.exports = CALC;
     153if (typeof module !== "undefined" && module.exports) module.exports = CALC;
  • readystack-payroll-tax-spreadsheet/tags/1.0.2/assets/tool-4.js

    r3712579 r3713964  
    1 (function(){
    2   var D=JSON.parse(document.getElementById('calcdata').textContent);
    3   var UI=D.ui, LOC=D.locale, CUR=D.currency;
    4   function fmt(v,f){
    5     if(!isFinite(v)) v=0;
    6     if(f==='currency') return new Intl.NumberFormat(LOC,{style:'currency',currency:CUR,maximumFractionDigits:2}).format(v);
    7     if(f==='percent') return new Intl.NumberFormat(LOC,{style:'percent',minimumFractionDigits:2,maximumFractionDigits:2}).format(v);
    8     if(f==='integer'||f==='int') return new Intl.NumberFormat(LOC,{maximumFractionDigits:0}).format(v);
    9     return new Intl.NumberFormat(LOC,{maximumFractionDigits:4}).format(v);
     1(function () {
     2  var D = JSON.parse(document.getElementById("calcdata").textContent);
     3  var UI = D.ui,
     4    LOC = D.locale,
     5    CUR = D.currency;
     6  function fmt(v, f) {
     7    if (!isFinite(v)) v = 0;
     8    if (f === "currency")
     9      return new Intl.NumberFormat(LOC, {
     10        style: "currency",
     11        currency: CUR,
     12        maximumFractionDigits: 2,
     13      }).format(v);
     14    if (f === "percent")
     15      return new Intl.NumberFormat(LOC, {
     16        style: "percent",
     17        minimumFractionDigits: 2,
     18        maximumFractionDigits: 2,
     19      }).format(v);
     20    if (f === "integer" || f === "int")
     21      return new Intl.NumberFormat(LOC, { maximumFractionDigits: 0 }).format(v);
     22    return new Intl.NumberFormat(LOC, { maximumFractionDigits: 4 }).format(v);
    1023  }
    11   var ins=[].slice.call(document.querySelectorAll('input[data-k]'));
    12   var outs=[].slice.call(document.querySelectorAll('td.v'));
    13   function recalc(){
    14     var ov={}; ins.forEach(function(el){
    15       var n=Number(el.value)||0; ov[el.dataset.k]= el.dataset.pct ? n/100 : n; });
    16     var r=CALC.run(D.spec,ov);
    17     var byKey={}, i=0;
    18     r.rows.forEach(function(x){ if(x.key) byKey[x.key]=x; });
    19     outs.forEach(function(td){
     24  var ins = [].slice.call(document.querySelectorAll("input[data-k]"));
     25  var outs = [].slice.call(document.querySelectorAll("td.v"));
     26  function recalc() {
     27    var ov = {};
     28    ins.forEach(function (el) {
     29      var n = Number(el.value) || 0;
     30      ov[el.dataset.k] = el.dataset.pct ? n / 100 : n;
     31    });
     32    var r = CALC.run(D.spec, ov);
     33    var byKey = {},
     34      i = 0;
     35    r.rows.forEach(function (x) {
     36      if (x.key) byKey[x.key] = x;
     37    });
     38    outs.forEach(function (td) {
    2039      var rec = td.dataset.k ? byKey[td.dataset.k] : r.rows[i];
    2140      i++;
    22       if(!rec){ td.textContent='-'; return; }
    23       td.textContent = rec.error ? '-' : fmt(rec.value, td.dataset.f);
     41      if (!rec) {
     42        td.textContent = "-";
     43        return;
     44      }
     45      td.textContent = rec.error ? "-" : fmt(rec.value, td.dataset.f);
    2446    });
    25     window.__last=r;
     47    window.__last = r;
    2648  }
    27   ins.forEach(function(el){ el.addEventListener('input',recalc); });
     49  ins.forEach(function (el) {
     50    el.addEventListener("input", recalc);
     51  });
    2852  recalc();
    2953
    … …  
    3458  //   So: the ANSWER stays free, and "the NEXT step" (taking your own numbers away as a file)
    3559  //   is what asks for the email (Zeigarnik). Whoever already gave it is never asked again.
    36   document.getElementById('dl').addEventListener('click',function(){
    37     var rr=[['label','value']];
    38     ins.forEach(function(el){
    39       var lb=(el.closest('label')||{}).textContent||el.dataset.k;
     60  document.getElementById("dl").addEventListener("click", function () {
     61    var rr = [["label", "value"]];
     62    ins.forEach(function (el) {
     63      var lb = (el.closest("label") || {}).textContent || el.dataset.k;
    4064      rr.push([String(lb).trim(), el.value]);
    4165    });
    42     ((window.__last&&window.__last.rows)||[]).forEach(function(r){
    43       rr.push([String(r.label), r.error?'':String(r.value)]);
     66    ((window.__last && window.__last.rows) || []).forEach(function (r) {
     67      rr.push([String(r.label), r.error ? "" : String(r.value)]);
    4468    });
    45     var csv=rr.map(function(r){
    46       return r.map(function(c){ return '"'+String(c).replace(/"/g,'""')+'"'; }).join(',');
    47     }).join('\n');
    48     var bl=new Blob(['\ufeff'+csv],{type:'text/csv;charset=utf-8'});
    49     var ae=document.createElement('a');
    50     ae.href=URL.createObjectURL(bl); ae.download=(D.slug||'result')+'.csv'; ae.click();
    51     var bt=this, tx=bt.textContent;
    52     bt.textContent='Downloaded.'; setTimeout(function(){ bt.textContent=tx; },1800);
     69    var csv = rr
     70      .map(function (r) {
     71        return r
     72          .map(function (c) {
     73            return '"' + String(c).replace(/"/g, '""') + '"';
     74          })
     75          .join(",");
     76      })
     77      .join("\n");
     78    var bl = new Blob(["\ufeff" + csv], { type: "text/csv;charset=utf-8" });
     79    var ae = document.createElement("a");
     80    ae.href = URL.createObjectURL(bl);
     81    ae.download = (D.slug || "result") + ".csv";
     82    ae.click();
     83    var bt = this,
     84      tx = bt.textContent;
     85    bt.textContent = "Downloaded.";
     86    setTimeout(function () {
     87      bt.textContent = tx;
     88    }, 1800);
    5389  });
    5490
    55   document.getElementById('sh').addEventListener('click',function(){
    56     var c=document.getElementById('cv'), x=c.getContext('2d');
    57     x.fillStyle='#0e1116'; x.fillRect(0,0,1200,630);
    58     x.fillStyle='#fff'; x.font='bold 52px system-ui,sans-serif';
    59     x.fillText(D.title.slice(0,42),64,120);
    60     var rws=(window.__last&&window.__last.rows)||[]; var y=230;
    61     rws.slice(0,5).forEach(function(r){
    62       x.fillStyle='#9aa4b2'; x.font='28px system-ui,sans-serif';
    63       x.fillText(String(r.label).slice(0,44),64,y);
    64       x.fillStyle='#7aa2f7'; x.font='bold 34px system-ui,sans-serif';
    65       x.textAlign='right'; x.fillText(fmt(r.value,r.format),1136,y); x.textAlign='left';
    66       y+=72;
     91  document.getElementById("sh").addEventListener("click", function () {
     92    var c = document.getElementById("cv"),
     93      x = c.getContext("2d");
     94    x.fillStyle = "#0e1116";
     95    x.fillRect(0, 0, 1200, 630);
     96    x.fillStyle = "#fff";
     97    x.font = "bold 52px system-ui,sans-serif";
     98    x.fillText(D.title.slice(0, 42), 64, 120);
     99    var rws = (window.__last && window.__last.rows) || [];
     100    var y = 230;
     101    rws.slice(0, 5).forEach(function (r) {
     102      x.fillStyle = "#9aa4b2";
     103      x.font = "28px system-ui,sans-serif";
     104      x.fillText(String(r.label).slice(0, 44), 64, y);
     105      x.fillStyle = "#7aa2f7";
     106      x.font = "bold 34px system-ui,sans-serif";
     107      x.textAlign = "right";
     108      x.fillText(fmt(r.value, r.format), 1136, y);
     109      x.textAlign = "left";
     110      y += 72;
    67111    });
    68     x.fillStyle='#6b7280'; x.font='24px system-ui,sans-serif';
    69     x.fillText(location.host+(D.slug?'/tools/'+D.slug:''),64,590);
    70     var a=document.createElement('a'); a.download=(D.slug||'result')+'.png';
    71     a.href=c.toDataURL('image/png'); a.click();
     112    x.fillStyle = "#6b7280";
     113    x.font = "24px system-ui,sans-serif";
     114    x.fillText(location.host + (D.slug ? "/tools/" + D.slug : ""), 64, 590);
     115    var a = document.createElement("a");
     116    a.download = (D.slug || "result") + ".png";
     117    a.href = c.toDataURL("image/png");
     118    a.click();
    72119  });
    73120  // ★★2026-08-22 s99 (B2) — ★링크 복사. ⛔이게 없으면 ★도메인이 같이 퍼지지 않는다.
    74121  //   ★결과 이미지는 ★도메인을 찍어서 나가고, ★링크는 ★그 자리로 되돌아오게 한다.
    75   document.getElementById('cp').addEventListener('click',function(){
    76     var b=this, t=b.textContent;
    77     var u=location.href.split('#')[0];
    78     function done(){ b.textContent='Link copied'; setTimeout(function(){ b.textContent=t; },1800); }
    79     if(navigator.clipboard&&navigator.clipboard.writeText){
    80       navigator.clipboard.writeText(u).then(done,function(){ prompt('',u); });
    81     } else { prompt('',u); }
     122  document.getElementById("cp").addEventListener("click", function () {
     123    var b = this,
     124      t = b.textContent;
     125    var u = location.href.split("#")[0];
     126    function done() {
     127      b.textContent = "Link copied";
     128      setTimeout(function () {
     129        b.textContent = t;
     130      }, 1800);
     131    }
     132    if (navigator.clipboard && navigator.clipboard.writeText) {
     133      navigator.clipboard.writeText(u).then(done, function () {
     134        prompt("", u);
     135      });
     136    } else {
     137      prompt("", u);
     138    }
    82139  });
    83140})();
  • readystack-payroll-tax-spreadsheet/tags/1.0.2/assets/tool.css

    r3712579 r3713964  
    1 .readystack-tool{--bg:#fff;--fg:#111;--mut:#666;--line:#e5e5e5;--acc:#1a56db;--ok:#0f7b3f}
    2 @media(prefers-color-scheme:dark){.readystack-tool{--bg:#111;--fg:#eee;--mut:#999;--line:#2a2a2a;--acc:#7aa2f7;--ok:#5fd08a}}
    3 .readystack-tool,.readystack-tool *{box-sizing:border-box}
    4 .readystack-tool{margin:0;background:var(--bg);color:var(--fg);font:16px/1.55 system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",sans-serif}
    5 .readystack-tool .wrap{max-width:820px;margin:0 auto;padding:24px 18px 64px}
    6 .readystack-tool h2{font-size:1.6rem;line-height:1.25;margin:.2em 0 .1em}
    7 .readystack-tool .sub{color:var(--mut);margin:0 0 1.4em}
    8 .readystack-tool .card{border:1px solid var(--line);border-radius:12px;padding:18px;margin:0 0 18px}
    9 .readystack-tool h2{font-size:.85rem;letter-spacing:.08em;text-transform:uppercase;color:var(--mut);margin:0 0 14px}
    10 .readystack-tool label{display:flex;justify-content:space-between;align-items:center;gap:14px;padding:7px 0;border-bottom:1px solid var(--line)}
    11 .readystack-tool label:last-child{border-bottom:0}
    12 .readystack-tool label span{flex:1;font-size:.95rem}
    13 .readystack-tool input[type=number]{width:9.5em;padding:8px 10px;border:1px solid var(--line);border-radius:8px;background:transparent;color:var(--fg);font:inherit;text-align:right}
    14 .readystack-tool .fld{display:inline-flex;align-items:center;gap:6px}
    15 .readystack-tool .fld i{font-style:normal;color:var(--mut);width:1em}
    16 .readystack-tool table{width:100%;border-collapse:collapse}
    17 .readystack-tool td{padding:8px 0;border-bottom:1px solid var(--line);vertical-align:top}
    18 .readystack-tool tr:last-child td{border-bottom:0}
    19 .readystack-tool td.l{font-size:.95rem}
    20 .readystack-tool td.v{text-align:right;font-variant-numeric:tabular-nums;font-weight:600;white-space:nowrap}
    21 .readystack-tool .hint{color:var(--mut);font-size:.85rem;margin:10px 0 0}
    22 .readystack-tool .cta{border:1px solid var(--acc);border-radius:12px;padding:18px;margin:22px 0 0}
    23 .readystack-tool .cta b{display:block;margin-bottom:6px}
    24 .readystack-tool .row{display:flex;gap:8px;flex-wrap:wrap;margin-top:12px}
    25 .readystack-tool input[type=email]{flex:1;min-width:200px;padding:10px 12px;border:1px solid var(--line);border-radius:8px;background:transparent;color:var(--fg);font:inherit}
    26 .readystack-tool button{padding:10px 16px;border:0;border-radius:8px;background:var(--acc);color:#fff;font:inherit;font-weight:600;cursor:pointer}
    27 .readystack-tool button.ghost{background:transparent;color:var(--acc);border:1px solid var(--acc)}
    28 .readystack-tool a.buy{display:inline-block;margin-top:10px;color:var(--acc);font-weight:600}
    29 .readystack-tool .ok{color:var(--ok);font-size:.9rem;margin-top:8px}
    30 .readystack-tool canvas{display:none}
     1.readystack-tool {
     2  --bg: #fff;
     3  --fg: #111;
     4  --mut: #666;
     5  --line: #e5e5e5;
     6  --acc: #1a56db;
     7  --ok: #0f7b3f;
     8}
     9@media (prefers-color-scheme: dark) {
     10  .readystack-tool {
     11    --bg: #111;
     12    --fg: #eee;
     13    --mut: #999;
     14    --line: #2a2a2a;
     15    --acc: #7aa2f7;
     16    --ok: #5fd08a;
     17  }
     18}
     19.readystack-tool,
     20.readystack-tool * {
     21  box-sizing: border-box;
     22}
     23.readystack-tool {
     24  margin: 0;
     25  background: var(--bg);
     26  color: var(--fg);
     27  font:
     28    16px/1.55 system-ui,
     29    -apple-system,
     30    "Segoe UI",
     31    Roboto,
     32    "Helvetica Neue",
     33    sans-serif;
     34}
     35.readystack-tool .wrap {
     36  max-width: 820px;
     37  margin: 0 auto;
     38  padding: 24px 18px 64px;
     39}
     40.readystack-tool h2 {
     41  font-size: 1.6rem;
     42  line-height: 1.25;
     43  margin: 0.2em 0 0.1em;
     44}
     45.readystack-tool .sub {
     46  color: var(--mut);
     47  margin: 0 0 1.4em;
     48}
     49.readystack-tool .card {
     50  border: 1px solid var(--line);
     51  border-radius: 12px;
     52  padding: 18px;
     53  margin: 0 0 18px;
     54}
     55.readystack-tool h2 {
     56  font-size: 0.85rem;
     57  letter-spacing: 0.08em;
     58  text-transform: uppercase;
     59  color: var(--mut);
     60  margin: 0 0 14px;
     61}
     62.readystack-tool label {
     63  display: flex;
     64  justify-content: space-between;
     65  align-items: center;
     66  gap: 14px;
     67  padding: 7px 0;
     68  border-bottom: 1px solid var(--line);
     69}
     70.readystack-tool label:last-child {
     71  border-bottom: 0;
     72}
     73.readystack-tool label span {
     74  flex: 1;
     75  font-size: 0.95rem;
     76}
     77.readystack-tool input[type="number"] {
     78  width: 9.5em;
     79  padding: 8px 10px;
     80  border: 1px solid var(--line);
     81  border-radius: 8px;
     82  background: transparent;
     83  color: var(--fg);
     84  font: inherit;
     85  text-align: right;
     86}
     87.readystack-tool .fld {
     88  display: inline-flex;
     89  align-items: center;
     90  gap: 6px;
     91}
     92.readystack-tool .fld i {
     93  font-style: normal;
     94  color: var(--mut);
     95  width: 1em;
     96}
     97.readystack-tool table {
     98  width: 100%;
     99  border-collapse: collapse;
     100}
     101.readystack-tool td {
     102  padding: 8px 0;
     103  border-bottom: 1px solid var(--line);
     104  vertical-align: top;
     105}
     106.readystack-tool tr:last-child td {
     107  border-bottom: 0;
     108}
     109.readystack-tool td.l {
     110  font-size: 0.95rem;
     111}
     112.readystack-tool td.v {
     113  text-align: right;
     114  font-variant-numeric: tabular-nums;
     115  font-weight: 600;
     116  white-space: nowrap;
     117}
     118.readystack-tool .hint {
     119  color: var(--mut);
     120  font-size: 0.85rem;
     121  margin: 10px 0 0;
     122}
     123.readystack-tool .cta {
     124  border: 1px solid var(--acc);
     125  border-radius: 12px;
     126  padding: 18px;
     127  margin: 22px 0 0;
     128}
     129.readystack-tool .cta b {
     130  display: block;
     131  margin-bottom: 6px;
     132}
     133.readystack-tool .row {
     134  display: flex;
     135  gap: 8px;
     136  flex-wrap: wrap;
     137  margin-top: 12px;
     138}
     139.readystack-tool input[type="email"] {
     140  flex: 1;
     141  min-width: 200px;
     142  padding: 10px 12px;
     143  border: 1px solid var(--line);
     144  border-radius: 8px;
     145  background: transparent;
     146  color: var(--fg);
     147  font: inherit;
     148}
     149.readystack-tool button {
     150  padding: 10px 16px;
     151  border: 0;
     152  border-radius: 8px;
     153  background: var(--acc);
     154  color: #fff;
     155  font: inherit;
     156  font-weight: 600;
     157  cursor: pointer;
     158}
     159.readystack-tool button.ghost {
     160  background: transparent;
     161  color: var(--acc);
     162  border: 1px solid var(--acc);
     163}
     164.readystack-tool a.buy {
     165  display: inline-block;
     166  margin-top: 10px;
     167  color: var(--acc);
     168  font-weight: 600;
     169}
     170.readystack-tool .ok {
     171  color: var(--ok);
     172  font-size: 0.9rem;
     173  margin-top: 8px;
     174}
     175.readystack-tool canvas {
     176  display: none;
     177}
  • readystack-payroll-tax-spreadsheet/tags/1.0.2/readme.txt

    r3712579 r3713964  
    55Tested up to: 7.1
    66Requires PHP: 7.4
    7 Stable tag: 1.0.1
     7Stable tag: 1.0.2
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    2929The tool runs in the browser of whoever opens the page. The plugin makes no external requests, sets no cookies, adds no tracking and stores nothing in your database. It has no settings screen.
    3030
     31= Source code =
     32
     33The JavaScript and CSS files in the plugin's assets folder are the complete, human-readable source. There is no build step: nothing is compiled, bundled or minified, so what you read is what runs.
     34
    3135== Installation ==
    3236
    … …  
    4751== Changelog ==
    4852
     53= 1.0.2 =
     54* JavaScript and CSS ship as formatted, human-readable source; the readme says where the source is.
     55
    4956= 1.0.1 =
    5057* The tool output is escaped, its styles are scoped to the tool and its scripts are enqueued; the readme explains how to use it.
  • readystack-payroll-tax-spreadsheet/tags/1.0.2/readystack-payroll-tax-spreadsheet.php

    r3712579 r3713964  
    33 * Plugin Name:       ReadyStack Payroll Tax Spreadsheet
    44 * Description:       Deposit short and the IRS adds 2% to 15% of the shortfall under IRC § 6656.
    5  * Version:           1.0.1
     5 * Version:           1.0.2
    66 * Requires at least: 6.0
    77 * Requires PHP:      7.4
    … …  
    2323 */
    2424function readystack_payroll_tax_spreadsheet_register_assets() {
    25         wp_register_style( 'readystack-payroll-tax-spreadsheet', plugins_url( 'assets/tool.css', __FILE__ ), array(), '1.0.1' );
    26         wp_register_script( 'readystack-payroll-tax-spreadsheet-1', plugins_url( 'assets/tool-1.js', __FILE__ ), array(), '1.0.1', true );
    27         wp_register_script( 'readystack-payroll-tax-spreadsheet-2', plugins_url( 'assets/tool-2.js', __FILE__ ), array( 'readystack-payroll-tax-spreadsheet-1' ), '1.0.1', true );
    28         wp_register_script( 'readystack-payroll-tax-spreadsheet-3', plugins_url( 'assets/tool-3.js', __FILE__ ), array( 'readystack-payroll-tax-spreadsheet-2' ), '1.0.1', true );
    29         wp_register_script( 'readystack-payroll-tax-spreadsheet-4', plugins_url( 'assets/tool-4.js', __FILE__ ), array( 'readystack-payroll-tax-spreadsheet-3' ), '1.0.1', true );
     25        wp_register_style( 'readystack-payroll-tax-spreadsheet', plugins_url( 'assets/tool.css', __FILE__ ), array(), '1.0.2' );
     26        wp_register_script( 'readystack-payroll-tax-spreadsheet-1', plugins_url( 'assets/tool-1.js', __FILE__ ), array(), '1.0.2', true );
     27        wp_register_script( 'readystack-payroll-tax-spreadsheet-2', plugins_url( 'assets/tool-2.js', __FILE__ ), array( 'readystack-payroll-tax-spreadsheet-1' ), '1.0.2', true );
     28        wp_register_script( 'readystack-payroll-tax-spreadsheet-3', plugins_url( 'assets/tool-3.js', __FILE__ ), array( 'readystack-payroll-tax-spreadsheet-2' ), '1.0.2', true );
     29        wp_register_script( 'readystack-payroll-tax-spreadsheet-4', plugins_url( 'assets/tool-4.js', __FILE__ ), array( 'readystack-payroll-tax-spreadsheet-3' ), '1.0.2', true );
    3030}
    3131add_action( 'wp_enqueue_scripts', 'readystack_payroll_tax_spreadsheet_register_assets' );
  • readystack-payroll-tax-spreadsheet/trunk/assets/tool-1.js

    r3712579 r3713964  
    1 (function(){var s=document.createElement("script");s.type="application/json";s.id="calcdata";s.textContent="{\"spec\": {\"sheets\": [{\"name\": \"Inputs - Your Payroll Numbers\", \"purpose\": \"Type your own eight numbers here; both calc sheets recalculate. A free online paycheck calculator cannot carry year-to-date wages from one run to the next, so it never knows that Social Security has already stopped for this employee — that is the whole reason this file exists. Rates you must confirm yourself are inputs, not guesses: the Social Security wage base is set by the SSA each October and the default shown is the 2025 figure of 176,100, so replace it with the figure for the year you are running. The $7,000 FUTA base is statutory. Your state UI rate and state UI taxable wage base come from your state agency's annual notice and differ in every state. Excel: open the file. Google Sheets: File, Import, Upload, Replace spreadsheet.\", \"inputs\": [{\"label\": \"Gross wages this pay period, one employee\", \"key\": \"gross_wages_this_period\", \"value\": 3000, \"format\": \"currency\"}, {\"label\": \"Wages already paid to this employee this year, before this period\", \"key\": \"ytd_wages_before_this_period\", \"value\": 6000, \"format\": \"currency\"}, {\"label\": \"Federal income tax withheld this period (from IRS Publication 15-T)\", \"key\": \"federal_income_tax_withheld\", \"value\": 316, \"format\": \"currency\"}, {\"label\": \"Social Security wage base for your tax year (SSA notice; 2025 figure shown)\", \"key\": \"social_security_wage_base\", \"value\": 176100, \"format\": \"currency\"}, {\"label\": \"FUTA wage base per employee per year (statutory)\", \"key\": \"futa_wage_base\", \"value\": 7000, \"format\": \"currency\"}, {\"label\": \"FUTA net rate after the 5.4% state credit (use 6.0% in a credit-reduction state)\", \"key\": \"futa_net_rate\", \"value\": 0.006, \"format\": \"percent\"}, {\"label\": \"Your state UI taxable wage base (from your state agency notice)\", \"key\": \"state_ui_wage_base\", \"value\": 7000, \"format\": \"currency\"}, {\"label\": \"Your assigned state UI rate for this year (from your state agency notice)\", \"key\": \"state_ui_rate\", \"value\": 0.034, \"format\": \"percent\"}]}, {\"name\": \"1. This Paycheck (Employee)\", \"purpose\": \"One question, answered completely: what comes out of this paycheck. Social Security stops at the wage base, Medicare never stops, and the extra 0.9% starts only after $200,000 of wages in the calendar year — employers withhold that 0.9% at $200,000 regardless of the employee's filing status, and do not match it (IRS Publication 15). Charge 6.2% on wages above the base and you over-withhold: the employee recovers it on their 1040, but your Form 941 no longer ties out and the correction lands on you. Net pay here is before state income tax and any benefit deductions.\", \"calc_rows\": [{\"label\": \"Wages subject to Social Security this period (capped at the wage base)\", \"key\": \"ss_taxable_wages\", \"formula\": \"=MIN({gross_wages_this_period},MAX(0,{social_security_wage_base}-{ytd_wages_before_this_period}))\"}, {\"label\": \"Social Security withheld from the employee at 6.2%\", \"key\": \"ss_withheld_employee\", \"formula\": \"=ROUND({ss_taxable_wages}*0.062,2)\"}, {\"label\": \"Medicare withheld from the employee at 1.45% (no wage cap)\", \"key\": \"medicare_withheld_employee\", \"formula\": \"=ROUND({gross_wages_this_period}*0.0145,2)\"}, {\"label\": \"Additional Medicare withheld at 0.9% on wages over $200,000 year to date\", \"key\": \"additional_medicare_withheld\", \"formula\": \"=ROUND((MAX(0,{ytd_wages_before_this_period}+{gross_wages_this_period}-200000)-MAX(0,{ytd_wages_before_this_period}-200000))*0.009,2)\"}, {\"label\": \"Total withheld from this paycheck, income tax plus FICA\", \"key\": \"employee_taxes_withheld_total\", \"formula\": \"=ROUND({federal_income_tax_withheld}+{ss_withheld_employee}+{medicare_withheld_employee}+{additional_medicare_withheld},2)\"}, {\"label\": \"Net pay before state income tax and benefit deductions\", \"key\": \"net_pay_this_period\", \"formula\": \"=ROUND({gross_wages_this_period}-{employee_taxes_withheld_total},2)\"}]}]}, \"lang\": \"en\", \"locale\": \"en-US\", \"currency\": \"USD\", \"paid\": \"\", \"slug\": \"payroll-tax-spreadsheet\", \"ui\": {\"inputs\": \"Your numbers\", \"results\": \"Your result\", \"recalc\": \"Everything updates as you type.\", \"get\": \"Get the full calculator\", \"email\": \"Email me the full version\", \"placeholder\": \"you@example.com\", \"sent\": \"Sent. Check your inbox.\", \"share\": \"Save result image\", \"copy\": \"Copy link\", \"copied\": \"Link copied\", \"more\": \"Full version: all tabs, your own file, works offline.\", \"free\": \"Free calculator\", \"note\": \"Nothing is uploaded. This runs in your browser.\", \"dl\": \"Download my numbers (CSV)\", \"dl_need\": \"Enter your email above and we will unlock the download.\", \"dl_done\": \"Downloaded.\"}, \"title\": \"Payroll Tax Spreadsheet for US Small Employers — FICA, FUTA and State UI Wage-Base Caps per Paycheck\"}";document.body.appendChild(s);})();
     1(function () {
     2  var s = document.createElement("script");
     3  s.type = "application/json";
     4  s.id = "calcdata";
     5  s.textContent = JSON.stringify({
     6    spec: {
     7      sheets: [
     8        {
     9          name: "Inputs - Your Payroll Numbers",
     10          purpose:
     11            "Type your own eight numbers here; both calc sheets recalculate. A free online paycheck calculator cannot carry year-to-date wages from one run to the next, so it never knows that Social Security has already stopped for this employee — that is the whole reason this file exists. Rates you must confirm yourself are inputs, not guesses: the Social Security wage base is set by the SSA each October and the default shown is the 2025 figure of 176,100, so replace it with the figure for the year you are running. The $7,000 FUTA base is statutory. Your state UI rate and state UI taxable wage base come from your state agency's annual notice and differ in every state. Excel: open the file. Google Sheets: File, Import, Upload, Replace spreadsheet.",
     12          inputs: [
     13            {
     14              label: "Gross wages this pay period, one employee",
     15              key: "gross_wages_this_period",
     16              value: 3000,
     17              format: "currency",
     18            },
     19            {
     20              label: "Wages already paid to this employee this year, before this period",
     21              key: "ytd_wages_before_this_period",
     22              value: 6000,
     23              format: "currency",
     24            },
     25            {
     26              label: "Federal income tax withheld this period (from IRS Publication 15-T)",
     27              key: "federal_income_tax_withheld",
     28              value: 316,
     29              format: "currency",
     30            },
     31            {
     32              label: "Social Security wage base for your tax year (SSA notice; 2025 figure shown)",
     33              key: "social_security_wage_base",
     34              value: 176100,
     35              format: "currency",
     36            },
     37            {
     38              label: "FUTA wage base per employee per year (statutory)",
     39              key: "futa_wage_base",
     40              value: 7000,
     41              format: "currency",
     42            },
     43            {
     44              label:
     45                "FUTA net rate after the 5.4% state credit (use 6.0% in a credit-reduction state)",
     46              key: "futa_net_rate",
     47              value: 0.006,
     48              format: "percent",
     49            },
     50            {
     51              label: "Your state UI taxable wage base (from your state agency notice)",
     52              key: "state_ui_wage_base",
     53              value: 7000,
     54              format: "currency",
     55            },
     56            {
     57              label: "Your assigned state UI rate for this year (from your state agency notice)",
     58              key: "state_ui_rate",
     59              value: 0.034,
     60              format: "percent",
     61            },
     62          ],
     63        },
     64        {
     65          name: "1. This Paycheck (Employee)",
     66          purpose:
     67            "One question, answered completely: what comes out of this paycheck. Social Security stops at the wage base, Medicare never stops, and the extra 0.9% starts only after $200,000 of wages in the calendar year — employers withhold that 0.9% at $200,000 regardless of the employee's filing status, and do not match it (IRS Publication 15). Charge 6.2% on wages above the base and you over-withhold: the employee recovers it on their 1040, but your Form 941 no longer ties out and the correction lands on you. Net pay here is before state income tax and any benefit deductions.",
     68          calc_rows: [
     69            {
     70              label: "Wages subject to Social Security this period (capped at the wage base)",
     71              key: "ss_taxable_wages",
     72              formula:
     73                "=MIN({gross_wages_this_period},MAX(0,{social_security_wage_base}-{ytd_wages_before_this_period}))",
     74            },
     75            {
     76              label: "Social Security withheld from the employee at 6.2%",
     77              key: "ss_withheld_employee",
     78              formula: "=ROUND({ss_taxable_wages}*0.062,2)",
     79            },
     80            {
     81              label: "Medicare withheld from the employee at 1.45% (no wage cap)",
     82              key: "medicare_withheld_employee",
     83              formula: "=ROUND({gross_wages_this_period}*0.0145,2)",
     84            },
     85            {
     86              label: "Additional Medicare withheld at 0.9% on wages over $200,000 year to date",
     87              key: "additional_medicare_withheld",
     88              formula:
     89                "=ROUND((MAX(0,{ytd_wages_before_this_period}+{gross_wages_this_period}-200000)-MAX(0,{ytd_wages_before_this_period}-200000))*0.009,2)",
     90            },
     91            {
     92              label: "Total withheld from this paycheck, income tax plus FICA",
     93              key: "employee_taxes_withheld_total",
     94              formula:
     95                "=ROUND({federal_income_tax_withheld}+{ss_withheld_employee}+{medicare_withheld_employee}+{additional_medicare_withheld},2)",
     96            },
     97            {
     98              label: "Net pay before state income tax and benefit deductions",
     99              key: "net_pay_this_period",
     100              formula: "=ROUND({gross_wages_this_period}-{employee_taxes_withheld_total},2)",
     101            },
     102          ],
     103        },
     104      ],
     105    },
     106    lang: "en",
     107    locale: "en-US",
     108    currency: "USD",
     109    paid: "",
     110    slug: "payroll-tax-spreadsheet",
     111    ui: {
     112      inputs: "Your numbers",
     113      results: "Your result",
     114      recalc: "Everything updates as you type.",
     115      get: "Get the full calculator",
     116      email: "Email me the full version",
     117      placeholder: "you@example.com",
     118      sent: "Sent. Check your inbox.",
     119      share: "Save result image",
     120      copy: "Copy link",
     121      copied: "Link copied",
     122      more: "Full version: all tabs, your own file, works offline.",
     123      free: "Free calculator",
     124      note: "Nothing is uploaded. This runs in your browser.",
     125      dl: "Download my numbers (CSV)",
     126      dl_need: "Enter your email above and we will unlock the download.",
     127      dl_done: "Downloaded.",
     128    },
     129    title:
     130      "Payroll Tax Spreadsheet for US Small Employers — FICA, FUTA and State UI Wage-Base Caps per Paycheck",
     131  });
     132  document.body.appendChild(s);
     133})();
  • readystack-payroll-tax-spreadsheet/trunk/assets/tool-2.js

    r3712579 r3713964  
    1 var PRE={"=MIN({gross_wages_this_period},MAX(0,{social_security_wage_base}-{ytd_wages_before_this_period}))":function(V,FN){return (FN.MIN( V["gross_wages_this_period"] ,FN.MAX(0, V["social_security_wage_base"] - V["ytd_wages_before_this_period"] )));},"=ROUND({ss_taxable_wages}*0.062,2)":function(V,FN){return (FN.ROUND( V["ss_taxable_wages"] *0.062,2));},"=ROUND({gross_wages_this_period}*0.0145,2)":function(V,FN){return (FN.ROUND( V["gross_wages_this_period"] *0.0145,2));},"=ROUND((MAX(0,{ytd_wages_before_this_period}+{gross_wages_this_period}-200000)-MAX(0,{ytd_wages_before_this_period}-200000))*0.009,2)":function(V,FN){return (FN.ROUND((FN.MAX(0, V["ytd_wages_before_this_period"] + V["gross_wages_this_period"] -200000)-FN.MAX(0, V["ytd_wages_before_this_period"] -200000))*0.009,2));},"=ROUND({federal_income_tax_withheld}+{ss_withheld_employee}+{medicare_withheld_employee}+{additional_medicare_withheld},2)":function(V,FN){return (FN.ROUND( V["federal_income_tax_withheld"] + V["ss_withheld_employee"] + V["medicare_withheld_employee"] + V["additional_medicare_withheld"] ,2));},"=ROUND({gross_wages_this_period}-{employee_taxes_withheld_total},2)":function(V,FN){return (FN.ROUND( V["gross_wages_this_period"] - V["employee_taxes_withheld_total"] ,2));},"=ROUND({ss_taxable_wages}*0.062+{gross_wages_this_period}*0.0145,2)":function(V,FN){return (FN.ROUND( V["ss_taxable_wages"] *0.062+ V["gross_wages_this_period"] *0.0145,2));},"=MIN({gross_wages_this_period},MAX(0,{futa_wage_base}-{ytd_wages_before_this_period}))":function(V,FN){return (FN.MIN( V["gross_wages_this_period"] ,FN.MAX(0, V["futa_wage_base"] - V["ytd_wages_before_this_period"] )));},"=ROUND({futa_taxable_wages}*{futa_net_rate},2)":function(V,FN){return (FN.ROUND( V["futa_taxable_wages"] * V["futa_net_rate"] ,2));},"=ROUND(MIN({gross_wages_this_period},MAX(0,{state_ui_wage_base}-{ytd_wages_before_this_period}))*{state_ui_rate},2)":function(V,FN){return (FN.ROUND(FN.MIN( V["gross_wages_this_period"] ,FN.MAX(0, V["state_ui_wage_base"] - V["ytd_wages_before_this_period"] ))* V["state_ui_rate"] ,2));},"=ROUND({federal_income_tax_withheld}+{ss_withheld_employee}+{medicare_withheld_employee}+{additional_medicare_withheld}+{employer_ss_and_medicare_match},2)":function(V,FN){return (FN.ROUND( V["federal_income_tax_withheld"] + V["ss_withheld_employee"] + V["medicare_withheld_employee"] + V["additional_medicare_withheld"] + V["employer_ss_and_medicare_match"] ,2));},"=ROUND({gross_wages_this_period}+{employer_ss_and_medicare_match}+{futa_tax_this_period}+{state_ui_tax_this_period},2)":function(V,FN){return (FN.ROUND( V["gross_wages_this_period"] + V["employer_ss_and_medicare_match"] + V["futa_tax_this_period"] + V["state_ui_tax_this_period"] ,2));}};
     1var PRE = {
     2  "=MIN({gross_wages_this_period},MAX(0,{social_security_wage_base}-{ytd_wages_before_this_period}))":
     3    function (V, FN) {
     4      return FN.MIN(
     5        V["gross_wages_this_period"],
     6        FN.MAX(0, V["social_security_wage_base"] - V["ytd_wages_before_this_period"]),
     7      );
     8    },
     9  "=ROUND({ss_taxable_wages}*0.062,2)": function (V, FN) {
     10    return FN.ROUND(V["ss_taxable_wages"] * 0.062, 2);
     11  },
     12  "=ROUND({gross_wages_this_period}*0.0145,2)": function (V, FN) {
     13    return FN.ROUND(V["gross_wages_this_period"] * 0.0145, 2);
     14  },
     15  "=ROUND((MAX(0,{ytd_wages_before_this_period}+{gross_wages_this_period}-200000)-MAX(0,{ytd_wages_before_this_period}-200000))*0.009,2)":
     16    function (V, FN) {
     17      return FN.ROUND(
     18        (FN.MAX(0, V["ytd_wages_before_this_period"] + V["gross_wages_this_period"] - 200000) -
     19          FN.MAX(0, V["ytd_wages_before_this_period"] - 200000)) *
     20          0.009,
     21        2,
     22      );
     23    },
     24  "=ROUND({federal_income_tax_withheld}+{ss_withheld_employee}+{medicare_withheld_employee}+{additional_medicare_withheld},2)":
     25    function (V, FN) {
     26      return FN.ROUND(
     27        V["federal_income_tax_withheld"] +
     28          V["ss_withheld_employee"] +
     29          V["medicare_withheld_employee"] +
     30          V["additional_medicare_withheld"],
     31        2,
     32      );
     33    },
     34  "=ROUND({gross_wages_this_period}-{employee_taxes_withheld_total},2)": function (V, FN) {
     35    return FN.ROUND(V["gross_wages_this_period"] - V["employee_taxes_withheld_total"], 2);
     36  },
     37  "=ROUND({ss_taxable_wages}*0.062+{gross_wages_this_period}*0.0145,2)": function (V, FN) {
     38    return FN.ROUND(V["ss_taxable_wages"] * 0.062 + V["gross_wages_this_period"] * 0.0145, 2);
     39  },
     40  "=MIN({gross_wages_this_period},MAX(0,{futa_wage_base}-{ytd_wages_before_this_period}))":
     41    function (V, FN) {
     42      return FN.MIN(
     43        V["gross_wages_this_period"],
     44        FN.MAX(0, V["futa_wage_base"] - V["ytd_wages_before_this_period"]),
     45      );
     46    },
     47  "=ROUND({futa_taxable_wages}*{futa_net_rate},2)": function (V, FN) {
     48    return FN.ROUND(V["futa_taxable_wages"] * V["futa_net_rate"], 2);
     49  },
     50  "=ROUND(MIN({gross_wages_this_period},MAX(0,{state_ui_wage_base}-{ytd_wages_before_this_period}))*{state_ui_rate},2)":
     51    function (V, FN) {
     52      return FN.ROUND(
     53        FN.MIN(
     54          V["gross_wages_this_period"],
     55          FN.MAX(0, V["state_ui_wage_base"] - V["ytd_wages_before_this_period"]),
     56        ) * V["state_ui_rate"],
     57        2,
     58      );
     59    },
     60  "=ROUND({federal_income_tax_withheld}+{ss_withheld_employee}+{medicare_withheld_employee}+{additional_medicare_withheld}+{employer_ss_and_medicare_match},2)":
     61    function (V, FN) {
     62      return FN.ROUND(
     63        V["federal_income_tax_withheld"] +
     64          V["ss_withheld_employee"] +
     65          V["medicare_withheld_employee"] +
     66          V["additional_medicare_withheld"] +
     67          V["employer_ss_and_medicare_match"],
     68        2,
     69      );
     70    },
     71  "=ROUND({gross_wages_this_period}+{employer_ss_and_medicare_match}+{futa_tax_this_period}+{state_ui_tax_this_period},2)":
     72    function (V, FN) {
     73      return FN.ROUND(
     74        V["gross_wages_this_period"] +
     75          V["employer_ss_and_medicare_match"] +
     76          V["futa_tax_this_period"] +
     77          V["state_ui_tax_this_period"],
     78        2,
     79      );
     80    },
     81};
  • readystack-payroll-tax-spreadsheet/trunk/assets/tool-3.js

    r3712579 r3713964  
    11var CALC = (function () {
    2   function n(x){ x = Number(x); return isFinite(x) ? x : 0; }
    3   function ROUND(x, d){ d = n(d); var f = Math.pow(10, d); var v = n(x) * f;
    4     var r = (v >= 0) ? Math.floor(v + 0.5) : Math.ceil(v - 0.5); return r / f; }
    5   function POWER(a, b){ return Math.pow(n(a), n(b)); }
    6   function ABS(a){ return Math.abs(n(a)); }
    7   function MIN(){ var a=[].slice.call(arguments).map(n); return a.length?Math.min.apply(null,a):0; }
    8   function MAX(){ var a=[].slice.call(arguments).map(n); return a.length?Math.max.apply(null,a):0; }
    9   function SUM(){ var s=0; for(var i=0;i<arguments.length;i++) s+=n(arguments[i]); return s; }
    10   function IF(c, a, b){ return c ? a : b; }
    11   function FLOOR(x, sig){ sig = (arguments.length > 1) ? n(sig) : 1;
    12     if (sig === 0) return 0; return Math.floor(n(x) / sig) * sig; }
    13   function CEILING(x, sig){ sig = (arguments.length > 1) ? n(sig) : 1;
    14     if (sig === 0) return 0; return Math.ceil(n(x) / sig) * sig; }
    15   function PMT(rate, nper, pv, fv, type){
    16     rate = n(rate); nper = n(nper); pv = n(pv); fv = n(fv); type = n(type);
     2  function n(x) {
     3    x = Number(x);
     4    return isFinite(x) ? x : 0;
     5  }
     6  function ROUND(x, d) {
     7    d = n(d);
     8    var f = Math.pow(10, d);
     9    var v = n(x) * f;
     10    var r = v >= 0 ? Math.floor(v + 0.5) : Math.ceil(v - 0.5);
     11    return r / f;
     12  }
     13  function POWER(a, b) {
     14    return Math.pow(n(a), n(b));
     15  }
     16  function ABS(a) {
     17    return Math.abs(n(a));
     18  }
     19  function MIN() {
     20    var a = [].slice.call(arguments).map(n);
     21    return a.length ? Math.min.apply(null, a) : 0;
     22  }
     23  function MAX() {
     24    var a = [].slice.call(arguments).map(n);
     25    return a.length ? Math.max.apply(null, a) : 0;
     26  }
     27  function SUM() {
     28    var s = 0;
     29    for (var i = 0; i < arguments.length; i++) s += n(arguments[i]);
     30    return s;
     31  }
     32  function IF(c, a, b) {
     33    return c ? a : b;
     34  }
     35  function FLOOR(x, sig) {
     36    sig = arguments.length > 1 ? n(sig) : 1;
     37    if (sig === 0) return 0;
     38    return Math.floor(n(x) / sig) * sig;
     39  }
     40  function CEILING(x, sig) {
     41    sig = arguments.length > 1 ? n(sig) : 1;
     42    if (sig === 0) return 0;
     43    return Math.ceil(n(x) / sig) * sig;
     44  }
     45  function PMT(rate, nper, pv, fv, type) {
     46    rate = n(rate);
     47    nper = n(nper);
     48    pv = n(pv);
     49    fv = n(fv);
     50    type = n(type);
    1751    if (nper === 0) return 0;
    1852    if (rate === 0) return -(pv + fv) / nper;
    1953    var p = Math.pow(1 + rate, nper);
    20     return -(pv * p + fv) * rate / ((p - 1) * (1 + rate * type));
     54    return (-(pv * p + fv) * rate) / ((p - 1) * (1 + rate * type));
    2155  }
    22   var FN = { ROUND:ROUND, POWER:POWER, ABS:ABS, MIN:MIN, MAX:MAX, SUM:SUM, IF:IF, PMT:PMT,
    23              FLOOR:FLOOR, CEILING:CEILING };
     56  var FN = {
     57    ROUND: ROUND,
     58    POWER: POWER,
     59    ABS: ABS,
     60    MIN: MIN,
     61    MAX: MAX,
     62    SUM: SUM,
     63    IF: IF,
     64    PMT: PMT,
     65    FLOOR: FLOOR,
     66    CEILING: CEILING,
     67  };
    2468  var FNAMES = Object.keys(FN);
    2569
    … …  
    3074  //     하는 대신 ★주석을 바꾼다 (⛔검사기를 무디게 만드는 것이 s67 의 병이었다).
    3175  //   ⇒ ★빌드 때 toExpr 로 식을 뽑아 HTML 에 ★구워 넣고, 브라우저는 ★조회만 한다.
    32   function toExpr(formula){
    33     var s = String(formula || '').trim();
    34     if (s.charAt(0) === '=') s = s.slice(1);
     76  function toExpr(formula) {
     77    var s = String(formula || "").trim();
     78    if (s.charAt(0) === "=") s = s.slice(1);
    3579    if (!s) return null;
    36     s = s.replace(/\{([A-Za-z0-9_]+)\}/g, function(_, k){ return ' V["' + k + '"] '; });
    37     s = s.replace(/<>/g, '!==').replace(/>=/g, '@GE@').replace(/<=/g, '@LE@');
    38     s = s.replace(/=/g, '===').replace(/@GE@/g, '>=').replace(/@LE@/g, '<=');
    39     s = s.replace(/\^/g, '**');
    40     FNAMES.forEach(function(f){ s = s.replace(new RegExp('\\b' + f + '\\s*\\(', 'gi'), 'FN.' + f + '('); });
    41     var probe = s.replace(/V\["[A-Za-z0-9_]+"\]/g, '0').replace(/FN\.[A-Z]+\(/g, '(');
     80    s = s.replace(/\{([A-Za-z0-9_]+)\}/g, function (_, k) {
     81      return ' V["' + k + '"] ';
     82    });
     83    s = s.replace(/<>/g, "!==").replace(/>=/g, "@GE@").replace(/<=/g, "@LE@");
     84    s = s.replace(/=/g, "===").replace(/@GE@/g, ">=").replace(/@LE@/g, "<=");
     85    s = s.replace(/\^/g, "**");
     86    FNAMES.forEach(function (f) {
     87      s = s.replace(new RegExp("\\b" + f + "\\s*\\(", "gi"), "FN." + f + "(");
     88    });
     89    var probe = s.replace(/V\["[A-Za-z0-9_]+"\]/g, "0").replace(/FN\.[A-Z]+\(/g, "(");
    4290    if (!/^[0-9+\-*/%().,\s<>=!&|*]*$/.test(probe)) {
    43       throw new Error('unsupported token in formula: ' + formula);
     91      throw new Error("unsupported token in formula: " + formula);
    4492    }
    4593    return s;
    4694  }
    47   function compile(formula){
     95  function compile(formula) {
    4896    // ★브라우저: 빌드 때 구워넣은 PRE 를 ★조회만 한다 (eval 0).
    49     var raw = String(formula || '').trim();
     97    var raw = String(formula || "").trim();
    5098    if (!raw) return null;
    51     if (typeof PRE !== 'undefined' && PRE && PRE[raw]) return PRE[raw];
    52     throw new Error('not precompiled: ' + raw);
     99    if (typeof PRE !== "undefined" && PRE && PRE[raw]) return PRE[raw];
     100    throw new Error("not precompiled: " + raw);
    53101  }
    54102
    55103  // 명세 -> {values, order, rows}. ⛔없는 key 는 0 (xlsx_builder 와 같은 규칙).
    56   function run(spec, override){
     104  function run(spec, override) {
    57105    var sheets = (spec.sheets_content && spec.sheets_content.sheets) || spec.sheets || [];
    58106    var V = Object.create(null);
    59     var order = [], rows = [];
    60     var handler = { get: function(t, k){ return (k in t) ? t[k] : 0; } };
    61     var PV = (typeof Proxy !== 'undefined') ? new Proxy(V, handler) : V;
    62     sheets.forEach(function(sh){
     107    var order = [],
     108      rows = [];
     109    var handler = {
     110      get: function (t, k) {
     111        return k in t ? t[k] : 0;
     112      },
     113    };
     114    var PV = typeof Proxy !== "undefined" ? new Proxy(V, handler) : V;
     115    sheets.forEach(function (sh) {
    63116      if (sh && sh.inputs && sh.inputs.length) {
    64         sh.inputs.forEach(function(it){
     117        sh.inputs.forEach(function (it) {
    65118          if (!it || !it.key) return;
    66           var v = (override && (it.key in override)) ? override[it.key] : it.value;
     119          var v = override && it.key in override ? override[it.key] : it.value;
    67120          V[it.key] = n(v);
    68121        });
    69122      }
    70123    });
    71     sheets.forEach(function(sh){
     124    sheets.forEach(function (sh) {
    72125      if (!sh || !sh.calc_rows || !sh.calc_rows.length) return;
    73       sh.calc_rows.forEach(function(cr){
     126      sh.calc_rows.forEach(function (cr) {
    74127        if (!cr) return;
    75         var val = 0, err = null;
    76         try { var f = compile(cr.formula); val = f ? n(f(PV, FN)) : 0; }
    77         catch (e) { err = String(e && e.message || e); }
     128        var val = 0,
     129          err = null;
     130        try {
     131          var f = compile(cr.formula);
     132          val = f ? n(f(PV, FN)) : 0;
     133        } catch (e) {
     134          err = String((e && e.message) || e);
     135        }
    78136        if (cr.key) V[cr.key] = val;
    79         var rec = { sheet: sh.name, label: cr.label, key: cr.key || null,
    80                     format: cr.format || 'number', value: val, error: err };
    81         order.push(rec); rows.push(rec);
     137        var rec = {
     138          sheet: sh.name,
     139          label: cr.label,
     140          key: cr.key || null,
     141          format: cr.format || "number",
     142          value: val,
     143          error: err,
     144        };
     145        order.push(rec);
     146        rows.push(rec);
    82147      });
    83148    });
    … …  
    86151  return { run: run, compile: compile, toExpr: toExpr, FN: FN };
    87152})();
    88 if (typeof module !== 'undefined' && module.exports) module.exports = CALC;
     153if (typeof module !== "undefined" && module.exports) module.exports = CALC;
  • readystack-payroll-tax-spreadsheet/trunk/assets/tool-4.js

    r3712579 r3713964  
    1 (function(){
    2   var D=JSON.parse(document.getElementById('calcdata').textContent);
    3   var UI=D.ui, LOC=D.locale, CUR=D.currency;
    4   function fmt(v,f){
    5     if(!isFinite(v)) v=0;
    6     if(f==='currency') return new Intl.NumberFormat(LOC,{style:'currency',currency:CUR,maximumFractionDigits:2}).format(v);
    7     if(f==='percent') return new Intl.NumberFormat(LOC,{style:'percent',minimumFractionDigits:2,maximumFractionDigits:2}).format(v);
    8     if(f==='integer'||f==='int') return new Intl.NumberFormat(LOC,{maximumFractionDigits:0}).format(v);
    9     return new Intl.NumberFormat(LOC,{maximumFractionDigits:4}).format(v);
     1(function () {
     2  var D = JSON.parse(document.getElementById("calcdata").textContent);
     3  var UI = D.ui,
     4    LOC = D.locale,
     5    CUR = D.currency;
     6  function fmt(v, f) {
     7    if (!isFinite(v)) v = 0;
     8    if (f === "currency")
     9      return new Intl.NumberFormat(LOC, {
     10        style: "currency",
     11        currency: CUR,
     12        maximumFractionDigits: 2,
     13      }).format(v);
     14    if (f === "percent")
     15      return new Intl.NumberFormat(LOC, {
     16        style: "percent",
     17        minimumFractionDigits: 2,
     18        maximumFractionDigits: 2,
     19      }).format(v);
     20    if (f === "integer" || f === "int")
     21      return new Intl.NumberFormat(LOC, { maximumFractionDigits: 0 }).format(v);
     22    return new Intl.NumberFormat(LOC, { maximumFractionDigits: 4 }).format(v);
    1023  }
    11   var ins=[].slice.call(document.querySelectorAll('input[data-k]'));
    12   var outs=[].slice.call(document.querySelectorAll('td.v'));
    13   function recalc(){
    14     var ov={}; ins.forEach(function(el){
    15       var n=Number(el.value)||0; ov[el.dataset.k]= el.dataset.pct ? n/100 : n; });
    16     var r=CALC.run(D.spec,ov);
    17     var byKey={}, i=0;
    18     r.rows.forEach(function(x){ if(x.key) byKey[x.key]=x; });
    19     outs.forEach(function(td){
     24  var ins = [].slice.call(document.querySelectorAll("input[data-k]"));
     25  var outs = [].slice.call(document.querySelectorAll("td.v"));
     26  function recalc() {
     27    var ov = {};
     28    ins.forEach(function (el) {
     29      var n = Number(el.value) || 0;
     30      ov[el.dataset.k] = el.dataset.pct ? n / 100 : n;
     31    });
     32    var r = CALC.run(D.spec, ov);
     33    var byKey = {},
     34      i = 0;
     35    r.rows.forEach(function (x) {
     36      if (x.key) byKey[x.key] = x;
     37    });
     38    outs.forEach(function (td) {
    2039      var rec = td.dataset.k ? byKey[td.dataset.k] : r.rows[i];
    2140      i++;
    22       if(!rec){ td.textContent='-'; return; }
    23       td.textContent = rec.error ? '-' : fmt(rec.value, td.dataset.f);
     41      if (!rec) {
     42        td.textContent = "-";
     43        return;
     44      }
     45      td.textContent = rec.error ? "-" : fmt(rec.value, td.dataset.f);
    2446    });
    25     window.__last=r;
     47    window.__last = r;
    2648  }
    27   ins.forEach(function(el){ el.addEventListener('input',recalc); });
     49  ins.forEach(function (el) {
     50    el.addEventListener("input", recalc);
     51  });
    2852  recalc();
    2953
    … …  
    3458  //   So: the ANSWER stays free, and "the NEXT step" (taking your own numbers away as a file)
    3559  //   is what asks for the email (Zeigarnik). Whoever already gave it is never asked again.
    36   document.getElementById('dl').addEventListener('click',function(){
    37     var rr=[['label','value']];
    38     ins.forEach(function(el){
    39       var lb=(el.closest('label')||{}).textContent||el.dataset.k;
     60  document.getElementById("dl").addEventListener("click", function () {
     61    var rr = [["label", "value"]];
     62    ins.forEach(function (el) {
     63      var lb = (el.closest("label") || {}).textContent || el.dataset.k;
    4064      rr.push([String(lb).trim(), el.value]);
    4165    });
    42     ((window.__last&&window.__last.rows)||[]).forEach(function(r){
    43       rr.push([String(r.label), r.error?'':String(r.value)]);
     66    ((window.__last && window.__last.rows) || []).forEach(function (r) {
     67      rr.push([String(r.label), r.error ? "" : String(r.value)]);
    4468    });
    45     var csv=rr.map(function(r){
    46       return r.map(function(c){ return '"'+String(c).replace(/"/g,'""')+'"'; }).join(',');
    47     }).join('\n');
    48     var bl=new Blob(['\ufeff'+csv],{type:'text/csv;charset=utf-8'});
    49     var ae=document.createElement('a');
    50     ae.href=URL.createObjectURL(bl); ae.download=(D.slug||'result')+'.csv'; ae.click();
    51     var bt=this, tx=bt.textContent;
    52     bt.textContent='Downloaded.'; setTimeout(function(){ bt.textContent=tx; },1800);
     69    var csv = rr
     70      .map(function (r) {
     71        return r
     72          .map(function (c) {
     73            return '"' + String(c).replace(/"/g, '""') + '"';
     74          })
     75          .join(",");
     76      })
     77      .join("\n");
     78    var bl = new Blob(["\ufeff" + csv], { type: "text/csv;charset=utf-8" });
     79    var ae = document.createElement("a");
     80    ae.href = URL.createObjectURL(bl);
     81    ae.download = (D.slug || "result") + ".csv";
     82    ae.click();
     83    var bt = this,
     84      tx = bt.textContent;
     85    bt.textContent = "Downloaded.";
     86    setTimeout(function () {
     87      bt.textContent = tx;
     88    }, 1800);
    5389  });
    5490
    55   document.getElementById('sh').addEventListener('click',function(){
    56     var c=document.getElementById('cv'), x=c.getContext('2d');
    57     x.fillStyle='#0e1116'; x.fillRect(0,0,1200,630);
    58     x.fillStyle='#fff'; x.font='bold 52px system-ui,sans-serif';
    59     x.fillText(D.title.slice(0,42),64,120);
    60     var rws=(window.__last&&window.__last.rows)||[]; var y=230;
    61     rws.slice(0,5).forEach(function(r){
    62       x.fillStyle='#9aa4b2'; x.font='28px system-ui,sans-serif';
    63       x.fillText(String(r.label).slice(0,44),64,y);
    64       x.fillStyle='#7aa2f7'; x.font='bold 34px system-ui,sans-serif';
    65       x.textAlign='right'; x.fillText(fmt(r.value,r.format),1136,y); x.textAlign='left';
    66       y+=72;
     91  document.getElementById("sh").addEventListener("click", function () {
     92    var c = document.getElementById("cv"),
     93      x = c.getContext("2d");
     94    x.fillStyle = "#0e1116";
     95    x.fillRect(0, 0, 1200, 630);
     96    x.fillStyle = "#fff";
     97    x.font = "bold 52px system-ui,sans-serif";
     98    x.fillText(D.title.slice(0, 42), 64, 120);
     99    var rws = (window.__last && window.__last.rows) || [];
     100    var y = 230;
     101    rws.slice(0, 5).forEach(function (r) {
     102      x.fillStyle = "#9aa4b2";
     103      x.font = "28px system-ui,sans-serif";
     104      x.fillText(String(r.label).slice(0, 44), 64, y);
     105      x.fillStyle = "#7aa2f7";
     106      x.font = "bold 34px system-ui,sans-serif";
     107      x.textAlign = "right";
     108      x.fillText(fmt(r.value, r.format), 1136, y);
     109      x.textAlign = "left";
     110      y += 72;
    67111    });
    68     x.fillStyle='#6b7280'; x.font='24px system-ui,sans-serif';
    69     x.fillText(location.host+(D.slug?'/tools/'+D.slug:''),64,590);
    70     var a=document.createElement('a'); a.download=(D.slug||'result')+'.png';
    71     a.href=c.toDataURL('image/png'); a.click();
     112    x.fillStyle = "#6b7280";
     113    x.font = "24px system-ui,sans-serif";
     114    x.fillText(location.host + (D.slug ? "/tools/" + D.slug : ""), 64, 590);
     115    var a = document.createElement("a");
     116    a.download = (D.slug || "result") + ".png";
     117    a.href = c.toDataURL("image/png");
     118    a.click();
    72119  });
    73120  // ★★2026-08-22 s99 (B2) — ★링크 복사. ⛔이게 없으면 ★도메인이 같이 퍼지지 않는다.
    74121  //   ★결과 이미지는 ★도메인을 찍어서 나가고, ★링크는 ★그 자리로 되돌아오게 한다.
    75   document.getElementById('cp').addEventListener('click',function(){
    76     var b=this, t=b.textContent;
    77     var u=location.href.split('#')[0];
    78     function done(){ b.textContent='Link copied'; setTimeout(function(){ b.textContent=t; },1800); }
    79     if(navigator.clipboard&&navigator.clipboard.writeText){
    80       navigator.clipboard.writeText(u).then(done,function(){ prompt('',u); });
    81     } else { prompt('',u); }
     122  document.getElementById("cp").addEventListener("click", function () {
     123    var b = this,
     124      t = b.textContent;
     125    var u = location.href.split("#")[0];
     126    function done() {
     127      b.textContent = "Link copied";
     128      setTimeout(function () {
     129        b.textContent = t;
     130      }, 1800);
     131    }
     132    if (navigator.clipboard && navigator.clipboard.writeText) {
     133      navigator.clipboard.writeText(u).then(done, function () {
     134        prompt("", u);
     135      });
     136    } else {
     137      prompt("", u);
     138    }
    82139  });
    83140})();
  • readystack-payroll-tax-spreadsheet/trunk/assets/tool.css

    r3712579 r3713964  
    1 .readystack-tool{--bg:#fff;--fg:#111;--mut:#666;--line:#e5e5e5;--acc:#1a56db;--ok:#0f7b3f}
    2 @media(prefers-color-scheme:dark){.readystack-tool{--bg:#111;--fg:#eee;--mut:#999;--line:#2a2a2a;--acc:#7aa2f7;--ok:#5fd08a}}
    3 .readystack-tool,.readystack-tool *{box-sizing:border-box}
    4 .readystack-tool{margin:0;background:var(--bg);color:var(--fg);font:16px/1.55 system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",sans-serif}
    5 .readystack-tool .wrap{max-width:820px;margin:0 auto;padding:24px 18px 64px}
    6 .readystack-tool h2{font-size:1.6rem;line-height:1.25;margin:.2em 0 .1em}
    7 .readystack-tool .sub{color:var(--mut);margin:0 0 1.4em}
    8 .readystack-tool .card{border:1px solid var(--line);border-radius:12px;padding:18px;margin:0 0 18px}
    9 .readystack-tool h2{font-size:.85rem;letter-spacing:.08em;text-transform:uppercase;color:var(--mut);margin:0 0 14px}
    10 .readystack-tool label{display:flex;justify-content:space-between;align-items:center;gap:14px;padding:7px 0;border-bottom:1px solid var(--line)}
    11 .readystack-tool label:last-child{border-bottom:0}
    12 .readystack-tool label span{flex:1;font-size:.95rem}
    13 .readystack-tool input[type=number]{width:9.5em;padding:8px 10px;border:1px solid var(--line);border-radius:8px;background:transparent;color:var(--fg);font:inherit;text-align:right}
    14 .readystack-tool .fld{display:inline-flex;align-items:center;gap:6px}
    15 .readystack-tool .fld i{font-style:normal;color:var(--mut);width:1em}
    16 .readystack-tool table{width:100%;border-collapse:collapse}
    17 .readystack-tool td{padding:8px 0;border-bottom:1px solid var(--line);vertical-align:top}
    18 .readystack-tool tr:last-child td{border-bottom:0}
    19 .readystack-tool td.l{font-size:.95rem}
    20 .readystack-tool td.v{text-align:right;font-variant-numeric:tabular-nums;font-weight:600;white-space:nowrap}
    21 .readystack-tool .hint{color:var(--mut);font-size:.85rem;margin:10px 0 0}
    22 .readystack-tool .cta{border:1px solid var(--acc);border-radius:12px;padding:18px;margin:22px 0 0}
    23 .readystack-tool .cta b{display:block;margin-bottom:6px}
    24 .readystack-tool .row{display:flex;gap:8px;flex-wrap:wrap;margin-top:12px}
    25 .readystack-tool input[type=email]{flex:1;min-width:200px;padding:10px 12px;border:1px solid var(--line);border-radius:8px;background:transparent;color:var(--fg);font:inherit}
    26 .readystack-tool button{padding:10px 16px;border:0;border-radius:8px;background:var(--acc);color:#fff;font:inherit;font-weight:600;cursor:pointer}
    27 .readystack-tool button.ghost{background:transparent;color:var(--acc);border:1px solid var(--acc)}
    28 .readystack-tool a.buy{display:inline-block;margin-top:10px;color:var(--acc);font-weight:600}
    29 .readystack-tool .ok{color:var(--ok);font-size:.9rem;margin-top:8px}
    30 .readystack-tool canvas{display:none}
     1.readystack-tool {
     2  --bg: #fff;
     3  --fg: #111;
     4  --mut: #666;
     5  --line: #e5e5e5;
     6  --acc: #1a56db;
     7  --ok: #0f7b3f;
     8}
     9@media (prefers-color-scheme: dark) {
     10  .readystack-tool {
     11    --bg: #111;
     12    --fg: #eee;
     13    --mut: #999;
     14    --line: #2a2a2a;
     15    --acc: #7aa2f7;
     16    --ok: #5fd08a;
     17  }
     18}
     19.readystack-tool,
     20.readystack-tool * {
     21  box-sizing: border-box;
     22}
     23.readystack-tool {
     24  margin: 0;
     25  background: var(--bg);
     26  color: var(--fg);
     27  font:
     28    16px/1.55 system-ui,
     29    -apple-system,
     30    "Segoe UI",
     31    Roboto,
     32    "Helvetica Neue",
     33    sans-serif;
     34}
     35.readystack-tool .wrap {
     36  max-width: 820px;
     37  margin: 0 auto;
     38  padding: 24px 18px 64px;
     39}
     40.readystack-tool h2 {
     41  font-size: 1.6rem;
     42  line-height: 1.25;
     43  margin: 0.2em 0 0.1em;
     44}
     45.readystack-tool .sub {
     46  color: var(--mut);
     47  margin: 0 0 1.4em;
     48}
     49.readystack-tool .card {
     50  border: 1px solid var(--line);
     51  border-radius: 12px;
     52  padding: 18px;
     53  margin: 0 0 18px;
     54}
     55.readystack-tool h2 {
     56  font-size: 0.85rem;
     57  letter-spacing: 0.08em;
     58  text-transform: uppercase;
     59  color: var(--mut);
     60  margin: 0 0 14px;
     61}
     62.readystack-tool label {
     63  display: flex;
     64  justify-content: space-between;
     65  align-items: center;
     66  gap: 14px;
     67  padding: 7px 0;
     68  border-bottom: 1px solid var(--line);
     69}
     70.readystack-tool label:last-child {
     71  border-bottom: 0;
     72}
     73.readystack-tool label span {
     74  flex: 1;
     75  font-size: 0.95rem;
     76}
     77.readystack-tool input[type="number"] {
     78  width: 9.5em;
     79  padding: 8px 10px;
     80  border: 1px solid var(--line);
     81  border-radius: 8px;
     82  background: transparent;
     83  color: var(--fg);
     84  font: inherit;
     85  text-align: right;
     86}
     87.readystack-tool .fld {
     88  display: inline-flex;
     89  align-items: center;
     90  gap: 6px;
     91}
     92.readystack-tool .fld i {
     93  font-style: normal;
     94  color: var(--mut);
     95  width: 1em;
     96}
     97.readystack-tool table {
     98  width: 100%;
     99  border-collapse: collapse;
     100}
     101.readystack-tool td {
     102  padding: 8px 0;
     103  border-bottom: 1px solid var(--line);
     104  vertical-align: top;
     105}
     106.readystack-tool tr:last-child td {
     107  border-bottom: 0;
     108}
     109.readystack-tool td.l {
     110  font-size: 0.95rem;
     111}
     112.readystack-tool td.v {
     113  text-align: right;
     114  font-variant-numeric: tabular-nums;
     115  font-weight: 600;
     116  white-space: nowrap;
     117}
     118.readystack-tool .hint {
     119  color: var(--mut);
     120  font-size: 0.85rem;
     121  margin: 10px 0 0;
     122}
     123.readystack-tool .cta {
     124  border: 1px solid var(--acc);
     125  border-radius: 12px;
     126  padding: 18px;
     127  margin: 22px 0 0;
     128}
     129.readystack-tool .cta b {
     130  display: block;
     131  margin-bottom: 6px;
     132}
     133.readystack-tool .row {
     134  display: flex;
     135  gap: 8px;
     136  flex-wrap: wrap;
     137  margin-top: 12px;
     138}
     139.readystack-tool input[type="email"] {
     140  flex: 1;
     141  min-width: 200px;
     142  padding: 10px 12px;
     143  border: 1px solid var(--line);
     144  border-radius: 8px;
     145  background: transparent;
     146  color: var(--fg);
     147  font: inherit;
     148}
     149.readystack-tool button {
     150  padding: 10px 16px;
     151  border: 0;
     152  border-radius: 8px;
     153  background: var(--acc);
     154  color: #fff;
     155  font: inherit;
     156  font-weight: 600;
     157  cursor: pointer;
     158}
     159.readystack-tool button.ghost {
     160  background: transparent;
     161  color: var(--acc);
     162  border: 1px solid var(--acc);
     163}
     164.readystack-tool a.buy {
     165  display: inline-block;
     166  margin-top: 10px;
     167  color: var(--acc);
     168  font-weight: 600;
     169}
     170.readystack-tool .ok {
     171  color: var(--ok);
     172  font-size: 0.9rem;
     173  margin-top: 8px;
     174}
     175.readystack-tool canvas {
     176  display: none;
     177}
  • readystack-payroll-tax-spreadsheet/trunk/readme.txt

    r3712579 r3713964  
    55Tested up to: 7.1
    66Requires PHP: 7.4
    7 Stable tag: 1.0.1
     7Stable tag: 1.0.2
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    2929The tool runs in the browser of whoever opens the page. The plugin makes no external requests, sets no cookies, adds no tracking and stores nothing in your database. It has no settings screen.
    3030
     31= Source code =
     32
     33The JavaScript and CSS files in the plugin's assets folder are the complete, human-readable source. There is no build step: nothing is compiled, bundled or minified, so what you read is what runs.
     34
    3135== Installation ==
    3236
    … …  
    4751== Changelog ==
    4852
     53= 1.0.2 =
     54* JavaScript and CSS ship as formatted, human-readable source; the readme says where the source is.
     55
    4956= 1.0.1 =
    5057* The tool output is escaped, its styles are scoped to the tool and its scripts are enqueued; the readme explains how to use it.
  • readystack-payroll-tax-spreadsheet/trunk/readystack-payroll-tax-spreadsheet.php

    r3712579 r3713964  
    33 * Plugin Name:       ReadyStack Payroll Tax Spreadsheet
    44 * Description:       Deposit short and the IRS adds 2% to 15% of the shortfall under IRC § 6656.
    5  * Version:           1.0.1
     5 * Version:           1.0.2
    66 * Requires at least: 6.0
    77 * Requires PHP:      7.4
    … …  
    2323 */
    2424function readystack_payroll_tax_spreadsheet_register_assets() {
    25         wp_register_style( 'readystack-payroll-tax-spreadsheet', plugins_url( 'assets/tool.css', __FILE__ ), array(), '1.0.1' );
    26         wp_register_script( 'readystack-payroll-tax-spreadsheet-1', plugins_url( 'assets/tool-1.js', __FILE__ ), array(), '1.0.1', true );
    27         wp_register_script( 'readystack-payroll-tax-spreadsheet-2', plugins_url( 'assets/tool-2.js', __FILE__ ), array( 'readystack-payroll-tax-spreadsheet-1' ), '1.0.1', true );
    28         wp_register_script( 'readystack-payroll-tax-spreadsheet-3', plugins_url( 'assets/tool-3.js', __FILE__ ), array( 'readystack-payroll-tax-spreadsheet-2' ), '1.0.1', true );
    29         wp_register_script( 'readystack-payroll-tax-spreadsheet-4', plugins_url( 'assets/tool-4.js', __FILE__ ), array( 'readystack-payroll-tax-spreadsheet-3' ), '1.0.1', true );
     25        wp_register_style( 'readystack-payroll-tax-spreadsheet', plugins_url( 'assets/tool.css', __FILE__ ), array(), '1.0.2' );
     26        wp_register_script( 'readystack-payroll-tax-spreadsheet-1', plugins_url( 'assets/tool-1.js', __FILE__ ), array(), '1.0.2', true );
     27        wp_register_script( 'readystack-payroll-tax-spreadsheet-2', plugins_url( 'assets/tool-2.js', __FILE__ ), array( 'readystack-payroll-tax-spreadsheet-1' ), '1.0.2', true );
     28        wp_register_script( 'readystack-payroll-tax-spreadsheet-3', plugins_url( 'assets/tool-3.js', __FILE__ ), array( 'readystack-payroll-tax-spreadsheet-2' ), '1.0.2', true );
     29        wp_register_script( 'readystack-payroll-tax-spreadsheet-4', plugins_url( 'assets/tool-4.js', __FILE__ ), array( 'readystack-payroll-tax-spreadsheet-3' ), '1.0.2', true );
    3030}
    3131add_action( 'wp_enqueue_scripts', 'readystack_payroll_tax_spreadsheet_register_assets' );
Note: See TracChangeset for help on using the changeset viewer.