evalstate HF Staff commited on
Commit
faa32d1
·
verified ·
1 Parent(s): 043ea61

Add logo sizing and optional editable header to preview and PNG/SVG exports

Browse files
.gitattributes CHANGED
@@ -36,3 +36,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
36
  previews/amber.png filter=lfs diff=lfs merge=lfs -text
37
  previews/ice.png filter=lfs diff=lfs merge=lfs -text
38
  previews/phosphor.png filter=lfs diff=lfs merge=lfs -text
 
 
36
  previews/amber.png filter=lfs diff=lfs merge=lfs -text
37
  previews/ice.png filter=lfs diff=lfs merge=lfs -text
38
  previews/phosphor.png filter=lfs diff=lfs merge=lfs -text
39
+ previews/options.png filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -19,10 +19,10 @@ npm ci
19
  npm run dev
20
  ```
21
 
22
- Build: `npm run build`. Deploy the source and `dist/` to this static Space after rebuilding.
23
 
24
  Tests: `npx playwright install chromium`, start the dev server, then `npm test`. Tests decode all three PNG exports, check invalid input, SVG download, logo toggle, and mobile overflow.
25
 
26
  Logo: https://huggingface.co/datasets/huggingface/brand-assets/blob/main/hf-logo-pirate.png
27
 
28
- Always test your final QR at its intended print size with your phone, especially when adding a logo or using a long URL. Downloads contain the clean themed QR and logo, without the decorative preview frame.
 
19
  npm run dev
20
  ```
21
 
22
+ Build: `npm run build`. Upload the contents of `dist/` to the Space root after rebuilding; source files live under `source/` on the Space.
23
 
24
  Tests: `npx playwright install chromium`, start the dev server, then `npm test`. Tests decode all three PNG exports, check invalid input, SVG download, logo toggle, and mobile overflow.
25
 
26
  Logo: https://huggingface.co/datasets/huggingface/brand-assets/blob/main/hf-logo-pirate.png
27
 
