diff --git a/.github/workflows/lighthouse.yml b/.github/workflows/lighthouse.yml new file mode 100644 index 0000000..76880f4 --- /dev/null +++ b/.github/workflows/lighthouse.yml @@ -0,0 +1,34 @@ +name: Lighthouse (website) + +on: + pull_request: + paths: + - 'website/**' + - '.github/workflows/lighthouse.yml' + push: + branches: [main] + paths: + - 'website/**' + workflow_dispatch: + +concurrency: + group: lighthouse-${{ github.ref }} + cancel-in-progress: true + +jobs: + lighthouse: + name: Lighthouse CI + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Node 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Run Lighthouse CI + uses: treosh/lighthouse-ci-action@v12 + with: + configPath: ./website/lighthouserc.json + temporaryPublicStorage: true diff --git a/.gitignore b/.gitignore index fede0f7..a7ec5f8 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,6 @@ node_modules/ # and agent worktrees. afca53e removed the loop file from tracking once already. .claude/ralph-loop.local.md .claude/worktrees/ + +# Lighthouse CI local reports +.lighthouseci/ diff --git a/website/assets/fonts/ibm-plex-mono-latin-400-normal.woff2 b/website/assets/fonts/ibm-plex-mono-latin-400-normal.woff2 new file mode 100644 index 0000000..0804aaf Binary files /dev/null and b/website/assets/fonts/ibm-plex-mono-latin-400-normal.woff2 differ diff --git a/website/assets/fonts/ibm-plex-mono-latin-500-normal.woff2 b/website/assets/fonts/ibm-plex-mono-latin-500-normal.woff2 new file mode 100644 index 0000000..090f82f Binary files /dev/null and b/website/assets/fonts/ibm-plex-mono-latin-500-normal.woff2 differ diff --git a/website/assets/fonts/ibm-plex-mono-latin-600-normal.woff2 b/website/assets/fonts/ibm-plex-mono-latin-600-normal.woff2 new file mode 100644 index 0000000..67aeeb0 Binary files /dev/null and b/website/assets/fonts/ibm-plex-mono-latin-600-normal.woff2 differ diff --git a/website/assets/fonts/ibm-plex-mono-latin-700-normal.woff2 b/website/assets/fonts/ibm-plex-mono-latin-700-normal.woff2 new file mode 100644 index 0000000..5d7ff0c Binary files /dev/null and b/website/assets/fonts/ibm-plex-mono-latin-700-normal.woff2 differ diff --git a/website/assets/ocr/app-history.json b/website/assets/ocr/app-history.json new file mode 100644 index 0000000..9810411 --- /dev/null +++ b/website/assets/ocr/app-history.json @@ -0,0 +1 @@ +{"words":[{"t":"11:16","x0":0.06787330316742081,"x1":0.1568627450980392,"y0":0.023214285714285715,"y1":0.03357142857142857,"a":0.017797888386123683},{"t":"local/coding-on-the-phone","x0":0.08069381598793364,"x1":0.5610859728506787,"y0":0.08035714285714286,"y1":0.09464285714285714,"a":0.019215686274509803},{"t":"history","x0":0.6063348416289592,"x1":0.7360482654600302,"y0":0.08071428571428571,"y1":0.09464285714285714,"a":0.018530489118724413},{"t":"commit","x0":0.08144796380090498,"x1":0.19984917043740574,"y0":0.11785714285714285,"y1":0.12964285714285714,"a":0.01973353443941679},{"t":"sync","x0":0.2420814479638009,"x1":0.31825037707390647,"y0":0.12142857142857143,"y1":0.1332142857142857,"a":0.019042232277526393},{"t":"branch","x0":0.36048265460030166,"x1":0.48114630467571645,"y0":0.1175,"y1":0.13,"a":0.020110608345902465},{"t":"merge","x0":0.521870286576169,"x1":0.6206636500754148,"y0":0.12035714285714286,"y1":0.1332142857142857,"a":0.01975867269984917},{"t":"refresh","x0":0.6628959276018099,"x1":0.801659125188537,"y0":0.1175,"y1":0.12964285714285714,"a":0.019823313940961},{"t":"main","x0":0.1515837104072398,"x1":0.21568627450980393,"y0":0.15964285714285714,"y1":0.1875,"a":0.016025641025641024},{"t":"|","x0":0.23001508295625941,"x1":0.24509803921568626,"y0":0.15964285714285714,"y1":0.1875,"a":0.015082956259426848},{"t":"initial","x0":0.27149321266968324,"x1":0.41704374057315236,"y0":0.16714285714285715,"y1":0.18,"a":0.020792932557638442},{"t":"commit","x0":0.44419306184012064,"x1":0.5686274509803921,"y0":0.16714285714285715,"y1":0.18035714285714285,"a":0.020739064856711915},{"t":"mouse","x0":0.31070889894419307,"x1":0.3974358974358974,"y0":0.2032142857142857,"y1":0.21,"a":0.017345399698340876},{"t":"(5","x0":0.09728506787330317,"x1":0.1334841628959276,"y0":0.29428571428571426,"y1":0.315,"a":0.01809954751131222},{"t":"(5","x0":0.09728506787330317,"x1":0.1334841628959276,"y0":0.3382142857142857,"y1":0.35928571428571426,"a":0.01809954751131222},{"t":"[5","x0":0.09728506787330317,"x1":0.1334841628959276,"y0":0.3825,"y1":0.4032142857142857,"a":0.01809954751131222},{"t":"(5","x0":0.09728506787330317,"x1":0.1334841628959276,"y0":0.42642857142857143,"y1":0.4471428571428571,"a":0.01809954751131222},{"t":"vite.config.ts","x0":0.1636500754147813,"x1":0.473604826546003,"y0":0.43,"y1":0.4475,"a":0.022139625080801553},{"t":"+","x0":0.10030165912518854,"x1":0.11613876319758673,"y0":0.47785714285714287,"y1":0.4857142857142857,"a":0.01583710407239819},{"t":"add","x0":0.14328808446455504,"x1":0.20211161387631976,"y0":0.4742857142857143,"y1":0.48678571428571427,"a":0.0196078431372549},{"t":"file","x0":0.23001508295625941,"x1":0.31070889894419307,"y0":0.4742857142857143,"y1":0.48678571428571427,"a":0.02017345399698341},{"t":"vite.config.ts","x0":0.08069381598793364,"x1":0.3431372549019608,"y0":0.5589285714285714,"y1":0.5728571428571428,"a":0.01874595992243051},{"t":"import","x0":0.08069381598793364,"x1":0.1907993966817496,"y0":0.5953571428571428,"y1":0.6096428571428572,"a":0.018350930115636},{"t":"tailwindcss","x0":0.21342383107088989,"x1":0.42081447963800905,"y0":0.5953571428571428,"y1":0.6067857142857143,"a":0.01885369532428356},{"t":"from","x0":0.44419306184012064,"x1":0.5173453996983409,"y0":0.5953571428571428,"y1":0.6067857142857143,"a":0.018288084464555054},{"t":"'@tailwindcss/vite’;","x0":0.5467571644042232,"x1":0.9140271493212669,"y0":0.595,"y1":0.6089285714285714,"a":0.018363499245852188},{"t":"import","x0":0.08069381598793364,"x1":0.1907993966817496,"y0":0.62,"y1":0.6342857142857142,"a":0.018350930115636},{"t":"{","x0":0.2149321266968326,"x1":0.2277526395173454,"y0":0.6196428571428572,"y1":0.6332142857142857,"a":0.01282051282051282},{"t":"defineConfig","x0":0.25188536953242835,"x1":0.47888386123680243,"y0":0.62,"y1":0.6342857142857142,"a":0.018916540975364503},{"t":"from","x0":0.5399698340874811,"x1":0.6131221719457014,"y0":0.62,"y1":0.6314285714285715,"a":0.018288084464555054},{"t":"import","x0":0.08069381598793364,"x1":0.1907993966817496,"y0":0.6689285714285714,"y1":0.6832142857142857,"a":0.018350930115636},{"t":"playwright","x0":0.2149321266968326,"x1":0.40120663650075417,"y0":0.6689285714285714,"y1":0.6835714285714286,"a":0.018627450980392157},{"t":"from","x0":0.42458521870286575,"x1":0.4984917043740573,"y0":0.6689285714285714,"y1":0.6803571428571429,"a":0.01847662141779789},{"t":"'@vitest/browser-","x0":0.5271493212669683,"x1":0.8393665158371041,"y0":0.6689285714285714,"y1":0.6821428571428572,"a":0.018365717327655042},{"t":"playwright';","x0":0.08144796380090498,"x1":0.3009049773755656,"y0":0.6935714285714286,"y1":0.7075,"a":0.018288084464555054},{"t":"import","x0":0.08069381598793364,"x1":0.1907993966817496,"y0":0.7182142857142857,"y1":0.7321428571428571,"a":0.018350930115636},{"t":"adapter","x0":0.21417797888386123,"x1":0.34464555052790347,"y0":0.7182142857142857,"y1":0.7321428571428571,"a":0.01863822452057746},{"t":"from","x0":0.36726998491704377,"x1":0.4411764705882353,"y0":0.7182142857142857,"y1":0.7296428571428571,"a":0.01847662141779789},{"t":"'@sveltejs/adapter-","x0":0.4698340874811463,"x1":0.8197586726998491,"y0":0.7178571428571429,"y1":0.7321428571428571,"a":0.01841708343256331},{"t":"auto';","x0":0.07993966817496229,"x1":0.18552036199095023,"y0":0.7425,"y1":0.7557142857142857,"a":0.017596782302664656},{"t":"import","x0":0.08069381598793364,"x1":0.1907993966817496,"y0":0.7671428571428571,"y1":0.7814285714285715,"a":0.018350930115636},{"t":"sveltekit","x0":0.2526395173453997,"x1":0.42081447963800905,"y0":0.7671428571428571,"y1":0.7785714285714286,"a":0.01868610692140104},{"t":"from","x0":0.48265460030165913,"x1":0.5558069381598794,"y0":0.7671428571428571,"y1":0.7785714285714286,"a":0.018288084464555054},{"t":"export","x0":0.07918552036199095,"x1":0.1907993966817496,"y0":0.8410714285714286,"y1":0.8546428571428571,"a":0.01860231271995978},{"t":"default","x0":0.21342383107088989,"x1":0.3438914027149321,"y0":0.8403571428571428,"y1":0.8517857142857143,"a":0.01863822452057746},{"t":"plugins:","x0":0.15761689291101055,"x1":0.3009049773755656,"y0":0.865,"y1":0.8796428571428572,"a":0.01791101055806938},{"t":"[","x0":0.33408748114630465,"x1":0.3431372549019608,"y0":0.865,"y1":0.8782142857142857,"a":0.00904977375565611},{"t":"tailwindcss(),","x0":0.23227752639517346,"x1":0.4924585218702866,"y0":0.8892857142857142,"y1":0.9025,"a":0.018584356819650937},{"t":"sveltekit({","x0":0.23378582202111614,"x1":0.43891402714932126,"y0":0.9139285714285714,"y1":0.9275,"a":0.018648018648018648},{"t":"compilerOptions:","x0":0.31070889894419307,"x1":0.6078431372549019,"y0":0.9385714285714286,"y1":0.9525,"a":0.018570889894419306},{"t":"//","x0":0.38838612368024134,"x1":0.4193061840120664,"y0":0.9628571428571429,"y1":0.9760714285714286,"a":0.01546003016591252},{"t":"Force","x0":0.4457013574660634,"x1":0.5354449472096531,"y0":0.9639285714285715,"y1":0.9742857142857143,"a":0.01794871794871795},{"t":"runes","x0":0.5603318250377074,"x1":0.6500754147812972,"y0":0.9667857142857142,"y1":0.9742857142857143,"a":0.01794871794871795},{"t":"mode","x0":0.6742081447963801,"x1":0.7458521870286576,"y0":0.9632142857142857,"y1":0.9742857142857143,"a":0.01791101055806938},{"t":"for","x0":0.770739064856712,"x1":0.8235294117647058,"y0":0.9632142857142857,"y1":0.9742857142857143,"a":0.017596782302664656},{"t":"the","x0":0.8461538461538461,"x1":0.8989441930618401,"y0":0.9632142857142857,"y1":0.9742857142857143,"a":0.017596782302664656},{"t":"runes:","x0":0.38687782805429866,"x1":0.4924585218702866,"y0":0.9907142857142858,"y1":0.9989285714285714,"a":0.017596782302664656},{"t":"(3","x0":0.5248868778280543,"x1":0.5535444947209653,"y0":0.9871428571428571,"y1":1,"a":0.014328808446455505},{"t":"filename","x0":0.5784313725490197,"x1":0.7277526395173454,"y0":0.9875,"y1":0.9989285714285714,"a":0.018665158371040724},{"t":"=>","x0":0.8076923076923077,"x1":0.8416289592760181,"y0":0.99,"y1":0.9982142857142857,"a":0.016968325791855202}],"advN":0.01847662141779789,"count":60} \ No newline at end of file diff --git a/website/assets/ocr/app-ruby.json b/website/assets/ocr/app-ruby.json new file mode 100644 index 0000000..f28a272 --- /dev/null +++ b/website/assets/ocr/app-ruby.json @@ -0,0 +1 @@ +{"words":[{"t":"~","x0":0.07033898305084746,"x1":0.08389830508474576,"y0":0.11463414634146342,"y1":0.12378048780487805,"a":0.013559322033898305},{"t":"$","x0":0.1038135593220339,"x1":0.11779661016949153,"y0":0.09817073170731708,"y1":0.13597560975609757,"a":0.013983050847457627},{"t":"pkg","x0":0.1385593220338983,"x1":0.18516949152542372,"y0":0.10182926829268292,"y1":0.1402439024390244,"a":0.015536723163841807},{"t":"install","x0":0.2059322033898305,"x1":0.31822033898305085,"y0":0.10121951219512196,"y1":0.1323170731707317,"a":0.016041162227602903},{"t":"ruby","x0":0.33940677966101696,"x1":0.4025423728813559,"y0":0.10182926829268292,"y1":0.13963414634146343,"a":0.015783898305084747},{"t":"fetching","x0":0.07076271186440677,"x1":0.2016949152542373,"y0":0.16158536585365854,"y1":0.200609756097561,"a":0.016366525423728812},{"t":"ruby","x0":0.22203389830508474,"x1":0.2851694915254237,"y0":0.16219512195121952,"y1":0.2,"a":0.015783898305084747},{"t":"3.4.1","x0":0.3050847457627119,"x1":0.38601694915254237,"y0":0.16341463414634147,"y1":0.1926829268292683,"a":0.016186440677966103},{"t":"(25","x0":0.4101694915254237,"x1":0.45254237288135596,"y0":0.16097560975609757,"y1":0.1975609756097561,"a":0.014124293785310736},{"t":"MB)","x0":0.4733050847457627,"x1":0.5161016949152543,"y0":0.16097560975609757,"y1":0.1975609756097561,"a":0.01426553672316384},{"t":"installed","x0":0.07161016949152542,"x1":0.21694915254237288,"y0":0.22195121951219512,"y1":0.2530487804878049,"a":0.016148775894538607},{"t":"ruby","x0":0.23855932203389832,"x1":0.3021186440677966,"y0":0.2225609756097561,"y1":0.2603658536585366,"a":0.015889830508474576},{"t":"3.4.1","x0":0.32161016949152543,"x1":0.40296610169491526,"y0":0.22378048780487805,"y1":0.2530487804878049,"a":0.016271186440677966},{"t":"~","x0":0.07033898305084746,"x1":0.08389830508474576,"y0":0.29573170731707316,"y1":0.3048780487804878,"a":0.013559322033898305},{"t":"$","x0":0.1038135593220339,"x1":0.11779661016949153,"y0":0.27926829268292686,"y1":0.3170731707317073,"a":0.013983050847457627},{"t":"echo","x0":0.13771186440677965,"x1":0.20127118644067796,"y0":0.28292682926829266,"y1":0.31341463414634146,"a":0.015889830508474576},{"t":"\"puts","x0":0.2245762711864407,"x1":0.30127118644067796,"y0":0.28292682926829266,"y1":0.3207317073170732,"a":0.015338983050847459},{"t":"'hello","x0":0.32754237288135596,"x1":0.4190677966101695,"y0":0.28292682926829266,"y1":0.31341463414634146,"a":0.015254237288135594},{"t":"from","x0":0.4398305084745763,"x1":0.5033898305084745,"y0":0.28292682926829266,"y1":0.31341463414634146,"a":0.015889830508474576},{"t":"ruby","x0":0.523728813559322,"x1":0.5872881355932204,"y0":0.28292682926829266,"y1":0.3207317073170732,"a":0.015889830508474576},{"t":"'","x0":0.6127118644067797,"x1":0.6152542372881356,"y0":0.28292682926829266,"y1":0.2939024390243902,"a":0.002542372881355932},{"t":"+","x0":0.6411016949152543,"x1":0.6542372881355932,"y0":0.29085365853658535,"y1":0.3103658536585366,"a":0.013135593220338982},{"t":"RUBY_VERSION;","x0":0.675,"x1":0.8847457627118644,"y0":0.2841463414634146,"y1":0.3195121951219512,"a":0.016134289439374186},{"t":"puts","x0":0.0711864406779661,"x1":0.13347457627118645,"y0":0.3445121951219512,"y1":0.38109756097560976,"a":0.015572033898305086},{"t":"(1..5).map","x0":0.15847457627118644,"x1":0.31864406779661014,"y0":0.34207317073170734,"y1":0.38109756097560976,"a":0.01601694915254237},{"t":"In|","x0":0.37796610169491524,"x1":0.4135593220338983,"y0":0.34207317073170734,"y1":0.37865853658536586,"a":0.011864406779661017},{"t":"n*n","x0":0.4406779661016949,"x1":0.48559322033898306,"y0":0.35,"y1":0.37317073170731707,"a":0.014971751412429379},{"t":"%.inspect\"","x0":0.5076271186440678,"x1":0.6677966101694915,"y0":0.3426829268292683,"y1":0.38109756097560976,"a":0.01601694915254237},{"t":">","x0":0.6919491525423729,"x1":0.7038135593220339,"y0":0.349390243902439,"y1":0.3725609756097561,"a":0.011864406779661017},{"t":"hello.rb;","x0":0.7258474576271187,"x1":0.8677966101694915,"y0":0.3432926829268293,"y1":0.37865853658536586,"a":0.01577212806026365},{"t":"ruby","x0":0.07076271186440677,"x1":0.13432203389830508,"y0":0.4036585365853659,"y1":0.44146341463414634,"a":0.015889830508474576},{"t":"hello","x0":0.0711864406779661,"x1":0.15084745762711865,"y0":0.46402439024390246,"y1":0.4945121951219512,"a":0.01593220338983051},{"t":"from","x0":0.1711864406779661,"x1":0.23516949152542374,"y0":0.46402439024390246,"y1":0.4945121951219512,"a":0.015995762711864408},{"t":"ruby","x0":0.2555084745762712,"x1":0.31864406779661014,"y0":0.46402439024390246,"y1":0.5018292682926829,"a":0.015783898305084747},{"t":"3.4.1","x0":0.3385593220338983,"x1":0.41991525423728815,"y0":0.4652439024390244,"y1":0.4945121951219512,"a":0.016271186440677966},{"t":"[1,","x0":0.075,"x1":0.11271186440677966,"y0":0.5237804878048781,"y1":0.5597560975609757,"a":0.012570621468926554},{"t":"4,","x0":0.13686440677966102,"x1":0.163135593220339,"y0":0.526219512195122,"y1":0.5597560975609757,"a":0.013135593220338982},{"t":"9,","x0":0.18771186440677967,"x1":0.2135593220338983,"y0":0.525,"y1":0.5597560975609757,"a":0.012923728813559321},{"t":"16,","x0":0.23813559322033898,"x1":0.2805084745762712,"y0":0.525609756097561,"y1":0.5597560975609757,"a":0.014124293785310736},{"t":"25]","x0":0.30550847457627117,"x1":0.3474576271186441,"y0":0.5237804878048781,"y1":0.5597560975609757,"a":0.013983050847457627},{"t":"~","x0":0.07033898305084746,"x1":0.08389830508474576,"y0":0.5975609756097561,"y1":0.6067073170731707,"a":0.013559322033898305},{"t":"$1","x0":0.1038135593220339,"x1":0.15338983050847457,"y0":0.5810975609756097,"y1":0.6219512195121951,"a":0.02478813559322034}],"advN":0.015889830508474576,"count":42} \ No newline at end of file diff --git a/website/assets/ocr/app-vite.json b/website/assets/ocr/app-vite.json new file mode 100644 index 0000000..690d06b --- /dev/null +++ b/website/assets/ocr/app-vite.json @@ -0,0 +1 @@ +{"words":[{"t":"~","x0":0.07033898305084746,"x1":0.08389830508474576,"y0":0.11951219512195121,"y1":0.12865853658536586,"a":0.013559322033898305},{"t":"$","x0":0.1038135593220339,"x1":0.11779661016949153,"y0":0.10304878048780487,"y1":0.14085365853658535,"a":0.013983050847457627},{"t":"npx","x0":0.1385593220338983,"x1":0.1843220338983051,"y0":0.1152439024390244,"y1":0.1445121951219512,"a":0.015254237288135594},{"t":"create-vite","x0":0.20508474576271185,"x1":0.3855932203389831,"y0":0.10609756097560975,"y1":0.13719512195121952,"a":0.01640986132511556},{"t":"|","x0":0.07627118644067797,"x1":0.07796610169491526,"y0":0.16646341463414635,"y1":0.20243902439024392,"a":0.001694915254237288},{"t":"Project","x0":0.12161016949152542,"x1":0.23432203389830508,"y0":0.22682926829268293,"y1":0.2658536585365854,"a":0.016101694915254237},{"t":"name:","x0":0.2559322033898305,"x1":0.3309322033898305,"y0":0.23597560975609755,"y1":0.2579268292682927,"a":0.015},{"t":"Project","x0":0.12161016949152542,"x1":0.23432203389830508,"y0":0.2871951219512195,"y1":0.32621951219512196,"a":0.016101694915254237},{"t":"name:","x0":0.2559322033898305,"x1":0.3309322033898305,"y0":0.29634146341463413,"y1":0.3182926829268293,"a":0.015},{"t":"|","x0":0.07627118644067797,"x1":0.07796610169491526,"y0":0.40792682926829266,"y1":0.44390243902439025,"a":0.001694915254237288},{"t":"m","x0":0.06906779661016949,"x1":0.08516949152542373,"y0":0.4768292682926829,"y1":0.5,"a":0.016101694915254237},{"t":"Select","x0":0.12033898305084746,"x1":0.21779661016949153,"y0":0.4689024390243902,"y1":0.499390243902439,"a":0.016242937853107344},{"t":"a","x0":0.23855932203389832,"x1":0.2521186440677966,"y0":0.4774390243902439,"y1":0.499390243902439,"a":0.013559322033898305},{"t":"framework:","x0":0.27203389830508473,"x1":0.43177966101694915,"y0":0.4689024390243902,"y1":0.499390243902439,"a":0.015974576271186442},{"t":"|","x0":0.07627118644067797,"x1":0.07796610169491526,"y0":0.5286585365853659,"y1":0.5646341463414634,"a":0.001694915254237288},{"t":"Vanilla","x0":0.11991525423728813,"x1":0.23516949152542374,"y0":0.5286585365853659,"y1":0.5597560975609757,"a":0.016464891041162225},{"t":"|","x0":0.07627118644067797,"x1":0.07796610169491526,"y0":0.5890243902439024,"y1":0.625,"a":0.001694915254237288},{"t":"L","x0":0.07627118644067797,"x1":0.08559322033898305,"y0":0.6420731707317073,"y1":0.6676829268292683,"a":0.009322033898305085},{"t":"cancelled","x0":0.28940677966101697,"x1":0.4351694915254237,"y0":0.65,"y1":0.6804878048780488,"a":0.016195856873822974}],"advN":0.016101694915254237,"count":19} \ No newline at end of file diff --git a/website/assets/og-cover.png b/website/assets/og-cover.png new file mode 100644 index 0000000..3c5428d Binary files /dev/null and b/website/assets/og-cover.png differ diff --git a/website/dither.html b/website/dither.html index 5bf96c4..db65d48 100644 --- a/website/dither.html +++ b/website/dither.html @@ -118,6 +118,11 @@ const slotsEl = document.getElementById('slots'); const blackEl = document.getElementById('black'); + // Marketing-embed mode: lighter defaults so the hero never blocks the main + // thread (no OCR/tesseract download, fewer particles). The standalone widget + // is unaffected. + const EMBED = new URLSearchParams(location.search).get('embed') === '1'; + const RAMP = '.:-=+*#%@'; // ramp first (indices 0..8 are the tonal glyphs), then every printable char let CHARS = RAMP; @@ -149,7 +154,7 @@ const MODES = ['dot', 'ascii', 'hybrid']; const GLYPH = { dot: '\u00b7', ascii: '@', hybrid: '\u259a' }; let mode = 'hybrid'; - let densityScale = 1, maxCharPx = 36; + let densityScale = EMBED ? 0.55 : 1, maxCharPx = 36; let W = 0, H = 0, DPR = 1, N = 0, frame = 0; let px, py, vx, vy, tx, ty, bx, by, pf, bo, wv, tg, tcol, cOrder, jit, cellOf, items; @@ -626,7 +631,7 @@ renderSlots(); seed(); applyTargets(idx); - if (!animated) runOCR(); + if (!animated && !EMBED) runOCR(); done(); }; img.src = url; @@ -1127,7 +1132,7 @@ // --- marketing embed hooks --- const params = new URLSearchParams(location.search); - const embed = params.get('embed') === '1'; + const embed = EMBED; if (embed) { document.documentElement.classList.add('embed'); setMenu(false); @@ -1151,16 +1156,44 @@ setColor: (on) => { colorOn = !!on; colorBtn.classList.toggle('on', colorOn); }, }; + // Pre-baked OCR: the words/boxes for each embed image are computed offline and + // saved next to the image at assets/ocr/.json, so the hero renders + // recognized text as glyphs without ever loading tesseract at runtime. Falls + // back to plain dithering if a file is missing. + function ocrUrlFor(url) { + const slash = url.lastIndexOf('/'); + const dir = slash >= 0 ? url.slice(0, slash + 1) : ''; + const base = (slash >= 0 ? url.slice(slash + 1) : url).replace(/\.[a-z0-9]+$/i, ''); + return dir + 'ocr/' + base + '.json'; + } + async function applyBakedOCR(i, url) { + try { + const res = await fetch(ocrUrlFor(url)); + if (!res.ok) return false; + const data = await res.json(); + if (!data || !data.words || !data.words.length) return false; + const sl = slots[i]; + sl.words = data.words; + sl.advN = data.advN || 0; + const { dw } = fitBox(sl.img); + sl.forceCw = Math.max(3, Math.min(30, sl.advN * dw)); + rebuild(i); + return true; + } catch (_) { return false; } + } + // Auto-boot images when listed via ?imgs=a,b,c (comma-separated relative paths) const imgs = (params.get('imgs') || '').split(',').map(s => s.trim()).filter(Boolean); if (imgs.length) { - window.DitherBoids.loadUrls(imgs).then(() => { - // mild black point so white backdrops drop out of the flock - if (active >= 0) { - slots[active].bp = 0.08; - blackEl.value = '8'; - schedule(false); + window.DitherBoids.loadUrls(imgs).then(async () => { + // mild black point so the screens' dark backdrops drop out of the flock + for (let i = 0; i < slots.length; i++) { + slots[i].bp = 0.08; + if (EMBED && imgs[i]) await applyBakedOCR(i, imgs[i]); } + blackEl.value = '8'; + if (active >= 0) applyTargets(active); + try { parent && parent !== window && parent.postMessage({ type: 'mouse-dither-ready' }, '*'); } catch (_) { /* no parent */ } }); } diff --git a/website/index.html b/website/index.html index f2a983d..b875ad9 100644 --- a/website/index.html +++ b/website/index.html @@ -3,17 +3,67 @@ - Mouse — the coding IDE built for a phone - - - - + Mouse — Mobile Coding App & IDE for Builders + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + Skip to content @@ -46,17 +96,16 @@
Native for iOS & Android · open source

