From a3b341f813e0824e7d2d76ea3709d6fefc593121 Mon Sep 17 00:00:00 2001 From: Jake Archibald Date: Sat, 5 Dec 2020 11:21:33 +0000 Subject: [PATCH] New homepage (#861) * Paste button * Logo and animated blobs * Predictable initial blob position * Initial blob shape * Update canvas on resize if not updating every frame * lol * Get styles from CSS * Background blobs * Fade into the center * Get initial focus * Pause time while page is hidden * Footer * Optimise amount of initial CSS * More CSS optimisation * Install button * Home page with demo loading * Tweak size * Replace thumbnails * Responsive demo section * Responsive main section * Remove debug stuff * Fix prerender SVG size * Changes from feedback * Blob nudges (#872) * more smaller blobs * less blobs that are practically invisible * more dynamic speed range and stronger gravity * Reverting resize observer change The content rect is different to getBoundingClientRect Co-authored-by: Adam Argyle --- lib/css-plugin.js | 1 + lib/initial-css-plugin.js | 2 +- src/client/initial-app/App/index.tsx | 12 +- src/client/initial-app/App/style.css | 4 +- .../initial-app/custom-els/missing-types.d.ts | 4 +- .../lazy-app/Compress/Options/Range/index.tsx | 2 +- src/client/lazy-app/Compress/Output/index.tsx | 2 +- src/client/lazy-app/Compress/Output/style.css | 6 +- .../lazy-app/Compress/Results/index.tsx | 2 +- .../lazy-app/Compress/Results/style.css | 2 +- src/client/lazy-app/Compress/index.tsx | 2 +- src/client/lazy-app/sw-bridge/index.ts | 2 +- src/client/missing-types.d.ts | 22 +- .../processors/resize/client/index.tsx | 2 +- .../custom-els/loading-spinner/index.ts | 1 + .../loading-spinner/missing-types.d.ts | 0 .../custom-els/loading-spinner/styles.css | 0 .../custom-els/snack-bar/index.ts | 1 + .../custom-els/snack-bar/missing-types.d.ts | 0 .../custom-els/snack-bar/styles.css | 0 .../Intro/imgs/demos/icon-demo-artwork.jpg | Bin 3489 -> 0 bytes .../imgs/demos/icon-demo-device-screen.jpg | Bin 4713 -> 0 bytes .../imgs/demos/icon-demo-large-photo.jpg | Bin 4413 -> 0 bytes .../Intro/imgs/demos/icon-demo-logo.png | Bin 1558 -> 0 bytes src/shared/initial-app/Intro/index.tsx | 255 ----------- src/shared/initial-app/Intro/style.css | 228 ---------- src/shared/initial-app/util.css | 22 - src/shared/missing-types.d.ts | 21 + .../prerendered-app/Intro/blob-anim/index.ts | 417 ++++++++++++++++++ .../prerendered-app/Intro/blob-anim/meta.ts | 41 ++ .../Intro/imgs/demos/demo-artwork.jpg | Bin .../Intro/imgs/demos/demo-device-screen.png | Bin .../Intro/imgs/demos/demo-large-photo.jpg | Bin .../Intro/imgs/demos/icon-demo-artwork.jpg | Bin 0 -> 3411 bytes .../imgs/demos/icon-demo-device-screen.jpg | Bin 0 -> 4226 bytes .../imgs/demos/icon-demo-large-photo.jpg | Bin 0 -> 3666 bytes .../Intro/imgs/demos/icon-demo-logo.png | Bin 0 -> 4800 bytes .../Intro/imgs/github-logo.svg | 1 + .../Intro/imgs/logo-with-text.svg | 1 + .../Intro/imgs/logo.svg | 0 src/shared/prerendered-app/Intro/index.tsx | 372 ++++++++++++++++ .../Intro/missing-types.d.ts | 9 + src/shared/prerendered-app/Intro/style.css | 243 ++++++++++ src/shared/prerendered-app/colors.css | 19 + src/shared/prerendered-app/util.css | 24 + .../{initial-app => prerendered-app}/util.ts | 0 src/static-build/index.tsx | 2 +- src/static-build/missing-types.d.ts | 2 +- src/static-build/pages/index/base.css | 14 +- src/static-build/pages/index/index.tsx | 2 +- 50 files changed, 1178 insertions(+), 562 deletions(-) rename src/shared/{initial-app => }/custom-els/loading-spinner/index.ts (98%) rename src/shared/{initial-app => }/custom-els/loading-spinner/missing-types.d.ts (100%) rename src/shared/{initial-app => }/custom-els/loading-spinner/styles.css (100%) rename src/shared/{initial-app => }/custom-els/snack-bar/index.ts (98%) rename src/shared/{initial-app => }/custom-els/snack-bar/missing-types.d.ts (100%) rename src/shared/{initial-app => }/custom-els/snack-bar/styles.css (100%) delete mode 100644 src/shared/initial-app/Intro/imgs/demos/icon-demo-artwork.jpg delete mode 100644 src/shared/initial-app/Intro/imgs/demos/icon-demo-device-screen.jpg delete mode 100644 src/shared/initial-app/Intro/imgs/demos/icon-demo-large-photo.jpg delete mode 100644 src/shared/initial-app/Intro/imgs/demos/icon-demo-logo.png delete mode 100644 src/shared/initial-app/Intro/index.tsx delete mode 100644 src/shared/initial-app/Intro/style.css delete mode 100644 src/shared/initial-app/util.css create mode 100644 src/shared/prerendered-app/Intro/blob-anim/index.ts create mode 100644 src/shared/prerendered-app/Intro/blob-anim/meta.ts rename src/shared/{initial-app => prerendered-app}/Intro/imgs/demos/demo-artwork.jpg (100%) rename src/shared/{initial-app => prerendered-app}/Intro/imgs/demos/demo-device-screen.png (100%) rename src/shared/{initial-app => prerendered-app}/Intro/imgs/demos/demo-large-photo.jpg (100%) create mode 100644 src/shared/prerendered-app/Intro/imgs/demos/icon-demo-artwork.jpg create mode 100644 src/shared/prerendered-app/Intro/imgs/demos/icon-demo-device-screen.jpg create mode 100644 src/shared/prerendered-app/Intro/imgs/demos/icon-demo-large-photo.jpg create mode 100644 src/shared/prerendered-app/Intro/imgs/demos/icon-demo-logo.png create mode 100644 src/shared/prerendered-app/Intro/imgs/github-logo.svg create mode 100644 src/shared/prerendered-app/Intro/imgs/logo-with-text.svg rename src/shared/{initial-app => prerendered-app}/Intro/imgs/logo.svg (100%) create mode 100644 src/shared/prerendered-app/Intro/index.tsx rename src/shared/{initial-app => prerendered-app}/Intro/missing-types.d.ts (87%) create mode 100644 src/shared/prerendered-app/Intro/style.css create mode 100644 src/shared/prerendered-app/colors.css create mode 100644 src/shared/prerendered-app/util.css rename src/shared/{initial-app => prerendered-app}/util.ts (100%) diff --git a/lib/css-plugin.js b/lib/css-plugin.js index 1e1ba6d1..b97bc1f9 100644 --- a/lib/css-plugin.js +++ b/lib/css-plugin.js @@ -41,6 +41,7 @@ const assetRe = new RegExp('/fake/path/to/asset/([^/]+)/', 'g'); const appendCssModule = '\0appendCss'; const appendCssSource = ` export default function appendCss(css) { + if (__PRERENDER__) return; const style = document.createElement('style'); style.textContent = css; document.head.append(style); diff --git a/lib/initial-css-plugin.js b/lib/initial-css-plugin.js index 055a2740..54461570 100644 --- a/lib/initial-css-plugin.js +++ b/lib/initial-css-plugin.js @@ -30,7 +30,7 @@ export default function initialCssPlugin() { async load(id) { if (id !== initialCssModule) return; - const matches = await globP('shared/initial-app/**/*.css', { + const matches = await globP('shared/prerendered-app/**/*.css', { nodir: true, cwd: path.join(process.cwd(), 'src'), }); diff --git a/src/client/initial-app/App/index.tsx b/src/client/initial-app/App/index.tsx index e4700312..36d92a8a 100644 --- a/src/client/initial-app/App/index.tsx +++ b/src/client/initial-app/App/index.tsx @@ -1,16 +1,16 @@ import type { FileDropEvent } from 'file-drop-element'; -import type SnackBarElement from 'shared/initial-app/custom-els/snack-bar'; -import type { SnackOptions } from 'shared/initial-app/custom-els/snack-bar'; +import type SnackBarElement from 'shared/custom-els/snack-bar'; +import type { SnackOptions } from 'shared/custom-els/snack-bar'; import { h, Component } from 'preact'; -import { linkRef } from 'shared/initial-app/util'; +import { linkRef } from 'shared/prerendered-app/util'; import * as style from './style.css'; import 'add-css:./style.css'; import 'file-drop-element'; -import 'shared/initial-app/custom-els/snack-bar'; -import Intro from 'shared/initial-app/Intro'; -import 'shared/initial-app/custom-els/loading-spinner'; +import 'shared/custom-els/snack-bar'; +import Intro from 'shared/prerendered-app/Intro'; +import 'shared/custom-els/loading-spinner'; const ROUTE_EDITOR = '/editor'; diff --git a/src/client/initial-app/App/style.css b/src/client/initial-app/App/style.css index d23b5483..0b143c35 100644 --- a/src/client/initial-app/App/style.css +++ b/src/client/initial-app/App/style.css @@ -24,8 +24,8 @@ right: 10px; bottom: 10px; border: 2px dashed #fff; - background-color: rgba(88, 116, 88, 0.2); - border-color: rgba(65, 129, 65, 0.5); + background-color: rgba(0, 0, 0, 0.1); + border-color: var(--pink); border-radius: 10px; opacity: 0; transform: scale(0.95); diff --git a/src/client/initial-app/custom-els/missing-types.d.ts b/src/client/initial-app/custom-els/missing-types.d.ts index 37aa4e00..d2c41034 100644 --- a/src/client/initial-app/custom-els/missing-types.d.ts +++ b/src/client/initial-app/custom-els/missing-types.d.ts @@ -1,5 +1,5 @@ -/// -/// +/// +/// import type { FileDropElement, FileDropEvent } from 'file-drop-element'; interface FileDropAttributes extends preact.JSX.HTMLAttributes { diff --git a/src/client/lazy-app/Compress/Options/Range/index.tsx b/src/client/lazy-app/Compress/Options/Range/index.tsx index e4ed66b9..da6e37e2 100644 --- a/src/client/lazy-app/Compress/Options/Range/index.tsx +++ b/src/client/lazy-app/Compress/Options/Range/index.tsx @@ -3,7 +3,7 @@ import * as style from './style.css'; import 'add-css:./style.css'; import RangeInputElement from './custom-els/RangeInput'; import './custom-els/RangeInput'; -import { linkRef } from 'shared/initial-app/util'; +import { linkRef } from 'shared/prerendered-app/util'; interface Props extends preact.JSX.HTMLAttributes {} interface State {} diff --git a/src/client/lazy-app/Compress/Output/index.tsx b/src/client/lazy-app/Compress/Output/index.tsx index 94a653a9..c459c2b3 100644 --- a/src/client/lazy-app/Compress/Output/index.tsx +++ b/src/client/lazy-app/Compress/Output/index.tsx @@ -18,7 +18,7 @@ import { twoUpHandle } from './custom-els/TwoUp/styles.css'; import type { PreprocessorState } from '../../feature-meta'; import { cleanSet } from '../../util/clean-modify'; import type { SourceImage } from '../../Compress'; -import { linkRef } from 'shared/initial-app/util'; +import { linkRef } from 'shared/prerendered-app/util'; interface Props { source?: SourceImage; diff --git a/src/client/lazy-app/Compress/Output/style.css b/src/client/lazy-app/Compress/Output/style.css index ab56f174..269a04a0 100644 --- a/src/client/lazy-app/Compress/Output/style.css +++ b/src/client/lazy-app/Compress/Output/style.css @@ -1,5 +1,5 @@ .output { - composes: abs-fill from '../../../../shared/initial-app/util.css'; + composes: abs-fill from global; &::before { content: ''; @@ -19,12 +19,12 @@ } .two-up { - composes: abs-fill from '../../../../shared/initial-app/util.css'; + composes: abs-fill from global; --accent-color: var(--button-fg); } .pinch-zoom { - composes: abs-fill from '../../../../shared/initial-app/util.css'; + composes: abs-fill from global; outline: none; display: flex; justify-content: center; diff --git a/src/client/lazy-app/Compress/Results/index.tsx b/src/client/lazy-app/Compress/Results/index.tsx index 57f8e73d..647c5fce 100644 --- a/src/client/lazy-app/Compress/Results/index.tsx +++ b/src/client/lazy-app/Compress/Results/index.tsx @@ -8,7 +8,7 @@ import { CopyAcrossIcon, CopyAcrossIconProps, } from 'client/lazy-app/icons'; -import 'shared/initial-app/custom-els/loading-spinner'; +import 'shared/custom-els/loading-spinner'; import { SourceImage } from '../'; interface Props { diff --git a/src/client/lazy-app/Compress/Results/style.css b/src/client/lazy-app/Compress/Results/style.css index 1918c841..8d56f36f 100644 --- a/src/client/lazy-app/Compress/Results/style.css +++ b/src/client/lazy-app/Compress/Results/style.css @@ -124,7 +124,7 @@ .copy-to-other { grid-row: 1; grid-column: copy-button; - composes: unbutton from '../../../../shared/initial-app/util.css'; + composes: unbutton from global; composes: download; background: #656565; diff --git a/src/client/lazy-app/Compress/index.tsx b/src/client/lazy-app/Compress/index.tsx index bcd84242..0b56d8fb 100644 --- a/src/client/lazy-app/Compress/index.tsx +++ b/src/client/lazy-app/Compress/index.tsx @@ -30,7 +30,7 @@ import './custom-els/MultiPanel'; import Results from './Results'; import WorkerBridge from '../worker-bridge'; import { resize } from 'features/processors/resize/client'; -import type SnackBarElement from 'shared/initial-app/custom-els/snack-bar'; +import type SnackBarElement from 'shared/custom-els/snack-bar'; import { CopyAcrossIconProps, ExpandIcon } from '../icons'; export type OutputType = EncoderType | 'identity'; diff --git a/src/client/lazy-app/sw-bridge/index.ts b/src/client/lazy-app/sw-bridge/index.ts index 13322685..d500955a 100644 --- a/src/client/lazy-app/sw-bridge/index.ts +++ b/src/client/lazy-app/sw-bridge/index.ts @@ -1,4 +1,4 @@ -import type SnackBarElement from 'shared/initial-app/custom-els/snack-bar'; +import type SnackBarElement from 'shared/custom-els/snack-bar'; import { get, set } from 'idb-keyval'; diff --git a/src/client/missing-types.d.ts b/src/client/missing-types.d.ts index a176b0a8..d25cdf43 100644 --- a/src/client/missing-types.d.ts +++ b/src/client/missing-types.d.ts @@ -11,7 +11,7 @@ * limitations under the License. */ /// -/// +/// interface Navigator { readonly standalone: boolean; @@ -25,23 +25,3 @@ declare module 'service-worker:*' { } declare module 'preact/debug' {} - -interface ResizeObserverCallback { - (entries: ResizeObserverEntry[], observer: ResizeObserver): void; -} - -interface ResizeObserverEntry { - readonly target: Element; - readonly contentRect: DOMRectReadOnly; -} - -interface ResizeObserver { - observe(target: Element): void; - unobserve(target: Element): void; - disconnect(): void; -} - -declare var ResizeObserver: { - prototype: ResizeObserver; - new (callback: ResizeObserverCallback): ResizeObserver; -}; diff --git a/src/features/processors/resize/client/index.tsx b/src/features/processors/resize/client/index.tsx index 5f048a8b..6b82da5b 100644 --- a/src/features/processors/resize/client/index.tsx +++ b/src/features/processors/resize/client/index.tsx @@ -22,7 +22,7 @@ import { inputFieldChecked, } from 'client/lazy-app/util'; import * as style from 'client/lazy-app/Compress/Options/style.css'; -import { linkRef } from 'shared/initial-app/util'; +import { linkRef } from 'shared/prerendered-app/util'; import Select from 'client/lazy-app/Compress/Options/Select'; import Expander from 'client/lazy-app/Compress/Options/Expander'; import Checkbox from 'client/lazy-app/Compress/Options/Checkbox'; diff --git a/src/shared/initial-app/custom-els/loading-spinner/index.ts b/src/shared/custom-els/loading-spinner/index.ts similarity index 98% rename from src/shared/initial-app/custom-els/loading-spinner/index.ts rename to src/shared/custom-els/loading-spinner/index.ts index 3abe3108..2985b6d9 100644 --- a/src/shared/initial-app/custom-els/loading-spinner/index.ts +++ b/src/shared/custom-els/loading-spinner/index.ts @@ -1,4 +1,5 @@ import * as styles from './styles.css'; +import 'add-css:./styles.css'; // So it doesn't cause an error when running in node const HTMLEl = ((__PRERENDER__ diff --git a/src/shared/initial-app/custom-els/loading-spinner/missing-types.d.ts b/src/shared/custom-els/loading-spinner/missing-types.d.ts similarity index 100% rename from src/shared/initial-app/custom-els/loading-spinner/missing-types.d.ts rename to src/shared/custom-els/loading-spinner/missing-types.d.ts diff --git a/src/shared/initial-app/custom-els/loading-spinner/styles.css b/src/shared/custom-els/loading-spinner/styles.css similarity index 100% rename from src/shared/initial-app/custom-els/loading-spinner/styles.css rename to src/shared/custom-els/loading-spinner/styles.css diff --git a/src/shared/initial-app/custom-els/snack-bar/index.ts b/src/shared/custom-els/snack-bar/index.ts similarity index 98% rename from src/shared/initial-app/custom-els/snack-bar/index.ts rename to src/shared/custom-els/snack-bar/index.ts index 8e530c46..4c6617f7 100644 --- a/src/shared/initial-app/custom-els/snack-bar/index.ts +++ b/src/shared/custom-els/snack-bar/index.ts @@ -1,4 +1,5 @@ import * as style from './styles.css'; +import 'add-css:./styles.css'; // So it doesn't cause an error when running in node const HTMLEl = ((__PRERENDER__ diff --git a/src/shared/initial-app/custom-els/snack-bar/missing-types.d.ts b/src/shared/custom-els/snack-bar/missing-types.d.ts similarity index 100% rename from src/shared/initial-app/custom-els/snack-bar/missing-types.d.ts rename to src/shared/custom-els/snack-bar/missing-types.d.ts diff --git a/src/shared/initial-app/custom-els/snack-bar/styles.css b/src/shared/custom-els/snack-bar/styles.css similarity index 100% rename from src/shared/initial-app/custom-els/snack-bar/styles.css rename to src/shared/custom-els/snack-bar/styles.css diff --git a/src/shared/initial-app/Intro/imgs/demos/icon-demo-artwork.jpg b/src/shared/initial-app/Intro/imgs/demos/icon-demo-artwork.jpg deleted file mode 100644 index 79d05f02907307008ca8097e9b43b2521a419a32..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3489 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 diff --git a/src/shared/initial-app/Intro/imgs/demos/icon-demo-device-screen.jpg b/src/shared/initial-app/Intro/imgs/demos/icon-demo-device-screen.jpg deleted file mode 100644 index 5f140f0f5b88320febee67f1a890010656bdc200..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4713 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 diff --git a/src/shared/initial-app/Intro/imgs/demos/icon-demo-large-photo.jpg b/src/shared/initial-app/Intro/imgs/demos/icon-demo-large-photo.jpg deleted file mode 100644 index f418b268b51a291dc01789df1f48ecc09422a049..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4413 zcmbtXXEfa1xBktH(c3UV^fqdUQKEN7i8{JL)QJ)V5kx0MZxh1kqL-+NAkjjU=+R4} zCCWsJgdoM0_x<0y*8Oll-M!CR=fhcRujlOZtmirVV*X+kpw&g`pa2jE06>=sTr2?( z0qDO$ObjI@B_SoHASWXur+`sXT_%i~mY()LXau}TF-uI@?OhIS>y>+< zOplv&u|l$wC&!LBAAR5MhxcDTM=5SZpicjM?bLccVE4c`I2;>e>`)P3(6GF;5K+~ipLGvA zOC~klLt&;d#kA|o)dE}1wypn1aWI7VKYfAxEnbB;nDc6yZu^i~Ay?b9;Hk$(cNjo# z39NghBQ-6a>kOHAJ^*6q6mHYd=({Y5LYH(>pX8{9#ra*9N6`Mo4uGKlY=4`7*>fwX3(ZR29(Q@!bQG;HY>JM?X=`49=1b8bdqx7ew@$DCLwW3MQTsy2a7G9aIR_vAyZHXHSL>kfl_%0)G)LE`ds z<0153tO*A&L#|0k+*_2EVnAyFOEjH(n7Vk#upzdb3N|BlM4T1LVMC{pv^Y{B4DQD_49}e$X zOQup_F)MZ$?%hqHj!c^nVd_GR+C%)KHe5!e%Hf`1OX3)S}8$FG;kYOye#l| z<)V5zSkBQZ``Vx~PmQg?m(HCCPW=Sr_<3`DI7M@>cR{W$irGQ#ms8h@P9Ak6G z`_+{hs_s?|2IffWh@6sZl`PtS!dljj8+|8;B70bC3JM{JeycA3C7uyy`Jk@(U&9PL za+3?yRz=Q2Sq$1y(CvoBxyIfJSBfF|{Y00UmKL>@9UFafCVlKP_YeMJbkN#EL83gx zJ%zoI?2`KlnHRt(yg%VqFR7yES5?El&10>88EbQFOvUTM(wfR_XYfvILu)g8O9Ahx zqB!U7oNGm6+@lVgNgQ<8SEu8;FP)O@qx6hWWm_7t__nKepmo34@BMx+E?f6Q4%5y7=N2!B2zTlqrRs3@eZ zR=aE!y-@J^drm~9dWBDN%6RC}tW`+Nv1^TlpWTF-UpG%MH~}*o8xQ9cpDbPOV6n>9 zva%APKG=O)_BxOM!)kezWqa#=z7-R)Z$fo5&#EW<|YU z=XK>T619qy+**uya&J9)hY^qQY?`$cV4k1TA-ermv1ZON*!Y~f_q#Ycx*k6%9t9f3 zY|ES-@3W^{Fa7f40ABzc%1RaI7XTAQ=mO)3236u)QA%lKmQSnsoXRcO$n6`tZjrn^KJ=DGf3=lhs*%lz8*r zli&RPMmLB^Dxu$6+|9DjaOf67=A@cUpooJrcPQ5#H^GxMEhV0kxg@~-ZESk|?B^PH zD-T#(p_qL{wm9Uc^Y=|Ld-4d)0edfnJQxjw$tL6Qz%rENbc?dgbg)cYfHtG2gK5X0 zxvcyhEbHkPC9!=TU4u1^te0bALRFJ0J~0b2jF~3wLj#J9a3uq~1vXVdddS;ZWuvp< z`BDz%t~>9w_jtsb*UKK`#u)@A_!>9wx$!03i=VG+zhj1NZqui|k&c@pBi=K0M%5Oi zBXrtGLSnZh+k-}iXm7`7aU1NWfggfzBtTVaU0v&oCezFkwZ_vFsFhnL2cvU{cd@_U zQuP3_U6t%98rAL3Rmh{{aj9M5DL&c0WM;`883uX@(q4{JP4$Ab>DfzWo5HP=%Elr+ zB1aN8h1IL-{fVPlMU@Gxao-1bUQ@Q%&|{+ZI47i>-Yq*DBd4@^UD^1#<4L3MMbN8u zpUP5PBdbgg*QyCULL+XG&*JzwbJDw67r(~9Ty9Tyusw!| zaY}HD71Gdk)#PUak1AdG_lki5TTUqq_L^aOQuW_=nnJb$_}c6it=XO zcMZGQ_Y2ZmeT!K!Ap>Sk`TSU zlDvCFO)VagYD4qdB1Sgo!@78m?(X3591!QSdW^obj={O7_dSHUSZ9QTbc z00=_Gv?&u!BYh-Zt05~ZIhbp9XAfKRiVo?B#S1$q1tT7u)2?O7% zd80xP30#=FOtd4!3j6K8sz=FzCRznr{L(Q*G4n-qqN5c2ToM>wk6?}{^eI{epq1$+ z1q}uWP92=szRoJ?J7k18P3`*Yt$NRgT2r-Ux^UBLs(bCI4sAv9PEVC|)JBH7;4Bfo ztjPz#igQT<%-SWk#~Ol8&kNsU6ufz)L~jgoEAV%4;hyUE97y45-<4q1eYLY-2?C0T ze)`6fDry4K4PXI*=I#wybR0C6I8K(OhP&=Dx_5vla&C)z)dHYt=wCFQ!=RSfw!B{B ztN=v+!x|yD=s{(HD`4n6&qU-CllBCL_RNUj&~e0+e3VXd%MHkTru`x&DjtH ziABv@=cJ`kF$~ekq;mUGikk~*k0sjfduRfyiA1x}3wOz2Rg_t-=~e?l?5_~11P{dG^iR!MW*-rrK@ zt?{mM!_N!@Q9O@R>G7kQTRjiPdOC%{Y$J&;v;%19Y(3sBgRH8qW!z}h%NV@>Z5Pr) z_x4vJ%aD9DDiK_NrN<6szA~jf))O`x-sc1WHCr^!u$zYx7IM5(oX`{c&5ykcqo`+Y znT8GNLl9-D*oACd+x&^;iMOOSIJH9;(j1V=s!mCKTZNgs&X6CJNQI4(xTIO)O?G3{{8BMK+1sC$@s0MaT$L=ih;;^(E@`H zlK!^DX@A&W=x13tLnkP`U5pR-NK-#Rbu!eWL#iCIV!Qt+&t6KbDY0=lwSW`(7Mv)Z z${I5){DnWmrv$Iq*+CMkMC~4b`tplV{kCWm8FbE^xv>M`Y)D&#JFqw%AT$4>*6@qf<36MSS(G00 zrAU8bEWZ@_Bs20*X`bs;Ot@qv8=9IvkW8Qew`MOfBF>`=})2{5O*z{3SncemWf4IA*o$lv+cMqin;ke1%(@|cf{H?ZC zY)55Ep|Sy6AI>B^+2Z%8!FD;P5gohI+4RIUi-09)6;WWXM)v|_kc4Zmbx;*kp z9j(FD&p8p(+(ktLg)3&K{hD?jh2ax3eldOTr7PMXe9(EH!BNsve91t2RJfHsYCa*1 zUVN6&-~#}-kII)s5xOJ_=yGm?5M54v{{#pOKww-%bVyZWC_N7?x445^G2AEeAC_E> zxynF=L7DY1ID-V)!dR+JB#kbSa~bnar+c=^)kH!o`pqc%E>Y*Xj^z52)_cZ<`h_6zn8M$;Q&@2Cs zN8bb4DY2)ffj98>X4NA5(1=$mEFGSXl+*5R6aLg)ZUt30KVd#uIcT0FEHT9B=NL*8 zhmq?5w^B;xN?)+-n}Ym466mLo)t(LOYPMYf%~rPV%udNA?@ve7qG)gB>6>ueksVAm zUS7c;)xqT+c>BI6zrMMwTe35zaoxD8x^hT1sr>tsTssepKUK|VqjL8djqSW~quPL< zPAPgWA4n5hQ<;$6hzAdAs!Vzq63U~AjSMy7D}Uzbrp}bgQ*4ogPUNN0(Lg0BManA8NyX)0%Cuz&@_mL(2&7k3VB=_LS za?S0;Y$<-v?e~^1`o@y#&av*+zU$Ak6Y)C|X;m(rYKQjBl5g!s4ib+JQpRuanhrEpSXWlVe2X zrEOeCIfYU)LqI+>c&GqepHhgf8f>Udjl4vMxZ-*v)_N|+p@hh!ht_T;*S4FCTRgXF zEY~VPn*aa=U`a$lRCodH*oA)NHWY<%uXjdm*f-#5>oiRAGBYzXGc$9ZzOps;u{<}H z0_A)U@b`(L-;lT5a{uyH_+~4Y3I$K{-D-IQV;le$0s<4@wW1Ff3)4{I+cTqzg|$-c+kT-Q;JC@s_iJ zFI90I_XSyzV8$Es16?dt7G9>UinCT}f#-q4m#gi5dKDs-Ks-N_+3=;!$8p+t3XC+r zrqW+K>FdkDX6Oyb*7?X+^Sf<4bkwJLvheG0`PXkS`HfoNh?6O%`Bj^$?|x8fbIW{2 z2Tx>wSwLI1Zr!@g^m!dTIqB=S=80073fQsldc>;5jJb$S2a$;KYg0W z&ZBF&eL3&=@zC%!enyD-@-z+|T`%}I8bQz?x_N7dNO&S={x(s-=y&eiIo|Y9^CQb$ zcbl;=NZGnxB3TJe!PS$8+wwq z>mn-}I>q`9Zzpl~l8i!aLyv|o$}}e)eqU-!;QHDh7sWcG=+Sq!MYHZ>EK&^k4d8aQlzn~ zO`YSPdtA@qzX%EeWcgO#o2!?vp1n4I^?LJGRlzg!_j^@QuD^Sm<;Gvsd?gP3En;=> zSyrBs4E_BFRa+Gg-$@7m@k29SXHLhR4?g5=<8}6QJU1SG)Y+=niu}_8f6H9U7hkauR|4>YK%})|+ZR$*tUdaV4 zM}Aso39tKU={v)#C#n4P)iZBl`CQ&oH`}4qj!AF1<^H<<0O>*ct>PPR$N&HU07*qo IM6N<$f@%>gTmS$7 diff --git a/src/shared/initial-app/Intro/index.tsx b/src/shared/initial-app/Intro/index.tsx deleted file mode 100644 index 10b01cbb..00000000 --- a/src/shared/initial-app/Intro/index.tsx +++ /dev/null @@ -1,255 +0,0 @@ -import { h, Component } from 'preact'; - -import { linkRef } from 'shared/initial-app/util'; -import '../custom-els/loading-spinner'; - -import logo from 'url:./imgs/logo.svg'; -import largePhoto from 'url:./imgs/demos/demo-large-photo.jpg'; -import artwork from 'url:./imgs/demos/demo-artwork.jpg'; -import deviceScreen from 'url:./imgs/demos/demo-device-screen.png'; -import largePhotoIcon from 'url:./imgs/demos/icon-demo-large-photo.jpg'; -import artworkIcon from 'url:./imgs/demos/icon-demo-artwork.jpg'; -import deviceScreenIcon from 'url:./imgs/demos/icon-demo-device-screen.jpg'; -import logoIcon from 'url:./imgs/demos/icon-demo-logo.png'; -import * as style from './style.css'; -import type SnackBarElement from 'shared/initial-app/custom-els/snack-bar'; -import 'shared/initial-app/custom-els/snack-bar'; - -const demos = [ - { - description: 'Large photo (2.8mb)', - filename: 'photo.jpg', - url: largePhoto, - iconUrl: largePhotoIcon, - }, - { - description: 'Artwork (2.9mb)', - filename: 'art.jpg', - url: artwork, - iconUrl: artworkIcon, - }, - { - description: 'Device screen (1.6mb)', - filename: 'pixel3.png', - url: deviceScreen, - iconUrl: deviceScreenIcon, - }, - { - description: 'SVG icon (13k)', - filename: 'squoosh.svg', - url: logo, - iconUrl: logoIcon, - }, -]; - -const installButtonSource = 'introInstallButton-Purple'; - -interface Props { - onFile?: (file: File) => void; - showSnack?: SnackBarElement['showSnackbar']; -} -interface State { - fetchingDemoIndex?: number; - beforeInstallEvent?: BeforeInstallPromptEvent; -} - -export default class Intro extends Component { - state: State = {}; - private fileInput?: HTMLInputElement; - private installingViaButton = false; - - constructor() { - super(); - - if (__PRERENDER__) return; - // Listen for beforeinstallprompt events, indicating Squoosh is installable. - window.addEventListener( - 'beforeinstallprompt', - this.onBeforeInstallPromptEvent, - ); - - // Listen for the appinstalled event, indicating Squoosh has been installed. - window.addEventListener('appinstalled', this.onAppInstalled); - } - - private resetFileInput = () => { - this.fileInput!.value = ''; - }; - - private onFileChange = (event: Event): void => { - const fileInput = event.target as HTMLInputElement; - const file = fileInput.files && fileInput.files[0]; - if (!file) return; - this.resetFileInput(); - this.props.onFile!(file); - }; - - private onButtonClick = () => { - this.fileInput!.click(); - }; - - private onDemoClick = async (index: number, event: Event) => { - try { - this.setState({ fetchingDemoIndex: index }); - const demo = demos[index]; - const blob = await fetch(demo.url).then((r) => r.blob()); - - // Firefox doesn't like content types like 'image/png; charset=UTF-8', which Webpack's dev - // server returns. https://bugzilla.mozilla.org/show_bug.cgi?id=1497925. - const type = /[^;]*/.exec(blob.type)![0]; - const file = new File([blob], demo.filename, { type }); - this.props.onFile!(file); - } catch (err) { - this.setState({ fetchingDemoIndex: undefined }); - this.props.showSnack!("Couldn't fetch demo image"); - } - }; - - private onBeforeInstallPromptEvent = (event: BeforeInstallPromptEvent) => { - // Don't show the mini-infobar on mobile - event.preventDefault(); - - // Save the beforeinstallprompt event so it can be called later. - this.setState({ beforeInstallEvent: event }); - - // Log the event. - const gaEventInfo = { - eventCategory: 'pwa-install', - eventAction: 'promo-shown', - nonInteraction: true, - }; - ga('send', 'event', gaEventInfo); - }; - - private onInstallClick = async (event: Event) => { - // Get the deferred beforeinstallprompt event - const beforeInstallEvent = this.state.beforeInstallEvent; - // If there's no deferred prompt, bail. - if (!beforeInstallEvent) return; - - this.installingViaButton = true; - - // Show the browser install prompt - beforeInstallEvent.prompt(); - - // Wait for the user to accept or dismiss the install prompt - const { outcome } = await beforeInstallEvent.userChoice; - // Send the analytics data - const gaEventInfo = { - eventCategory: 'pwa-install', - eventAction: 'promo-clicked', - eventLabel: installButtonSource, - eventValue: outcome === 'accepted' ? 1 : 0, - }; - ga('send', 'event', gaEventInfo); - - // If the prompt was dismissed, we aren't going to install via the button. - if (outcome === 'dismissed') { - this.installingViaButton = false; - } - }; - - private onAppInstalled = () => { - // We don't need the install button, if it's shown - this.setState({ beforeInstallEvent: undefined }); - - // Don't log analytics if page is not visible - if (document.hidden) { - return; - } - - // Try to get the install, if it's not set, use 'browser' - const source = this.installingViaButton ? installButtonSource : 'browser'; - ga('send', 'event', 'pwa-install', 'installed', source); - - // Clear the install method property - this.installingViaButton = false; - }; - - render({}: Props, { fetchingDemoIndex, beforeInstallEvent }: State) { - return ( -
-
-
-
- Squoosh -
-
-

- Drag & drop or{' '} - - -

-

Or try one of these:

-
    - {demos.map((demo, i) => ( -
  • - -
  • - ))} -
-
- {beforeInstallEvent && ( - - )} - -
- ); - } -} diff --git a/src/shared/initial-app/Intro/style.css b/src/shared/initial-app/Intro/style.css deleted file mode 100644 index 6e936e5c..00000000 --- a/src/shared/initial-app/Intro/style.css +++ /dev/null @@ -1,228 +0,0 @@ -@font-face { - font-family: 'intro-text'; - font-style: normal; - font-weight: 300; - font-display: block; - /* This only contains the chars for "Drag & drop or" */ - src: url('data:font/woff2;base64,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') - format('woff2'); -} - -@font-face { - font-family: 'intro-text'; - font-style: normal; - font-weight: 500; - font-display: block; - /* Only contains the chars for "select an image" */ - src: url('data:font/woff2;base64,d09GMgABAAAAAAXMAA4AAAAACwQAAAV5AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGhYbg1IcMAZgAGwRCAqJUId/CxoAATYCJAMwBCAFgnAHIBskCcjEh6dNff8Ou/9Tj9VZGnUhJeqFzWGiVVOkxkTthr9f6kWkRdsBbkIj3YuLaloFZWr7aBg22z7IOoWqBWCW5cZU3GBrh0n+dAcBYAUlzYHAzWTYchqKXEyAT0zOLIS1qqm+B8q2ur4OhEMy0PHHUH8KklaSr8T0mp/EU7kRvXlI1E09HXA1qLN8Djxa0AsSDOg3cJARb9mtQJoSK3gvEn372/gcAigg/gOnbsT/MYv491GTReW4rJC5LA+h5FFclF6QQgoZ5Kx7GbsuGeytUgFClkOomY2Gdake3m9HegkHieAx/a0hBTALsy4jvpxBcnFXUnjC+2ZS5zHnDeEaJVwi+ZWqzOm4Uvgy4k6kGv4kFDVkfjk1gkVRRkk2zlo42PBbRJmG30cClJQjak7BnfQqHza4ITKftQZ/ZMUaEiyy1+mYCh4clKhDA5rQglZ0oG9jw+qiNvT+SfxIXCeuFdeIq8VV4gpxyRaGOl0JiChiCocfc7e93DwZIPvWgPiZJLcJugxyjW7UQyl4TJk6dWqYU7Cn0WQiWnNJCdGeprqjW63fpVS3mKy4YGZ6I3ya4nbIVgM1mwkpNEBzixlNxfPmH7owvdE4973OM9quvk11dwvnzDUy/Zn5S5Ywpn/PeqXBQI2m4lna05CRtsI6+GIENjS9K4jWRHUGYA2ozdZm2Smmf0DI3aqpeNbsJfxe7YdFmcZAn5gXLCFa2/Umqiu017APFhMZ0rfQp4sJX0ZrJ+n9UtAljr5VYWb6oj1MrpvX3qe6u8WRJg0bj7aPkDOa7m+E0Oa9Y8eY/gbRbr+efH7hcO49bMd28fbDVHcUmm3XkozQGKjeBHSJ4TQnI879LIFmF2v/BJuEQlffJPfE9oKayS/PsPE44fvM4MsBESxbuEEV39d5pw6oW4vD6S1WQC3UpSbHNbK0Jikl0bphSs+0CGW8Ew4Kzw7zarmcVz873JHTFhKYay18R8vY0ozPiHPAGyROAqlW5fLj5+HbWBn9TpgekKsOy8N+4dlFfL9i/Nk3+gY1bwzZUAvLVNiFpvqHRenetSoVrgn2obGtPsltEVxEeHJAQFhyBIcHT9rDvTJm06e0TLgase2gd2RffGJNg0o1zrdRyi9s1bYE5bi85cK+o/nUwvBR5+jweEBaSMoCub29fEFISmib9Dn5yl5kVFpoGrPQSmZhafQ7WimttNCH7Cktohb6kFpoEfIsdDF7SgvZTbaY3mKFyLXQh+wpzWE3mUNQQkWnR+lgiW9Afkunej49Nz3sYlI8XFTRdkNhUR5d6h4oOpJc8OjcItMXVqoHW2fSW6ycWuiM8NDYoICouLAZ9BYrwwmhycvKlt4Q8hUlCV5nZ7vOm2ut2rizcFpuWpSrT3K1Z3xinbuHnXBTyGAljV/XzHZaNGu6y7vLDziMpIyUdOBBRXXlxznUQiOoheZsZk9njG8er1mNmz2eOCQ3x9BbLP+Zxt+VrbEz9aWxmimRvyl4/sumyoM/nw+LNzV4/uP0/9T/P5f08cvhl38USAS/xTYs2fL/VNFF0vd+SVsRB/khPwW4SCi5SHhx8fDgVPAiAqIJRQL/EuK5bsRzNnAiezD1i7u2VyHHAKRU+E2YUaA5DUsE2ZfbApAmsJcxjBwmQ2Xk4Y2BqZJ+oxSzsNEogz1O3/xkBMKEBHSiC8PoQStaoEIflPCHL/wQBCUKoUITlMhHP+olt5ojykUPOrEQTWgY+f449KMPKnSiB72jGrLQhEZO24925LtbkG5DndTkD2/4um+NQBEyUIJsRBxX24tcDGmbWtGJjq05jhzTaMgCcR+6EA4f+KAXDay5u6RsL7xJsm3w3mzvFvggB8nI/AYBdVnxNvx/2wA=') - format('woff2'); -} - -@keyframes fade-in { - from { - opacity: 0; - } -} - -.intro { - display: grid; - grid-template-rows: 1fr min-content; - align-items: center; - background: rgba(255, 255, 255, 0.25); - text-align: center; - font-size: 2rem; - -webkit-overflow-scrolling: touch; - overflow: auto; - padding: 20px 0 0; - height: 100%; - box-sizing: border-box; - overscroll-behavior: contain; - position: relative; -} - -.logo-container { - position: relative; - padding-top: 100%; -} - -.logo-sizer { - width: 90%; - max-width: 52vh; - margin: 0 auto; -} - -.logo { - composes: abs-fill from '../util.css'; - pointer-events: none; -} - -.open-image-guide { - font: 300 11vw intro-text, sans-serif; - margin-bottom: 0; - - @media (min-width: 460px) { - font-size: 50.6px; - padding: 0 40px; - } -} - -.select-button { - composes: unbutton from '../util.css'; - font-weight: 500; - color: #5d509e; - - &:hover, - &:focus { - text-decoration: underline; - } -} - -.hide { - display: none; -} - -.demos { - display: block; - padding: 0; - border-top: 1px solid #e8e8e8; - margin: 0 auto; - - @media (min-width: 400px) { - display: grid; - grid-template-columns: 1fr 1fr; - } - - @media (min-width: 580px) { - border-top: none; - width: 523px; - } - - @media (min-width: 900px) { - display: grid; - grid-template-columns: 1fr 1fr 1fr 1fr; - width: 773px; - } -} - -.demo-item { - background: #fff; - display: flex; - border-bottom: 1px solid #e8e8e8; - - @media (min-width: 580px) { - border: 1px solid #e8e8e8; - border-radius: 4px; - margin: 3px; - } -} - -.demo-button { - composes: unbutton from '../util.css'; - flex: 1; - - &:hover, - &:focus { - background: #f5f5f5; - } -} - -.demo { - display: flex; - align-items: center; - padding: 7px; - font-size: 1.3rem; -} - -.demo-img-container { - overflow: hidden; - display: block; - width: 47px; - background: #ccc; - border-radius: 3px; - flex: 0 0 auto; -} - -.demo-img-aspect { - position: relative; - padding-top: 100%; -} - -.demo-icon { - composes: abs-fill from '../util.css'; - pointer-events: none; -} - -.demo-description { - display: flex; - align-items: center; - justify-content: flex-start; - text-align: left; - flex: 1; - padding: 0 10px; -} - -.demo-loading { - composes: abs-fill from '../util.css'; - background: rgba(0, 0, 0, 0.6); - display: flex; - align-items: center; - justify-content: center; - animation: fade-in 300ms ease-in-out; -} - -.demo-loading-spinner { - --color: #fff; -} - -.install-button { - composes: unbutton from '../util.css'; - - &:hover, - &:focus { - background: #504488; - } - - background: #5d509e; - border: 1px solid #e8e8e8; - color: #fff; - padding: 14px; - font-size: 1.3rem; - - position: absolute; - top: 1rem; - right: 1rem; - - animation: fade-in 0.3s ease-in-out; -} - -@keyframes fade-in { - from { - opacity: 0; - } -} - -.related-links { - display: flex; - padding: 0; - justify-content: center; - font-size: 1.3rem; - - & li { - display: block; - border-left: 1px solid #000; - padding: 0 0.6em; - - &:first-child { - border-left: none; - } - } - - & a:link { - color: #5d509e; - text-decoration: none; - - &:hover { - text-decoration: underline; - } - } -} diff --git a/src/shared/initial-app/util.css b/src/shared/initial-app/util.css deleted file mode 100644 index b8c7ba48..00000000 --- a/src/shared/initial-app/util.css +++ /dev/null @@ -1,22 +0,0 @@ -.abs-fill { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - box-sizing: border-box; - contain: strict; -} - -.unbutton { - cursor: pointer; - background: none; - border: none; - font: inherit; - padding: 0; - margin: 0; - - &:focus { - outline: none; - } -} diff --git a/src/shared/missing-types.d.ts b/src/shared/missing-types.d.ts index baf404ec..0320ec90 100644 --- a/src/shared/missing-types.d.ts +++ b/src/shared/missing-types.d.ts @@ -13,3 +13,24 @@ /// declare const __PRERENDER__: boolean; + +type ResizeObserverCallback = ( + entries: ResizeObserverEntry[], + observer: ResizeObserver, +) => void; + +interface ResizeObserverEntry { + readonly target: Element; + readonly contentRect: DOMRectReadOnly; +} + +interface ResizeObserver { + observe(target: Element): void; + unobserve(target: Element): void; + disconnect(): void; +} + +declare var ResizeObserver: { + prototype: ResizeObserver; + new (callback: ResizeObserverCallback): ResizeObserver; +}; diff --git a/src/shared/prerendered-app/Intro/blob-anim/index.ts b/src/shared/prerendered-app/Intro/blob-anim/index.ts new file mode 100644 index 00000000..8b1d9e58 --- /dev/null +++ b/src/shared/prerendered-app/Intro/blob-anim/index.ts @@ -0,0 +1,417 @@ +import * as style from '../style.css'; +import { startBlobs } from './meta'; + +/** + * Control point x,y - point x,y - control point x,y + */ +export type BlobPoint = [number, number, number, number, number, number]; + +const maxPointDistance = 0.25; + +function randomisePoint(point: BlobPoint): BlobPoint { + const distance = Math.random() * maxPointDistance; + const angle = Math.random() * Math.PI * 2; + const xShift = Math.sin(angle) * distance; + const yShift = Math.cos(angle) * distance; + return [ + point[0] + xShift, + point[1] + yShift, + point[2] + xShift, + point[3] + yShift, + point[4] + xShift, + point[5] + yShift, + ]; +} + +function easeInOutQuad(x: number): number { + return x < 0.5 ? 2 * x * x : 1 - Math.pow(-2 * x + 2, 2) / 2; +} + +function easeInExpo(x: number): number { + return x === 0 ? 0 : Math.pow(2, 10 * x - 10); +} + +const rand = (min: number, max: number) => Math.random() * (max - min) + min; + +interface CircleBlobPointState { + basePoint: BlobPoint; + pos: number; + duration: number; + startPoint: BlobPoint; + endPoint: BlobPoint; +} + +/** Bezier points for a seven point circle, to 3 decimal places */ +const sevenPointCircle: BlobPoint[] = [ + [-0.304, -1, 0, -1, 0.304, -1], + [0.592, -0.861, 0.782, -0.623, 0.972, -0.386], + [1.043, -0.074, 0.975, 0.223, 0.907, 0.519], + [0.708, 0.769, 0.434, 0.901, 0.16, 1.033], + [-0.16, 1.033, -0.434, 0.901, -0.708, 0.769], + [-0.907, 0.519, -0.975, 0.223, -1.043, -0.074], + [-0.972, -0.386, -0.782, -0.623, -0.592, -0.861], +]; + +/* +// Should it be needed, here's how the above was created: +function createBezierCirclePoints(points: number): BlobPoint[] { + const anglePerPoint = 360 / points; + const matrix = new DOMMatrix(); + const point = new DOMPoint(); + const controlDistance = (4 / 3) * Math.tan(Math.PI / (2 * points)); + return Array.from({ length: points }, (_, i) => { + point.x = -controlDistance; + point.y = -1; + const cp1 = point.matrixTransform(matrix); + point.x = 0; + point.y = -1; + const p = point.matrixTransform(matrix); + point.x = controlDistance; + point.y = -1; + const cp2 = point.matrixTransform(matrix); + const basePoint: BlobPoint = [cp1.x, cp1.y, p.x, p.y, cp2.x, cp2.y]; + matrix.rotateSelf(0, 0, anglePerPoint); + return basePoint; + }); +} +*/ + +interface CircleBlobOptions { + minDuration?: number; + maxDuration?: number; + startPoints?: BlobPoint[]; +} + +class CircleBlob { + private animStates: CircleBlobPointState[]; + private minDuration: number; + private maxDuration: number; + private points: BlobPoint[]; + + constructor( + basePoints: BlobPoint[], + { + startPoints = basePoints.map((point) => randomisePoint(point)), + minDuration = 4000, + maxDuration = 11000, + }: CircleBlobOptions = {}, + ) { + this.points = startPoints; + this.minDuration = minDuration; + this.maxDuration = maxDuration; + this.animStates = basePoints.map((basePoint, i) => ({ + basePoint, + pos: 0, + duration: rand(minDuration, maxDuration), + startPoint: startPoints[i], + endPoint: randomisePoint(basePoint), + })); + } + + advance(timeDelta: number): void { + this.points = this.animStates.map((animState) => { + animState.pos += timeDelta / animState.duration; + if (animState.pos >= 1) { + animState.startPoint = animState.endPoint; + animState.pos = 0; + animState.duration = rand(this.minDuration, this.maxDuration); + animState.endPoint = randomisePoint(animState.basePoint); + } + const eased = easeInOutQuad(animState.pos); + + const point = animState.startPoint.map((startPoint, i) => { + const endPoint = animState.endPoint[i]; + return (endPoint - startPoint) * eased + startPoint; + }) as BlobPoint; + + return point; + }); + } + + draw(ctx: CanvasRenderingContext2D) { + const points = this.points; + ctx.beginPath(); + ctx.moveTo(points[0][2], points[0][3]); + + for (let i = 0; i < points.length; i++) { + const nextI = i === points.length - 1 ? 0 : i + 1; + ctx.bezierCurveTo( + points[i][4], + points[i][5], + points[nextI][0], + points[nextI][1], + points[nextI][2], + points[nextI][3], + ); + } + + ctx.closePath(); + ctx.fill(); + } +} + +const centralBlobsRotationTime = 120000; + +class CentralBlobs { + private rotatePos: number = 0; + private blobs = Array.from( + { length: 4 }, + (_, i) => new CircleBlob(sevenPointCircle, { startPoints: startBlobs[i] }), + ); + + advance(timeDelta: number) { + this.rotatePos = + (this.rotatePos + timeDelta / centralBlobsRotationTime) % 1; + for (const blob of this.blobs) blob.advance(timeDelta); + } + + draw(ctx: CanvasRenderingContext2D, x: number, y: number, radius: number) { + ctx.save(); + ctx.translate(x, y); + ctx.scale(radius, radius); + ctx.rotate(Math.PI * 2 * this.rotatePos); + for (const blob of this.blobs) blob.draw(ctx); + ctx.restore(); + } +} + +const bgBlobsMinRadius = 7; +const bgBlobsMaxRadius = 60; +const bgBlobsMinAlpha = 0.2; +const bgBlobsMaxAlpha = 0.8; +const bgBlobsPerPx = 0.000025; +const bgBlobsMinSpinTime = 20000; +const bgBlobsMaxSpinTime = 60000; +const bgBlobsMinVelocity = 0.0015; +const bgBlobsMaxVelocity = 0.007; +const gravityVelocityMultiplier = 15; +const gravityStartDistance = 300; + +interface BackgroundBlob { + blob: CircleBlob; + velocity: number; + spinTime: number; + alpha: number; + alphaMultiplier: number; + rotatePos: number; + radius: number; + x: number; + y: number; +} + +const bgBlobsAlphaTime = 2000; + +class BackgroundBlobs { + private bgBlobs: BackgroundBlob[] = []; + private overallAlphaPos = 0; + + constructor(bounds: DOMRect) { + const blobs = Math.round(bounds.width * bounds.height * bgBlobsPerPx); + this.bgBlobs = Array.from({ length: blobs }, () => { + const radiusPos = easeInExpo(Math.random()); + + return { + blob: new CircleBlob(sevenPointCircle, { + minDuration: 2000, + maxDuration: 5000, + }), + // Velocity is based on the size + velocity: + (1 - radiusPos) * (bgBlobsMaxVelocity - bgBlobsMinVelocity) + + bgBlobsMinVelocity, + alpha: + Math.random() ** 3 * (bgBlobsMaxAlpha - bgBlobsMinAlpha) + + bgBlobsMinAlpha, + alphaMultiplier: 1, + spinTime: rand(bgBlobsMinSpinTime, bgBlobsMaxSpinTime), + rotatePos: 0, + radius: + radiusPos * (bgBlobsMaxRadius - bgBlobsMinRadius) + bgBlobsMinRadius, + x: Math.random() * bounds.width, + y: Math.random() * bounds.height, + }; + }); + } + + advance( + timeDelta: number, + bounds: DOMRect, + targetX: number, + targetY: number, + targetRadius: number, + ) { + if (this.overallAlphaPos !== 1) { + this.overallAlphaPos = Math.min( + 1, + this.overallAlphaPos + timeDelta / bgBlobsAlphaTime, + ); + } + for (const bgBlob of this.bgBlobs) { + bgBlob.blob.advance(timeDelta); + let dist = Math.hypot(bgBlob.x - targetX, bgBlob.y - targetY); + bgBlob.rotatePos = (bgBlob.rotatePos + timeDelta / bgBlob.spinTime) % 1; + + if (dist < 10) { + // Move the circle out to a random edge + switch (Math.floor(Math.random() * 4)) { + case 0: // top + bgBlob.x = Math.random() * bounds.width; + bgBlob.y = -(bgBlob.radius * (1 + maxPointDistance)); + break; + case 1: // left + bgBlob.x = -(bgBlob.radius * (1 + maxPointDistance)); + bgBlob.y = Math.random() * bounds.height; + break; + case 2: // bottom + bgBlob.x = Math.random() * bounds.width; + bgBlob.y = bounds.height + bgBlob.radius * (1 + maxPointDistance); + break; + case 3: // right + bgBlob.x = bounds.width + bgBlob.radius * (1 + maxPointDistance); + bgBlob.y = Math.random() * bounds.height; + break; + } + } + dist = Math.hypot(bgBlob.x - targetX, bgBlob.y - targetY); + const velocity = + dist > gravityStartDistance + ? bgBlob.velocity + : ((1 - dist / gravityStartDistance) * + (gravityVelocityMultiplier - 1) + + 1) * + bgBlob.velocity; + const shiftDist = velocity * timeDelta; + const direction = Math.atan2(targetX - bgBlob.x, targetY - bgBlob.y); + const xShift = Math.sin(direction) * shiftDist; + const yShift = Math.cos(direction) * shiftDist; + bgBlob.x += xShift; + bgBlob.y += yShift; + bgBlob.alphaMultiplier = Math.min(dist / targetRadius, 1); + } + } + + draw(ctx: CanvasRenderingContext2D) { + const overallAlpha = easeInOutQuad(this.overallAlphaPos); + + for (const bgBlob of this.bgBlobs) { + ctx.save(); + ctx.globalAlpha = bgBlob.alpha * bgBlob.alphaMultiplier * overallAlpha; + ctx.translate(bgBlob.x, bgBlob.y); + ctx.scale(bgBlob.radius, bgBlob.radius); + ctx.rotate(Math.PI * 2 * bgBlob.rotatePos); + bgBlob.blob.draw(ctx); + ctx.restore(); + } + } +} + +const deltaMultiplierStep = 0.01; + +export function startBlobAnim(canvas: HTMLCanvasElement) { + let lastTime: number; + const ctx = canvas.getContext('2d')!; + const centralBlobs = new CentralBlobs(); + let backgroundBlobs: BackgroundBlobs; + const loadImgEl = document.querySelector('.' + style.loadImg)!; + let hasFocus = document.hasFocus(); + let deltaMultiplier = hasFocus ? 1 : 0; + let animating = true; + + const visibilityListener = () => { + // 'Pause time' while page is hidden + if (document.visibilityState === 'visible') lastTime = performance.now(); + }; + const focusListener = () => { + hasFocus = true; + if (!animating) startAnim(); + }; + const blurListener = () => { + hasFocus = false; + }; + + new ResizeObserver(() => { + // Redraw for new canvas size + if (!animating) drawFrame(0); + }).observe(canvas); + + addEventListener('focus', focusListener); + addEventListener('blur', blurListener); + document.addEventListener('visibilitychange', visibilityListener); + + function destruct() { + removeEventListener('focus', focusListener); + removeEventListener('blur', blurListener); + document.removeEventListener('visibilitychange', visibilityListener); + } + + function drawFrame(delta: number) { + const canvasBounds = canvas.getBoundingClientRect(); + canvas.width = canvasBounds.width * devicePixelRatio; + canvas.height = canvasBounds.height * devicePixelRatio; + const loadImgBounds = loadImgEl.getBoundingClientRect(); + const computedStyles = getComputedStyle(canvas); + const blobPink = computedStyles.getPropertyValue('--blob-pink'); + const loadImgCenterX = + loadImgBounds.left - canvasBounds.left + loadImgBounds.width / 2; + const loadImgCenterY = + loadImgBounds.top - canvasBounds.top + loadImgBounds.height / 2; + const loadImgRadius = loadImgBounds.height / 2 / (1 + maxPointDistance); + + ctx.scale(devicePixelRatio, devicePixelRatio); + + if (!backgroundBlobs) backgroundBlobs = new BackgroundBlobs(canvasBounds); + backgroundBlobs.advance( + delta, + canvasBounds, + loadImgCenterX, + loadImgCenterY, + loadImgRadius, + ); + centralBlobs.advance(delta); + + ctx.globalAlpha = Number( + computedStyles.getPropertyValue('--center-blob-opacity'), + ); + ctx.fillStyle = blobPink; + + backgroundBlobs.draw(ctx); + centralBlobs.draw(ctx, loadImgCenterX, loadImgCenterY, loadImgRadius); + } + + function frame(time: number) { + // Stop the loop if the canvas is gone + if (!canvas.isConnected) { + destruct(); + return; + } + + // Be kind: If the window isn't focused, bring the animation to a stop. + if (!hasFocus) { + // Bring the anim to a slow stop + deltaMultiplier = Math.max(0, deltaMultiplier - deltaMultiplierStep); + if (deltaMultiplier === 0) { + animating = false; + return; + } + } else if (deltaMultiplier !== 1) { + deltaMultiplier = Math.min(1, deltaMultiplier + deltaMultiplierStep); + } + + const delta = (time - lastTime) * deltaMultiplier; + lastTime = time; + + drawFrame(delta); + + requestAnimationFrame(frame); + } + + function startAnim() { + animating = true; + requestAnimationFrame((time: number) => { + lastTime = time; + frame(time); + }); + } + + startAnim(); +} diff --git a/src/shared/prerendered-app/Intro/blob-anim/meta.ts b/src/shared/prerendered-app/Intro/blob-anim/meta.ts new file mode 100644 index 00000000..66ed610e --- /dev/null +++ b/src/shared/prerendered-app/Intro/blob-anim/meta.ts @@ -0,0 +1,41 @@ +import type { BlobPoint } from '.'; + +/** Start points, for the shape we use in prerender */ +export const startBlobs: BlobPoint[][] = [ + [ + [-0.232, -1.029, 0.073, -1.029, 0.377, -1.029], + [0.565, -1.098, 0.755, -0.86, 0.945, -0.622], + [0.917, -0.01, 0.849, 0.286, 0.782, 0.583], + [0.85, 0.687, 0.576, 0.819, 0.302, 0.951], + [-0.198, 1.009, -0.472, 0.877, -0.746, 0.745], + [-0.98, 0.513, -1.048, 0.216, -1.116, -0.08], + [-0.964, -0.395, -0.774, -0.633, -0.584, -0.871], + ], + [ + [-0.505, -1.109, -0.201, -1.109, 0.104, -1.109], + [0.641, -0.684, 0.831, -0.446, 1.02, -0.208], + [1.041, 0.034, 0.973, 0.331, 0.905, 0.628], + [0.734, 0.794, 0.46, 0.926, 0.186, 1.058], + [-0.135, 0.809, -0.409, 0.677, -0.684, 0.545], + [-0.935, 0.404, -1.002, 0.108, -1.07, -0.189], + [-0.883, -0.402, -0.693, -0.64, -0.503, -0.878], + ], + [ + [-0.376, -1.168, -0.071, -1.168, 0.233, -1.168], + [0.732, -0.956, 0.922, -0.718, 1.112, -0.48], + [1.173, 0.027, 1.105, 0.324, 1.038, 0.621], + [0.707, 0.81, 0.433, 0.943, 0.159, 1.075], + [-0.096, 1.135, -0.37, 1.003, -0.644, 0.871], + [-0.86, 0.457, -0.927, 0.161, -0.995, -0.136], + [-0.87, -0.516, -0.68, -0.754, -0.49, -0.992], + ], + [ + [-0.309, -0.998, -0.004, -0.998, 0.3, -0.998], + [0.535, -0.852, 0.725, -0.614, 0.915, -0.376], + [1.05, -0.09, 0.982, 0.207, 0.915, 0.504], + [0.659, 0.807, 0.385, 0.939, 0.111, 1.071], + [-0.178, 1.048, -0.452, 0.916, -0.727, 0.784], + [-0.942, 0.582, -1.009, 0.285, -1.077, -0.011], + [-1.141, -0.335, -0.951, -0.573, -0.761, -0.811], + ], +]; diff --git a/src/shared/initial-app/Intro/imgs/demos/demo-artwork.jpg b/src/shared/prerendered-app/Intro/imgs/demos/demo-artwork.jpg similarity index 100% rename from src/shared/initial-app/Intro/imgs/demos/demo-artwork.jpg rename to src/shared/prerendered-app/Intro/imgs/demos/demo-artwork.jpg diff --git a/src/shared/initial-app/Intro/imgs/demos/demo-device-screen.png b/src/shared/prerendered-app/Intro/imgs/demos/demo-device-screen.png similarity index 100% rename from src/shared/initial-app/Intro/imgs/demos/demo-device-screen.png rename to src/shared/prerendered-app/Intro/imgs/demos/demo-device-screen.png diff --git a/src/shared/initial-app/Intro/imgs/demos/demo-large-photo.jpg b/src/shared/prerendered-app/Intro/imgs/demos/demo-large-photo.jpg similarity index 100% rename from src/shared/initial-app/Intro/imgs/demos/demo-large-photo.jpg rename to src/shared/prerendered-app/Intro/imgs/demos/demo-large-photo.jpg diff --git a/src/shared/prerendered-app/Intro/imgs/demos/icon-demo-artwork.jpg b/src/shared/prerendered-app/Intro/imgs/demos/icon-demo-artwork.jpg new file mode 100644 index 0000000000000000000000000000000000000000..aae2b9adf64ebdd9185fb568cffb07abf21210b4 GIT binary patch literal 3411 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 literal 0 HcmV?d00001 diff --git a/src/shared/prerendered-app/Intro/imgs/demos/icon-demo-device-screen.jpg b/src/shared/prerendered-app/Intro/imgs/demos/icon-demo-device-screen.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f7bfce16cfdb69a517f78309148b8c3493f91bcc GIT binary patch literal 4226 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 H!Q?*y=%P*m literal 0 HcmV?d00001 diff --git a/src/shared/prerendered-app/Intro/imgs/demos/icon-demo-large-photo.jpg b/src/shared/prerendered-app/Intro/imgs/demos/icon-demo-large-photo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9bdb4c08e40bec073fde0ec6297bfa68d60119be GIT binary patch literal 3666 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 a#$#?B7I2<&{$)!50000 diff --git a/src/shared/prerendered-app/Intro/imgs/logo-with-text.svg b/src/shared/prerendered-app/Intro/imgs/logo-with-text.svg new file mode 100644 index 00000000..8ea594f2 --- /dev/null +++ b/src/shared/prerendered-app/Intro/imgs/logo-with-text.svg @@ -0,0 +1 @@ +Squoosh \ No newline at end of file diff --git a/src/shared/initial-app/Intro/imgs/logo.svg b/src/shared/prerendered-app/Intro/imgs/logo.svg similarity index 100% rename from src/shared/initial-app/Intro/imgs/logo.svg rename to src/shared/prerendered-app/Intro/imgs/logo.svg diff --git a/src/shared/prerendered-app/Intro/index.tsx b/src/shared/prerendered-app/Intro/index.tsx new file mode 100644 index 00000000..9e2be760 --- /dev/null +++ b/src/shared/prerendered-app/Intro/index.tsx @@ -0,0 +1,372 @@ +import { h, Component } from 'preact'; + +import { linkRef } from 'shared/prerendered-app/util'; +import '../../custom-els/loading-spinner'; +import logo from 'url:./imgs/logo.svg'; +import githubLogo from 'url:./imgs/github-logo.svg'; +import largePhoto from 'url:./imgs/demos/demo-large-photo.jpg'; +import artwork from 'url:./imgs/demos/demo-artwork.jpg'; +import deviceScreen from 'url:./imgs/demos/demo-device-screen.png'; +import largePhotoIcon from 'url:./imgs/demos/icon-demo-large-photo.jpg'; +import artworkIcon from 'url:./imgs/demos/icon-demo-artwork.jpg'; +import deviceScreenIcon from 'url:./imgs/demos/icon-demo-device-screen.jpg'; +import logoIcon from 'url:./imgs/demos/icon-demo-logo.png'; +import logoWithText from 'url:./imgs/logo-with-text.svg'; +import * as style from './style.css'; +import type SnackBarElement from 'shared/custom-els/snack-bar'; +import 'shared/custom-els/snack-bar'; +import { startBlobs } from './blob-anim/meta'; + +const demos = [ + { + description: 'Large photo', + size: '2.8mb', + filename: 'photo.jpg', + url: largePhoto, + iconUrl: largePhotoIcon, + }, + { + description: 'Artwork', + size: '2.9mb', + filename: 'art.jpg', + url: artwork, + iconUrl: artworkIcon, + }, + { + description: 'Device screen', + size: '1.6mb', + filename: 'pixel3.png', + url: deviceScreen, + iconUrl: deviceScreenIcon, + }, + { + description: 'SVG icon', + size: '13k', + filename: 'squoosh.svg', + url: logo, + iconUrl: logoIcon, + }, +]; + +const blobAnimImport = + !__PRERENDER__ && matchMedia('(prefers-reduced-motion: reduce)').matches + ? undefined + : import('./blob-anim'); +const installButtonSource = 'introInstallButton-Purple'; +const supportsClipboardAPI = + !__PRERENDER__ && navigator.clipboard && navigator.clipboard.read; + +async function getImageClipboardItem( + items: ClipboardItem[], +): Promise { + for (const item of items) { + const type = item.types.find((type) => type.startsWith('image/')); + if (type) return item.getType(type); + } +} + +interface Props { + onFile?: (file: File) => void; + showSnack?: SnackBarElement['showSnackbar']; +} +interface State { + fetchingDemoIndex?: number; + beforeInstallEvent?: BeforeInstallPromptEvent; + showBlobSVG: boolean; +} + +export default class Intro extends Component { + state: State = { + showBlobSVG: true, + }; + private fileInput?: HTMLInputElement; + private blobCanvas?: HTMLCanvasElement; + private installingViaButton = false; + + componentDidMount() { + // Listen for beforeinstallprompt events, indicating Squoosh is installable. + window.addEventListener( + 'beforeinstallprompt', + this.onBeforeInstallPromptEvent, + ); + + // Listen for the appinstalled event, indicating Squoosh has been installed. + window.addEventListener('appinstalled', this.onAppInstalled); + + if (blobAnimImport) { + blobAnimImport.then((module) => { + this.setState( + { + showBlobSVG: false, + }, + () => module.startBlobAnim(this.blobCanvas!), + ); + }); + } + } + + componentWillUnmount() { + window.removeEventListener( + 'beforeinstallprompt', + this.onBeforeInstallPromptEvent, + ); + window.removeEventListener('appinstalled', this.onAppInstalled); + } + + private onFileChange = (event: Event): void => { + const fileInput = event.target as HTMLInputElement; + const file = fileInput.files && fileInput.files[0]; + if (!file) return; + this.fileInput!.value = ''; + this.props.onFile!(file); + }; + + private onOpenClick = () => { + this.fileInput!.click(); + }; + + private onDemoClick = async (index: number, event: Event) => { + try { + this.setState({ fetchingDemoIndex: index }); + const demo = demos[index]; + const blob = await fetch(demo.url).then((r) => r.blob()); + const file = new File([blob], demo.filename, { type: blob.type }); + this.props.onFile!(file); + } catch (err) { + this.setState({ fetchingDemoIndex: undefined }); + this.props.showSnack!("Couldn't fetch demo image"); + } + }; + + private onBeforeInstallPromptEvent = (event: BeforeInstallPromptEvent) => { + // Don't show the mini-infobar on mobile + event.preventDefault(); + + // Save the beforeinstallprompt event so it can be called later. + this.setState({ beforeInstallEvent: event }); + + // Log the event. + const gaEventInfo = { + eventCategory: 'pwa-install', + eventAction: 'promo-shown', + nonInteraction: true, + }; + ga('send', 'event', gaEventInfo); + }; + + private onInstallClick = async (event: Event) => { + // Get the deferred beforeinstallprompt event + const beforeInstallEvent = this.state.beforeInstallEvent; + // If there's no deferred prompt, bail. + if (!beforeInstallEvent) return; + + this.installingViaButton = true; + + // Show the browser install prompt + beforeInstallEvent.prompt(); + + // Wait for the user to accept or dismiss the install prompt + const { outcome } = await beforeInstallEvent.userChoice; + // Send the analytics data + const gaEventInfo = { + eventCategory: 'pwa-install', + eventAction: 'promo-clicked', + eventLabel: installButtonSource, + eventValue: outcome === 'accepted' ? 1 : 0, + }; + ga('send', 'event', gaEventInfo); + + // If the prompt was dismissed, we aren't going to install via the button. + if (outcome === 'dismissed') { + this.installingViaButton = false; + } + }; + + private onAppInstalled = () => { + // We don't need the install button, if it's shown + this.setState({ beforeInstallEvent: undefined }); + + // Don't log analytics if page is not visible + if (document.hidden) return; + + // Try to get the install, if it's not set, use 'browser' + const source = this.installingViaButton ? installButtonSource : 'browser'; + ga('send', 'event', 'pwa-install', 'installed', source); + + // Clear the install method property + this.installingViaButton = false; + }; + + private onPasteClick = async () => { + let clipboardItems: ClipboardItem[]; + + try { + clipboardItems = await navigator.clipboard.read(); + } catch (err) { + this.props.showSnack!(`No permission to access clipboard`); + return; + } + + const blob = await getImageClipboardItem(clipboardItems); + + if (!blob) { + this.props.showSnack!(`No image found in the clipboard`); + return; + } + + this.props.onFile!(new File([blob], 'image.unknown')); + }; + + render( + {}: Props, + { fetchingDemoIndex, beforeInstallEvent, showBlobSVG }: State, + ) { + return ( + + ); + } +} diff --git a/src/shared/initial-app/Intro/missing-types.d.ts b/src/shared/prerendered-app/Intro/missing-types.d.ts similarity index 87% rename from src/shared/initial-app/Intro/missing-types.d.ts rename to src/shared/prerendered-app/Intro/missing-types.d.ts index a35205c5..6ef3d345 100644 --- a/src/shared/initial-app/Intro/missing-types.d.ts +++ b/src/shared/prerendered-app/Intro/missing-types.d.ts @@ -29,3 +29,12 @@ interface BeforeInstallPromptEvent extends Event { interface WindowEventMap { beforeinstallprompt: BeforeInstallPromptEvent; } + +interface ClipboardItem { + types: string[]; + getType(type: string): Promise; +} + +interface Clipboard { + read(): Promise; +} diff --git a/src/shared/prerendered-app/Intro/style.css b/src/shared/prerendered-app/Intro/style.css new file mode 100644 index 00000000..dea2ffe4 --- /dev/null +++ b/src/shared/prerendered-app/Intro/style.css @@ -0,0 +1,243 @@ +.intro { + composes: abs-fill from global; + -webkit-overflow-scrolling: touch; + overflow: auto; + overscroll-behavior: contain; + display: grid; + grid-template-rows: 1fr max-content max-content; + font-size: 1.2rem; + color: var(--dark-text); +} + +.blob-canvas { + composes: abs-fill from global; + width: 100%; + height: 100%; +} + +.hide { + display: none; +} + +.main { + min-height: 541px; + display: grid; + grid-template-rows: max-content max-content; + justify-items: center; + position: relative; + --blob-pink: var(--hot-pink); + --center-blob-opacity: 0.3; + + @media (min-width: 600px) { + min-height: 688px; + } +} + +.logo-container { + margin: 5rem 0 1rem; +} + +.logo { + transform: translate(-1%, 0); + width: 189px; + height: auto; +} + +.load-img { + position: relative; + color: var(--white); + font-style: italic; + font-size: 1.2rem; +} + +.blob-svg { + composes: abs-fill from global; + width: 100%; + height: 100%; + fill: var(--blob-pink); + + & path { + opacity: var(--center-blob-opacity); + } +} + +.load-img-content { + position: relative; + --size: 29rem; + max-width: var(--size); + width: 100vw; + height: var(--size); + display: grid; + grid-template-rows: max-content max-content; + justify-items: center; + align-content: center; + gap: 0.7rem; + + @media (min-width: 600px) { + --size: 36rem; + } +} + +.load-btn { + composes: unbutton from global; +} + +.load-icon { + --size: 5rem; + width: var(--size); + height: var(--size); + fill: var(--white); + transform: translate(4.3%, -1%); +} + +.paste-btn { + composes: unbutton from global; + text-decoration: underline; + font: inherit; + color: inherit; +} + +.demos-container { + position: relative; + background: var(--deep-blue); + padding-bottom: 5.2vw; +} + +.top-wave { + position: absolute; + left: 0; + right: 0; + bottom: 100%; +} + +.main-wave { + fill: var(--deep-blue); +} + +.sub-wave { + fill: var(--light-blue); +} + +.footer { + position: relative; + background: var(--light-gray); +} + +.footer-wave { + fill: var(--light-gray); +} + +.content-padding { + padding: 2rem; +} + +.footer-items { + display: grid; + justify-content: end; + grid-auto-columns: max-content; + grid-auto-flow: column; + align-items: center; + gap: 4rem; +} + +.footer-link { + text-decoration: none; + color: inherit; +} + +.footer-link-with-logo { + composes: footer-link; + display: grid; + grid-template-columns: 1.8em max-content; + align-items: center; + gap: 0.6em; + + img { + width: 100%; + height: auto; + } +} + +@keyframes fade-in { + from { + opacity: 0; + } +} + +.install-btn { + composes: unbutton from global; + position: absolute; + top: 1rem; + right: 1rem; + background: var(--deep-blue); + border-radius: 0.4em; + color: var(--white); + padding: 0.5em 1em; + font-size: 1.6rem; + animation: fade-in 600ms ease-in-out; +} + +.demo-title { + color: var(--white); + margin: 0; + font-size: 2rem; + text-align: center; +} + +.demos { + display: grid; + gap: 3rem; + justify-items: center; + justify-content: center; + padding: 0; + margin: 3rem auto; + --demo-size: 80px; + grid-template-columns: repeat(auto-fit, var(--demo-size)); + + @media (min-width: 740px) { + --demo-size: 100px; + gap: 6rem; + } + + & > li { + display: block; + } +} + +.demo-size { + background: var(--dim-blue); + border-radius: 1000px; + color: var(--white); + width: max-content; + padding: 0.5rem 1.2rem; + margin: 0.7rem auto 0; +} + +.demo-icon-container { + border-radius: var(--demo-size); + position: relative; + overflow: hidden; +} +.demo-icon { + width: var(--demo-size); + height: var(--demo-size); + display: block; +} +.demo-loader { + composes: abs-fill from global; + background: rgba(0, 0, 0, 0.5); + display: grid; + justify-content: center; + align-content: center; + animation: fade-in 600ms ease-in-out; + + & > loading-spinner { + --color: var(--white); + } +} + +.drop-text { + @media (max-width: 599px) { + display: none; + } +} diff --git a/src/shared/prerendered-app/colors.css b/src/shared/prerendered-app/colors.css new file mode 100644 index 00000000..1f0ce297 --- /dev/null +++ b/src/shared/prerendered-app/colors.css @@ -0,0 +1,19 @@ +html { + --pink: #ff3385; + --hot-pink: #ff0066; + --white: #fff; + --dim-blue: #0a7bcc; + --deep-blue: #09f; + --light-blue: #76c8ff; + --light-gray: #eaeaea; + --dark-text: #343a3e; + + /* Old stuff: */ + --gray-dark: rgba(0, 0, 0, 0.8); + + --button-fg-color: 95, 180, 228; + --button-fg: rgb(95, 180, 228); + + --negative: rgb(207, 113, 127); + --positive: rgb(149, 212, 159); +} diff --git a/src/shared/prerendered-app/util.css b/src/shared/prerendered-app/util.css new file mode 100644 index 00000000..fc48b374 --- /dev/null +++ b/src/shared/prerendered-app/util.css @@ -0,0 +1,24 @@ +:global { + .abs-fill { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + box-sizing: border-box; + contain: strict; + } + + .unbutton { + cursor: pointer; + background: none; + border: none; + font: inherit; + padding: 0; + margin: 0; + + &:focus { + outline: none; + } + } +} diff --git a/src/shared/initial-app/util.ts b/src/shared/prerendered-app/util.ts similarity index 100% rename from src/shared/initial-app/util.ts rename to src/shared/prerendered-app/util.ts diff --git a/src/static-build/index.tsx b/src/static-build/index.tsx index cf4d4a3d..4d104375 100644 --- a/src/static-build/index.tsx +++ b/src/static-build/index.tsx @@ -29,7 +29,7 @@ const toOutput: Output = { display: 'standalone', orientation: 'any', background_color: '#fff', - theme_color: '#f78f21', + theme_color: '#ff3385', icons: [ { src: iconLarge, diff --git a/src/static-build/missing-types.d.ts b/src/static-build/missing-types.d.ts index 5c48298c..1de4bdfc 100644 --- a/src/static-build/missing-types.d.ts +++ b/src/static-build/missing-types.d.ts @@ -11,7 +11,7 @@ * limitations under the License. */ /// -/// +/// declare module 'client-bundle:*' { const url: string; diff --git a/src/static-build/pages/index/base.css b/src/static-build/pages/index/base.css index 71399504..43713da8 100644 --- a/src/static-build/pages/index/base.css +++ b/src/static-build/pages/index/base.css @@ -25,8 +25,8 @@ body { height: 100%; padding: 0; margin: 0; - font: 12px/1.3 system-ui, -apple-system, BlinkMacSystemFont, Roboto, Helvetica, - sans-serif; + font: 12px/1.3 'HelveticaNeue-Light', 'Helvetica Neue Light', 'Helvetica Neue', + Helvetica, Arial, 'Lucida Grande', sans-serif; overflow: hidden; overscroll-behavior: none; contain: strict; @@ -34,16 +34,6 @@ body { background-size: 20px 20px; } -:root { - --gray-dark: rgba(0, 0, 0, 0.8); - - --button-fg-color: 95, 180, 228; - --button-fg: rgb(95, 180, 228); - - --negative: rgb(207, 113, 127); - --positive: rgb(149, 212, 159); -} - :global(#app) { position: absolute; left: 0; diff --git a/src/static-build/pages/index/index.tsx b/src/static-build/pages/index/index.tsx index c2c4ca6f..e1ab1a44 100644 --- a/src/static-build/pages/index/index.tsx +++ b/src/static-build/pages/index/index.tsx @@ -17,7 +17,7 @@ import initialCss from 'initial-css:'; import { allSrc } from 'client-bundle:client/initial-app'; import favicon from 'url:static-build/assets/favicon.ico'; import { escapeStyleScriptContent } from 'static-build/utils'; -import Intro from 'shared/initial-app/Intro'; +import Intro from 'shared/prerendered-app/Intro'; interface Props {}