28
+ Always test your final QR at its intended print size with your phone, especially when adding a logo or using a long URL. Logo size is adjustable from 12% to 26%. Downloads contain the themed QR and logo, plus an optional editable header (up to 40 characters), with no footer. Header settings apply to PNG and SVG. Automated scan tests cover 12%, 17%, 22%, and 26% in all themes for the default URL; other URLs and real-world cameras may behave differently.
assets/index-C7KWe_AC.js ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),s=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},c=(n,r,o)=>(o=n==null?{}:e(i(n)),s(r||!n||!n.__esModule||!a.call(n,`default`)?t(o,`default`,{value:n,enumerable:!0}):o,n));(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var l=o(((e,t)=>{t.exports=function(){return typeof Promise==`function`&&Promise.prototype&&Promise.prototype.then}})),u=o((e=>{var t,n=[0,26,44,70,100,134,172,196,242,292,346,404,466,532,581,655,733,815,901,991,1085,1156,1258,1364,1474,1588,1706,1828,1921,2051,2185,2323,2465,2611,2761,2876,3034,3196,3362,3532,3706];e.getSymbolSize=function(e){if(!e)throw Error(`"version" cannot be null or undefined`);if(e<1||e>40)throw Error(`"version" should be in range from 1 to 40`);return e*4+17},e.getSymbolTotalCodewords=function(e){return n[e]},e.getBCHDigit=function(e){let t=0;for(;e!==0;)t++,e>>>=1;return t},e.setToSJISFunction=function(e){if(typeof e!=`function`)throw Error(`"toSJISFunc" is not a valid function.`);t=e},e.isKanjiModeEnabled=function(){return t!==void 0},e.toSJIS=function(e){return t(e)}})),d=o((e=>{e.L={bit:1},e.M={bit:0},e.Q={bit:3},e.H={bit:2};function t(t){if(typeof t!=`string`)throw Error(`Param is not a string`);switch(t.toLowerCase()){case`l`:case`low`:return e.L;case`m`:case`medium`:return e.M;case`q`:case`quartile`:return e.Q;case`h`:case`high`:return e.H;default:throw Error(`Unknown EC Level: `+t)}}e.isValid=function(e){return e&&e.bit!==void 0&&e.bit>=0&&e.bit<4},e.from=function(n,r){if(e.isValid(n))return n;try{return t(n)}catch{return r}}})),f=o(((e,t)=>{function n(){this.buffer=[],this.length=0}n.prototype={get:function(e){let t=Math.floor(e/8);return(this.buffer[t]>>>7-e%8&1)==1},put:function(e,t){for(let n=0;n<t;n++)this.putBit((e>>>t-n-1&1)==1)},getLengthInBits:function(){return this.length},putBit:function(e){let t=Math.floor(this.length/8);this.buffer.length<=t&&this.buffer.push(0),e&&(this.buffer[t]|=128>>>this.length%8),this.length++}},t.exports=n})),p=o(((e,t)=>{function n(e){if(!e||e<1)throw Error(`BitMatrix size must be defined and greater than 0`);this.size=e,this.data=new Uint8Array(e*e),this.reservedBit=new Uint8Array(e*e)}n.prototype.set=function(e,t,n,r){let i=e*this.size+t;this.data[i]=n,r&&(this.reservedBit[i]=!0)},n.prototype.get=function(e,t){return this.data[e*this.size+t]},n.prototype.xor=function(e,t,n){this.data[e*this.size+t]^=n},n.prototype.isReserved=function(e,t){return this.reservedBit[e*this.size+t]},t.exports=n})),m=o((e=>{var t=u().getSymbolSize;e.getRowColCoords=function(e){if(e===1)return[];let n=Math.floor(e/7)+2,r=t(e),i=r===145?26:Math.ceil((r-13)/(2*n-2))*2,a=[r-7];for(let e=1;e<n-1;e++)a[e]=a[e-1]-i;return a.push(6),a.reverse()},e.getPositions=function(t){let n=[],r=e.getRowColCoords(t),i=r.length;for(let e=0;e<i;e++)for(let t=0;t<i;t++)e===0&&t===0||e===0&&t===i-1||e===i-1&&t===0||n.push([r[e],r[t]]);return n}})),h=o((e=>{var t=u().getSymbolSize,n=7;e.getPositions=function(e){let r=t(e);return[[0,0],[r-n,0],[0,r-n]]}})),g=o((e=>{e.Patterns={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7};var t={N1:3,N2:3,N3:40,N4:10};e.isValid=function(e){return e!=null&&e!==``&&!isNaN(e)&&e>=0&&e<=7},e.from=function(t){return e.isValid(t)?parseInt(t,10):void 0},e.getPenaltyN1=function(e){let n=e.size,r=0,i=0,a=0,o=null,s=null;for(let c=0;c<n;c++){i=a=0,o=s=null;for(let l=0;l<n;l++){let n=e.get(c,l);n===o?i++:(i>=5&&(r+=t.N1+(i-5)),o=n,i=1),n=e.get(l,c),n===s?a++:(a>=5&&(r+=t.N1+(a-5)),s=n,a=1)}i>=5&&(r+=t.N1+(i-5)),a>=5&&(r+=t.N1+(a-5))}return r},e.getPenaltyN2=function(e){let n=e.size,r=0;for(let t=0;t<n-1;t++)for(let i=0;i<n-1;i++){let n=e.get(t,i)+e.get(t,i+1)+e.get(t+1,i)+e.get(t+1,i+1);(n===4||n===0)&&r++}return r*t.N2},e.getPenaltyN3=function(e){let n=e.size,r=0,i=0,a=0;for(let t=0;t<n;t++){i=a=0;for(let o=0;o<n;o++)i=i<<1&2047|e.get(t,o),o>=10&&(i===1488||i===93)&&r++,a=a<<1&2047|e.get(o,t),o>=10&&(a===1488||a===93)&&r++}return r*t.N3},e.getPenaltyN4=function(e){let n=0,r=e.data.length;for(let t=0;t<r;t++)n+=e.data[t];return Math.abs(Math.ceil(n*100/r/5)-10)*t.N4};function n(t,n,r){switch(t){case e.Patterns.PATTERN000:return(n+r)%2==0;case e.Patterns.PATTERN001:return n%2==0;case e.Patterns.PATTERN010:return r%3==0;case e.Patterns.PATTERN011:return(n+r)%3==0;case e.Patterns.PATTERN100:return(Math.floor(n/2)+Math.floor(r/3))%2==0;case e.Patterns.PATTERN101:return n*r%2+n*r%3==0;case e.Patterns.PATTERN110:return(n*r%2+n*r%3)%2==0;case e.Patterns.PATTERN111:return(n*r%3+(n+r)%2)%2==0;default:throw Error(`bad maskPattern:`+t)}}e.applyMask=function(e,t){let r=t.size;for(let i=0;i<r;i++)for(let a=0;a<r;a++)t.isReserved(a,i)||t.xor(a,i,n(e,a,i))},e.getBestMask=function(t,n){let r=Object.keys(e.Patterns).length,i=0,a=1/0;for(let o=0;o<r;o++){n(o),e.applyMask(o,t);let r=e.getPenaltyN1(t)+e.getPenaltyN2(t)+e.getPenaltyN3(t)+e.getPenaltyN4(t);e.applyMask(o,t),r<a&&(a=r,i=o)}return i}})),_=o((e=>{var t=d(),n=[1,1,1,1,1,1,1,1,1,1,2,2,1,2,2,4,1,2,4,4,2,4,4,4,2,4,6,5,2,4,6,6,2,5,8,8,4,5,8,8,4,5,8,11,4,8,10,11,4,9,12,16,4,9,16,16,6,10,12,18,6,10,17,16,6,11,16,19,6,13,18,21,7,14,21,25,8,16,20,25,8,17,23,25,9,17,23,34,9,18,25,30,10,20,27,32,12,21,29,35,12,23,34,37,12,25,34,40,13,26,35,42,14,28,38,45,15,29,40,48,16,31,43,51,17,33,45,54,18,35,48,57,19,37,51,60,19,38,53,63,20,40,56,66,21,43,59,70,22,45,62,74,24,47,65,77,25,49,68,81],r=[7,10,13,17,10,16,22,28,15,26,36,44,20,36,52,64,26,48,72,88,36,64,96,112,40,72,108,130,48,88,132,156,60,110,160,192,72,130,192,224,80,150,224,264,96,176,260,308,104,198,288,352,120,216,320,384,132,240,360,432,144,280,408,480,168,308,448,532,180,338,504,588,196,364,546,650,224,416,600,700,224,442,644,750,252,476,690,816,270,504,750,900,300,560,810,960,312,588,870,1050,336,644,952,1110,360,700,1020,1200,390,728,1050,1260,420,784,1140,1350,450,812,1200,1440,480,868,1290,1530,510,924,1350,1620,540,980,1440,1710,570,1036,1530,1800,570,1064,1590,1890,600,1120,1680,1980,630,1204,1770,2100,660,1260,1860,2220,720,1316,1950,2310,750,1372,2040,2430];e.getBlocksCount=function(e,r){switch(r){case t.L:return n[(e-1)*4+0];case t.M:return n[(e-1)*4+1];case t.Q:return n[(e-1)*4+2];case t.H:return n[(e-1)*4+3];default:return}},e.getTotalCodewordsCount=function(e,n){switch(n){case t.L:return r[(e-1)*4+0];case t.M:return r[(e-1)*4+1];case t.Q:return r[(e-1)*4+2];case t.H:return r[(e-1)*4+3];default:return}}})),v=o((e=>{var t=new Uint8Array(512),n=new Uint8Array(256);(function(){let e=1;for(let r=0;r<255;r++)t[r]=e,n[e]=r,e<<=1,e&256&&(e^=285);for(let e=255;e<512;e++)t[e]=t[e-255]})(),e.log=function(e){if(e<1)throw Error(`log(`+e+`)`);return n[e]},e.exp=function(e){return t[e]},e.mul=function(e,r){return e===0||r===0?0:t[n[e]+n[r]]}})),y=o((e=>{var t=v();e.mul=function(e,n){let r=new Uint8Array(e.length+n.length-1);for(let i=0;i<e.length;i++)for(let a=0;a<n.length;a++)r[i+a]^=t.mul(e[i],n[a]);return r},e.mod=function(e,n){let r=new Uint8Array(e);for(;r.length-n.length>=0;){let e=r[0];for(let i=0;i<n.length;i++)r[i]^=t.mul(n[i],e);let i=0;for(;i<r.length&&r[i]===0;)i++;r=r.slice(i)}return r},e.generateECPolynomial=function(n){let r=new Uint8Array([1]);for(let i=0;i<n;i++)r=e.mul(r,new Uint8Array([1,t.exp(i)]));return r}})),b=o(((e,t)=>{var n=y();function r(e){this.genPoly=void 0,this.degree=e,this.degree&&this.initialize(this.degree)}r.prototype.initialize=function(e){this.degree=e,this.genPoly=n.generateECPolynomial(this.degree)},r.prototype.encode=function(e){if(!this.genPoly)throw Error(`Encoder not initialized`);let t=new Uint8Array(e.length+this.degree);t.set(e);let r=n.mod(t,this.genPoly),i=this.degree-r.length;if(i>0){let e=new Uint8Array(this.degree);return e.set(r,i),e}return r},t.exports=r})),x=o((e=>{e.isValid=function(e){return!isNaN(e)&&e>=1&&e<=40}})),S=o((e=>{var t=`[0-9]+`,n=`[A-Z $%*+\\-./:]+`,r=`(?:[u3000-u303F]|[u3040-u309F]|[u30A0-u30FF]|[uFF00-uFFEF]|[u4E00-u9FAF]|[u2605-u2606]|[u2190-u2195]|u203B|[u2010u2015u2018u2019u2025u2026u201Cu201Du2225u2260]|[u0391-u0451]|[u00A7u00A8u00B1u00B4u00D7u00F7])+`;r=r.replace(/u/g,`\\u`);var i=`(?:(?![A-Z0-9 $%*+\\-./:]|`+r+`)(?:.|[\r
2
+ ]))+`;e.KANJI=new RegExp(r,`g`),e.BYTE_KANJI=RegExp(`[^A-Z0-9 $%*+\\-./:]+`,`g`),e.BYTE=new RegExp(i,`g`),e.NUMERIC=new RegExp(t,`g`),e.ALPHANUMERIC=new RegExp(n,`g`);var a=RegExp(`^`+r+`$`),o=RegExp(`^[0-9]+$`),s=RegExp(`^[A-Z0-9 $%*+\\-./:]+$`);e.testKanji=function(e){return a.test(e)},e.testNumeric=function(e){return o.test(e)},e.testAlphanumeric=function(e){return s.test(e)}})),C=o((e=>{var t=x(),n=S();e.NUMERIC={id:`Numeric`,bit:1,ccBits:[10,12,14]},e.ALPHANUMERIC={id:`Alphanumeric`,bit:2,ccBits:[9,11,13]},e.BYTE={id:`Byte`,bit:4,ccBits:[8,16,16]},e.KANJI={id:`Kanji`,bit:8,ccBits:[8,10,12]},e.MIXED={bit:-1},e.getCharCountIndicator=function(e,n){if(!e.ccBits)throw Error(`Invalid mode: `+e);if(!t.isValid(n))throw Error(`Invalid version: `+n);return n>=1&&n<10?e.ccBits[0]:n<27?e.ccBits[1]:e.ccBits[2]},e.getBestModeForData=function(t){return n.testNumeric(t)?e.NUMERIC:n.testAlphanumeric(t)?e.ALPHANUMERIC:n.testKanji(t)?e.KANJI:e.BYTE},e.toString=function(e){if(e&&e.id)return e.id;throw Error(`Invalid mode`)},e.isValid=function(e){return e&&e.bit&&e.ccBits};function r(t){if(typeof t!=`string`)throw Error(`Param is not a string`);switch(t.toLowerCase()){case`numeric`:return e.NUMERIC;case`alphanumeric`:return e.ALPHANUMERIC;case`kanji`:return e.KANJI;case`byte`:return e.BYTE;default:throw Error(`Unknown mode: `+t)}}e.from=function(t,n){if(e.isValid(t))return t;try{return r(t)}catch{return n}}})),w=o((e=>{var t=u(),n=_(),r=d(),i=C(),a=x(),o=7973,s=t.getBCHDigit(o);function c(t,n,r){for(let i=1;i<=40;i++)if(n<=e.getCapacity(i,r,t))return i}function l(e,t){return i.getCharCountIndicator(e,t)+4}function f(e,t){let n=0;return e.forEach(function(e){let r=l(e.mode,t);n+=r+e.getBitsLength()}),n}function p(t,n){for(let r=1;r<=40;r++)if(f(t,r)<=e.getCapacity(r,n,i.MIXED))return r}e.from=function(e,t){return a.isValid(e)?parseInt(e,10):t},e.getCapacity=function(e,r,o){if(!a.isValid(e))throw Error(`Invalid QR Code version`);o===void 0&&(o=i.BYTE);let s=(t.getSymbolTotalCodewords(e)-n.getTotalCodewordsCount(e,r))*8;if(o===i.MIXED)return s;let c=s-l(o,e);switch(o){case i.NUMERIC:return Math.floor(c/10*3);case i.ALPHANUMERIC:return Math.floor(c/11*2);case i.KANJI:return Math.floor(c/13);case i.BYTE:default:return Math.floor(c/8)}},e.getBestVersionForData=function(e,t){let n,i=r.from(t,r.M);if(Array.isArray(e)){if(e.length>1)return p(e,i);if(e.length===0)return 1;n=e[0]}else n=e;return c(n.mode,n.getLength(),i)},e.getEncodedBits=function(e){if(!a.isValid(e)||e<7)throw Error(`Invalid QR Code version`);let n=e<<12;for(;t.getBCHDigit(n)-s>=0;)n^=o<<t.getBCHDigit(n)-s;return e<<12|n}})),T=o((e=>{var t=u(),n=1335,r=21522,i=t.getBCHDigit(n);e.getEncodedBits=function(e,a){let o=e.bit<<3|a,s=o<<10;for(;t.getBCHDigit(s)-i>=0;)s^=n<<t.getBCHDigit(s)-i;return(o<<10|s)^r}})),E=o(((e,t)=>{var n=C();function r(e){this.mode=n.NUMERIC,this.data=e.toString()}r.getBitsLength=function(e){return 10*Math.floor(e/3)+(e%3?e%3*3+1:0)},r.prototype.getLength=function(){return this.data.length},r.prototype.getBitsLength=function(){return r.getBitsLength(this.data.length)},r.prototype.write=function(e){let t,n,r;for(t=0;t+3<=this.data.length;t+=3)n=this.data.substr(t,3),r=parseInt(n,10),e.put(r,10);let i=this.data.length-t;i>0&&(n=this.data.substr(t),r=parseInt(n,10),e.put(r,i*3+1))},t.exports=r})),D=o(((e,t)=>{var n=C(),r=`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:`.split(``);function i(e){this.mode=n.ALPHANUMERIC,this.data=e}i.getBitsLength=function(e){return 11*Math.floor(e/2)+e%2*6},i.prototype.getLength=function(){return this.data.length},i.prototype.getBitsLength=function(){return i.getBitsLength(this.data.length)},i.prototype.write=function(e){let t=0;for(;t+2<=this.data.length;t+=2){let n=r.indexOf(this.data[t])*45;n+=r.indexOf(this.data[t+1]),e.put(n,11)}this.data.length%2&&e.put(r.indexOf(this.data[t]),6)},t.exports=i})),O=o(((e,t)=>{var n=C();function r(e){this.mode=n.BYTE,this.data=typeof e==`string`?new TextEncoder().encode(e):new Uint8Array(e)}r.getBitsLength=function(e){return e*8},r.prototype.getLength=function(){return this.data.length},r.prototype.getBitsLength=function(){return r.getBitsLength(this.data.length)},r.prototype.write=function(e){for(let t=0,n=this.data.length;t<n;t++)e.put(this.data[t],8)},t.exports=r})),k=o(((e,t)=>{var n=C(),r=u();function i(e){this.mode=n.KANJI,this.data=e}i.getBitsLength=function(e){return e*13},i.prototype.getLength=function(){return this.data.length},i.prototype.getBitsLength=function(){return i.getBitsLength(this.data.length)},i.prototype.write=function(e){let t=0;for(;t<this.data.length;t++){let n=r.toSJIS(this.data[t]);if(n>=33088&&n<=40956)n-=33088;else if(n>=57408&&n<=60351)n-=49472;else throw Error(`Invalid SJIS character: `+this.data[t]+`
3
+ Make sure your charset is UTF-8`);n=(n>>>8&255)*192+(n&255),e.put(n,13)}},t.exports=i})),A=o(((e,t)=>{var n={single_source_shortest_paths:function(e,t,r){var i={},a={};a[t]=0;var o=n.PriorityQueue.make();o.push(t,0);for(var s,c,l,u,d,f,p,m,h;!o.empty();)for(l in s=o.pop(),c=s.value,u=s.cost,d=e[c]||{},d)d.hasOwnProperty(l)&&(f=d[l],p=u+f,m=a[l],h=a[l]===void 0,(h||m>p)&&(a[l]=p,o.push(l,p),i[l]=c));if(r!==void 0&&a[r]===void 0){var g=[`Could not find a path from `,t,` to `,r,`.`].join(``);throw Error(g)}return i},extract_shortest_path_from_predecessor_list:function(e,t){for(var n=[],r=t;r;)n.push(r),e[r],r=e[r];return n.reverse(),n},find_path:function(e,t,r){var i=n.single_source_shortest_paths(e,t,r);return n.extract_shortest_path_from_predecessor_list(i,r)},PriorityQueue:{make:function(e){var t=n.PriorityQueue,r={},i;for(i in e||={},t)t.hasOwnProperty(i)&&(r[i]=t[i]);return r.queue=[],r.sorter=e.sorter||t.default_sorter,r},default_sorter:function(e,t){return e.cost-t.cost},push:function(e,t){var n={value:e,cost:t};this.queue.push(n),this.queue.sort(this.sorter)},pop:function(){return this.queue.shift()},empty:function(){return this.queue.length===0}}};t!==void 0&&(t.exports=n)})),j=o((e=>{var t=C(),n=E(),r=D(),i=O(),a=k(),o=S(),s=u(),c=A();function l(e){return unescape(encodeURIComponent(e)).length}function d(e,t,n){let r=[],i;for(;(i=e.exec(n))!==null;)r.push({data:i[0],index:i.index,mode:t,length:i[0].length});return r}function f(e){let n=d(o.NUMERIC,t.NUMERIC,e),r=d(o.ALPHANUMERIC,t.ALPHANUMERIC,e),i,a;return s.isKanjiModeEnabled()?(i=d(o.BYTE,t.BYTE,e),a=d(o.KANJI,t.KANJI,e)):(i=d(o.BYTE_KANJI,t.BYTE,e),a=[]),n.concat(r,i,a).sort(function(e,t){return e.index-t.index}).map(function(e){return{data:e.data,mode:e.mode,length:e.length}})}function p(e,o){switch(o){case t.NUMERIC:return n.getBitsLength(e);case t.ALPHANUMERIC:return r.getBitsLength(e);case t.KANJI:return a.getBitsLength(e);case t.BYTE:return i.getBitsLength(e)}}function m(e){return e.reduce(function(e,t){let n=e.length-1>=0?e[e.length-1]:null;return n&&n.mode===t.mode?(e[e.length-1].data+=t.data,e):(e.push(t),e)},[])}function h(e){let n=[];for(let r=0;r<e.length;r++){let i=e[r];switch(i.mode){case t.NUMERIC:n.push([i,{data:i.data,mode:t.ALPHANUMERIC,length:i.length},{data:i.data,mode:t.BYTE,length:i.length}]);break;case t.ALPHANUMERIC:n.push([i,{data:i.data,mode:t.BYTE,length:i.length}]);break;case t.KANJI:n.push([i,{data:i.data,mode:t.BYTE,length:l(i.data)}]);break;case t.BYTE:n.push([{data:i.data,mode:t.BYTE,length:l(i.data)}])}}return n}function g(e,n){let r={},i={start:{}},a=[`start`];for(let o=0;o<e.length;o++){let s=e[o],c=[];for(let e=0;e<s.length;e++){let l=s[e],u=``+o+e;c.push(u),r[u]={node:l,lastCount:0},i[u]={};for(let e=0;e<a.length;e++){let o=a[e];r[o]&&r[o].node.mode===l.mode?(i[o][u]=p(r[o].lastCount+l.length,l.mode)-p(r[o].lastCount,l.mode),r[o].lastCount+=l.length):(r[o]&&(r[o].lastCount=l.length),i[o][u]=p(l.length,l.mode)+4+t.getCharCountIndicator(l.mode,n))}}a=c}for(let e=0;e<a.length;e++)i[a[e]].end=0;return{map:i,table:r}}function _(e,o){let c,l=t.getBestModeForData(e);if(c=t.from(o,l),c!==t.BYTE&&c.bit<l.bit)throw Error(`"`+e+`" cannot be encoded with mode `+t.toString(c)+`.
4
+ Suggested mode is: `+t.toString(l));switch(c===t.KANJI&&!s.isKanjiModeEnabled()&&(c=t.BYTE),c){case t.NUMERIC:return new n(e);case t.ALPHANUMERIC:return new r(e);case t.KANJI:return new a(e);case t.BYTE:return new i(e)}}e.fromArray=function(e){return e.reduce(function(e,t){return typeof t==`string`?e.push(_(t,null)):t.data&&e.push(_(t.data,t.mode)),e},[])},e.fromString=function(t,n){let r=g(h(f(t,s.isKanjiModeEnabled())),n),i=c.find_path(r.map,`start`,`end`),a=[];for(let e=1;e<i.length-1;e++)a.push(r.table[i[e]].node);return e.fromArray(m(a))},e.rawSplit=function(t){return e.fromArray(f(t,s.isKanjiModeEnabled()))}})),M=o((e=>{var t=u(),n=d(),r=f(),i=p(),a=m(),o=h(),s=g(),c=_(),l=b(),v=w(),y=T(),x=C(),S=j();function E(e,t){let n=e.size,r=o.getPositions(t);for(let t=0;t<r.length;t++){let i=r[t][0],a=r[t][1];for(let t=-1;t<=7;t++)if(!(i+t<=-1||n<=i+t))for(let r=-1;r<=7;r++)a+r<=-1||n<=a+r||(t>=0&&t<=6&&(r===0||r===6)||r>=0&&r<=6&&(t===0||t===6)||t>=2&&t<=4&&r>=2&&r<=4?e.set(i+t,a+r,!0,!0):e.set(i+t,a+r,!1,!0))}}function D(e){let t=e.size;for(let n=8;n<t-8;n++){let t=n%2==0;e.set(n,6,t,!0),e.set(6,n,t,!0)}}function O(e,t){let n=a.getPositions(t);for(let t=0;t<n.length;t++){let r=n[t][0],i=n[t][1];for(let t=-2;t<=2;t++)for(let n=-2;n<=2;n++)t===-2||t===2||n===-2||n===2||t===0&&n===0?e.set(r+t,i+n,!0,!0):e.set(r+t,i+n,!1,!0)}}function k(e,t){let n=e.size,r=v.getEncodedBits(t),i,a,o;for(let t=0;t<18;t++)i=Math.floor(t/3),a=t%3+n-8-3,o=(r>>t&1)==1,e.set(i,a,o,!0),e.set(a,i,o,!0)}function A(e,t,n){let r=e.size,i=y.getEncodedBits(t,n),a,o;for(a=0;a<15;a++)o=(i>>a&1)==1,a<6?e.set(a,8,o,!0):a<8?e.set(a+1,8,o,!0):e.set(r-15+a,8,o,!0),a<8?e.set(8,r-a-1,o,!0):a<9?e.set(8,15-a-1+1,o,!0):e.set(8,15-a-1,o,!0);e.set(r-8,8,1,!0)}function M(e,t){let n=e.size,r=-1,i=n-1,a=7,o=0;for(let s=n-1;s>0;s-=2)for(s===6&&s--;;){for(let n=0;n<2;n++)if(!e.isReserved(i,s-n)){let r=!1;o<t.length&&(r=(t[o]>>>a&1)==1),e.set(i,s-n,r),a--,a===-1&&(o++,a=7)}if(i+=r,i<0||n<=i){i-=r,r=-r;break}}}function N(e,n,i){let a=new r;i.forEach(function(t){a.put(t.mode.bit,4),a.put(t.getLength(),x.getCharCountIndicator(t.mode,e)),t.write(a)});let o=(t.getSymbolTotalCodewords(e)-c.getTotalCodewordsCount(e,n))*8;for(a.getLengthInBits()+4<=o&&a.put(0,4);a.getLengthInBits()%8!=0;)a.putBit(0);let s=(o-a.getLengthInBits())/8;for(let e=0;e<s;e++)a.put(e%2?17:236,8);return P(a,e,n)}function P(e,n,r){let i=t.getSymbolTotalCodewords(n),a=i-c.getTotalCodewordsCount(n,r),o=c.getBlocksCount(n,r),s=o-i%o,u=Math.floor(i/o),d=Math.floor(a/o),f=d+1,p=u-d,m=new l(p),h=0,g=Array(o),_=Array(o),v=0,y=new Uint8Array(e.buffer);for(let e=0;e<o;e++){let t=e<s?d:f;g[e]=y.slice(h,h+t),_[e]=m.encode(g[e]),h+=t,v=Math.max(v,t)}let b=new Uint8Array(i),x=0,S,C;for(S=0;S<v;S++)for(C=0;C<o;C++)S<g[C].length&&(b[x++]=g[C][S]);for(S=0;S<p;S++)for(C=0;C<o;C++)b[x++]=_[C][S];return b}function F(e,n,r,a){let o;if(Array.isArray(e))o=S.fromArray(e);else if(typeof e==`string`){let t=n;if(!t){let n=S.rawSplit(e);t=v.getBestVersionForData(n,r)}o=S.fromString(e,t||40)}else throw Error(`Invalid data`);let c=v.getBestVersionForData(o,r);if(!c)throw Error(`The amount of data is too big to be stored in a QR Code`);if(!n)n=c;else if(n<c)throw Error(`
5
+ The chosen QR Code version cannot contain this amount of data.
6
+ Minimum version required to store current data is: `+c+`.
7
+ `);let l=N(n,r,o),u=new i(t.getSymbolSize(n));return E(u,n),D(u),O(u,n),A(u,r,0),n>=7&&k(u,n),M(u,l),isNaN(a)&&(a=s.getBestMask(u,A.bind(null,u,r))),s.applyMask(a,u),A(u,r,a),{modules:u,version:n,errorCorrectionLevel:r,maskPattern:a,segments:o}}e.create=function(e,r){if(e===void 0||e===``)throw Error(`No input text`);let i=n.M,a,o;return r!==void 0&&(i=n.from(r.errorCorrectionLevel,n.M),a=v.from(r.version),o=s.from(r.maskPattern),r.toSJISFunc&&t.setToSJISFunction(r.toSJISFunc)),F(e,a,i,o)}})),N=o((e=>{function t(e){if(typeof e==`number`&&(e=e.toString()),typeof e!=`string`)throw Error(`Color should be defined as hex string`);let t=e.slice().replace(`#`,``).split(``);if(t.length<3||t.length===5||t.length>8)throw Error(`Invalid hex color: `+e);(t.length===3||t.length===4)&&(t=Array.prototype.concat.apply([],t.map(function(e){return[e,e]}))),t.length===6&&t.push(`F`,`F`);let n=parseInt(t.join(``),16);return{r:n>>24&255,g:n>>16&255,b:n>>8&255,a:n&255,hex:`#`+t.slice(0,6).join(``)}}e.getOptions=function(e){e||={},e.color||(e.color={});let n=e.margin===void 0||e.margin===null||e.margin<0?4:e.margin,r=e.width&&e.width>=21?e.width:void 0,i=e.scale||4;return{width:r,scale:r?4:i,margin:n,color:{dark:t(e.color.dark||`#000000ff`),light:t(e.color.light||`#ffffffff`)},type:e.type,rendererOpts:e.rendererOpts||{}}},e.getScale=function(e,t){return t.width&&t.width>=e+t.margin*2?t.width/(e+t.margin*2):t.scale},e.getImageWidth=function(t,n){let r=e.getScale(t,n);return Math.floor((t+n.margin*2)*r)},e.qrToImageData=function(t,n,r){let i=n.modules.size,a=n.modules.data,o=e.getScale(i,r),s=Math.floor((i+r.margin*2)*o),c=r.margin*o,l=[r.color.light,r.color.dark];for(let e=0;e<s;e++)for(let n=0;n<s;n++){let u=(e*s+n)*4,d=r.color.light;if(e>=c&&n>=c&&e<s-c&&n<s-c){let t=Math.floor((e-c)/o),r=Math.floor((n-c)/o);d=l[+!!a[t*i+r]]}t[u++]=d.r,t[u++]=d.g,t[u++]=d.b,t[u]=d.a}}})),P=o((e=>{var t=N();function n(e,t,n){e.clearRect(0,0,t.width,t.height),t.style||={},t.height=n,t.width=n,t.style.height=n+`px`,t.style.width=n+`px`}function r(){try{return document.createElement(`canvas`)}catch{throw Error(`You need to specify a canvas element`)}}e.render=function(e,i,a){let o=a,s=i;o===void 0&&(!i||!i.getContext)&&(o=i,i=void 0),i||(s=r()),o=t.getOptions(o);let c=t.getImageWidth(e.modules.size,o),l=s.getContext(`2d`),u=l.createImageData(c,c);return t.qrToImageData(u.data,e,o),n(l,s,c),l.putImageData(u,0,0),s},e.renderToDataURL=function(t,n,r){let i=r;i===void 0&&(!n||!n.getContext)&&(i=n,n=void 0),i||={};let a=e.render(t,n,i),o=i.type||`image/png`,s=i.rendererOpts||{};return a.toDataURL(o,s.quality)}})),F=o((e=>{var t=N();function n(e,t){let n=e.a/255,r=t+`="`+e.hex+`"`;return n<1?r+` `+t+`-opacity="`+n.toFixed(2).slice(1)+`"`:r}function r(e,t,n){let r=e+t;return n!==void 0&&(r+=` `+n),r}function i(e,t,n){let i=``,a=0,o=!1,s=0;for(let c=0;c<e.length;c++){let l=Math.floor(c%t),u=Math.floor(c/t);!l&&!o&&(o=!0),e[c]?(s++,c>0&&l>0&&e[c-1]||(i+=o?r(`M`,l+n,.5+u+n):r(`m`,a,0),a=0,o=!1),l+1<t&&e[c+1]||(i+=r(`h`,s),s=0)):a++}return i}e.render=function(e,r,a){let o=t.getOptions(r),s=e.modules.size,c=e.modules.data,l=s+o.margin*2,u=o.color.light.a?`<path `+n(o.color.light,`fill`)+` d="M0 0h`+l+`v`+l+`H0z"/>`:``,d=`<path `+n(o.color.dark,`stroke`)+` d="`+i(c,s,o.margin)+`"/>`,f=`viewBox="0 0 `+l+` `+l+`"`,p=`<svg xmlns="http://www.w3.org/2000/svg" `+(o.width?`width="`+o.width+`" height="`+o.width+`" `:``)+f+` shape-rendering="crispEdges">`+u+d+`</svg>
8
+ `;return typeof a==`function`&&a(null,p),p}})),I=c(o((e=>{var t=l(),n=M(),r=P(),i=F();function a(e,r,i,a,o){let s=[].slice.call(arguments,1),c=s.length,l=typeof s[c-1]==`function`;if(!l&&!t())throw Error(`Callback required as last argument`);if(l){if(c<2)throw Error(`Too few arguments provided`);c===2?(o=i,i=r,r=a=void 0):c===3&&(r.getContext&&o===void 0?(o=a,a=void 0):(o=a,a=i,i=r,r=void 0))}else{if(c<1)throw Error(`Too few arguments provided`);return c===1?(i=r,r=a=void 0):c===2&&!r.getContext&&(a=i,i=r,r=void 0),new Promise(function(t,o){try{t(e(n.create(i,a),r,a))}catch(e){o(e)}})}try{let t=n.create(i,a);o(null,e(t,r,a))}catch(e){o(e)}}e.create=n.create,e.toCanvas=a.bind(null,r.render),e.toDataURL=a.bind(null,r.renderToDataURL),e.toString=a.bind(null,function(e,t,n){return i.render(e,n)})}))(),1),L=`https://huggingface.co/datasets/evalstate/mcp-clients`,R=[{id:`phosphor`,name:`Green phosphor`,code:`P1`,accent:`#a7f878`,ink:`#123822`,paper:`#edfadf`,description:`Late nights. Green screens. Pure signal.`},{id:`amber`,name:`Amber console`,code:`P2`,accent:`#ffbd69`,ink:`#492b0d`,paper:`#fff0d4`,description:`A warm glow from a different era.`},{id:`ice`,name:`Arctic terminal`,code:`P3`,accent:`#91d9ff`,ink:`#12304a`,paper:`#e9f6ff`,description:`Cold pixels. A very clear connection.`}],z=R[0],B=``,V=0,H=``,U=new Image,W=await(await fetch(`/hf-logo-pirate.png`)).blob();H=await new Promise(e=>{let t=new FileReader;t.onload=()=>e(t.result),t.readAsDataURL(W)}),U.src=H,await U.decode(),document.querySelector(`#app`).innerHTML=`
9
+ <header><a class="wordmark" href="./"><span class="mark">▥</span> SIGNAL<span class="slash">/</span><span class="muted">QR STUDIO</span></a><span class="online"><i></i> LOCAL ENCODING · NO TRACKING</span></header>
10
+ <main><div class="eyebrow">UTILITY_001 / LINKS → PIXELS</div><h1>Your link.<br>A little more <em>signal.</em></h1><p class="intro">QR codes with an old-school soul. Pick a frequency,<br class="desktop"> drop in a link, and send it out into the world.</p>
11
+ <div class="workspace"><section class="controls"><div class="section-heading"><span>01 / INPUT</span><span class="muted">HTTP(S)</span></div><label for="url">Where are we going?</label><div class="input-shell"><span>›</span><input id="url" type="url" spellcheck="false" aria-describedby="error" value="${L}"></div><p id="error" role="status" class="hint">Static by design. Your URL, directly encoded.</p>
12
+ <div class="section-heading theme-heading">02 / FREQUENCY</div><div class="themes">${R.map((e,t)=>`<button class="theme ${t===0?`selected`:``}" data-theme="${e.id}" aria-pressed="${t===0}" style="--swatch:${e.accent}"><span class="swatch"></span><span>${e.name}<small>${e.code} / ${[`CLASSIC`,`WARM`,`COOL`][t]}</small></span><span class="radio"></span></button>`).join(``)}</div>
13
+ <div class="logo-row"><div><label for="logo">Pirate on board</label><p>Hugging Face logo, front and center.</p></div><input id="logo" type="checkbox" checked role="switch"></div>
14
+ <div class="size-control"><label for="logo-size">Logo size <output id="size-value">17%</output></label><input id="logo-size" type="range" min="12" max="26" value="17" step="1"><p class="hint">Larger logos cover more data. Test with your phone before printing.</p></div>
15
+ <div class="logo-row"><div><label for="include-header">Include header</label><p>Shown in the preview and both downloads.</p></div><input id="include-header" type="checkbox" checked role="switch"></div><label class="header-label" for="header-text">Header text</label><input id="header-text" type="text" maxlength="40" value="HF / TRANSMISSION"><div class="note"><span>↳</span><p>Built for the real world.<br><span class="muted">High error correction. A clean quiet zone.<br>No redirects, accounts, or expiration.</span></p></div></section>
16
+ <section class="preview-panel"><div class="preview-bar"><span><i></i> LIVE PREVIEW</span><span id="preview-code">P1 / PHOSPHOR</span></div><div class="preview-stage"><div class="print-card"><div class="card-top"><span id="header-preview">HF / TRANSMISSION</span></div><div id="qr" aria-label="Generated QR code"></div></div></div><div class="preview-caption"><span id="description">${z.description}</span><span>ECC / H</span></div></section></div>
17
+ <div class="export-row"><div><span class="ready" id="status">● SIGNAL READY</span><p>Take your pixels with you.</p></div><div class="actions"><button id="svg">↓ Download SVG</button><button class="primary" id="png">↓ Download PNG <span>↗</span></button></div></div>
18
+ <footer><span>MADE OF PIXELS. BUILT FOR PEOPLE.</span><span>STATIC QR / OPEN DESTINATIONS <span class="cursor">▮</span></span></footer></main>`;var G=document.querySelector(`#url`);function K(e){try{let t=new URL(e);return[`http:`,`https:`].includes(t.protocol)&&!!t.hostname}catch{return!1}}function q(){V++;let e=document.querySelector(`#include-header`).checked;document.querySelector(`.card-top`).hidden=!e,document.querySelector(`#header-preview`).textContent=document.querySelector(`#header-text`).value,document.querySelector(`#header-text`).disabled=!e,document.querySelector(`#size-value`).textContent=document.querySelector(`#logo-size`).value+`%`,document.querySelector(`#logo-size`).disabled=!document.querySelector(`#logo`).checked;let t=G.value.trim(),n=K(t);if(document.querySelector(`#error`).textContent=n?`Static by design. Your URL, directly encoded.`:`Enter a complete URL starting with https:// or http://.`,G.setAttribute(`aria-invalid`,String(!n)),document.querySelectorAll(`.actions button`).forEach(e=>e.disabled=!n),!n){B=``,document.querySelector(`#qr`).innerHTML=`<p class="empty">AWAITING VALID URL</p>`,document.querySelector(`#status`).textContent=`○ AWAITING INPUT`;return}try{let e=I.create(t,{errorCorrectionLevel:`H`}),n=e.modules.size,r=n+8,i=Math.floor(n*Number(document.querySelector(`#logo-size`).value)/100),a=(r-i)/2,o=``;for(let t=0;t<n;t++)for(let r=0;r<n;r++)e.modules.get(t,r)&&(o+=`M${r+4} ${t+4}h1v1h-1z`);let s=document.querySelector(`#logo`).checked;B=`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1024" height="1024" viewBox="0 0 ${r} ${r}"><rect width="${r}" height="${r}" fill="${z.paper}"/><path d="${o}" fill="${z.ink}"/>${s?`<rect x="${a-.6}" y="${a-.6}" width="${i+1.2}" height="${i+1.2}" rx=".7" fill="${z.paper}"/><image x="${a}" y="${a}" width="${i}" height="${i}" xlink:href="${H}"/>`:``}</svg>`,document.querySelector(`#qr`).innerHTML=B,document.querySelector(`#status`).textContent=`● SIGNAL READY`}catch{B=``,document.querySelector(`#qr`).innerHTML=`<p class="empty">URL TOO LONG</p>`,document.querySelector(`#error`).textContent=`This URL exceeds QR capacity. Try a shorter URL.`,document.querySelector(`#status`).textContent=`○ INPUT TOO LONG`,document.querySelectorAll(`.actions button`).forEach(e=>e.disabled=!0)}}function J(e,t){let n=document.createElement(`a`);n.href=URL.createObjectURL(e),n.download=`signal-${z.id}.${t}`,n.click(),setTimeout(()=>URL.revokeObjectURL(n.href),1e3)}function Y(){if(!document.querySelector(`#include-header`).checked)return B;let e=document.querySelector(`#header-text`).value.replace(/[&<>"']/g,e=>({"&":`&amp;`,"<":`&lt;`,">":`&gt;`,'"':`&quot;`,"'":`&apos;`})[e]),t=B.replace(`width="1024" height="1024"`,`x="0" y="112" width="1024" height="1024"`);return`<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1136" viewBox="0 0 1024 1136"><rect width="1024" height="1136" fill="${z.paper}"/><text x="64" y="68" font-family="monospace" font-size="28" fill="${z.ink}">${e}</text><path d="M64 104H960" stroke="${z.ink}" stroke-opacity=".3"/>${t}</svg>`}document.querySelector(`#svg`).onclick=()=>{B&&J(new Blob([Y()],{type:`image/svg+xml`}),`svg`)},document.querySelector(`#png`).onclick=async()=>{if(!B)return;let e=V,t=new Image;t.src=URL.createObjectURL(new Blob([Y()],{type:`image/svg+xml`}));try{if(await t.decode(),e!==V)return;let n=document.createElement(`canvas`);n.width=1024,n.height=t.naturalHeight,n.getContext(`2d`).drawImage(t,0,0),n.toBlob(e=>{e&&J(e,`png`)},`image/png`)}finally{URL.revokeObjectURL(t.src)}};for(let e of[`logo-size`,`include-header`,`header-text`])document.getElementById(e).addEventListener(`input`,q);G.addEventListener(`input`,q),document.querySelector(`#logo`).onchange=q,document.querySelectorAll(`.theme`).forEach(e=>e.onclick=()=>{z=R.find(t=>t.id===e.dataset.theme),document.documentElement.style.setProperty(`--accent`,z.accent),document.documentElement.style.setProperty(`--paper`,z.paper),document.documentElement.style.setProperty(`--ink`,z.ink),document.querySelectorAll(`.theme`).forEach(t=>{t.classList.toggle(`selected`,t===e),t.setAttribute(`aria-pressed`,String(t===e))}),document.querySelector(`#preview-code`).textContent=`${z.code} / ${z.id.toUpperCase()}`,document.querySelector(`#description`).textContent=z.description,q()}),q();
assets/index-b1QQwCCK.css ADDED
@@ -0,0 +1 @@
 
 
1
+ @import "https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=Space+Grotesk:wght@400;500;600;700&display=swap";:root{--accent:#a7f878;--paper:#edfadf;--ink:#123822;--bg:#0a0e0b;--muted:#869085;color:#e6ebe2;background:var(--bg);font-synthesis:none;font-family:IBM Plex Mono,monospace}*{box-sizing:border-box}body{background-image:linear-gradient(#ffffff02 1px,#0000 1px);background-size:100% 4px;margin:0}button,input{font:inherit}button,a,input{-webkit-tap-highlight-color:transparent}button{cursor:pointer}button:focus-visible,a:focus-visible,input:focus-visible{outline:2px solid var(--accent);outline-offset:4px}header{letter-spacing:1px;border-bottom:1px solid #283026;justify-content:space-between;align-items:center;height:88px;margin:0 5%;font-size:12px;display:flex}.wordmark{color:inherit;align-items:center;gap:13px;font-weight:600;text-decoration:none;display:flex}.mark{color:var(--accent);font-size:28px}.slash{color:#56604f}.muted{color:var(--muted)}.online{color:#9ca995;font-size:10px}.online i,.preview-bar i{background:var(--accent);border-radius:50%;width:6px;height:6px;margin-right:9px;display:inline-block}main{max-width:1100px;margin:0 auto;padding:58px 30px 0}.eyebrow{color:var(--accent);letter-spacing:2px;font-size:11px}h1{letter-spacing:-3px;margin:24px 0 20px;font-family:Space Grotesk,sans-serif;font-size:70px;font-weight:500;line-height:1.04}h1 em{color:var(--accent);font-style:normal}.intro{color:#99a393;margin:0 0 43px;font-size:12px;line-height:1.9}.workspace{grid-template-columns:1fr 1fr;gap:64px;display:grid}.section-heading{letter-spacing:1.7px;color:#adb7a6;justify-content:space-between;margin-bottom:23px;font-size:10px;display:flex}label{margin-bottom:12px;font-size:12px;display:block}.input-shell{background:#10160e;border:1px solid #3e5036;border-radius:3px;align-items:center;gap:10px;padding:0 14px;display:flex}.input-shell:focus-within{border-color:var(--accent)}.input-shell>span{color:var(--accent);font-size:22px}input[type=url]{color:#e4eddb;background:0 0;border:0;outline:none;width:100%;min-width:0;height:49px;font-size:11px}.hint{color:var(--muted);min-height:32px;margin:10px 0 20px;font-size:10px;line-height:1.6}.theme-heading{margin-bottom:14px}.themes{gap:8px;display:grid}.theme{text-align:left;color:#c7cfc1;background:#10150f;border:1px solid #293027;border-radius:3px;align-items:center;gap:14px;padding:12px 16px;font-size:12px;display:flex}.theme:hover{background:#1b2317}.theme.selected{border-color:var(--accent);background:#182014}.swatch{background:var(--swatch);width:27px;height:27px;box-shadow:inset 0 0 0 6px #0002}.theme small{color:var(--muted);letter-spacing:1px;margin-top:5px;font-size:9px;display:block}.radio{border:1px solid #5b6652;border-radius:50%;width:12px;height:12px;margin-left:auto}.selected .radio{background:var(--accent);outline:1px solid var(--accent);border:3px solid #24341c}.logo-row{border-bottom:1px solid #293027;justify-content:space-between;align-items:center;padding:24px 0 19px;display:flex}.logo-row label{margin:0}.logo-row p{color:var(--muted);margin:8px 0 0;font-size:10px}input[type=checkbox]{appearance:none;cursor:pointer;background:#495043;border-radius:20px;width:34px;height:19px;position:relative}input[type=checkbox]:before{content:"";background:#10160d;border-radius:50%;width:13px;height:13px;position:absolute;top:3px;left:3px}input[type=checkbox]:checked{background:var(--accent)}input[type=checkbox]:checked:before{left:18px}.note{color:var(--accent);gap:13px;padding-top:17px;font-size:11px;line-height:1.8;display:flex}.note p{margin:0}.preview-panel{background:#11170f;border:1px solid #35402f;border-radius:5px;align-self:start;overflow:hidden}.preview-bar{letter-spacing:1px;color:#a9b49e;border-bottom:1px solid #35402f;justify-content:space-between;align-items:center;height:43px;padding:0 17px;font-size:9px;display:flex}.preview-stage{background-image:radial-gradient(#71856335 .7px,#0000 .7px);background-size:12px 12px;padding:34px 42px}.print-card{background:var(--paper);color:var(--ink);padding:20px 20px 24px;transform:rotate(-2deg);box-shadow:8px 8px #0005}.card-top{letter-spacing:1px;border-bottom:1px solid #12382240;justify-content:space-between;padding-bottom:14px;font-size:9px;display:flex}#qr{aspect-ratio:1;place-items:center;margin:10px -3px 6px;display:grid}#qr svg{width:100%;height:auto;display:block}.card-bottom{border-top:1px solid #12382240;padding-top:17px}.card-bottom strong{justify-content:space-between;font-size:15px;display:flex}.card-bottom>span{letter-spacing:1.7px;margin-top:9px;font-size:8px;display:block}.preview-caption{color:#96a38b;border-top:1px solid #35402f;justify-content:space-between;gap:10px;padding:17px 15px;font-size:8px;display:flex}.export-row{border-top:1px solid #30392a;justify-content:space-between;align-items:center;margin-top:35px;padding:25px 0 35px;display:flex}.ready{color:var(--accent);letter-spacing:1px;font-size:10px}.export-row p{color:var(--muted);margin:9px 0 0;font-size:11px}.actions{gap:10px;display:flex}.actions button{color:#dbe4d3;background:0 0;border:1px solid #3b4732;border-radius:3px;padding:16px 20px;font-size:11px}.actions .primary{background:var(--accent);color:#14210e;border-color:var(--accent);font-weight:600}.primary span{margin-left:24px}button:disabled{opacity:.4;cursor:not-allowed}footer{letter-spacing:1px;color:#7f8a75;border-top:1px solid #283026;justify-content:space-between;padding:23px 0 30px;font-size:8px;display:flex}.cursor{color:var(--accent);margin-left:12px}.empty{font-size:11px}@media (width>=1100px){.preview-panel{margin-top:-95px}.preview-stage{padding:40px 48px}.print-card{padding:22px 23px 26px}.workspace{gap:75px}}@media (width<=700px){header{height:65px}.online{display:none}main{padding:35px 22px 0}h1{font-size:52px}.intro{font-size:11px}.workspace{grid-template-columns:1fr;gap:25px}.preview-stage{padding:30px 50px}.export-row{flex-direction:column;align-items:flex-start;gap:22px}.actions{width:100%}.actions button{flex:1;padding:15px 9px}.primary span{margin-left:6px}footer{gap:20px;font-size:7px;line-height:1.8}.desktop{display:none}}[hidden]{display:none!important}.size-control{padding-top:20px}.size-control label{justify-content:space-between;display:flex}.size-control output{color:var(--accent)}input[type=range]{width:100%;accent-color:var(--accent);cursor:pointer}.size-control .hint{margin-bottom:0}.header-label{margin-top:18px}#header-text{color:#e4eddb;background:#10160e;border:1px solid #3e5036;border-radius:3px;width:100%;padding:14px;font-size:12px}#header-text:disabled{opacity:.4}#header-preview{overflow-wrap:anywhere;line-height:1.6}.print-card{transform:none}.card-top{min-height:36px}#qr{margin:10px -3px 0}
index.html CHANGED
@@ -1,4 +1,4 @@
1
  <!doctype html>
2
- <html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="theme-color" content="#0a0e0b"><title>Signal / Terminal QR Studio</title> <script type="module" crossorigin src="/assets/index-B4mOtIox.js"></script>
3
- <link rel="stylesheet" crossorigin href="/assets/index-B4Ipo8v_.css">
4
  </head><body><div id="app"></div></body></html>
 
1
  <!doctype html>
2
+ <html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="theme-color" content="#0a0e0b"><title>Signal / Terminal QR Studio</title> <script type="module" crossorigin src="/assets/index-C7KWe_AC.js"></script>
3
+ <link rel="stylesheet" crossorigin href="/assets/index-b1QQwCCK.css">
4
  </head><body><div id="app"></div></body></html>
previews/amber.png CHANGED

Git LFS Details

  • SHA256: a9155b5f273cf87c575c9042b39727a4e2238f892578ea6c3feebb511fcd57fa
  • Pointer size: 131 Bytes
  • Size of remote file: 125 kB

Git LFS Details

  • SHA256: 447722d6e9871b7b11b78699de9f22e21ed6d6f92702cf5d1994b40c5375ce62
  • Pointer size: 131 Bytes
  • Size of remote file: 101 kB
previews/ice.png CHANGED

Git LFS Details

  • SHA256: 7da05fe931c0bf68576bb86e5262f13bf116cbed9b4dca9006afc42150351048
  • Pointer size: 131 Bytes
  • Size of remote file: 125 kB

Git LFS Details

  • SHA256: 1c26bc79122d01670b0508e55639d905763d488c5ee76324567c9ccc77d68ebb
  • Pointer size: 131 Bytes
  • Size of remote file: 101 kB
previews/mobile.png CHANGED
previews/options.png ADDED

Git LFS Details

  • SHA256: 007313e00f0733b9aa7eaaf8a235522652e71501f232703c49b6f0e9cfb4964e
  • Pointer size: 131 Bytes
  • Size of remote file: 114 kB
previews/phosphor.png CHANGED

Git LFS Details

  • SHA256: c7664d748b76fb563d6330118c280f67cf24f282f54d97509e6472d5b6d0425d
  • Pointer size: 131 Bytes
  • Size of remote file: 125 kB

Git LFS Details

  • SHA256: b74e5781936e79a6f1a5a890aef0f875a9fd5431b95880d755bb9f494903550f
  • Pointer size: 131 Bytes
  • Size of remote file: 101 kB
previews/qr-amber.png CHANGED
previews/qr-ice.png CHANGED
previews/qr-ice.svg CHANGED
previews/qr-phosphor.png CHANGED
source/src.js CHANGED
@@ -19,14 +19,21 @@ document.querySelector('#app').innerHTML=`
19
  <div class="workspace"><section class="controls"><div class="section-heading"><span>01 / INPUT</span><span class="muted">HTTP(S)</span></div><label for="url">Where are we going?</label><div class="input-shell"><span>›</span><input id="url" type="url" spellcheck="false" aria-describedby="error" value="${initial}"></div><p id="error" role="status" class="hint">Static by design. Your URL, directly encoded.</p>
20
  <div class="section-heading theme-heading">02 / FREQUENCY</div><div class="themes">${themes.map((t,i)=>`<button class="theme ${i===0?'selected':''}" data-theme="${t.id}" aria-pressed="${i===0}" style="--swatch:${t.accent}"><span class="swatch"></span><span>${t.name}<small>${t.code} / ${['CLASSIC','WARM','COOL'][i]}</small></span><span class="radio"></span></button>`).join('')}</div>
21
  <div class="logo-row"><div><label for="logo">Pirate on board</label><p>Hugging Face logo, front and center.</p></div><input id="logo" type="checkbox" checked role="switch"></div>
22
- <div class="note"><span>↳</span><p>Built for the real world.<br><span class="muted">High error correction. A clean quiet zone.<br>No redirects, accounts, or expiration.</span></p></div></section>
23
- <section class="preview-panel"><div class="preview-bar"><span><i></i> LIVE PREVIEW</span><span id="preview-code">P1 / PHOSPHOR</span></div><div class="preview-stage"><div class="print-card"><div class="card-top"><span>HF / TRANSMISSION</span><span>001</span></div><div id="qr" aria-label="Generated QR code"></div><div class="card-bottom"><strong>SCAN TO CONNECT <span>↗</span></strong><span>ONE LINK. NO DETOURS.</span></div></div></div><div class="preview-caption"><span id="description">${current.description}</span><span>ECC / H</span></div></section></div>
 
24
  <div class="export-row"><div><span class="ready" id="status">● SIGNAL READY</span><p>Take your pixels with you.</p></div><div class="actions"><button id="svg">↓ Download SVG</button><button class="primary" id="png">↓ Download PNG <span>↗</span></button></div></div>
25
  <footer><span>MADE OF PIXELS. BUILT FOR PEOPLE.</span><span>STATIC QR / OPEN DESTINATIONS <span class="cursor">▮</span></span></footer></main>`;
26
  const input=document.querySelector('#url');
27
  function validURL(value){try {const u=new URL(value);return ['http:','https:'].includes(u.protocol)&&!!u.hostname;}catch{return false}}
28
  function update(){
29
  revision++;
 
 
 
 
 
 
30
  const value=input.value.trim();
31
  const valid=validURL(value);
32
  document.querySelector('#error').textContent=valid?'Static by design. Your URL, directly encoded.':'Enter a complete URL starting with https:// or http://.';
@@ -36,7 +43,7 @@ function update(){
36
  try {
37
  const qr=QRCode.create(value,{errorCorrectionLevel:'H'});const n=qr.modules.size, size=n+8;
38
  // A modest centered logo; all finder/timing patterns and the 4-module quiet zone remain intact.
39
- const logoSize=Math.floor(n*.17),start=(size-logoSize)/2;
40
  let path='';for(let y=0;y<n;y++)for(let x=0;x<n;x++)if(qr.modules.get(y,x))path+=`M${x+4} ${y+4}h1v1h-1z`;
41
  const withLogo=document.querySelector('#logo').checked;
42
  svg=`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1024" height="1024" viewBox="0 0 ${size} ${size}"><rect width="${size}" height="${size}" fill="${current.paper}"/><path d="${path}" fill="${current.ink}"/>${withLogo?`<rect x="${start-.6}" y="${start-.6}" width="${logoSize+1.2}" height="${logoSize+1.2}" rx=".7" fill="${current.paper}"/><image x="${start}" y="${start}" width="${logoSize}" height="${logoSize}" xlink:href="${logoData}"/>`:''}</svg>`;
@@ -44,7 +51,14 @@ function update(){
44
  }catch{svg='';document.querySelector('#qr').innerHTML='<p class="empty">URL TOO LONG</p>';document.querySelector('#error').textContent='This URL exceeds QR capacity. Try a shorter URL.';document.querySelector('#status').textContent='○ INPUT TOO LONG';document.querySelectorAll('.actions button').forEach(b=>b.disabled=true)}
45
  }
46
  function save(blob,ext){const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download=`signal-${current.id}.${ext}`;a.click();setTimeout(()=>URL.revokeObjectURL(a.href),1000)}
47
- document.querySelector('#svg').onclick=()=>{if(svg)save(new Blob([svg],{type:'image/svg+xml'}),'svg')};
48
- document.querySelector('#png').onclick=async()=>{if(!svg)return;const version=revision;const image=new Image();image.src=URL.createObjectURL(new Blob([svg],{type:'image/svg+xml'}));try{await image.decode();if(version!==revision)return;const canvas=document.createElement('canvas');canvas.width=canvas.height=1024;canvas.getContext('2d').drawImage(image,0,0);canvas.toBlob(blob=>{if(blob)save(blob,'png')},'image/png')}finally{URL.revokeObjectURL(image.src)}};
 
 
 
 
 
 
 
49
  input.addEventListener('input',update);document.querySelector('#logo').onchange=update;
50
  document.querySelectorAll('.theme').forEach(button=>button.onclick=()=>{current=themes.find(t=>t.id===button.dataset.theme);document.documentElement.style.setProperty('--accent',current.accent);document.documentElement.style.setProperty('--paper',current.paper);document.documentElement.style.setProperty('--ink',current.ink);document.querySelectorAll('.theme').forEach(b=>{b.classList.toggle('selected',b===button);b.setAttribute('aria-pressed',String(b===button))});document.querySelector('#preview-code').textContent=`${current.code} / ${current.id.toUpperCase()}`;document.querySelector('#description').textContent=current.description;update()});update();
 
19
  <div class="workspace"><section class="controls"><div class="section-heading"><span>01 / INPUT</span><span class="muted">HTTP(S)</span></div><label for="url">Where are we going?</label><div class="input-shell"><span>›</span><input id="url" type="url" spellcheck="false" aria-describedby="error" value="${initial}"></div><p id="error" role="status" class="hint">Static by design. Your URL, directly encoded.</p>
20
  <div class="section-heading theme-heading">02 / FREQUENCY</div><div class="themes">${themes.map((t,i)=>`<button class="theme ${i===0?'selected':''}" data-theme="${t.id}" aria-pressed="${i===0}" style="--swatch:${t.accent}"><span class="swatch"></span><span>${t.name}<small>${t.code} / ${['CLASSIC','WARM','COOL'][i]}</small></span><span class="radio"></span></button>`).join('')}</div>
21
  <div class="logo-row"><div><label for="logo">Pirate on board</label><p>Hugging Face logo, front and center.</p></div><input id="logo" type="checkbox" checked role="switch"></div>
22
+ <div class="size-control"><label for="logo-size">Logo size <output id="size-value">17%</output></label><input id="logo-size" type="range" min="12" max="26" value="17" step="1"><p class="hint">Larger logos cover more data. Test with your phone before printing.</p></div>
23
+ <div class="logo-row"><div><label for="include-header">Include header</label><p>Shown in the preview and both downloads.</p></div><input id="include-header" type="checkbox" checked role="switch"></div><label class="header-label" for="header-text">Header text</label><input id="header-text" type="text" maxlength="40" value="HF / TRANSMISSION"><div class="note"><span>↳</span><p>Built for the real world.<br><span class="muted">High error correction. A clean quiet zone.<br>No redirects, accounts, or expiration.</span></p></div></section>
24
+ <section class="preview-panel"><div class="preview-bar"><span><i></i> LIVE PREVIEW</span><span id="preview-code">P1 / PHOSPHOR</span></div><div class="preview-stage"><div class="print-card"><div class="card-top"><span id="header-preview">HF / TRANSMISSION</span></div><div id="qr" aria-label="Generated QR code"></div></div></div><div class="preview-caption"><span id="description">${current.description}</span><span>ECC / H</span></div></section></div>
25
  <div class="export-row"><div><span class="ready" id="status">● SIGNAL READY</span><p>Take your pixels with you.</p></div><div class="actions"><button id="svg">↓ Download SVG</button><button class="primary" id="png">↓ Download PNG <span>↗</span></button></div></div>
26
  <footer><span>MADE OF PIXELS. BUILT FOR PEOPLE.</span><span>STATIC QR / OPEN DESTINATIONS <span class="cursor">▮</span></span></footer></main>`;
27
  const input=document.querySelector('#url');
28
  function validURL(value){try {const u=new URL(value);return ['http:','https:'].includes(u.protocol)&&!!u.hostname;}catch{return false}}
29
  function update(){
30
  revision++;
31
+ const includeHeader=document.querySelector("#include-header").checked;
32
+ document.querySelector(".card-top").hidden=!includeHeader;
33
+ document.querySelector("#header-preview").textContent=document.querySelector("#header-text").value;
34
+ document.querySelector("#header-text").disabled=!includeHeader;
35
+ document.querySelector("#size-value").textContent=document.querySelector("#logo-size").value+"%";
36
+ document.querySelector("#logo-size").disabled=!document.querySelector("#logo").checked;
37
  const value=input.value.trim();
38
  const valid=validURL(value);
39
  document.querySelector('#error').textContent=valid?'Static by design. Your URL, directly encoded.':'Enter a complete URL starting with https:// or http://.';
 
43
  try {
44
  const qr=QRCode.create(value,{errorCorrectionLevel:'H'});const n=qr.modules.size, size=n+8;
45
  // A modest centered logo; all finder/timing patterns and the 4-module quiet zone remain intact.
46
+ const logoSize=Math.floor(n*Number(document.querySelector("#logo-size").value)/100),start=(size-logoSize)/2;
47
  let path='';for(let y=0;y<n;y++)for(let x=0;x<n;x++)if(qr.modules.get(y,x))path+=`M${x+4} ${y+4}h1v1h-1z`;
48
  const withLogo=document.querySelector('#logo').checked;
49
  svg=`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1024" height="1024" viewBox="0 0 ${size} ${size}"><rect width="${size}" height="${size}" fill="${current.paper}"/><path d="${path}" fill="${current.ink}"/>${withLogo?`<rect x="${start-.6}" y="${start-.6}" width="${logoSize+1.2}" height="${logoSize+1.2}" rx=".7" fill="${current.paper}"/><image x="${start}" y="${start}" width="${logoSize}" height="${logoSize}" xlink:href="${logoData}"/>`:''}</svg>`;
 
51
  }catch{svg='';document.querySelector('#qr').innerHTML='<p class="empty">URL TOO LONG</p>';document.querySelector('#error').textContent='This URL exceeds QR capacity. Try a shorter URL.';document.querySelector('#status').textContent='○ INPUT TOO LONG';document.querySelectorAll('.actions button').forEach(b=>b.disabled=true)}
52
  }
53
  function save(blob,ext){const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download=`signal-${current.id}.${ext}`;a.click();setTimeout(()=>URL.revokeObjectURL(a.href),1000)}
54
+ function exportSVG(){
55
+ if(!document.querySelector('#include-header').checked)return svg;
56
+ const text=document.querySelector('#header-text').value.replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&apos;'}[c]));
57
+ const nested=svg.replace('width="1024" height="1024"','x="0" y="112" width="1024" height="1024"');
58
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1136" viewBox="0 0 1024 1136"><rect width="1024" height="1136" fill="${current.paper}"/><text x="64" y="68" font-family="monospace" font-size="28" fill="${current.ink}">${text}</text><path d="M64 104H960" stroke="${current.ink}" stroke-opacity=".3"/>${nested}</svg>`;
59
+ }
60
+ document.querySelector('#svg').onclick=()=>{if(svg)save(new Blob([exportSVG()],{type:'image/svg+xml'}),'svg')};
61
+ document.querySelector('#png').onclick=async()=>{if(!svg)return;const version=revision;const image=new Image();image.src=URL.createObjectURL(new Blob([exportSVG()],{type:'image/svg+xml'}));try{await image.decode();if(version!==revision)return;const canvas=document.createElement('canvas');canvas.width=1024;canvas.height=image.naturalHeight;canvas.getContext('2d').drawImage(image,0,0);canvas.toBlob(blob=>{if(blob)save(blob,'png')},'image/png')}finally{URL.revokeObjectURL(image.src)}};
62
+ for(const id of ['logo-size','include-header','header-text'])document.getElementById(id).addEventListener('input',update);
63
  input.addEventListener('input',update);document.querySelector('#logo').onchange=update;
64
  document.querySelectorAll('.theme').forEach(button=>button.onclick=()=>{current=themes.find(t=>t.id===button.dataset.theme);document.documentElement.style.setProperty('--accent',current.accent);document.documentElement.style.setProperty('--paper',current.paper);document.documentElement.style.setProperty('--ink',current.ink);document.querySelectorAll('.theme').forEach(b=>{b.classList.toggle('selected',b===button);b.setAttribute('aria-pressed',String(b===button))});document.querySelector('#preview-code').textContent=`${current.code} / ${current.id.toUpperCase()}`;document.querySelector('#description').textContent=current.description;update()});update();
source/style.css CHANGED
@@ -1,2 +1,3 @@
1
  @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=Space+Grotesk:wght@400;500;600;700&display=swap');
2
  :root{--accent:#a7f878;--paper:#edfadf;--ink:#123822;--bg:#0a0e0b;--muted:#869085;font-family:'IBM Plex Mono',monospace;color:#e6ebe2;background:var(--bg);font-synthesis:none}*{box-sizing:border-box}body{margin:0;background-image:linear-gradient(#ffffff02 1px,transparent 1px);background-size:100% 4px}button,input{font:inherit}button,a,input{ -webkit-tap-highlight-color:transparent}button{cursor:pointer}button:focus-visible,a:focus-visible,input:focus-visible{outline:2px solid var(--accent);outline-offset:4px}header{height:88px;margin:0 5%;border-bottom:1px solid #283026;display:flex;align-items:center;justify-content:space-between;font-size:12px;letter-spacing:1px}.wordmark{display:flex;align-items:center;gap:13px;color:inherit;text-decoration:none;font-weight:600}.mark{font-size:28px;color:var(--accent)}.slash{color:#56604f}.muted{color:var(--muted)}.online{font-size:10px;color:#9ca995}.online i,.preview-bar i{display:inline-block;width:6px;height:6px;background:var(--accent);margin-right:9px;border-radius:50%}main{max-width:1100px;margin:0 auto;padding:58px 30px 0}.eyebrow{color:var(--accent);font-size:11px;letter-spacing:2px}h1{font-family:'Space Grotesk',sans-serif;font-size:70px;line-height:1.04;letter-spacing:-3px;font-weight:500;margin:24px 0 20px}h1 em{font-style:normal;color:var(--accent)}.intro{color:#99a393;font-size:12px;line-height:1.9;margin:0 0 43px}.workspace{display:grid;grid-template-columns:1fr 1fr;gap:64px}.section-heading{font-size:10px;letter-spacing:1.7px;display:flex;justify-content:space-between;color:#adb7a6;margin-bottom:23px}label{font-size:12px;display:block;margin-bottom:12px}.input-shell{display:flex;align-items:center;border:1px solid #3e5036;background:#10160e;border-radius:3px;padding:0 14px;gap:10px}.input-shell:focus-within{border-color:var(--accent)}.input-shell>span{color:var(--accent);font-size:22px}input[type=url]{color:#e4eddb;background:transparent;border:0;outline:none;width:100%;font-size:11px;height:49px;min-width:0}.hint{font-size:10px;color:var(--muted);line-height:1.6;min-height:32px;margin:10px 0 20px}.theme-heading{margin-bottom:14px}.themes{display:grid;gap:8px}.theme{display:flex;align-items:center;text-align:left;gap:14px;border:1px solid #293027;border-radius:3px;background:#10150f;color:#c7cfc1;padding:12px 16px;font-size:12px}.theme:hover{background:#1b2317}.theme.selected{border-color:var(--accent);background:#182014}.swatch{width:27px;height:27px;background:var(--swatch);box-shadow:inset 0 0 0 6px #0002}.theme small{display:block;font-size:9px;color:var(--muted);margin-top:5px;letter-spacing:1px}.radio{margin-left:auto;width:12px;height:12px;border:1px solid #5b6652;border-radius:50%}.selected .radio{background:var(--accent);border:3px solid #24341c;outline:1px solid var(--accent)}.logo-row{display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #293027;padding:24px 0 19px}.logo-row label{margin:0}.logo-row p{font-size:10px;color:var(--muted);margin:8px 0 0}input[type=checkbox]{appearance:none;cursor:pointer;width:34px;height:19px;border-radius:20px;background:#495043;position:relative}input[type=checkbox]:before{content:'';position:absolute;width:13px;height:13px;background:#10160d;border-radius:50%;top:3px;left:3px}input[type=checkbox]:checked{background:var(--accent)}input[type=checkbox]:checked:before{left:18px}.note{display:flex;gap:13px;color:var(--accent);font-size:11px;line-height:1.8;padding-top:17px}.note p{margin:0}.preview-panel{border:1px solid #35402f;border-radius:5px;overflow:hidden;align-self:start;background:#11170f}.preview-bar{height:43px;display:flex;align-items:center;justify-content:space-between;padding:0 17px;border-bottom:1px solid #35402f;font-size:9px;letter-spacing:1px;color:#a9b49e}.preview-stage{padding:34px 42px;background-image:radial-gradient(#71856335 .7px,transparent .7px);background-size:12px 12px}.print-card{background:var(--paper);color:var(--ink);padding:20px 20px 24px;box-shadow:8px 8px 0 #0005;transform:rotate(-2deg)}.card-top{display:flex;justify-content:space-between;font-size:9px;letter-spacing:1px;border-bottom:1px solid #12382240;padding-bottom:14px}#qr{aspect-ratio:1;margin:10px -3px 6px;display:grid;place-items:center}#qr svg{width:100%;height:auto;display:block}.card-bottom{border-top:1px solid #12382240;padding-top:17px}.card-bottom strong{font-size:15px;display:flex;justify-content:space-between}.card-bottom>span{display:block;font-size:8px;letter-spacing:1.7px;margin-top:9px}.preview-caption{display:flex;justify-content:space-between;font-size:8px;color:#96a38b;padding:17px 15px;border-top:1px solid #35402f;gap:10px}.export-row{margin-top:35px;border-top:1px solid #30392a;padding:25px 0 35px;display:flex;justify-content:space-between;align-items:center}.ready{color:var(--accent);font-size:10px;letter-spacing:1px}.export-row p{font-size:11px;color:var(--muted);margin:9px 0 0}.actions{display:flex;gap:10px}.actions button{border:1px solid #3b4732;border-radius:3px;background:transparent;color:#dbe4d3;padding:16px 20px;font-size:11px}.actions .primary{background:var(--accent);color:#14210e;border-color:var(--accent);font-weight:600}.primary span{margin-left:24px}button:disabled{opacity:.4;cursor:not-allowed}footer{border-top:1px solid #283026;display:flex;justify-content:space-between;padding:23px 0 30px;font-size:8px;letter-spacing:1px;color:#7f8a75}.cursor{color:var(--accent);margin-left:12px}.empty{font-size:11px}@media(min-width:1100px){.preview-panel{margin-top:-95px}.preview-stage{padding:40px 48px}.print-card{padding:22px 23px 26px}.workspace{gap:75px}}@media(max-width:700px){header{height:65px}.online{display:none}main{padding:35px 22px 0}h1{font-size:52px}.intro{font-size:11px}.workspace{grid-template-columns:1fr;gap:25px}.preview-stage{padding:30px 50px}.export-row{align-items:flex-start;gap:22px;flex-direction:column}.actions{width:100%}.actions button{flex:1;padding:15px 9px}.primary span{margin-left:6px}footer{gap:20px;line-height:1.8;font-size:7px}.desktop{display:none}}
 
 
1
  @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=Space+Grotesk:wght@400;500;600;700&display=swap');
2
  :root{--accent:#a7f878;--paper:#edfadf;--ink:#123822;--bg:#0a0e0b;--muted:#869085;font-family:'IBM Plex Mono',monospace;color:#e6ebe2;background:var(--bg);font-synthesis:none}*{box-sizing:border-box}body{margin:0;background-image:linear-gradient(#ffffff02 1px,transparent 1px);background-size:100% 4px}button,input{font:inherit}button,a,input{ -webkit-tap-highlight-color:transparent}button{cursor:pointer}button:focus-visible,a:focus-visible,input:focus-visible{outline:2px solid var(--accent);outline-offset:4px}header{height:88px;margin:0 5%;border-bottom:1px solid #283026;display:flex;align-items:center;justify-content:space-between;font-size:12px;letter-spacing:1px}.wordmark{display:flex;align-items:center;gap:13px;color:inherit;text-decoration:none;font-weight:600}.mark{font-size:28px;color:var(--accent)}.slash{color:#56604f}.muted{color:var(--muted)}.online{font-size:10px;color:#9ca995}.online i,.preview-bar i{display:inline-block;width:6px;height:6px;background:var(--accent);margin-right:9px;border-radius:50%}main{max-width:1100px;margin:0 auto;padding:58px 30px 0}.eyebrow{color:var(--accent);font-size:11px;letter-spacing:2px}h1{font-family:'Space Grotesk',sans-serif;font-size:70px;line-height:1.04;letter-spacing:-3px;font-weight:500;margin:24px 0 20px}h1 em{font-style:normal;color:var(--accent)}.intro{color:#99a393;font-size:12px;line-height:1.9;margin:0 0 43px}.workspace{display:grid;grid-template-columns:1fr 1fr;gap:64px}.section-heading{font-size:10px;letter-spacing:1.7px;display:flex;justify-content:space-between;color:#adb7a6;margin-bottom:23px}label{font-size:12px;display:block;margin-bottom:12px}.input-shell{display:flex;align-items:center;border:1px solid #3e5036;background:#10160e;border-radius:3px;padding:0 14px;gap:10px}.input-shell:focus-within{border-color:var(--accent)}.input-shell>span{color:var(--accent);font-size:22px}input[type=url]{color:#e4eddb;background:transparent;border:0;outline:none;width:100%;font-size:11px;height:49px;min-width:0}.hint{font-size:10px;color:var(--muted);line-height:1.6;min-height:32px;margin:10px 0 20px}.theme-heading{margin-bottom:14px}.themes{display:grid;gap:8px}.theme{display:flex;align-items:center;text-align:left;gap:14px;border:1px solid #293027;border-radius:3px;background:#10150f;color:#c7cfc1;padding:12px 16px;font-size:12px}.theme:hover{background:#1b2317}.theme.selected{border-color:var(--accent);background:#182014}.swatch{width:27px;height:27px;background:var(--swatch);box-shadow:inset 0 0 0 6px #0002}.theme small{display:block;font-size:9px;color:var(--muted);margin-top:5px;letter-spacing:1px}.radio{margin-left:auto;width:12px;height:12px;border:1px solid #5b6652;border-radius:50%}.selected .radio{background:var(--accent);border:3px solid #24341c;outline:1px solid var(--accent)}.logo-row{display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #293027;padding:24px 0 19px}.logo-row label{margin:0}.logo-row p{font-size:10px;color:var(--muted);margin:8px 0 0}input[type=checkbox]{appearance:none;cursor:pointer;width:34px;height:19px;border-radius:20px;background:#495043;position:relative}input[type=checkbox]:before{content:'';position:absolute;width:13px;height:13px;background:#10160d;border-radius:50%;top:3px;left:3px}input[type=checkbox]:checked{background:var(--accent)}input[type=checkbox]:checked:before{left:18px}.note{display:flex;gap:13px;color:var(--accent);font-size:11px;line-height:1.8;padding-top:17px}.note p{margin:0}.preview-panel{border:1px solid #35402f;border-radius:5px;overflow:hidden;align-self:start;background:#11170f}.preview-bar{height:43px;display:flex;align-items:center;justify-content:space-between;padding:0 17px;border-bottom:1px solid #35402f;font-size:9px;letter-spacing:1px;color:#a9b49e}.preview-stage{padding:34px 42px;background-image:radial-gradient(#71856335 .7px,transparent .7px);background-size:12px 12px}.print-card{background:var(--paper);color:var(--ink);padding:20px 20px 24px;box-shadow:8px 8px 0 #0005;transform:rotate(-2deg)}.card-top{display:flex;justify-content:space-between;font-size:9px;letter-spacing:1px;border-bottom:1px solid #12382240;padding-bottom:14px}#qr{aspect-ratio:1;margin:10px -3px 6px;display:grid;place-items:center}#qr svg{width:100%;height:auto;display:block}.card-bottom{border-top:1px solid #12382240;padding-top:17px}.card-bottom strong{font-size:15px;display:flex;justify-content:space-between}.card-bottom>span{display:block;font-size:8px;letter-spacing:1.7px;margin-top:9px}.preview-caption{display:flex;justify-content:space-between;font-size:8px;color:#96a38b;padding:17px 15px;border-top:1px solid #35402f;gap:10px}.export-row{margin-top:35px;border-top:1px solid #30392a;padding:25px 0 35px;display:flex;justify-content:space-between;align-items:center}.ready{color:var(--accent);font-size:10px;letter-spacing:1px}.export-row p{font-size:11px;color:var(--muted);margin:9px 0 0}.actions{display:flex;gap:10px}.actions button{border:1px solid #3b4732;border-radius:3px;background:transparent;color:#dbe4d3;padding:16px 20px;font-size:11px}.actions .primary{background:var(--accent);color:#14210e;border-color:var(--accent);font-weight:600}.primary span{margin-left:24px}button:disabled{opacity:.4;cursor:not-allowed}footer{border-top:1px solid #283026;display:flex;justify-content:space-between;padding:23px 0 30px;font-size:8px;letter-spacing:1px;color:#7f8a75}.cursor{color:var(--accent);margin-left:12px}.empty{font-size:11px}@media(min-width:1100px){.preview-panel{margin-top:-95px}.preview-stage{padding:40px 48px}.print-card{padding:22px 23px 26px}.workspace{gap:75px}}@media(max-width:700px){header{height:65px}.online{display:none}main{padding:35px 22px 0}h1{font-size:52px}.intro{font-size:11px}.workspace{grid-template-columns:1fr;gap:25px}.preview-stage{padding:30px 50px}.export-row{align-items:flex-start;gap:22px;flex-direction:column}.actions{width:100%}.actions button{flex:1;padding:15px 9px}.primary span{margin-left:6px}footer{gap:20px;line-height:1.8;font-size:7px}.desktop{display:none}}
3
+ [hidden]{display:none!important}.size-control{padding-top:20px}.size-control label{display:flex;justify-content:space-between}.size-control output{color:var(--accent)}input[type=range]{width:100%;accent-color:var(--accent);cursor:pointer}.size-control .hint{margin-bottom:0}.header-label{margin-top:18px}#header-text{width:100%;background:#10160e;color:#e4eddb;border:1px solid #3e5036;border-radius:3px;padding:14px;font-size:12px}#header-text:disabled{opacity:.4}#header-preview{overflow-wrap:anywhere;line-height:1.6}.print-card{transform:none}.card-top{min-height:36px}#qr{margin:10px -3px 0}
source/tests/studio.spec.js CHANGED
@@ -23,3 +23,21 @@ test('themes scan, export and validate; responsive layout',async({page})=>{
23
  await page.setViewportSize({width:390,height:844});await page.screenshot({path:'previews/mobile.png',fullPage:true});
24
  expect(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth)).toBe(true);
25
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  await page.setViewportSize({width:390,height:844});await page.screenshot({path:'previews/mobile.png',fullPage:true});
24
  expect(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth)).toBe(true);
25
  });
26
+ test('logo sizes and optional custom header exports scan',async({page})=>{
27
+ await page.goto('http://localhost:5173');await expect(page.locator('#qr svg')).toBeVisible();
28
+ await page.locator('#header-text').fill('MY DATASET <HF> & FRIENDS');
29
+ await expect(page.locator('#header-preview')).toHaveText('MY DATASET <HF> & FRIENDS');
30
+ for(const theme of ['phosphor','amber','ice']){
31
+ await page.locator(`[data-theme=${theme}]`).click();
32
+ for(const size of [12,17,22,26]){
33
+ await page.locator('#logo-size').fill(String(size));
34
+ await expect(page.locator('#size-value')).toHaveText(`${size}%`);
35
+ const pending=page.waitForEvent('download');await page.locator('#png').click();const file=await (await pending).path();
36
+ const png=PNG.sync.read(fs.readFileSync(file));expect(png.height).toBe(1136);expect(jsQR(new Uint8ClampedArray(png.data),png.width,png.height)?.data).toBe(url);
37
+ }
38
+ }
39
+ const pending=page.waitForEvent('download');await page.locator('#svg').click();const text=fs.readFileSync(await (await pending).path(),'utf8');expect(text).toContain('MY DATASET &lt;HF&gt; &amp; FRIENDS');expect(text).not.toContain('SCAN TO CONNECT');
40
+ await page.locator('#include-header').uncheck();await expect(page.locator('.card-top')).toBeHidden();await expect(page.locator('#header-text')).toBeDisabled();
41
+ const bare=page.waitForEvent('download');await page.locator('#png').click();const png=PNG.sync.read(fs.readFileSync(await (await bare).path()));expect(png.height).toBe(1024);expect(jsQR(new Uint8ClampedArray(png.data),png.width,png.height)?.data).toBe(url);
42
+ await page.locator('#logo').uncheck();await expect(page.locator('#logo-size')).toBeDisabled();
43
+ });