From 7a6ba23c998b83f4597e36a3e505271b9f2c6433 Mon Sep 17 00:00:00 2001 From: drjones Date: Mon, 7 Sep 2026 20:00:46 -0700 Subject: [PATCH] v1.2 mobile PWA: installable app + mobile-first UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Installable PWA: manifest.json (standalone, portrait, maskable icons), theme-color + apple-mobile-web-app meta, generated 192/512 PNG icons, service worker (network-first, never caches /api/). - Mobile UI (≤720px): sticky blurred header, agent grid -> horizontal snap-scroll carousel, fixed bottom composer with safe-area insets, modals -> full-screen bottom sheets, 16px inputs (no iOS zoom), bigger touch targets, icon-only header actions. - /app remains the app shell; / is the landing page. --- static/icon-192.png | Bin 0 -> 2602 bytes static/icon-512.png | Bin 0 -> 9864 bytes static/manifest.json | 14 ++++++++++ static/sw.js | 31 +++++++++++++++++++++++ templates/index.html | 59 ++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 static/icon-192.png create mode 100644 static/icon-512.png create mode 100644 static/manifest.json create mode 100644 static/sw.js diff --git a/static/icon-192.png b/static/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..15c5272419f85e12c304d63d00b8d1bd2e1e1fd4 GIT binary patch literal 2602 zcma)8dsGuw9=?-@fq;bP3L*w@eL*cm3d%DGqD8Gz6|f)%9}DLIYgQ2Oftf)8Q9yib z1+_NRXOZ`5HKvOsfPk)GP?v&&CP4*3Fq7hAio?v_Nzd*%yJt_&{*iq7Wqx<={l5FV z_xl}*TDx*Oe?8`jeDMqKm7`Qw8Fk%SBPl0 zHus@Ri><4Lwd>e;$4J@87YldUIXyn@J16y$&&3Cgnl`0IGb#S8#5eiV1S+-RCOLd2 zqjq=ad;>XAnH;kJwOn&xVkpfR(UCK9wl}$`E?_8C+ZYg^^GKwv9ctaTMYbuYSET*h zP+Pj}Re4o^#!ug*x@0`{)s#(KOx}5O=yAGiv|O$j9@!_`bssH~D28_gt1>$c{S!Na z(LF;$?}N5zYD$zG8D2H`VH%L(p1N!Xs&rB=6X~TKCemxG*g#A;7-JDkNM0iXnP_!~ z>cbs7Ks483S|-fq(H}0v1G{%@TnLEY^lI= z%!^Y9)BBGSRX{C}MU&abf|(qA&CP$6&}mPW>gl)*VDrW&-;iSAG(#!89pSFVLEc49TvctuUuTeXS0BZQY z=ZcWdlZy{l;_w z9Rpbw8=<5N2!Cx?N#<<@AmmlB+thczVcsgd1qBTBDO0U@bKCL8;%aH|ChK6_Fgv-zxCimSD zmFwHEfC=E<*kj(ecW2rUmWeZ<{B8=-&@t7I79*=}mWW;PanI;wrsqCnS>PT6jl`$vplr)e+2s);9+o7IV0-PXfWknvE26Vz1p);tCbI99fsSP%hazy@G ztX&Kc2f7=DfywUml)M;7Q5W))tr-~;jA`N}mc zYqit~<5LZAQ>1VrRU~jHYFJS%LQgOQo@9imizjDtsI4{_8U;egdw^w(=Tu1_qDD!gAtU%IT*u66{33&I%`SF+0|{Z`<^%J2RBWRK7(o=*H&EO0IBcWjmq*WMq1 zcfRFqgH>XS#fVxQ#KI3hm-3D`=kLyFm4}0CL;~b4eoSZ2!HVC!V4-~&>Ij)TJ-gdh z7u*^irB=D9t;uy&U~$uFGg8l{D5&#`oJBSSZ$*?h)dkvhb*y^kaD3;H)0#Rv%uSNs z)?%2RI5v9H-Ud@?F>R^V3F~?GZMoojo+99?mD{i)V1r)DO;rBq(Q~NlSI6zOr}{p>Zdieg#2lsz_qP*?QHi`bKnJb z;&hDB`^*3i3%aqPdgE~dPI-_hW8eZINi$izUShKNf4Sj)#uR<=Kn5~#ll`aawPw(} zdt2#rlffUMU!I@%&6>vl-Ze(muUdNikwo_wW4TA-m(W>KK5Aw)#fshde^ zIh;;~=V;;yjZ>t1WDxX-L7Ad)bY_?gOL6vuGTYefb$@FQ!tUPt&-vq?^WE>e|0sJs ze((31;wKlR2KS5YhY$*$J7?DK5mLjSY82=X|J`~0yI&D{NI!Sh^hK|K-+p!Rs>IYi zTfbTPvZ-qQ4;Pc)UH8Vl&XjX|&o`W#b2T>djQRof-Z|lK$9U@A5Tc(v(=dUt3=t~L z#A9M5k&`~p!Hd^mQaZY;Xh2toEl;eU(N%uD>%w{Iizey$cIov>oO}!~uVnwxZVPH> zANMpnx}}X|=^Adym$naXmlk#&O6t13i5q!r{ExR=&gE7%UE5ckHPY2SBRAvglX2Io zOFY)I*Y9 zZaNGj6!Z=fnG+*I6-51;B*Cmu@}nW&C`g{@W)%FGqe+QE{;xQ3l!AO#8m8FnvXFs& zn;au*l$$l&5{0}c2+h=hVeivk`(`qhQvbtW_Xg=)hAINGFMGOt9kc0OC+!UF+rsDMUP0YEwYE;KZ?g zn_Q6SQpi8MfIOif=?KsI_9@a8L{=#PIqim_3X(>MiXJ6wTuq{yC73Yd}pq&WOaE32??Rx3p-q~BIYl%Ad^>J=n; z^2uWgG_1fVHw#I5W3wXqXo|>}3T6}BhPXZnvm>N)ipxcHI8VVWFElDWTTOma(0{?s zCM&3TM66IqSYJe5R{+{A;BiW3lciDxv*mSUiULxOs8?)e=aY;^j#Jo^Em#__prg9x z|5}`A{MW<@Gux%KMuSkH@L(o{ys8l5%vMgRf+b?9GHmQ5qQZh4ZgaH4g8ZZl3U!sA z!po@QtGaCDUiAupETo>s6&d~#Z(tDb8bD5je413iik>?5+YU7r^k87w`S>Gi=F<^y zEs}2(zOrz7k)a86VXN8A?~Cx|i^vQycXPTOlI1xSB8vo1jxjh`;<>F{+Yi(GTi7HT zMM#7&`Pk3CD)E(8*Tf}pK0E~XwaCJbkwVJ9d<>Ciglb!}M*6Fp1EfW@wuEC(1X@^) zbldg$DkOC|a5&;Mg`73L^P*sR5cvZv-xVgK4I$E9J{uRR8|L&aF;hng0i@aK`NoZ_H_Ws5%f?H zX{|P&bqfIuUo|NKk$0&nEI$!yWQ9)`_*(ELJuPn9oUZA|CxWs*-4tjclf2Pud1As_ z5xDv43$u_|?w}#e3^aWEm)S@>|4q!$rGvhV%!u^n~?!l@;|#$2><3l~to>T#g04ummJP0y#&FMpB*JXh8w z!b-=GvMjM27(O3sLc;#@B}g!UtL!h_4*y&%Hm(y)5Zcbq2$s@c9TJ}?S?SI9!{DE# zVQ)o}avHePCsIh2L*i4w67BQy8a7a*>3+J`Nl< z@0`{+bKls^>}<$KKQh6@dh(0?nG>g~?FLQv9ygxQ zykV8^&{`TRU0K|Lf;=R;Thv`p%lWaXXA3K{6=|5dAE}#Vhnu_edGQ z8Np`0nWseqoU{UEe7fV@(dz7616owb`bewn(|w25adW`eLd2;~t?wtPNN`|m-WaD| z>NY?{W%6OgLd)(z#EMKS#zW#Y(jj%(t72|{V}js-3$x$?yA?MkycI@HgAq^Qt>o8h zUwnXWwAsjXm5_UyYYsiL)myBkw2}oy2~CMog}n&Jh`BqBBZNk<>iT>}I>);nQ`N=iD0JJdp5mXiRf9S6 z<{jkO#;#q)r=j*W&Ji*ocU2V=?^YqXd3PX6?r{>q1j71$DRBe0Ezg$#Mn)Y!MB1y4pCpYM z-ID#3yD>`j4bRb@iQLVWLeeT<^8wkjxl;GDYZJ?!6;st^E5u4)-5yi-U`zUXf6C=- z-B2-rO2I6p;DDn`b5)_iWMfz;B0;LAaZ-#rz6A@fp#8@q5g-_#dWy#o5j{z42dUzG zE+75?ZQF(S=~Zew)}rt0xvFgPRNlo#)w)ZMqvtGKxn9*-N2te>>s*@)jcZ}iXoqd4 z?=d4(4r8|9JLrs`s zj{mv2Ym)K&ssP$!kR`)wIwz$09@|RO2}tR=Is>|$R%<5bDQ0^Wq#Cy;U6;OF;CA#w_d|VEq zo`xwco$9h-V#+Mb0YTf(xKGFkG#=oiQF^1<|hsdutW)|c^=!NlnghzfylP7CN(%!+N0v1{B?~K zSKUE7D^5Wm+oq~(rnGmcIQpFlDp7zQMjf(W}APtv@|-V zT*C>^ZZv&z>-yczi`FoPmXftyA2n?{+c-t6oCQ2H>a;BJ0wfZLAruG$**06q*$bCd zM!1 z>_Te%poIukZY!%rk2uJo8x+UlI(8o2TJ8WDcGln=v~d?nqbm>E%`}CQWVh+gYLJ9E z3Prj#w815h3#qgk#F=7lKjX)c?~H#JifECIXNrlCx*Xgv>4dK(R;>3)^ATrCB1}wd zDlkVt=qkh?1%mZB(B(k#lDx77ACsx9V_!kLEo1{)w+$bpE7`T|3QEemZoF>{`kjOP zG6IdOW9QSg$xbtE%t=`rVTauwpUA5e~UkivWx?S3_8!^{H3>P>azKew^T>NpkpnIkhlf3cgD#*qKcLr z>a>|+Mu2Xvm_qqCg!kyL7<6-aAX!QS%sNV2a~&zruA+v^jhCKPrv-oy>w?7;dBsom zEGqJhxjy#mPph5`A|vRntU|ACEa0hEW0pzj8XN;?xrJko?C((o;SVu}b}P)5XN@N8)9txI#%UB5#0l!95?| zfxVLib~r6Mz86yaLmY@ZWlJ4g?xT4$||@we0g0b0kfWNi_)OcLMB$FC~ zXAAkxtI%Z@uNTQ-HxBIis2>bX*Z7e&s!`)XDVP#&CE$&P(40M-PY#{fduJh4bhgM) z$_)L;e3w*J3-#;nT5^7iY>8_77)swtHxuMdtRF)aCCj)mpvRZJX*C~z5(w1#={}%D zJGeMVs`tIX%0V;5KqZ!@7o+`j>uJ2`R-8_Bl2NFW4w%;E;~(@Y8v2`yfi(6)HHo6N zE9AwylvaE;js`?_tmN@2i=-L$l2Gm)Y`+7I2j3Whk{vL7>a?)yz3LkztM8_RTs)30 zhm(y|3M{0%{?t{>!=ZQrr|hhQJoC&BfXqlkCLi|tUQw5(UA2# z2c@fn@eUaEaR6r$inWqMjd8xZOo4l~`Y2V6m76bTU`dR07QH6v|f-9xJxdA|X{{TnwG)`g98V z13$|i7)==myIs&IYA|d#p!xkt)r6ohO#=gFgCup?0&1!te2vKcdW$^dt4kC-9m9c@ ztkuvoeIx*#s zCw+mD=ize&-^lDZL zz}fFKcgL0N0^NM*v@Q3w;N@a|>=a*WlZFszusj2LmB>hU)nJ9WfyMzaJh&+oR91u< zNsgHNh3PXocU@LPE?)cTVfCIZ09*?Vz;`w{m`=($?_dnnfpzRwir4;8BSe17pWyK7lz2^rt4 zhH8FDT2B92HMX&pS69aml#axR+!s+O#*L3>&$b*szLm>d^;AKcR$LJ-7;5k+G}=jy z1Rjj@PvSkX%Rj$ZXUptQCJMOYr$Snchoz;bPo<9_Qz0BSG~CKVcea3L?rh}(N2OG* zgo;#GA(Wt$6v0EMAvyI1-}m;O;>Lqd=zcHGHtvPyw+80)L+i=a3Ou2~`fR#Cxdo$o z;{rZfm7dPKR4}M-ijiDbzqYP|y~DK9Tj0j}!1wmv`ww%iJlT-pENo?=^pY>>X^JqC z>zk9*4Gng)59xrfc&VbzF*>CHZdUB11{OBgPVNA*;i>EDlH@IuV)bWeZl+B|mo_hX{gYl;?Xt+fv$zSil zJ2|v19O+YkJid%8Wv7#05=358K(fvKp`BUnAg2l@zW?_>l2EJ)O+*L2ey@4`v|o9X ziM}}EbpSq+H%Kp4`3V(Q%9}ekeLrn-i>IXX-sJ;N*qk$RM;|Ni=-haH$}r~*D;oe| zf(}WU&eK=RXcN*>*)pAxmO=tXLR%ePbJYl|jkRQ*{-kR_uF&QkCWRACK-5qCuQ!G@ zxinCd6q2UVt2Gc_88w7M^OsM*#l*^mdugY5turTmrMRWz=7|wwgf?oq7A{j{Zhy1; z#8)Rx{h)fu3MON7UtVi!15k8gEWh|d>}7+c3lv_@cX4{Gu7fd)XXI&AInk_a|sO`ZjHTQnp+!cn?>fBH7CK*A))hl=hM|41zGtcizJt ztyuXg^P9>d3?*y}4(Rd}UH_=yu-p!FbN4tvqy!^MvCQY+KifvY?qWXASAO|)8aPN$# zhaG1t_ppN8b#j2aPZ0zSxs~qup2(Y(uc8|7Oti;j+iHmEw{jpRh7MTn18Xeyjv#tC z_)0J+48;7P_er4V@I5)g^h!KHf{fDZpxw}UE7TGFKfX5gU|A&3?e7CJhY+^{&~P`S z3RdlL%I@klLS{}^z`saATreM_7kFfv$`v+(Rf(Xlgsr+5ezv{VL zo;>xK(w})wsmH;RQ>kRR#OV#+|J)?M%tVI3Dv7!R{#J2 literal 0 HcmV?d00001 diff --git a/static/manifest.json b/static/manifest.json new file mode 100644 index 0000000..ecbef17 --- /dev/null +++ b/static/manifest.json @@ -0,0 +1,14 @@ +{ + "name": "Astraea — Washington Family Law Assistant", + "short_name": "Astraea", + "description": "Eight specialist AI attorneys grounded in Washington statute and case law. Not legal advice.", + "start_url": "/", + "display": "standalone", + "background_color": "#070b1a", + "theme_color": "#070b1a", + "orientation": "portrait-primary", + "icons": [ + { "src": "/static/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any maskable" }, + { "src": "/static/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" } + ] +} diff --git a/static/sw.js b/static/sw.js new file mode 100644 index 0000000..6bf11ba --- /dev/null +++ b/static/sw.js @@ -0,0 +1,31 @@ +const CACHE = 'astraea-v1'; +const ASSETS = ['/', '/static/manifest.json', '/static/icon-192.png', '/static/icon-512.png']; + +self.addEventListener('install', (e) => { + e.waitUntil(caches.open(CACHE).then((c) => c.addAll(ASSETS)).then(() => self.skipWaiting())); +}); + +self.addEventListener('activate', (e) => { + e.waitUntil( + caches.keys().then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))) + .then(() => self.clients.claim()) + ); +}); + +// Network-first for everything except static assets (the app shell is tiny; +// API responses must never be served stale). +self.addEventListener('fetch', (e) => { + const url = new URL(e.request.url); + if (e.request.method !== 'GET') return; + if (url.pathname.startsWith('/api/')) return; // always live + + e.respondWith( + fetch(e.request) + .then((resp) => { + const copy = resp.clone(); + caches.open(CACHE).then((c) => c.put(e.request, copy)); + return resp; + }) + .catch(() => caches.match(e.request).then((c) => c || caches.match('/'))) + ); +}); diff --git a/templates/index.html b/templates/index.html index 87b2499..49c8261 100644 --- a/templates/index.html +++ b/templates/index.html @@ -2,7 +2,15 @@ - + + + + + + + + + Astraea — Your Attorney @@ -110,8 +159,8 @@

ASTRAEA

- - + +
⚖
@@ -241,6 +290,10 @@