A real IDE,
built for a phone.

- Mouse is a gesture shell where the whole development environment lives: - swipe between editors, pinch workspaces open, and run real - node and git against your GitHub repos. + Mouse is a mobile coding app. A gesture shell where the whole + development environment lives: swipe between editors, pinch workspaces + open, and run real node and git against your GitHub repos.

@@ -324,6 +373,6 @@

Build on a phone that can build.

- + diff --git a/website/lighthouserc.json b/website/lighthouserc.json new file mode 100644 index 0000000..edee715 --- /dev/null +++ b/website/lighthouserc.json @@ -0,0 +1,24 @@ +{ + "ci": { + "collect": { + "startServerCommand": "npx --yes http-server ./website -p 8099 -c-1", + "startServerReadyPattern": "Available on", + "url": ["http://localhost:8099/index.html"], + "numberOfRuns": 3, + "settings": { + "chromeFlags": "--no-sandbox --disable-dev-shm-usage" + } + }, + "assert": { + "assertions": { + "categories:performance": ["error", { "minScore": 0.85 }], + "categories:accessibility": ["error", { "minScore": 1 }], + "categories:best-practices": ["error", { "minScore": 1 }], + "categories:seo": ["error", { "minScore": 1 }] + } + }, + "upload": { + "target": "temporary-public-storage" + } + } +} diff --git a/website/main.js b/website/main.js index b9cbc58..3a497ae 100644 --- a/website/main.js +++ b/website/main.js @@ -34,8 +34,28 @@ reveals.forEach(show); } - // Pause dither iframe work when hero leaves the viewport + // Defer the heavy dither iframe until the page has loaded and gone idle, so + // its canvas simulation never blocks first paint or inflates blocking time. const frame = document.querySelector('.hero-viz'); + if (frame && frame.dataset.src) { + const load = () => { if (!frame.src) frame.src = frame.dataset.src; }; + const idle = () => ('requestIdleCallback' in window) + ? requestIdleCallback(load, { timeout: 1500 }) + : setTimeout(load, 200); + if (document.readyState === 'complete') idle(); + else window.addEventListener('load', idle, { once: true }); + + // Fade the hero up from black once the flock has formed. The dither posts + // 'mouse-dither-ready' when its first screen is applied; the load handler is + // a fallback so the hero never stays black if that signal is missed. + const reveal = () => frame.classList.add('is-loaded'); + window.addEventListener('message', (e) => { + if (e.data && e.data.type === 'mouse-dither-ready') reveal(); + }); + frame.addEventListener('load', () => setTimeout(reveal, 1600), { once: true }); + } + + // Pause dither iframe work when the hero leaves the viewport. if (frame && 'IntersectionObserver' in window) { const freeze = new IntersectionObserver(([e]) => { try { diff --git a/website/robots.txt b/website/robots.txt new file mode 100644 index 0000000..f5f5f0e --- /dev/null +++ b/website/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://reagent-systems.github.io/mouse/sitemap.xml diff --git a/website/sitemap.xml b/website/sitemap.xml new file mode 100644 index 0000000..d967e93 --- /dev/null +++ b/website/sitemap.xml @@ -0,0 +1,8 @@ + + + + https://reagent-systems.github.io/mouse/ + weekly + 1.0 + + diff --git a/website/styles.css b/website/styles.css index 0a835a5..0f0427f 100644 --- a/website/styles.css +++ b/website/styles.css @@ -1,5 +1,11 @@ /* Mouse marketing site — dark, terminal honesty, monochrome, IBM Plex Mono */ +/* Self-hosted so the font never render-blocks on a third-party request. */ +@font-face { font-family: "IBM Plex Mono"; font-style: normal; font-weight: 400; font-display: swap; src: url("assets/fonts/ibm-plex-mono-latin-400-normal.woff2") format("woff2"); } +@font-face { font-family: "IBM Plex Mono"; font-style: normal; font-weight: 500; font-display: swap; src: url("assets/fonts/ibm-plex-mono-latin-500-normal.woff2") format("woff2"); } +@font-face { font-family: "IBM Plex Mono"; font-style: normal; font-weight: 600; font-display: swap; src: url("assets/fonts/ibm-plex-mono-latin-600-normal.woff2") format("woff2"); } +@font-face { font-family: "IBM Plex Mono"; font-style: normal; font-weight: 700; font-display: swap; src: url("assets/fonts/ibm-plex-mono-latin-700-normal.woff2") format("woff2"); } + :root { --bg: #060608; --bg-2: #0b0b0f; @@ -200,8 +206,13 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi height: 100%; pointer-events: auto; z-index: 1; + opacity: 0; + transition: opacity 0.9s var(--ease); } +/* Fades up from the black hero once the flock has formed (see main.js). */ +.hero-viz.is-loaded { opacity: 1; } + .hero-fade { position: absolute; inset: 0;