stepped checkout, auth pages, sticky stepper bar, action footer

- 4-step checkout (cart, account, shipping, payment) with Pinia store and route children
- minimal cream sticky top bar (logo + stepper + back-to-shopping) replacing nav/footer in checkout
- fixed cream action footer at viewport bottom for back/continue on every step
- guest / sign-in / register tabs on account step + standalone /login and /register
- src/api/auth.js stub matching expected backend contract
- legal pages (/impressum, /datenschutz) and category landings (/pflege, /haushalt)
- success page stacked CTAs (back-to-home primary, keep-shopping secondary)
- footer logo size aligned with navbar; cream variant footer

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-05-03 16:06:26 +01:00
parent e5bd1f65fc
commit c600fadadb
90 changed files with 2005 additions and 615 deletions

View File

@@ -1 +1 @@
import{C as e,G as t,m as n,o as r,u as i}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as a}from"./i18n-BE70RUdV.js";import{t as o}from"./About-5a4hUocC.js";var s={class:`min-h-screen bg-cream`},c={__name:`AboutPreview`,setup(c){let{t:l}=a(),u=r(()=>[1,2,3].map(e=>({year:l(`about.milestone.${e}.year`),title:l(`about.milestone.${e}.title`),text:l(`about.milestone.${e}.text`)})));return(r,a)=>(e(),i(`div`,s,[n(o,{eyebrow:t(l)(`about.eyebrow`),headline:t(l)(`about.headline`),sub:t(l)(`about.sub`),milestones:u.value},null,8,[`eyebrow`,`headline`,`sub`,`milestones`])]))}};export{c as default};
import{C as e,G as t,m as n,o as r,u as i}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as a}from"./i18n-DhVF3Pwg.js";import{t as o}from"./About-DF_uf9aY.js";var s={class:`min-h-screen bg-cream`},c={__name:`AboutPreview`,setup(c){let{t:l}=a(),u=r(()=>[1,2,3].map(e=>({year:l(`about.milestone.${e}.year`),title:l(`about.milestone.${e}.title`),text:l(`about.milestone.${e}.text`)})));return(r,a)=>(e(),i(`div`,s,[n(o,{eyebrow:t(l)(`about.eyebrow`),headline:t(l)(`about.headline`),sub:t(l)(`about.sub`),milestones:u.value},null,8,[`eyebrow`,`headline`,`sub`,`milestones`])]))}};export{c as default};

View File

@@ -1,4 +1,4 @@
import{C as e,G as t,c as n,ft as r,j as i,m as a,p as o,s}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as c}from"./i18n-BE70RUdV.js";import{t as l}from"./SectionShell-CU6IpKMJ.js";import{t as u}from"./DevicePreview-9pDgIJdz.js";var d={class:`eyebrow mb-5`},f=`/design/preview/about`,p={__name:`AboutSection`,setup(p){let{t:m}=c();return(c,p)=>(e(),n(l,{eyebrow:t(m)(`ds.eyebrow.components`),title:t(m)(`ds.about.title`),description:t(m)(`ds.about.description`),wide:``},{default:i(()=>[s(`section`,null,[a(u,{src:f,initial:`desktop`,height:820})]),s(`section`,null,[s(`h2`,d,r(t(m)(`ds.heading.usage`)),1),p[0]||=s(`div`,{class:`rounded-md border border-line bg-paper p-6 font-mono text-[12px] text-ink`},[s(`pre`,{class:`whitespace-pre-wrap`},`<About
import{C as e,G as t,c as n,ft as r,j as i,m as a,p as o,s}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as c}from"./i18n-DhVF3Pwg.js";import{t as l}from"./SectionShell-DGDgzO-7.js";import{t as u}from"./DevicePreview-C43cRj6M.js";var d={class:`eyebrow mb-5`},f=`/design/preview/about`,p={__name:`AboutSection`,setup(p){let{t:m}=c();return(c,p)=>(e(),n(l,{eyebrow:t(m)(`ds.eyebrow.components`),title:t(m)(`ds.about.title`),description:t(m)(`ds.about.description`),wide:``},{default:i(()=>[s(`section`,null,[a(u,{src:f,initial:`desktop`,height:820})]),s(`section`,null,[s(`h2`,d,r(t(m)(`ds.heading.usage`)),1),p[0]||=s(`div`,{class:`rounded-md border border-line bg-paper p-6 font-mono text-[12px] text-ink`},[s(`pre`,{class:`whitespace-pre-wrap`},`<About
:eyebrow="t('about.eyebrow')"
:headline="t('about.headline')"
:sub="t('about.sub')"

1
dist/assets/AccountStep-BTc6o5QE.js vendored Normal file
View File

@@ -0,0 +1 @@
import{B as e,C as t,D as n,G as r,S as i,T as a,c as o,ft as s,j as c,l,m as u,o as d,p as f,r as p,s as m,u as h,ut as g}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{s as _}from"./runtime-dom.esm-bundler-BrsyFPJA.js";import{o as v}from"./vue-router-Cyqru1db.js";import{t as y}from"./i18n-DhVF3Pwg.js";import{i as b,n as x,t as S}from"./api-DlB23x7f.js";import{t as C}from"./Button-D1Rp2Xe1.js";import{t as w}from"./checkout-stUpOmaV.js";import{t as T}from"./Input-os2-AnNh.js";var E={class:`flex flex-col gap-6`},D=[`aria-label`],O=[`aria-selected`,`onClick`],k={key:0,class:`grid gap-5 md:grid-cols-2`},A={key:3,class:`inline-flex items-center gap-3 cursor-pointer select-none`},j=[`checked`],M={class:`text-sm text-ink`},N={key:4,class:`text-sm text-danger`,role:`alert`,"aria-live":`polite`},P={key:5,class:`text-[13px] text-muted`},F={href:`/account/reset-password`,class:`text-brand hover:underline`},I={class:`fixed inset-x-0 bottom-0 z-30 bg-cream border-t border-line`,role:`contentinfo`},L={class:`mx-auto w-full max-w-7xl px-6 sm:px-8 md:px-12 lg:px-16 py-3 flex gap-3 sm:justify-between`,style:{paddingBottom:`max(0.75rem, env(safe-area-inset-bottom))`}},R={class:`flex-1 sm:flex-initial`},z={__name:`AccountStep`,setup(z){let{t:B}=y(),V=w(),H=v(),U=e(!1),W=e(``),G=[{key:`guest`,label:`checkout.account.tab.guest`},{key:`signin`,label:`checkout.account.tab.signin`},{key:`register`,label:`checkout.account.tab.register`}];function K(e){W.value=``,V.setAccountMode(e)}let q=d(()=>V.accountMode===`guest`),J=d(()=>V.accountMode===`signin`),Y=d(()=>V.accountMode===`register`),X=d(()=>!Y.value||V.password===V.passwordConfirm),Z=d(()=>V.email?q.value?!1:!!(!V.password||V.password.length<8||Y.value&&!X.value):!0);async function Q(){W.value=``,U.value=!0;try{J.value?await b({email:V.email,password:V.password}):Y.value&&await x({email:V.email,password:V.password,firstName:V.firstName,lastName:V.lastName,acceptsMarketing:V.acceptsMarketing}),V.persist(),H.push(`/checkout/shipping`)}catch(e){W.value=e?.message||B(`checkout.error.generic`)}finally{U.value=!1}}return i(async()=>{let e=await S();e?.user?.email&&!V.email&&V.update({email:e.user.email})}),(e,i)=>{let d=n(`RouterLink`);return t(),h(p,null,[m(`section`,E,[m(`div`,{role:`tablist`,"aria-label":r(B)(`checkout.account.tabsLabel`),class:`flex w-full rounded-pill border border-line bg-paper p-1`},[(t(),h(p,null,a(G,e=>m(`button`,{key:e.key,role:`tab`,type:`button`,"aria-selected":r(V).accountMode===e.key,class:g([`flex-1 px-4 py-2 rounded-pill text-[13px] font-semibold tracking-label transition-colors duration-base text-center`,r(V).accountMode===e.key?`bg-brand text-cream`:`text-brand hover:bg-cream`]),onClick:t=>K(e.key)},s(r(B)(e.label)),11,O)),64))],8,D),m(`form`,{class:`flex flex-col gap-5 md:min-h-[28rem]`,novalidate:``,onSubmit:_(Q,[`prevent`])},[u(T,{"model-value":r(V).email,label:r(B)(`checkout.field.email`),type:`email`,required:``,placeholder:r(B)(`checkout.placeholder.email`),hint:q.value?r(B)(`checkout.hint.email`):``,"onUpdate:modelValue":i[0]||=e=>r(V).update({email:e})},null,8,[`model-value`,`label`,`placeholder`,`hint`]),Y.value?(t(),h(`div`,k,[u(T,{"model-value":r(V).firstName,label:r(B)(`checkout.field.firstName`),"onUpdate:modelValue":i[1]||=e=>r(V).update({firstName:e})},null,8,[`model-value`,`label`]),u(T,{"model-value":r(V).lastName,label:r(B)(`checkout.field.lastName`),"onUpdate:modelValue":i[2]||=e=>r(V).update({lastName:e})},null,8,[`model-value`,`label`])])):l(``,!0),J.value||Y.value?(t(),o(T,{key:1,"model-value":r(V).password,label:r(B)(`checkout.field.password`),type:`password`,required:``,hint:Y.value?r(B)(`checkout.hint.password`):``,"onUpdate:modelValue":i[3]||=e=>r(V).update({password:e})},null,8,[`model-value`,`label`,`hint`])):l(``,!0),Y.value?(t(),o(T,{key:2,"model-value":r(V).passwordConfirm,label:r(B)(`checkout.field.passwordConfirm`),type:`password`,required:``,error:X.value?``:r(B)(`checkout.error.passwordMismatch`),"onUpdate:modelValue":i[4]||=e=>r(V).update({passwordConfirm:e})},null,8,[`model-value`,`label`,`error`])):l(``,!0),q.value||Y.value?(t(),h(`label`,A,[m(`input`,{checked:r(V).acceptsMarketing,type:`checkbox`,class:`w-5 h-5 rounded-xs border border-line accent-brand`,onChange:i[5]||=e=>r(V).update({acceptsMarketing:e.target.checked})},null,40,j),m(`span`,M,s(r(B)(`checkout.field.marketing`)),1)])):l(``,!0),W.value?(t(),h(`p`,N,s(W.value),1)):l(``,!0),J.value?(t(),h(`p`,P,[m(`a`,F,s(r(B)(`checkout.account.forgot`)),1)])):l(``,!0)],32)]),m(`footer`,I,[m(`div`,L,[u(d,{to:`/checkout/cart`,class:`flex-1 sm:flex-initial`},{default:c(()=>[u(C,{variant:`primary`,size:`lg`,block:``,type:`button`},{default:c(()=>[f(s(r(B)(`checkout.back`)),1)]),_:1})]),_:1}),m(`div`,R,[u(C,{variant:`accent`,size:`lg`,block:``,loading:U.value,disabled:Z.value,onClick:Q},{default:c(()=>[f(s(r(B)(`checkout.account.cta.continue`)),1)]),_:1},8,[`loading`,`disabled`])])])])],64)}}};export{z as default};

View File

@@ -1 +1 @@
import{C as e,G as t,c as n,ft as r,j as i,m as a,p as o,s}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as c}from"./i18n-BE70RUdV.js";import{t as l}from"./Badge-Bu_yseK_.js";import{t as u}from"./SectionShell-CU6IpKMJ.js";import{t as d}from"./Card-C-QeZ_to.js";var f={class:`eyebrow mb-5`},p={class:`flex flex-wrap gap-3`},m={class:`eyebrow mb-5`},h={class:`flex flex-wrap gap-3`},g={__name:`BadgesSection`,setup(g){let{t:_}=c();return(c,g)=>(e(),n(u,{eyebrow:t(_)(`ds.eyebrow.components`),title:t(_)(`ds.badges.title`),description:t(_)(`ds.badges.description`)},{default:i(()=>[s(`section`,null,[s(`h2`,f,r(t(_)(`ds.heading.variants`)),1),a(d,{tone:`paper`},{default:i(()=>[s(`div`,p,[a(l,{variant:`neutral`},{default:i(()=>[o(r(t(_)(`ds.badges.neutral`)),1)]),_:1}),a(l,{variant:`brand`},{default:i(()=>[o(r(t(_)(`ds.badges.brand`)),1)]),_:1}),a(l,{variant:`accent`},{default:i(()=>[o(r(t(_)(`ds.badges.accent`)),1)]),_:1}),a(l,{variant:`subtle`},{default:i(()=>[o(r(t(_)(`ds.badges.subtle`)),1)]),_:1}),a(l,{variant:`success`},{default:i(()=>[o(r(t(_)(`ds.badges.success`)),1)]),_:1}),a(l,{variant:`warning`},{default:i(()=>[o(r(t(_)(`ds.badges.warning`)),1)]),_:1}),a(l,{variant:`danger`},{default:i(()=>[o(r(t(_)(`ds.badges.danger`)),1)]),_:1})])]),_:1})]),s(`section`,null,[s(`h2`,m,r(t(_)(`ds.heading.nonUppercase`)),1),a(d,{tone:`paper`},{default:i(()=>[s(`div`,h,[a(l,{variant:`brand`,uppercase:!1},{default:i(()=>[o(r(t(_)(`ds.badges.newRelease`)),1)]),_:1}),a(l,{variant:`accent`,uppercase:!1},{default:i(()=>[o(r(t(_)(`ds.badges.featured`)),1)]),_:1}),a(l,{variant:`subtle`,uppercase:!1},{default:i(()=>[...g[0]||=[o(`v2.1.0`,-1)]]),_:1})])]),_:1})])]),_:1},8,[`eyebrow`,`title`,`description`]))}};export{g as default};
import{C as e,G as t,c as n,ft as r,j as i,m as a,p as o,s}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as c}from"./i18n-DhVF3Pwg.js";import{t as l}from"./Badge-Bu_yseK_.js";import{t as u}from"./SectionShell-DGDgzO-7.js";import{t as d}from"./Card-DcusAcLZ.js";var f={class:`eyebrow mb-5`},p={class:`flex flex-wrap gap-3`},m={class:`eyebrow mb-5`},h={class:`flex flex-wrap gap-3`},g={__name:`BadgesSection`,setup(g){let{t:_}=c();return(c,g)=>(e(),n(u,{eyebrow:t(_)(`ds.eyebrow.components`),title:t(_)(`ds.badges.title`),description:t(_)(`ds.badges.description`)},{default:i(()=>[s(`section`,null,[s(`h2`,f,r(t(_)(`ds.heading.variants`)),1),a(d,{tone:`paper`},{default:i(()=>[s(`div`,p,[a(l,{variant:`neutral`},{default:i(()=>[o(r(t(_)(`ds.badges.neutral`)),1)]),_:1}),a(l,{variant:`brand`},{default:i(()=>[o(r(t(_)(`ds.badges.brand`)),1)]),_:1}),a(l,{variant:`accent`},{default:i(()=>[o(r(t(_)(`ds.badges.accent`)),1)]),_:1}),a(l,{variant:`subtle`},{default:i(()=>[o(r(t(_)(`ds.badges.subtle`)),1)]),_:1}),a(l,{variant:`success`},{default:i(()=>[o(r(t(_)(`ds.badges.success`)),1)]),_:1}),a(l,{variant:`warning`},{default:i(()=>[o(r(t(_)(`ds.badges.warning`)),1)]),_:1}),a(l,{variant:`danger`},{default:i(()=>[o(r(t(_)(`ds.badges.danger`)),1)]),_:1})])]),_:1})]),s(`section`,null,[s(`h2`,m,r(t(_)(`ds.heading.nonUppercase`)),1),a(d,{tone:`paper`},{default:i(()=>[s(`div`,h,[a(l,{variant:`brand`,uppercase:!1},{default:i(()=>[o(r(t(_)(`ds.badges.newRelease`)),1)]),_:1}),a(l,{variant:`accent`,uppercase:!1},{default:i(()=>[o(r(t(_)(`ds.badges.featured`)),1)]),_:1}),a(l,{variant:`subtle`,uppercase:!1},{default:i(()=>[...g[0]||=[o(`v2.1.0`,-1)]]),_:1})])]),_:1})])]),_:1},8,[`eyebrow`,`title`,`description`]))}};export{g as default};

View File

@@ -1 +1 @@
import{C as e,G as t,O as n,T as r,c as i,ft as a,j as o,l as s,m as c,o as l,p as u,r as d,s as f,u as p,ut as m}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as h}from"./Icon-Dcq1PyHo.js";import{t as g}from"./i18n-BE70RUdV.js";import{t as _}from"./Badge-Bu_yseK_.js";import{t as v}from"./Button-DAQ-3-Uk.js";var y=[`src`,`alt`],b={class:`flex flex-col gap-1.5`},x={key:0,class:`text-xs font-semibold tracking-label text-muted uppercase`},S={class:`flex flex-col gap-1.5`},C={key:0,class:`text-sm text-muted tracking-label`},w={class:`flex flex-col gap-0.5`},T={class:`text-xs tracking-label text-muted uppercase`},E={class:`font-display text-2xl font-normal text-brand leading-none`},D={key:0,class:`text-xs text-muted mt-1`},O={key:1,class:`text-xs font-semibold tracking-label uppercase text-danger mt-1`},k=3,A={__name:`BundleCard`,props:{name:{type:String,required:!0},items:{type:Array,required:!0},price:{type:Number,required:!0},memberPrice:{type:Number,default:null},usage:{type:String,default:``},image:{type:String,required:!0},imageAlt:{type:String,default:``},badge:{type:String,default:``},badgeVariant:{type:String,default:`accent`,validator:e=>[`neutral`,`brand`,`accent`,`subtle`,`success`,`warning`,`danger`].includes(e)},tone:{type:String,default:`paper`,validator:e=>[`paper`,`cream`].includes(e)},layout:{type:String,default:`vertical`,validator:e=>[`vertical`,`horizontal`].includes(e)},inStock:{type:Boolean,default:!0},currency:{type:String,default:``},href:{type:String,default:``}},emits:[`add`],setup(A){let j=A,{t:M}=g(),N={paper:{surface:`bg-paper`,media:`bg-cream`,border:`border-line`},cream:{surface:`bg-cream`,media:`bg-paper`,border:`border-line`}},P=l(()=>N[j.tone]);function F(e){return`${j.currency} ${e.toFixed(2).replace(`.`,`,`)}`}let I=l(()=>F(j.price)),L=l(()=>j.memberPrice==null?``:F(j.memberPrice)),R=l(()=>j.items.slice(0,k)),z=l(()=>Math.max(0,j.items.length-k));return(l,g)=>(e(),p(`article`,{class:m([`group flex overflow-hidden rounded-md border transition-all duration-base ease-out`,A.layout===`horizontal`?`flex-col md:flex-row`:`flex-col`,P.value.surface,P.value.border,`hover:-translate-y-1 hover:shadow-md hover:border-brand-soft`])},[(e(),i(n(A.href?`a`:`div`),{href:A.href||null,class:m([`relative block overflow-hidden`,A.layout===`horizontal`?`aspect-[4/3] md:aspect-auto md:w-[38%] md:shrink-0 md:min-h-[300px]`:`aspect-[4/3]`,P.value.media])},{default:o(()=>[A.badge?(e(),i(_,{key:0,variant:A.badgeVariant,class:`absolute top-4 left-4 z-[1]`},{default:o(()=>[u(a(A.badge),1)]),_:1},8,[`variant`])):s(``,!0),f(`img`,{src:A.image,alt:A.imageAlt||A.name,loading:`lazy`,decoding:`async`,class:m([`absolute inset-0 w-full h-full object-contain transition-transform duration-slow ease-out group-hover:scale-105`,A.layout===`horizontal`?`p-6 md:p-5`:`p-8`])},null,10,y)]),_:1},8,[`href`,`class`])),f(`div`,{class:m([`flex flex-col gap-4 p-6`,A.layout===`horizontal`?`md:p-6 md:flex-1`:``])},[f(`div`,b,[A.usage?(e(),p(`span`,x,a(A.usage),1)):s(``,!0),(e(),i(n(A.href?`a`:`h3`),{href:A.href||null,class:m([`font-display text-xl font-normal leading-tight text-ink`,A.href?`hover:text-brand transition-colors duration-base`:``])},{default:o(()=>[u(a(A.name),1)]),_:1},8,[`href`,`class`]))]),f(`ul`,S,[(e(!0),p(d,null,r(R.value,t=>(e(),p(`li`,{key:t,class:`text-sm text-ink/80 leading-relaxed`},a(t),1))),128)),z.value>0?(e(),p(`li`,C,`+ `+a(z.value)+` `+a(t(M)(`bundles.card.moreItems`)),1)):s(``,!0)]),f(`div`,{class:m([`mt-auto pt-4 border-t border-line flex gap-3`,A.layout===`horizontal`?`flex-col sm:flex-row sm:items-end sm:justify-between`:`flex-col`])},[f(`div`,w,[f(`span`,T,a(t(M)(`bundles.card.priceLabel`)),1),f(`span`,E,a(I.value),1),L.value?(e(),p(`span`,D,a(t(M)(`bundles.card.memberPrefix`))+` `+a(L.value),1)):s(``,!0),A.inStock?s(``,!0):(e(),p(`span`,O,a(t(M)(`ds.product.outOfStock`)),1))]),c(v,{variant:`primary`,size:`md`,block:A.layout===`vertical`,disabled:!A.inStock,onClick:g[0]||=e=>l.$emit(`add`)},{before:o(()=>[c(h,{name:`plus`,size:16})]),default:o(()=>[u(` `+a(t(M)(`ds.buttons.addToCart`)),1)]),_:1},8,[`block`,`disabled`])],2)],2)],2))}};export{A as t};
import{C as e,G as t,O as n,T as r,c as i,ft as a,j as o,l as s,m as c,o as l,p as u,r as d,s as f,u as p,ut as m}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as h}from"./Icon-Dcq1PyHo.js";import{t as g}from"./i18n-DhVF3Pwg.js";import{t as _}from"./Badge-Bu_yseK_.js";import{t as v}from"./Button-D1Rp2Xe1.js";var y=[`src`,`alt`],b={class:`flex flex-col gap-1.5`},x={key:0,class:`text-xs font-semibold tracking-label text-muted uppercase`},S={class:`flex flex-col gap-1.5`},C={key:0,class:`text-sm text-muted tracking-label`},w={class:`flex flex-col gap-0.5`},T={class:`text-xs tracking-label text-muted uppercase`},E={class:`font-display text-2xl font-normal text-brand leading-none`},D={key:0,class:`text-xs text-muted mt-1`},O={key:1,class:`text-xs font-semibold tracking-label uppercase text-danger mt-1`},k=3,A={__name:`BundleCard`,props:{name:{type:String,required:!0},items:{type:Array,required:!0},price:{type:Number,required:!0},memberPrice:{type:Number,default:null},usage:{type:String,default:``},image:{type:String,required:!0},imageAlt:{type:String,default:``},badge:{type:String,default:``},badgeVariant:{type:String,default:`accent`,validator:e=>[`neutral`,`brand`,`accent`,`subtle`,`success`,`warning`,`danger`].includes(e)},tone:{type:String,default:`paper`,validator:e=>[`paper`,`cream`].includes(e)},layout:{type:String,default:`vertical`,validator:e=>[`vertical`,`horizontal`].includes(e)},inStock:{type:Boolean,default:!0},currency:{type:String,default:``},href:{type:String,default:``}},emits:[`add`],setup(A){let j=A,{t:M}=g(),N={paper:{surface:`bg-paper`,media:`bg-cream`,border:`border-line`},cream:{surface:`bg-cream`,media:`bg-paper`,border:`border-line`}},P=l(()=>N[j.tone]);function F(e){return`${j.currency} ${e.toFixed(2).replace(`.`,`,`)}`}let I=l(()=>F(j.price)),L=l(()=>j.memberPrice==null?``:F(j.memberPrice)),R=l(()=>j.items.slice(0,k)),z=l(()=>Math.max(0,j.items.length-k));return(l,g)=>(e(),p(`article`,{class:m([`group flex overflow-hidden rounded-md border transition-all duration-base ease-out`,A.layout===`horizontal`?`flex-col md:flex-row`:`flex-col`,P.value.surface,P.value.border,`hover:-translate-y-1 hover:shadow-md hover:border-brand-soft`])},[(e(),i(n(A.href?`a`:`div`),{href:A.href||null,class:m([`relative block overflow-hidden`,A.layout===`horizontal`?`aspect-[4/3] md:aspect-auto md:w-[38%] md:shrink-0 md:min-h-[300px]`:`aspect-[4/3]`,P.value.media])},{default:o(()=>[A.badge?(e(),i(_,{key:0,variant:A.badgeVariant,class:`absolute top-4 left-4 z-[1]`},{default:o(()=>[u(a(A.badge),1)]),_:1},8,[`variant`])):s(``,!0),f(`img`,{src:A.image,alt:A.imageAlt||A.name,loading:`lazy`,decoding:`async`,class:m([`absolute inset-0 w-full h-full object-contain transition-transform duration-slow ease-out group-hover:scale-105`,A.layout===`horizontal`?`p-6 md:p-5`:`p-8`])},null,10,y)]),_:1},8,[`href`,`class`])),f(`div`,{class:m([`flex flex-col gap-4 p-6`,A.layout===`horizontal`?`md:p-6 md:flex-1`:``])},[f(`div`,b,[A.usage?(e(),p(`span`,x,a(A.usage),1)):s(``,!0),(e(),i(n(A.href?`a`:`h3`),{href:A.href||null,class:m([`font-display text-xl font-normal leading-tight text-ink`,A.href?`hover:text-brand transition-colors duration-base`:``])},{default:o(()=>[u(a(A.name),1)]),_:1},8,[`href`,`class`]))]),f(`ul`,S,[(e(!0),p(d,null,r(R.value,t=>(e(),p(`li`,{key:t,class:`text-sm text-ink/80 leading-relaxed`},a(t),1))),128)),z.value>0?(e(),p(`li`,C,`+ `+a(z.value)+` `+a(t(M)(`bundles.card.moreItems`)),1)):s(``,!0)]),f(`div`,{class:m([`mt-auto pt-4 border-t border-line flex gap-3`,A.layout===`horizontal`?`flex-col sm:flex-row sm:items-end sm:justify-between`:`flex-col`])},[f(`div`,w,[f(`span`,T,a(t(M)(`bundles.card.priceLabel`)),1),f(`span`,E,a(I.value),1),L.value?(e(),p(`span`,D,a(t(M)(`bundles.card.memberPrefix`))+` `+a(L.value),1)):s(``,!0),A.inStock?s(``,!0):(e(),p(`span`,O,a(t(M)(`ds.product.outOfStock`)),1))]),c(v,{variant:`primary`,size:`md`,block:A.layout===`vertical`,disabled:!A.inStock,onClick:g[0]||=e=>l.$emit(`add`)},{before:o(()=>[c(h,{name:`plus`,size:16})]),default:o(()=>[u(` `+a(t(M)(`ds.buttons.addToCart`)),1)]),_:1},8,[`block`,`disabled`])],2)],2)],2))}};export{A as t};

View File

@@ -1,4 +1,4 @@
import{B as e,C as t,G as n,c as r,ft as i,j as a,l as o,m as s,p as c,s as l,u,y as d}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as f}from"./i18n-BE70RUdV.js";import{t as p}from"./BundleCard-Ba-30KBv.js";import{t as m}from"./SectionShell-CU6IpKMJ.js";var h={class:`eyebrow mb-5`},g={class:`grid sm:grid-cols-2 lg:grid-cols-3 gap-6`},_={key:0,class:`mt-5 text-sm text-muted`},v={class:`font-mono text-[12px]`},y={class:`grid gap-6`},b={class:`eyebrow mb-5`},x={class:`grid sm:grid-cols-2 gap-6`},S={class:`eyebrow mb-5`},C=`/products/kaiser-natron-pulver-250-g-grosspackung.webp`,w={__name:`BundleCardSection`,setup(w){let{t:T}=f(),E=e(``);function D(e){E.value=e,setTimeout(()=>{E.value===e&&(E.value=``)},2e3)}let O={name:`Haushalts-Bundle`,usage:`23× pro Quartal empfohlen`,items:[`1× Kaiser-Natron Pulver 250 g`,`1× Allzweck-Spray 500 ml`,`1× Spülmittel 500 ml`],price:24.9,memberPrice:21.17,image:C,imageAlt:`Haushalts-Bundle`},k={...O,items:[...O.items,`1× Holste Wasch-Soda 500 g`,`1× Allzweckreiniger 750 ml`]};return(e,f)=>(t(),r(m,{eyebrow:n(T)(`ds.eyebrow.components`),title:n(T)(`ds.bundleCard.title`),description:n(T)(`ds.bundleCard.description`)},{default:a(()=>[l(`section`,null,[l(`h2`,h,i(n(T)(`ds.heading.default`)),1),l(`div`,g,[s(p,d(O,{onAdd:f[0]||=e=>D(`default`)}),null,16),s(p,d(O,{badge:`Bestseller`,"badge-variant":`accent`,onAdd:f[1]||=e=>D(`bestseller`)}),null,16),s(p,d(O,{tone:`cream`,onAdd:f[2]||=e=>D(`cream`)}),null,16)]),E.value?(t(),u(`p`,_,[c(i(n(T)(`ds.product.added`))+`: `,1),l(`code`,v,i(E.value),1)])):o(``,!0)]),l(`section`,null,[f[5]||=l(`h2`,{class:`eyebrow mb-5`},`Horizontal layout`,-1),f[6]||=l(`p`,{class:`text-sm text-muted mb-5 max-w-2xl`},[c(` Passed as `),l(`code`,{class:`font-mono text-[12px]`},`layout="horizontal"`),c(`. From `),l(`code`,{class:`font-mono text-[12px]`},`md`),c(` up the media takes ~38% of the row and the body fills the rest, with the CTA inlined next to the price block. Below `),l(`code`,{class:`font-mono text-[12px]`},`md`),c(` it collapses back to vertical. `)],-1),l(`div`,y,[s(p,d(O,{layout:`horizontal`,badge:`Bestseller`,"badge-variant":`accent`,onAdd:f[3]||=e=>D(`horizontal`)}),null,16)])]),l(`section`,null,[l(`h2`,b,i(n(T)(`ds.heading.states`)),1),f[7]||=l(`p`,{class:`text-sm text-muted mb-5 max-w-2xl`},` Same bundle across both cards — only the state being demonstrated changes. `,-1),l(`div`,x,[s(p,d(O,{"in-stock":!1}),null,16),s(p,d(k,{onAdd:f[4]||=e=>D(`overflow`)}),null,16)]),f[8]||=l(`p`,{class:`mt-3 text-sm text-muted max-w-2xl`},[c(` More than three items collapse the tail into a `),l(`code`,{class:`font-mono text-[12px]`},`+ N weitere`),c(` line so the card stays scannable. `)],-1)]),l(`section`,null,[l(`h2`,S,i(n(T)(`ds.heading.usage`)),1),f[9]||=l(`div`,{class:`rounded-md border border-line bg-paper p-6 font-mono text-[12px] text-ink`},[l(`pre`,{class:`whitespace-pre-wrap`},`<BundleCard
import{B as e,C as t,G as n,c as r,ft as i,j as a,l as o,m as s,p as c,s as l,u,y as d}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as f}from"./i18n-DhVF3Pwg.js";import{t as p}from"./BundleCard-Dg099ort.js";import{t as m}from"./SectionShell-DGDgzO-7.js";var h={class:`eyebrow mb-5`},g={class:`grid sm:grid-cols-2 lg:grid-cols-3 gap-6`},_={key:0,class:`mt-5 text-sm text-muted`},v={class:`font-mono text-[12px]`},y={class:`grid gap-6`},b={class:`eyebrow mb-5`},x={class:`grid sm:grid-cols-2 gap-6`},S={class:`eyebrow mb-5`},C=`/products/kaiser-natron-pulver-250-g-grosspackung.webp`,w={__name:`BundleCardSection`,setup(w){let{t:T}=f(),E=e(``);function D(e){E.value=e,setTimeout(()=>{E.value===e&&(E.value=``)},2e3)}let O={name:`Haushalts-Bundle`,usage:`23× pro Quartal empfohlen`,items:[`1× Kaiser-Natron Pulver 250 g`,`1× Allzweck-Spray 500 ml`,`1× Spülmittel 500 ml`],price:24.9,memberPrice:21.17,image:C,imageAlt:`Haushalts-Bundle`},k={...O,items:[...O.items,`1× Holste Wasch-Soda 500 g`,`1× Allzweckreiniger 750 ml`]};return(e,f)=>(t(),r(m,{eyebrow:n(T)(`ds.eyebrow.components`),title:n(T)(`ds.bundleCard.title`),description:n(T)(`ds.bundleCard.description`)},{default:a(()=>[l(`section`,null,[l(`h2`,h,i(n(T)(`ds.heading.default`)),1),l(`div`,g,[s(p,d(O,{onAdd:f[0]||=e=>D(`default`)}),null,16),s(p,d(O,{badge:`Bestseller`,"badge-variant":`accent`,onAdd:f[1]||=e=>D(`bestseller`)}),null,16),s(p,d(O,{tone:`cream`,onAdd:f[2]||=e=>D(`cream`)}),null,16)]),E.value?(t(),u(`p`,_,[c(i(n(T)(`ds.product.added`))+`: `,1),l(`code`,v,i(E.value),1)])):o(``,!0)]),l(`section`,null,[f[5]||=l(`h2`,{class:`eyebrow mb-5`},`Horizontal layout`,-1),f[6]||=l(`p`,{class:`text-sm text-muted mb-5 max-w-2xl`},[c(` Passed as `),l(`code`,{class:`font-mono text-[12px]`},`layout="horizontal"`),c(`. From `),l(`code`,{class:`font-mono text-[12px]`},`md`),c(` up the media takes ~38% of the row and the body fills the rest, with the CTA inlined next to the price block. Below `),l(`code`,{class:`font-mono text-[12px]`},`md`),c(` it collapses back to vertical. `)],-1),l(`div`,y,[s(p,d(O,{layout:`horizontal`,badge:`Bestseller`,"badge-variant":`accent`,onAdd:f[3]||=e=>D(`horizontal`)}),null,16)])]),l(`section`,null,[l(`h2`,b,i(n(T)(`ds.heading.states`)),1),f[7]||=l(`p`,{class:`text-sm text-muted mb-5 max-w-2xl`},` Same bundle across both cards — only the state being demonstrated changes. `,-1),l(`div`,x,[s(p,d(O,{"in-stock":!1}),null,16),s(p,d(k,{onAdd:f[4]||=e=>D(`overflow`)}),null,16)]),f[8]||=l(`p`,{class:`mt-3 text-sm text-muted max-w-2xl`},[c(` More than three items collapse the tail into a `),l(`code`,{class:`font-mono text-[12px]`},`+ N weitere`),c(` line so the card stays scannable. `)],-1)]),l(`section`,null,[l(`h2`,S,i(n(T)(`ds.heading.usage`)),1),f[9]||=l(`div`,{class:`rounded-md border border-line bg-paper p-6 font-mono text-[12px] text-ink`},[l(`pre`,{class:`whitespace-pre-wrap`},`<BundleCard
name="Haushalts-Bundle"
usage="23× pro Quartal empfohlen"
:items="[

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
import{C as e,G as t,m as n,o as r,u as i}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{a}from"./vue-router-Cyqru1db.js";import{t as o}from"./i18n-BE70RUdV.js";import{t as s}from"./Bundles-BwnDtFDw.js";var c={class:`min-h-screen bg-surface`},l={__name:`BundlesPreview`,setup(l){let{t:u}=o(),d=a(),f=r(()=>d.query.layout===`stacked`?`stacked`:`sidebar`),p=[{id:`haushalt`,name:`Haushalts-Bundle`,usage:`23× pro Quartal empfohlen`,items:[`1× Kaiser-Natron Pulver 250 g`,`1× Allzweck-Spray 500 ml`,`1× Spülmittel 500 ml`],price:24.9,memberPrice:21.17,image:`/products/kaiser-natron-pulver-250-g-grosspackung.webp`,imageAlt:`Haushalts-Bundle mit Kaiser-Natron`,badge:`Bestseller`,badgeVariant:`accent`},{id:`waesche`,name:`Wäsche & Pflege`,usage:`12× pro Quartal`,items:[`1× Holste Wasch-Soda 500 g`,`1× Gazelle Wäschestärke 1 l`,`1× Linda Fleckenweg 200 ml`],price:22.9,memberPrice:19.47,image:`/products/holste-wasch-soda-500-g-beutel.webp`,imageAlt:`Wäsche & Pflege Bundle`},{id:`wohlfuehl`,name:`Wohlfühl-Bundle`,usage:`1× pro Quartal`,items:[`1× Kaiser-Natron Tabletten 100 g`,`1× Kaiser-Natron Bad 500 g`,`1× Kaiser-Natron Fußbad 500 g`],price:29.9,memberPrice:25.42,image:`/products/kaiser-natron-bad-500-g.webp`,imageAlt:`Wohlfühl-Bundle`}],m=r(()=>[u(`bundles.benefit.1.title`),u(`bundles.benefit.2.title`),u(`bundles.benefit.3.title`)]);return(r,a)=>(e(),i(`div`,c,[n(s,{layout:f.value,bundles:p,headline:t(u)(`bundles.headline.a`),"headline-em":t(u)(`bundles.headline.em`),sub:t(u)(`bundles.sub`),benefits:m.value,"join-cta":t(u)(`bundles.joinCta`)},null,8,[`layout`,`headline`,`headline-em`,`sub`,`benefits`,`join-cta`])]))}};export{l as default};
import{C as e,G as t,m as n,o as r,u as i}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{a}from"./vue-router-Cyqru1db.js";import{t as o}from"./i18n-DhVF3Pwg.js";import{t as s}from"./Bundles-Dt57zqLD.js";var c={class:`min-h-screen bg-surface`},l={__name:`BundlesPreview`,setup(l){let{t:u}=o(),d=a(),f=r(()=>d.query.layout===`stacked`?`stacked`:`sidebar`),p=[{id:`haushalt`,name:`Haushalts-Bundle`,usage:`23× pro Quartal empfohlen`,items:[`1× Kaiser-Natron Pulver 250 g`,`1× Allzweck-Spray 500 ml`,`1× Spülmittel 500 ml`],price:24.9,memberPrice:21.17,image:`/products/kaiser-natron-pulver-250-g-grosspackung.webp`,imageAlt:`Haushalts-Bundle mit Kaiser-Natron`,badge:`Bestseller`,badgeVariant:`accent`},{id:`waesche`,name:`Wäsche & Pflege`,usage:`12× pro Quartal`,items:[`1× Holste Wasch-Soda 500 g`,`1× Gazelle Wäschestärke 1 l`,`1× Linda Fleckenweg 200 ml`],price:22.9,memberPrice:19.47,image:`/products/holste-wasch-soda-500-g-beutel.webp`,imageAlt:`Wäsche & Pflege Bundle`},{id:`wohlfuehl`,name:`Wohlfühl-Bundle`,usage:`1× pro Quartal`,items:[`1× Kaiser-Natron Tabletten 100 g`,`1× Kaiser-Natron Bad 500 g`,`1× Kaiser-Natron Fußbad 500 g`],price:29.9,memberPrice:25.42,image:`/products/kaiser-natron-bad-500-g.webp`,imageAlt:`Wohlfühl-Bundle`}],m=r(()=>[u(`bundles.benefit.1.title`),u(`bundles.benefit.2.title`),u(`bundles.benefit.3.title`)]);return(r,a)=>(e(),i(`div`,c,[n(s,{layout:f.value,bundles:p,headline:t(u)(`bundles.headline.a`),"headline-em":t(u)(`bundles.headline.em`),sub:t(u)(`bundles.sub`),benefits:m.value,"join-cta":t(u)(`bundles.joinCta`)},null,8,[`layout`,`headline`,`headline-em`,`sub`,`benefits`,`join-cta`])]))}};export{l as default};

View File

@@ -1,4 +1,4 @@
import{B as e,C as t,G as n,T as r,c as i,ft as a,j as o,m as s,o as c,p as l,r as u,s as d,u as f,ut as p}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as m}from"./i18n-BE70RUdV.js";import{t as h}from"./SectionShell-CU6IpKMJ.js";import{t as g}from"./DevicePreview-9pDgIJdz.js";var _={role:`tablist`,"aria-label":`Layout variant`,class:`inline-flex items-center p-1 gap-0.5 rounded-pill border border-line bg-paper`},v=[`aria-selected`,`onClick`],y={class:`eyebrow mb-5`},b={__name:`BundlesSection`,setup(b){let{t:x}=m(),S=[{id:`sidebar`,label:`Option 1`},{id:`stacked`,label:`Option 2`}],C=e(`sidebar`),w=c(()=>`/design/preview/bundles?layout=${C.value}`);return(e,c)=>(t(),i(h,{eyebrow:n(x)(`ds.eyebrow.components`),title:n(x)(`ds.bundles.title`),description:n(x)(`ds.bundles.description`),wide:``},{default:o(()=>[d(`section`,null,[s(g,{src:w.value,initial:`desktop`,height:1080},{controls:o(()=>[d(`div`,_,[(t(),f(u,null,r(S,e=>d(`button`,{key:e.id,type:`button`,role:`tab`,"aria-selected":C.value===e.id,class:p([`inline-flex items-center gap-2 px-3 py-1.5 text-[12px] font-semibold tracking-label rounded-pill transition-colors duration-base`,C.value===e.id?`bg-brand text-accent`:`text-muted hover:text-brand`]),onClick:t=>C.value=e.id},a(e.label),11,v)),64))])]),_:1},8,[`src`])]),d(`section`,null,[d(`h2`,y,a(n(x)(`ds.heading.usage`)),1),c[0]||=d(`div`,{class:`rounded-md border border-line bg-paper p-6 font-mono text-[12px] text-ink`},[d(`pre`,{class:`whitespace-pre-wrap`},`<Bundles
import{B as e,C as t,G as n,T as r,c as i,ft as a,j as o,m as s,o as c,p as l,r as u,s as d,u as f,ut as p}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as m}from"./i18n-DhVF3Pwg.js";import{t as h}from"./SectionShell-DGDgzO-7.js";import{t as g}from"./DevicePreview-C43cRj6M.js";var _={role:`tablist`,"aria-label":`Layout variant`,class:`inline-flex items-center p-1 gap-0.5 rounded-pill border border-line bg-paper`},v=[`aria-selected`,`onClick`],y={class:`eyebrow mb-5`},b={__name:`BundlesSection`,setup(b){let{t:x}=m(),S=[{id:`sidebar`,label:`Option 1`},{id:`stacked`,label:`Option 2`}],C=e(`sidebar`),w=c(()=>`/design/preview/bundles?layout=${C.value}`);return(e,c)=>(t(),i(h,{eyebrow:n(x)(`ds.eyebrow.components`),title:n(x)(`ds.bundles.title`),description:n(x)(`ds.bundles.description`),wide:``},{default:o(()=>[d(`section`,null,[s(g,{src:w.value,initial:`desktop`,height:1080},{controls:o(()=>[d(`div`,_,[(t(),f(u,null,r(S,e=>d(`button`,{key:e.id,type:`button`,role:`tab`,"aria-selected":C.value===e.id,class:p([`inline-flex items-center gap-2 px-3 py-1.5 text-[12px] font-semibold tracking-label rounded-pill transition-colors duration-base`,C.value===e.id?`bg-brand text-accent`:`text-muted hover:text-brand`]),onClick:t=>C.value=e.id},a(e.label),11,v)),64))])]),_:1},8,[`src`])]),d(`section`,null,[d(`h2`,y,a(n(x)(`ds.heading.usage`)),1),c[0]||=d(`div`,{class:`rounded-md border border-line bg-paper p-6 font-mono text-[12px] text-ink`},[d(`pre`,{class:`whitespace-pre-wrap`},`<Bundles
:bundles="bundles"
:headline="t('bundles.headline.a')"
:headline-em="t('bundles.headline.em')"

View File

@@ -1,4 +1,4 @@
import{C as e,G as t,c as n,ft as r,j as i,m as a,p as o,s}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as c}from"./Icon-Dcq1PyHo.js";import{t as l}from"./i18n-BE70RUdV.js";import{t as u}from"./Button-DAQ-3-Uk.js";import{t as d}from"./SectionShell-CU6IpKMJ.js";import{t as f}from"./Card-C-QeZ_to.js";var p={class:`eyebrow mb-5`},m={class:`flex flex-wrap gap-3`},h={class:`eyebrow mb-5`},g={class:`grid md:grid-cols-3 gap-4`},_={class:`eyebrow mb-4`},v={class:`flex flex-wrap gap-3`},y={class:`eyebrow mb-4`},b={class:`flex flex-wrap gap-3`},x={class:`eyebrow mb-4 !text-cream opacity-80`},S={class:`flex flex-wrap gap-3`},C={class:`eyebrow mb-5`},w={class:`flex flex-wrap items-center gap-3`},T={class:`eyebrow mb-5`},E={class:`flex flex-wrap items-center gap-3`},D={class:`eyebrow mb-5`},O={class:`flex flex-wrap gap-3 items-center`},k={class:`eyebrow mb-5`},A={class:`max-w-md space-y-3`},j={class:`eyebrow mb-5`},M={class:`rounded-md border border-line bg-paper p-6 font-mono text-[12px] text-ink`},N={class:`whitespace-pre-wrap`},P={__name:`ButtonsSection`,setup(P){let{t:F}=l();return(l,P)=>(e(),n(d,{eyebrow:t(F)(`ds.eyebrow.components`),title:t(F)(`ds.buttons.title`),description:t(F)(`ds.buttons.description`)},{default:i(()=>[s(`section`,null,[s(`h2`,p,r(t(F)(`ds.heading.variants`)),1),a(f,{tone:`paper`},{default:i(()=>[s(`div`,m,[a(u,{variant:`primary`},{default:i(()=>[o(r(t(F)(`ds.buttons.primary`)),1)]),_:1}),a(u,{variant:`accent`},{default:i(()=>[o(r(t(F)(`ds.buttons.accent`)),1)]),_:1}),a(u,{variant:`secondary`},{default:i(()=>[o(r(t(F)(`ds.buttons.secondary`)),1)]),_:1}),a(u,{variant:`ghost`},{default:i(()=>[o(r(t(F)(`ds.buttons.ghost`)),1)]),_:1}),a(u,{variant:`danger`},{default:i(()=>[o(r(t(F)(`ds.buttons.danger`)),1)]),_:1})])]),_:1})]),s(`section`,null,[s(`h2`,h,r(t(F)(`ds.heading.onDifferentSurfaces`)),1),s(`div`,g,[a(f,{tone:`paper`},{default:i(()=>[s(`p`,_,r(t(F)(`ds.cards.paper`)),1),s(`div`,v,[a(u,{variant:`primary`},{before:i(()=>[a(c,{name:`cart`,size:18})]),default:i(()=>[o(` `+r(t(F)(`ds.buttons.addToCart`)),1)]),_:1}),a(u,{variant:`ghost`},{default:i(()=>[o(r(t(F)(`ds.buttons.learnMore`)),1)]),_:1})])]),_:1}),a(f,{tone:`cream`},{default:i(()=>[s(`p`,y,r(t(F)(`ds.cards.cream`)),1),s(`div`,b,[a(u,{variant:`primary`},{before:i(()=>[a(c,{name:`cart`,size:18})]),default:i(()=>[o(` `+r(t(F)(`ds.buttons.addToCart`)),1)]),_:1}),a(u,{variant:`ghost`},{default:i(()=>[o(r(t(F)(`ds.buttons.learnMore`)),1)]),_:1})])]),_:1}),a(f,{tone:`brand`},{default:i(()=>[s(`p`,x,r(t(F)(`ds.cards.brand`)),1),s(`div`,S,[a(u,{variant:`accent`},{before:i(()=>[a(c,{name:`cart`,size:18})]),default:i(()=>[o(` `+r(t(F)(`ds.buttons.addToCart`)),1)]),_:1}),a(u,{variant:`ghost`,class:`!text-cream hover:!bg-cream-wash`},{default:i(()=>[o(r(t(F)(`ds.buttons.learnMore`)),1)]),_:1})])]),_:1})])]),s(`section`,null,[s(`h2`,C,r(t(F)(`ds.heading.sizes`)),1),a(f,{tone:`paper`},{default:i(()=>[s(`div`,w,[a(u,{size:`sm`},{default:i(()=>[o(r(t(F)(`ds.buttons.small`)),1)]),_:1}),a(u,{size:`md`},{default:i(()=>[o(r(t(F)(`ds.buttons.medium`)),1)]),_:1}),a(u,{size:`lg`},{default:i(()=>[o(r(t(F)(`ds.buttons.large`)),1)]),_:1})])]),_:1})]),s(`section`,null,[s(`h2`,T,r(t(F)(`ds.heading.withIcons`)),1),a(f,{tone:`paper`},{default:i(()=>[s(`div`,E,[a(u,{variant:`primary`},{before:i(()=>[a(c,{name:`cart`,size:18})]),default:i(()=>[o(` `+r(t(F)(`ds.buttons.addToCart`)),1)]),_:1}),a(u,{variant:`secondary`},{after:i(()=>[a(c,{name:`arrow-right`,size:18})]),default:i(()=>[o(r(t(F)(`ds.buttons.learnMore`))+` `,1)]),_:1}),a(u,{variant:`ghost`},{before:i(()=>[a(c,{name:`heart`,size:18})]),default:i(()=>[o(` `+r(t(F)(`ds.buttons.save`)),1)]),_:1}),a(u,{variant:`accent`,size:`sm`},{before:i(()=>[a(c,{name:`check`,size:16})]),default:i(()=>[o(` `+r(t(F)(`ds.buttons.confirm`)),1)]),_:1})])]),_:1})]),s(`section`,null,[s(`h2`,D,r(t(F)(`ds.heading.states`)),1),a(f,{tone:`paper`},{default:i(()=>[s(`div`,O,[a(u,null,{default:i(()=>[o(r(t(F)(`ds.heading.default`)),1)]),_:1}),a(u,{disabled:``},{default:i(()=>[o(r(t(F)(`ds.buttons.disabled`)),1)]),_:1}),a(u,{loading:``},{default:i(()=>[o(r(t(F)(`ds.buttons.loading`)),1)]),_:1})])]),_:1})]),s(`section`,null,[s(`h2`,k,r(t(F)(`ds.heading.block`)),1),a(f,{tone:`paper`},{default:i(()=>[s(`div`,A,[a(u,{block:``,variant:`primary`},{before:i(()=>[a(c,{name:`cart`,size:18})]),default:i(()=>[o(` `+r(t(F)(`ds.buttons.addToCart`)),1)]),_:1}),a(u,{block:``,variant:`secondary`},{default:i(()=>[o(r(t(F)(`ds.buttons.continueShopping`)),1)]),_:1})])]),_:1})]),s(`section`,null,[s(`h2`,j,r(t(F)(`ds.heading.usage`)),1),s(`div`,M,[s(`pre`,N,`<Button variant="primary" size="md">
import{C as e,G as t,c as n,ft as r,j as i,m as a,p as o,s}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as c}from"./Icon-Dcq1PyHo.js";import{t as l}from"./i18n-DhVF3Pwg.js";import{t as u}from"./Button-D1Rp2Xe1.js";import{t as d}from"./SectionShell-DGDgzO-7.js";import{t as f}from"./Card-DcusAcLZ.js";var p={class:`eyebrow mb-5`},m={class:`flex flex-wrap gap-3`},h={class:`eyebrow mb-5`},g={class:`grid md:grid-cols-3 gap-4`},_={class:`eyebrow mb-4`},v={class:`flex flex-wrap gap-3`},y={class:`eyebrow mb-4`},b={class:`flex flex-wrap gap-3`},x={class:`eyebrow mb-4 !text-cream opacity-80`},S={class:`flex flex-wrap gap-3`},C={class:`eyebrow mb-5`},w={class:`flex flex-wrap items-center gap-3`},T={class:`eyebrow mb-5`},E={class:`flex flex-wrap items-center gap-3`},D={class:`eyebrow mb-5`},O={class:`flex flex-wrap gap-3 items-center`},k={class:`eyebrow mb-5`},A={class:`max-w-md space-y-3`},j={class:`eyebrow mb-5`},M={class:`rounded-md border border-line bg-paper p-6 font-mono text-[12px] text-ink`},N={class:`whitespace-pre-wrap`},P={__name:`ButtonsSection`,setup(P){let{t:F}=l();return(l,P)=>(e(),n(d,{eyebrow:t(F)(`ds.eyebrow.components`),title:t(F)(`ds.buttons.title`),description:t(F)(`ds.buttons.description`)},{default:i(()=>[s(`section`,null,[s(`h2`,p,r(t(F)(`ds.heading.variants`)),1),a(f,{tone:`paper`},{default:i(()=>[s(`div`,m,[a(u,{variant:`primary`},{default:i(()=>[o(r(t(F)(`ds.buttons.primary`)),1)]),_:1}),a(u,{variant:`accent`},{default:i(()=>[o(r(t(F)(`ds.buttons.accent`)),1)]),_:1}),a(u,{variant:`secondary`},{default:i(()=>[o(r(t(F)(`ds.buttons.secondary`)),1)]),_:1}),a(u,{variant:`ghost`},{default:i(()=>[o(r(t(F)(`ds.buttons.ghost`)),1)]),_:1}),a(u,{variant:`danger`},{default:i(()=>[o(r(t(F)(`ds.buttons.danger`)),1)]),_:1})])]),_:1})]),s(`section`,null,[s(`h2`,h,r(t(F)(`ds.heading.onDifferentSurfaces`)),1),s(`div`,g,[a(f,{tone:`paper`},{default:i(()=>[s(`p`,_,r(t(F)(`ds.cards.paper`)),1),s(`div`,v,[a(u,{variant:`primary`},{before:i(()=>[a(c,{name:`cart`,size:18})]),default:i(()=>[o(` `+r(t(F)(`ds.buttons.addToCart`)),1)]),_:1}),a(u,{variant:`ghost`},{default:i(()=>[o(r(t(F)(`ds.buttons.learnMore`)),1)]),_:1})])]),_:1}),a(f,{tone:`cream`},{default:i(()=>[s(`p`,y,r(t(F)(`ds.cards.cream`)),1),s(`div`,b,[a(u,{variant:`primary`},{before:i(()=>[a(c,{name:`cart`,size:18})]),default:i(()=>[o(` `+r(t(F)(`ds.buttons.addToCart`)),1)]),_:1}),a(u,{variant:`ghost`},{default:i(()=>[o(r(t(F)(`ds.buttons.learnMore`)),1)]),_:1})])]),_:1}),a(f,{tone:`brand`},{default:i(()=>[s(`p`,x,r(t(F)(`ds.cards.brand`)),1),s(`div`,S,[a(u,{variant:`accent`},{before:i(()=>[a(c,{name:`cart`,size:18})]),default:i(()=>[o(` `+r(t(F)(`ds.buttons.addToCart`)),1)]),_:1}),a(u,{variant:`ghost`,class:`!text-cream hover:!bg-cream-wash`},{default:i(()=>[o(r(t(F)(`ds.buttons.learnMore`)),1)]),_:1})])]),_:1})])]),s(`section`,null,[s(`h2`,C,r(t(F)(`ds.heading.sizes`)),1),a(f,{tone:`paper`},{default:i(()=>[s(`div`,w,[a(u,{size:`sm`},{default:i(()=>[o(r(t(F)(`ds.buttons.small`)),1)]),_:1}),a(u,{size:`md`},{default:i(()=>[o(r(t(F)(`ds.buttons.medium`)),1)]),_:1}),a(u,{size:`lg`},{default:i(()=>[o(r(t(F)(`ds.buttons.large`)),1)]),_:1})])]),_:1})]),s(`section`,null,[s(`h2`,T,r(t(F)(`ds.heading.withIcons`)),1),a(f,{tone:`paper`},{default:i(()=>[s(`div`,E,[a(u,{variant:`primary`},{before:i(()=>[a(c,{name:`cart`,size:18})]),default:i(()=>[o(` `+r(t(F)(`ds.buttons.addToCart`)),1)]),_:1}),a(u,{variant:`secondary`},{after:i(()=>[a(c,{name:`arrow-right`,size:18})]),default:i(()=>[o(r(t(F)(`ds.buttons.learnMore`))+` `,1)]),_:1}),a(u,{variant:`ghost`},{before:i(()=>[a(c,{name:`heart`,size:18})]),default:i(()=>[o(` `+r(t(F)(`ds.buttons.save`)),1)]),_:1}),a(u,{variant:`accent`,size:`sm`},{before:i(()=>[a(c,{name:`check`,size:16})]),default:i(()=>[o(` `+r(t(F)(`ds.buttons.confirm`)),1)]),_:1})])]),_:1})]),s(`section`,null,[s(`h2`,D,r(t(F)(`ds.heading.states`)),1),a(f,{tone:`paper`},{default:i(()=>[s(`div`,O,[a(u,null,{default:i(()=>[o(r(t(F)(`ds.heading.default`)),1)]),_:1}),a(u,{disabled:``},{default:i(()=>[o(r(t(F)(`ds.buttons.disabled`)),1)]),_:1}),a(u,{loading:``},{default:i(()=>[o(r(t(F)(`ds.buttons.loading`)),1)]),_:1})])]),_:1})]),s(`section`,null,[s(`h2`,k,r(t(F)(`ds.heading.block`)),1),a(f,{tone:`paper`},{default:i(()=>[s(`div`,A,[a(u,{block:``,variant:`primary`},{before:i(()=>[a(c,{name:`cart`,size:18})]),default:i(()=>[o(` `+r(t(F)(`ds.buttons.addToCart`)),1)]),_:1}),a(u,{block:``,variant:`secondary`},{default:i(()=>[o(r(t(F)(`ds.buttons.continueShopping`)),1)]),_:1})])]),_:1})]),s(`section`,null,[s(`h2`,j,r(t(F)(`ds.heading.usage`)),1),s(`div`,M,[s(`pre`,N,`<Button variant="primary" size="md">
<template #before><Icon name="cart" :size="18" /></template>
`+r(t(F)(`ds.buttons.addToCart`))+`
</Button>`,1)])])]),_:1},8,[`eyebrow`,`title`,`description`]))}};export{P as default};

View File

@@ -1 +1 @@
import{C as e,G as t,c as n,ft as r,j as i,m as a,p as o,s}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as c}from"./i18n-BE70RUdV.js";import{t as l}from"./Badge-Bu_yseK_.js";import{t as u}from"./SectionShell-CU6IpKMJ.js";import{t as d}from"./Card-C-QeZ_to.js";var f={class:`eyebrow mb-5`},p={class:`grid md:grid-cols-3 gap-6`},m={class:`font-display text-2xl font-normal mb-2`},h={class:`text-[14px] text-muted leading-relaxed`},g={class:`font-display text-2xl font-normal mb-2`},_={class:`text-[14px] text-muted leading-relaxed`},v={class:`font-display text-2xl font-normal mb-2`},y={class:`text-[14px] opacity-80 leading-relaxed`},b={class:`eyebrow mb-5`},x={class:`grid md:grid-cols-2 gap-6`},S={class:`font-display text-2xl font-normal mb-2`},C={class:`text-[14px] text-muted leading-relaxed`},w={class:`font-display text-2xl font-normal mb-2`},T={class:`text-[14px] text-muted leading-relaxed`},E={class:`eyebrow mb-5`},D={class:`p-7`},O={class:`font-display text-2xl font-normal mb-2`},k={class:`text-[14px] text-muted leading-relaxed`},A={__name:`CardsSection`,setup(A){let{t:j}=c();return(c,A)=>(e(),n(u,{eyebrow:t(j)(`ds.eyebrow.components`),title:t(j)(`ds.cards.title`),description:t(j)(`ds.cards.description`)},{default:i(()=>[s(`section`,null,[s(`h2`,f,r(t(j)(`ds.heading.tones`)),1),s(`div`,p,[a(d,{tone:`paper`},{default:i(()=>[a(l,{variant:`subtle`,class:`mb-4`},{default:i(()=>[o(r(t(j)(`ds.cards.paper`)),1)]),_:1}),s(`h3`,m,r(t(j)(`ds.cards.paperTitle`)),1),s(`p`,h,r(t(j)(`ds.cards.paperBody`)),1)]),_:1}),a(d,{tone:`cream`},{default:i(()=>[a(l,{variant:`subtle`,class:`mb-4`},{default:i(()=>[o(r(t(j)(`ds.cards.cream`)),1)]),_:1}),s(`h3`,g,r(t(j)(`ds.cards.creamTitle`)),1),s(`p`,_,r(t(j)(`ds.cards.creamBody`)),1)]),_:1}),a(d,{tone:`brand`},{default:i(()=>[a(l,{variant:`accent`,class:`mb-4`},{default:i(()=>[o(r(t(j)(`ds.cards.brand`)),1)]),_:1}),s(`h3`,v,r(t(j)(`ds.cards.brandTitle`)),1),s(`p`,y,r(t(j)(`ds.cards.brandBody`)),1)]),_:1})])]),s(`section`,null,[s(`h2`,b,r(t(j)(`ds.heading.interactive`)),1),s(`div`,x,[a(d,{tone:`paper`,interactive:``},{default:i(()=>[s(`h3`,S,r(t(j)(`ds.cards.hoverMe`)),1),s(`p`,C,r(t(j)(`ds.cards.hoverBody`)),1)]),_:1}),a(d,{tone:`cream`,interactive:``},{default:i(()=>[s(`h3`,w,r(t(j)(`ds.cards.hoverMeToo`)),1),s(`p`,T,r(t(j)(`ds.cards.hoverBodyAlt`)),1)]),_:1})])]),s(`section`,null,[s(`h2`,E,r(t(j)(`ds.heading.withoutPadding`)),1),a(d,{tone:`paper`,padded:!1},{default:i(()=>[A[2]||=s(`div`,{class:`h-40 bg-cream rounded-t-md`},null,-1),s(`div`,D,[s(`h3`,O,r(t(j)(`ds.cards.mediaTitle`)),1),s(`p`,k,[o(r(t(j)(`ds.cards.mediaBody`))+` `,1),A[0]||=s(`code`,{class:`font-mono text-[12px]`},`:padded="false"`,-1),A[1]||=o(`. `,-1)])])]),_:1})])]),_:1},8,[`eyebrow`,`title`,`description`]))}};export{A as default};
import{C as e,G as t,c as n,ft as r,j as i,m as a,p as o,s}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as c}from"./i18n-DhVF3Pwg.js";import{t as l}from"./Badge-Bu_yseK_.js";import{t as u}from"./SectionShell-DGDgzO-7.js";import{t as d}from"./Card-DcusAcLZ.js";var f={class:`eyebrow mb-5`},p={class:`grid md:grid-cols-3 gap-6`},m={class:`font-display text-2xl font-normal mb-2`},h={class:`text-[14px] text-muted leading-relaxed`},g={class:`font-display text-2xl font-normal mb-2`},_={class:`text-[14px] text-muted leading-relaxed`},v={class:`font-display text-2xl font-normal mb-2`},y={class:`text-[14px] opacity-80 leading-relaxed`},b={class:`eyebrow mb-5`},x={class:`grid md:grid-cols-2 gap-6`},S={class:`font-display text-2xl font-normal mb-2`},C={class:`text-[14px] text-muted leading-relaxed`},w={class:`font-display text-2xl font-normal mb-2`},T={class:`text-[14px] text-muted leading-relaxed`},E={class:`eyebrow mb-5`},D={class:`p-7`},O={class:`font-display text-2xl font-normal mb-2`},k={class:`text-[14px] text-muted leading-relaxed`},A={__name:`CardsSection`,setup(A){let{t:j}=c();return(c,A)=>(e(),n(u,{eyebrow:t(j)(`ds.eyebrow.components`),title:t(j)(`ds.cards.title`),description:t(j)(`ds.cards.description`)},{default:i(()=>[s(`section`,null,[s(`h2`,f,r(t(j)(`ds.heading.tones`)),1),s(`div`,p,[a(d,{tone:`paper`},{default:i(()=>[a(l,{variant:`subtle`,class:`mb-4`},{default:i(()=>[o(r(t(j)(`ds.cards.paper`)),1)]),_:1}),s(`h3`,m,r(t(j)(`ds.cards.paperTitle`)),1),s(`p`,h,r(t(j)(`ds.cards.paperBody`)),1)]),_:1}),a(d,{tone:`cream`},{default:i(()=>[a(l,{variant:`subtle`,class:`mb-4`},{default:i(()=>[o(r(t(j)(`ds.cards.cream`)),1)]),_:1}),s(`h3`,g,r(t(j)(`ds.cards.creamTitle`)),1),s(`p`,_,r(t(j)(`ds.cards.creamBody`)),1)]),_:1}),a(d,{tone:`brand`},{default:i(()=>[a(l,{variant:`accent`,class:`mb-4`},{default:i(()=>[o(r(t(j)(`ds.cards.brand`)),1)]),_:1}),s(`h3`,v,r(t(j)(`ds.cards.brandTitle`)),1),s(`p`,y,r(t(j)(`ds.cards.brandBody`)),1)]),_:1})])]),s(`section`,null,[s(`h2`,b,r(t(j)(`ds.heading.interactive`)),1),s(`div`,x,[a(d,{tone:`paper`,interactive:``},{default:i(()=>[s(`h3`,S,r(t(j)(`ds.cards.hoverMe`)),1),s(`p`,C,r(t(j)(`ds.cards.hoverBody`)),1)]),_:1}),a(d,{tone:`cream`,interactive:``},{default:i(()=>[s(`h3`,w,r(t(j)(`ds.cards.hoverMeToo`)),1),s(`p`,T,r(t(j)(`ds.cards.hoverBodyAlt`)),1)]),_:1})])]),s(`section`,null,[s(`h2`,E,r(t(j)(`ds.heading.withoutPadding`)),1),a(d,{tone:`paper`,padded:!1},{default:i(()=>[A[2]||=s(`div`,{class:`h-40 bg-cream rounded-t-md`},null,-1),s(`div`,D,[s(`h3`,O,r(t(j)(`ds.cards.mediaTitle`)),1),s(`p`,k,[o(r(t(j)(`ds.cards.mediaBody`))+` `,1),A[0]||=s(`code`,{class:`font-mono text-[12px]`},`:padded="false"`,-1),A[1]||=o(`. `,-1)])])]),_:1})])]),_:1},8,[`eyebrow`,`title`,`description`]))}};export{A as default};

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
import{B as e,C as t,G as n,S as r,c as i,ft as a,j as o,m as s,p as c,s as l}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as u}from"./Icon-Dcq1PyHo.js";import{t as d}from"./i18n-BE70RUdV.js";import{a as f,c as p,i as m,l as h,o as g,s as _}from"./api-BkifONga.js";import{t as v}from"./Button-DAQ-3-Uk.js";import{t as y}from"./CartDrawer-BOCuIGNH.js";import{t as b}from"./SectionShell-CU6IpKMJ.js";var x={class:`eyebrow mb-5`},S={class:`rounded-md border border-line bg-paper p-6 flex flex-col sm:flex-row sm:items-center gap-4`},C={class:`text-[13px] text-muted`},w={class:`eyebrow mb-5`},T={class:`text-[15px] text-muted leading-relaxed mb-4 max-w-2xl`},E={class:`eyebrow mb-5`},D={__name:`CartDrawerSection`,setup(D){let{t:O}=d(),k=h(),A=e(!1);async function j(){await f(),await m(`kaiser-natron-pulver-250-g-grosspackung`,2),await m(`kaiser-natron-bad-500-g`,1),A.value=!0}async function M({productId:e,quantity:t}){await p(e,t)}async function N(e){await _(e)}return r(()=>{g()}),(e,r)=>(t(),i(b,{eyebrow:n(O)(`ds.eyebrow.components`),title:n(O)(`ds.cartDrawer.title`),description:n(O)(`ds.cartDrawer.description`)},{default:o(()=>[l(`section`,null,[l(`h2`,x,a(n(O)(`ds.heading.default`)),1),l(`div`,S,[s(v,{variant:`primary`,onClick:j},{before:o(()=>[s(u,{name:`cart`,size:18})]),default:o(()=>[c(` `+a(n(O)(`ds.cartDrawer.demoLabel`)),1)]),_:1}),l(`p`,C,a(n(O)(`ds.cartDrawer.demoHint`)),1)])]),l(`section`,null,[l(`h2`,w,a(n(O)(`ds.cartDrawer.integrationTitle`)),1),l(`p`,T,a(n(O)(`ds.cartDrawer.integrationBody`)),1),r[2]||=l(`div`,{class:`rounded-md border border-line bg-paper p-6 font-mono text-[12px] text-ink`},[l(`pre`,{class:`whitespace-pre-wrap`},`import {
import{B as e,C as t,G as n,S as r,c as i,ft as a,j as o,m as s,p as c,s as l}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as u}from"./Icon-Dcq1PyHo.js";import{t as d}from"./i18n-DhVF3Pwg.js";import{c as f,d as p,f as m,l as h,p as g,u as _}from"./api-DlB23x7f.js";import{t as v}from"./Button-D1Rp2Xe1.js";import{t as y}from"./CartDrawer-f57jysS_.js";import{t as b}from"./SectionShell-DGDgzO-7.js";var x={class:`eyebrow mb-5`},S={class:`rounded-md border border-line bg-paper p-6 flex flex-col sm:flex-row sm:items-center gap-4`},C={class:`text-[13px] text-muted`},w={class:`eyebrow mb-5`},T={class:`text-[15px] text-muted leading-relaxed mb-4 max-w-2xl`},E={class:`eyebrow mb-5`},D={__name:`CartDrawerSection`,setup(D){let{t:O}=d(),k=g(),A=e(!1);async function j(){await h(),await f(`kaiser-natron-pulver-250-g-grosspackung`,2),await f(`kaiser-natron-bad-500-g`,1),A.value=!0}async function M({productId:e,quantity:t}){await m(e,t)}async function N(e){await p(e)}return r(()=>{_()}),(e,r)=>(t(),i(b,{eyebrow:n(O)(`ds.eyebrow.components`),title:n(O)(`ds.cartDrawer.title`),description:n(O)(`ds.cartDrawer.description`)},{default:o(()=>[l(`section`,null,[l(`h2`,x,a(n(O)(`ds.heading.default`)),1),l(`div`,S,[s(v,{variant:`primary`,onClick:j},{before:o(()=>[s(u,{name:`cart`,size:18})]),default:o(()=>[c(` `+a(n(O)(`ds.cartDrawer.demoLabel`)),1)]),_:1}),l(`p`,C,a(n(O)(`ds.cartDrawer.demoHint`)),1)])]),l(`section`,null,[l(`h2`,w,a(n(O)(`ds.cartDrawer.integrationTitle`)),1),l(`p`,T,a(n(O)(`ds.cartDrawer.integrationBody`)),1),r[2]||=l(`div`,{class:`rounded-md border border-line bg-paper p-6 font-mono text-[12px] text-ink`},[l(`pre`,{class:`whitespace-pre-wrap`},`import {
fetchCart,
addToCart,
updateCartItem,

1
dist/assets/CartStep-BoEUq9IW.js vendored Normal file
View File

@@ -0,0 +1 @@
import{C as e,G as t,T as n,ft as r,j as i,l as a,m as o,o as s,p as c,r as l,s as u,u as d}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{o as ee,t as te}from"./vue-router-Cyqru1db.js";import{t as f}from"./Icon-Dcq1PyHo.js";import{t as p}from"./i18n-DhVF3Pwg.js";import{n as m}from"./products-BqW5PUnm.js";import{d as ne,f as re,p as ie}from"./api-DlB23x7f.js";import{t as h}from"./Button-D1Rp2Xe1.js";import{t as ae}from"./QuantityStepper-DEp73rTJ.js";var g={key:0,class:`min-h-[55svh] flex items-center justify-center`},_={class:`flex flex-col gap-4 items-center text-center rounded-md border border-line bg-paper p-10 max-w-md w-full`},v={class:`inline-flex items-center justify-center w-14 h-14 rounded-full bg-cream text-brand`},y={class:`font-display text-xl text-brand`},b={class:`text-[14px] text-muted max-w-sm`},x={key:1,class:`flex flex-col gap-6`},S={class:`flex flex-col rounded-md border border-line bg-paper divide-y divide-line`},C={class:`shrink-0 w-20 h-20 rounded-sm bg-cream overflow-hidden flex items-center justify-center`},w=[`src`,`alt`],T={class:`flex-1 min-w-0`},E={class:`font-display text-base text-ink leading-tight`},D={key:0,class:`text-[12px] text-muted`},O={class:`mt-2 text-[13px] text-muted`},k={class:`flex items-center gap-4`},A={class:`text-[14px] font-semibold text-ink tabular-nums w-20 text-right`},j=[`aria-label`,`onClick`],M={class:`rounded-md border border-line bg-paper px-6 py-5 flex flex-col gap-2`},N={class:`flex items-baseline justify-between text-[14px]`},P={class:`text-muted`},F={class:`text-ink tabular-nums`},oe={class:`flex items-baseline justify-between text-[14px]`},I={class:`text-muted`},L={class:`text-ink tabular-nums`},R={class:`mt-1 pt-3 border-t border-line flex items-baseline justify-between`},z={class:`eyebrow`},B={class:`font-display text-2xl text-brand tabular-nums`},V={key:2,class:`fixed inset-x-0 bottom-0 z-30 bg-cream border-t border-line`,role:`contentinfo`},H={class:`mx-auto w-full max-w-7xl px-6 sm:px-8 md:px-12 lg:px-16 py-3 flex gap-3 sm:justify-between`,style:{paddingBottom:`max(0.75rem, env(safe-area-inset-bottom))`}},U={class:`flex-1 sm:flex-initial`},W={class:`flex-1 sm:flex-initial`},G=4.9,K={__name:`CartStep`,setup(K){let{t:q}=p(),J=ie(),Y=ee(),X=s(()=>J.isEmpty),Z=s(()=>J.subtotal),Q=s(()=>J.items.length?G:0),se=s(()=>+(Z.value+Q.value).toFixed(2));async function ce(e,t){await re(e,t)}async function le(e){await ne(e)}function $(){Y.push(`/checkout/account`)}function ue(){Y.push(`/`)}return(s,ee)=>(e(),d(l,null,[X.value?(e(),d(`div`,g,[u(`div`,_,[u(`span`,v,[o(f,{name:`cart`,size:24,"stroke-width":2})]),u(`p`,y,r(t(q)(`cart.empty.title`)),1),u(`p`,b,r(t(q)(`cart.empty.subtitle`)),1),o(t(te),{to:`/shop`,class:`inline-flex`},{default:i(()=>[o(h,{variant:`primary`,size:`md`},{default:i(()=>[c(r(t(q)(`checkout.cart.cta.shop`)),1)]),_:1})]),_:1})])])):(e(),d(`section`,x,[u(`ul`,S,[(e(!0),d(l,null,n(t(J).items,n=>(e(),d(`li`,{key:n.productId,class:`flex flex-col sm:flex-row sm:items-center gap-4 sm:gap-5 px-5 py-5 sm:px-6`},[u(`div`,C,[n.product?.image?(e(),d(`img`,{key:0,src:n.product.image,alt:n.product?.title||``,loading:`lazy`,decoding:`async`,class:`w-full h-full object-contain p-2`},null,8,w)):a(``,!0)]),u(`div`,T,[u(`p`,E,r(n.product?.title),1),n.product?.size?(e(),d(`p`,D,r(n.product.size),1)):a(``,!0),u(`p`,O,r(t(m)(n.unitPrice))+` `+r(t(q)(`checkout.cart.perItem`)),1)]),u(`div`,k,[o(ae,{"model-value":n.quantity,min:1,"onUpdate:modelValue":e=>ce(n.productId,e)},null,8,[`model-value`,`onUpdate:modelValue`]),u(`span`,A,r(t(m)(n.lineTotal)),1),u(`button`,{type:`button`,class:`text-muted hover:text-danger transition-colors duration-base`,"aria-label":t(q)(`cart.remove`),onClick:e=>le(n.productId)},[o(f,{name:`trash`,size:18,"stroke-width":2})],8,j)])]))),128))]),u(`dl`,M,[u(`div`,N,[u(`dt`,P,r(t(q)(`cart.subtotal`)),1),u(`dd`,F,r(t(m)(Z.value)),1)]),u(`div`,oe,[u(`dt`,I,r(t(q)(`checkout.cart.shipping`)),1),u(`dd`,L,r(t(m)(Q.value)),1)]),u(`div`,R,[u(`dt`,z,r(t(q)(`checkout.cart.total`)),1),u(`dd`,B,r(t(m)(se.value)),1)])])])),X.value?a(``,!0):(e(),d(`footer`,V,[u(`div`,H,[u(`div`,U,[o(h,{variant:`primary`,size:`lg`,block:``,type:`button`,onClick:ue},{default:i(()=>[c(r(t(q)(`checkout.exit`)),1)]),_:1})]),u(`div`,W,[o(h,{variant:`accent`,size:`lg`,block:``,onClick:$},{default:i(()=>[c(r(t(q)(`checkout.cart.cta.continue`)),1)]),_:1})])])]))],64))}};export{K as default};

1
dist/assets/CategoryPage-CcfzUZaJ.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/assets/CheckoutPage-BWQ6uexD.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
import{B as e,C as t,G as n,S as r,ft as i,j as a,l as o,m as s,o as c,p as l,r as u,s as d,u as f}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{a as p,t as m}from"./vue-router-Cyqru1db.js";import{t as h}from"./Icon-Dcq1PyHo.js";import{t as g}from"./i18n-BE70RUdV.js";import{r as _}from"./products-BqW5PUnm.js";import{t as v}from"./api-BkifONga.js";import{t as y}from"./Navbar-lf1WjUkF.js";import{t as b}from"./Footer-B0iLDEww.js";import{t as x}from"./Button-DAQ-3-Uk.js";var S={class:`bg-cream text-ink min-h-svh`},C={class:`mx-auto w-full max-w-3xl px-6 py-16 sm:px-8 sm:py-20 md:px-12 md:py-24 lg:px-16 lg:py-28`},w={class:`rounded-md border border-line bg-paper p-8 md:p-10 flex flex-col gap-6 items-center text-center`},T={class:`relative w-20 h-20`},E={class:`absolute inset-0 rounded-full bg-brand text-accent flex items-center justify-center`},D={class:`eyebrow`},O={class:`font-display font-normal leading-[1.05] tracking-tight text-ink text-headline-md`},k={class:`italic font-light text-brand`},A={class:`text-base leading-relaxed text-muted max-w-md`},j={key:0,class:`mt-2 inline-flex items-center gap-2 rounded-pill border border-cream-dark bg-cream px-4 py-2 text-[13px] text-ink`},M={class:`eyebrow text-muted`},N={class:`font-mono text-[12px] text-brand`},P={key:1,class:`text-[13px] text-danger mt-2`,role:`alert`,"aria-live":`polite`},F={class:`mt-4 flex flex-col sm:flex-row items-stretch sm:items-center gap-3`},I={key:2,class:`text-[12px] text-muted mt-2`},L={__name:`CheckoutSuccessPage`,setup(L){let{t:R}=g(),z=p(),B=[{key:`nav.shop`,href:`/shop`},{key:`nav.bundles`,href:`/#bundles`},{key:`nav.revitalization`,href:`/#revitalize`},{key:`nav.about`,href:`/#about`}],V=[],H=e(null),U=e(``),W=e(!0),G=c(()=>String(z.query.order||``));return r(async()=>{if(!G.value){U.value=R(`checkout.success.missing`),W.value=!1;return}try{H.value=await v({orderId:G.value})}catch(e){U.value=e?.message||R(`checkout.error.generic`)}finally{W.value=!1}}),(e,r)=>(t(),f(u,null,[s(y,{variant:`paper`,layout:`standard`,items:B,"secondary-items":V,products:n(_)},null,8,[`products`]),d(`main`,S,[d(`div`,C,[d(`article`,w,[d(`div`,T,[r[0]||=d(`span`,{"aria-hidden":`true`,class:`absolute inset-0 rounded-full bg-brand-wash`,style:{animation:`var(--animate-pulse-soft)`}},null,-1),d(`span`,E,[s(h,{name:`check`,size:36,"stroke-width":2.4})])]),d(`p`,D,i(n(R)(`checkout.success.eyebrow`)),1),d(`h1`,O,[l(i(n(R)(`checkout.success.headline`))+` `,1),d(`em`,k,i(n(R)(`checkout.success.headline.em`)),1)]),d(`p`,A,i(n(R)(`checkout.success.sub`)),1),G.value?(t(),f(`div`,j,[d(`span`,M,i(n(R)(`checkout.success.orderId`)),1),d(`span`,N,i(G.value),1)])):o(``,!0),U.value?(t(),f(`p`,P,i(U.value),1)):o(``,!0),d(`div`,F,[s(n(m),{to:`/shop`,class:`inline-flex`},{default:a(()=>[s(x,{variant:`primary`,size:`lg`},{default:a(()=>[l(i(n(R)(`checkout.success.cta.shop`)),1)]),_:1})]),_:1}),s(n(m),{to:`/`,class:`inline-flex`},{default:a(()=>[s(x,{variant:`secondary`,size:`lg`},{default:a(()=>[l(i(n(R)(`checkout.success.cta.home`)),1)]),_:1})]),_:1})]),!W.value&&H.value?(t(),f(`p`,I,i(n(R)(`checkout.success.email`)),1)):o(``,!0)])])]),s(b)],64))}};export{L as default};

View File

@@ -0,0 +1 @@
import{B as e,C as t,G as n,S as r,ft as i,j as a,l as o,m as s,o as c,p as l,s as u,u as d}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{a as f,t as p}from"./vue-router-Cyqru1db.js";import{t as m}from"./Icon-Dcq1PyHo.js";import{t as h}from"./i18n-DhVF3Pwg.js";import{a as g}from"./api-DlB23x7f.js";import{t as _}from"./Button-D1Rp2Xe1.js";var v={class:`min-h-svh flex flex-col bg-cream`},y={class:`text-ink flex-1 flex flex-col justify-center`},b={class:`mx-auto w-full max-w-3xl px-6 sm:px-8 md:px-12 lg:px-16 py-10 sm:py-14 md:py-16 lg:py-20`},x={class:`rounded-md border border-line bg-paper p-8 md:p-10 flex flex-col gap-6 items-center text-center`},S={class:`relative w-20 h-20`},C={class:`absolute inset-0 rounded-full bg-brand text-accent flex items-center justify-center`},w={class:`eyebrow`},T={class:`font-display font-normal leading-[1.05] tracking-tight text-ink text-headline-md`},E={class:`italic font-light text-brand`},D={class:`text-base leading-relaxed text-muted max-w-md`},O={key:0,class:`mt-2 inline-flex items-center gap-2 rounded-pill border border-cream-dark bg-cream px-4 py-2 text-[13px] text-ink`},k={class:`eyebrow text-muted`},A={class:`font-mono text-[12px] text-brand`},j={key:1,class:`text-[13px] text-danger mt-2`,role:`alert`,"aria-live":`polite`},M={class:`mt-4 flex flex-col gap-3 w-full max-w-sm`},N={key:2,class:`text-[12px] text-muted mt-2`},P={__name:`CheckoutSuccessPage`,setup(P){let{t:F}=h(),I=f(),L=e(null),R=e(``),z=e(!0),B=c(()=>String(I.query.order||``));return r(async()=>{if(!B.value){R.value=F(`checkout.success.missing`),z.value=!1;return}try{L.value=await g({orderId:B.value})}catch(e){R.value=e?.message||F(`checkout.error.generic`)}finally{z.value=!1}}),(e,r)=>(t(),d(`div`,v,[u(`main`,y,[u(`div`,b,[u(`article`,x,[u(`div`,S,[r[0]||=u(`span`,{"aria-hidden":`true`,class:`absolute inset-0 rounded-full bg-brand-wash`,style:{animation:`var(--animate-pulse-soft)`}},null,-1),u(`span`,C,[s(m,{name:`check`,size:36,"stroke-width":2.4})])]),u(`p`,w,i(n(F)(`checkout.success.eyebrow`)),1),u(`h1`,T,[l(i(n(F)(`checkout.success.headline`))+` `,1),u(`em`,E,i(n(F)(`checkout.success.headline.em`)),1)]),u(`p`,D,i(n(F)(`checkout.success.sub`)),1),B.value?(t(),d(`div`,O,[u(`span`,k,i(n(F)(`checkout.success.orderId`)),1),u(`span`,A,i(B.value),1)])):o(``,!0),R.value?(t(),d(`p`,j,i(R.value),1)):o(``,!0),u(`div`,M,[s(n(p),{to:`/`,class:`block`},{default:a(()=>[s(_,{variant:`primary`,size:`lg`,class:`w-full`},{default:a(()=>[l(i(n(F)(`checkout.success.cta.home`)),1)]),_:1})]),_:1}),s(n(p),{to:`/shop`,class:`block`},{default:a(()=>[s(_,{variant:`secondary`,size:`lg`,class:`w-full`},{default:a(()=>[l(i(n(F)(`checkout.success.cta.shop`)),1)]),_:1})]),_:1})]),!z.value&&L.value?(t(),d(`p`,N,i(n(F)(`checkout.success.email`)),1)):o(``,!0)])])])]))}};export{P as default};

View File

@@ -1 +1 @@
import{C as e,G as t,T as n,c as r,dt as i,ft as a,j as o,o as s,r as c,s as l,u}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as d}from"./i18n-BE70RUdV.js";import{t as f}from"./SectionShell-CU6IpKMJ.js";var p={class:`eyebrow mb-5`},m={class:`grid grid-cols-2 sm:grid-cols-3 gap-4`},h={class:`px-4 py-3`},g={class:`font-mono text-[12px] text-ink block`},_={__name:`ColorsSection`,setup(_){let{t:v}=d(),y=s(()=>[{title:v(`ds.colors.group.brand`),names:[`brand`,`brand-hover`,`brand-soft`]},{title:v(`ds.colors.group.accent`),names:[`accent`,`accent-soft`,`accent-ink`]},{title:v(`ds.colors.group.surface`),names:[`surface`,`paper`,`cream`]},{title:v(`ds.colors.group.ink`),names:[`ink`,`muted`]},{title:v(`ds.colors.group.line`),names:[`line`,`line-strong`]},{title:v(`ds.colors.group.semantic`),names:[`success`,`warning`,`danger`]}]);return(s,d)=>(e(),r(f,{eyebrow:t(v)(`ds.eyebrow.tokens`),title:t(v)(`ds.colors.title`),description:t(v)(`ds.colors.description`)},{default:o(()=>[(e(!0),u(c,null,n(y.value,t=>(e(),u(`section`,{key:t.title},[l(`h2`,p,a(t.title),1),l(`div`,m,[(e(!0),u(c,null,n(t.names,t=>(e(),u(`div`,{key:t,class:`rounded-md border border-line overflow-hidden bg-paper`},[l(`div`,{class:`h-28 border-b border-line`,style:i({background:`var(--color-${t})`})},null,4),l(`div`,h,[l(`code`,g,`--color-`+a(t),1)])]))),128))])]))),128))]),_:1},8,[`eyebrow`,`title`,`description`]))}};export{_ as default};
import{C as e,G as t,T as n,c as r,dt as i,ft as a,j as o,o as s,r as c,s as l,u}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as d}from"./i18n-DhVF3Pwg.js";import{t as f}from"./SectionShell-DGDgzO-7.js";var p={class:`eyebrow mb-5`},m={class:`grid grid-cols-2 sm:grid-cols-3 gap-4`},h={class:`px-4 py-3`},g={class:`font-mono text-[12px] text-ink block`},_={__name:`ColorsSection`,setup(_){let{t:v}=d(),y=s(()=>[{title:v(`ds.colors.group.brand`),names:[`brand`,`brand-hover`,`brand-soft`]},{title:v(`ds.colors.group.accent`),names:[`accent`,`accent-soft`,`accent-ink`]},{title:v(`ds.colors.group.surface`),names:[`surface`,`paper`,`cream`]},{title:v(`ds.colors.group.ink`),names:[`ink`,`muted`]},{title:v(`ds.colors.group.line`),names:[`line`,`line-strong`]},{title:v(`ds.colors.group.semantic`),names:[`success`,`warning`,`danger`]}]);return(s,d)=>(e(),r(f,{eyebrow:t(v)(`ds.eyebrow.tokens`),title:t(v)(`ds.colors.title`),description:t(v)(`ds.colors.description`)},{default:o(()=>[(e(!0),u(c,null,n(y.value,t=>(e(),u(`section`,{key:t.title},[l(`h2`,p,a(t.title),1),l(`div`,m,[(e(!0),u(c,null,n(t.names,t=>(e(),u(`div`,{key:t,class:`rounded-md border border-line overflow-hidden bg-paper`},[l(`div`,{class:`h-28 border-b border-line`,style:i({background:`var(--color-${t})`})},null,4),l(`div`,h,[l(`code`,g,`--color-`+a(t),1)])]))),128))])]))),128))]),_:1},8,[`eyebrow`,`title`,`description`]))}};export{_ as default};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
import{A as e,B as t,C as n,G as r,S as i,f as a,ft as o,l as s,o as c,s as l,u,ut as d,x as f}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as p}from"./preload-helper-DzyYoeor.js";import{t as m}from"./i18n-DhVF3Pwg.js";var h={class:`flex flex-col gap-3`},g={key:1,class:`grid gap-3 sm:grid-cols-2`},_=[`disabled`,`aria-label`],v=[`disabled`,`aria-label`],y={key:2,class:`text-[13px] text-danger`},b={__name:`ExpressCheckoutMount`,props:{intent:{type:Object,default:null},returnUrl:{type:String,required:!0},busy:{type:Boolean,default:!1}},emits:[`pay`,`error`],setup(b,{expose:x,emit:S}){let{t:C}=m(),w=b,T=S,E=t(null),D=t(null),O=t(!1),k=t(``),A=c(()=>/^pi_stub_express_/.test(w.intent?.clientSecret||``));function j(e){w.busy||w.intent||T(`pay`,{wallet:e})}async function M(){if(!(!w.intent||typeof window>`u`)){if(A.value){O.value=!0;return}try{let e=[`@stripe`,`stripe-js`].join(`/`),{loadStripe:t}=await p(()=>import(e),[]),n=await t(w.intent.publishableKey);if(!n)throw Error(`Stripe.js failed to initialise.`);E.value=n;let r=n.elements({clientSecret:w.intent.clientSecret,appearance:{theme:`flat`,variables:{colorPrimary:`#006548`,colorBackground:`#ffffff`,colorText:`#0f3825`,fontFamily:`"DM Sans", ui-sans-serif, system-ui, sans-serif`,borderRadius:`10px`,spacingUnit:`4px`}}});D.value=r;let i=r.create(`expressCheckout`,{buttonType:{applePay:`buy`,googlePay:`buy`},buttonTheme:{applePay:`black`,googlePay:`black`},buttonHeight:48});i.mount(`#express-checkout-element`),i.on(`ready`,()=>{O.value=!0}),i.on(`loaderror`,e=>{k.value=e?.error?.message||`Failed to load wallet buttons.`}),i.on(`confirm`,async()=>{let e=await n.confirmPayment({elements:r,clientSecret:w.intent.clientSecret,confirmParams:{return_url:w.returnUrl}});e?.error&&T(`error`,e.error)})}catch(e){k.value=e?.message||String(e)}}}async function N(){return w.intent?A.value?(await new Promise(e=>setTimeout(e,600)),{paymentIntent:{id:(w.intent.clientSecret||``).split(`_secret_`)[0]||`pi_stub`,status:`succeeded`}}):{error:{message:`Express confirm is driven by Stripe.`}}:{error:{message:`No intent.`}}}return x({confirm:N,ready:O}),i(()=>{w.intent&&M()}),e(()=>w.intent?.clientSecret,e=>{e&&M()}),f(()=>{E.value=null,D.value=null}),(e,t)=>(n(),u(`div`,h,[b.intent&&!A.value?(n(),u(`div`,{key:0,id:`express-checkout-element`,class:d([`min-h-[52px]`,{"opacity-60":!O.value}]),"aria-live":`polite`},null,2)):(n(),u(`div`,g,[l(`button`,{type:`button`,class:`inline-flex items-center justify-center gap-2 rounded-sm bg-ink h-12 px-5 text-paper transition-colors duration-base hover:bg-brand disabled:opacity-50 disabled:cursor-not-allowed`,disabled:b.busy,"aria-label":r(C)(`checkout.express.applePay.aria`),onClick:t[0]||=e=>j(`apple`)},[...t[2]||=[l(`svg`,{width:`18`,height:`22`,viewBox:`0 0 18 22`,fill:`currentColor`,"aria-hidden":`true`},[l(`path`,{d:`M14.94 11.36c-.02-2.5 2.04-3.7 2.13-3.76-1.16-1.7-2.97-1.94-3.61-1.96-1.54-.16-3 .9-3.78.9-.78 0-1.98-.88-3.26-.86-1.68.02-3.23.97-4.1 2.47-1.74 3.02-.45 7.5 1.25 9.95.83 1.2 1.82 2.55 3.12 2.5 1.25-.05 1.72-.81 3.23-.81 1.51 0 1.93.81 3.25.78 1.34-.02 2.2-1.22 3.02-2.43.95-1.4 1.34-2.75 1.36-2.82-.03-.01-2.61-1-2.61-3.96zM12.6 4.05c.7-.85 1.16-2.02 1.04-3.18-1 .04-2.21.66-2.93 1.5-.65.74-1.21 1.93-1.06 3.07 1.11.09 2.25-.56 2.95-1.39z`})],-1),l(`span`,{class:`text-[14px] font-medium tracking-tight`},`Pay`,-1)]],8,_),l(`button`,{type:`button`,class:`inline-flex items-center justify-center gap-2 rounded-sm bg-ink h-12 px-5 text-paper transition-colors duration-base hover:bg-brand disabled:opacity-50 disabled:cursor-not-allowed`,disabled:b.busy,"aria-label":r(C)(`checkout.express.googlePay.aria`),onClick:t[1]||=e=>j(`google`)},[...t[3]||=[a(`<svg width="20" height="20" viewBox="0 0 20 20" aria-hidden="true"><path d="M19.6 10.23c0-.68-.06-1.36-.18-2.03H10v3.84h5.4a4.62 4.62 0 0 1-2 3.03v2.5h3.22c1.89-1.74 2.98-4.31 2.98-7.34z" fill="#4285F4"></path><path d="M10 20c2.7 0 4.97-.89 6.62-2.42l-3.22-2.5c-.9.6-2.04.96-3.4.96-2.6 0-4.81-1.76-5.6-4.13H1.07v2.59A10 10 0 0 0 10 20z" fill="#34A853"></path><path d="M4.4 11.91a6 6 0 0 1 0-3.83V5.5H1.07a10 10 0 0 0 0 9l3.33-2.59z" fill="#FBBC04"></path><path d="M10 3.96a5.4 5.4 0 0 1 3.83 1.5l2.85-2.85A9.6 9.6 0 0 0 10 0 10 10 0 0 0 1.07 5.5L4.4 8.08C5.19 5.71 7.4 3.96 10 3.96z" fill="#EA4335"></path></svg><span class="text-[14px] font-medium tracking-tight">Pay</span>`,2)]],8,v)])),k.value?(n(),u(`p`,y,o(k.value),1)):s(``,!0)]))}};export{b as t};

View File

@@ -1 +0,0 @@
import{C as e,G as t,T as n,ft as r,m as i,o as a,r as o,s,u as c,ut as l}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as u}from"./Logo-xADhl1RY.js";import{t as d}from"./i18n-BE70RUdV.js";var f={class:`mx-auto w-full max-w-6xl px-6 py-12 sm:px-8 sm:py-14 md:px-12 md:py-16 lg:px-16`},p={class:`grid gap-10 md:grid-cols-[1.4fr_1fr_1fr] md:gap-12`},m={class:`flex flex-col gap-4 max-w-sm`},h=[`aria-label`],g={class:`flex flex-col gap-2`},_=[`href`],v=[`aria-label`],y={class:`flex flex-col gap-2`},b=[`href`],x={__name:`Footer`,props:{variant:{type:String,default:`cream`,validator:e=>[`cream`,`brand`,`paper`].includes(e)}},setup(x){let S=x,{t:C}=d(),w=a(()=>S.variant===`brand`?{surface:`bg-brand text-cream`,topRule:`border-cream-line`,bottomRule:`border-cream-line/40`,muted:`text-cream/80`,logo:`text-cream`,link:`hover:text-accent`}:S.variant===`paper`?{surface:`bg-paper text-brand`,topRule:`border-line`,bottomRule:`border-line/60`,muted:`text-muted`,logo:`text-brand`,link:`hover:text-brand-hover`}:{surface:`bg-cream text-brand`,topRule:`border-line`,bottomRule:`border-line/60`,muted:`text-muted`,logo:`text-brand`,link:`hover:text-brand-hover`}),T=[{key:`footer.legal.impressum`,href:`/impressum`},{key:`footer.legal.datenschutz`,href:`/datenschutz`}],E=[{key:`footer.explore.shop`,href:`/shop`},{key:`footer.explore.bundles`,href:`/#bundles`},{key:`footer.explore.about`,href:`/#about`}],D=new Date().getFullYear();return(a,d)=>(e(),c(`footer`,{class:l([`border-t`,w.value.surface,w.value.topRule])},[s(`div`,f,[s(`div`,p,[s(`div`,m,[i(u,{class:l(w.value.logo)},null,8,[`class`]),s(`p`,{class:l([`text-sm leading-relaxed`,w.value.muted])},r(t(C)(`footer.tagline`)),3)]),s(`nav`,{"aria-label":t(C)(`footer.explore.heading`),class:`flex flex-col gap-3`},[s(`p`,{class:l([`eyebrow`,w.value.muted])},r(t(C)(`footer.explore.heading`)),3),s(`ul`,g,[(e(),c(o,null,n(E,e=>s(`li`,{key:e.href},[s(`a`,{href:e.href,class:l([`text-sm underline-offset-4 decoration-1 hover:underline`,w.value.link])},r(t(C)(e.key)),11,_)])),64))])],8,h),s(`nav`,{"aria-label":t(C)(`footer.legal.heading`),class:`flex flex-col gap-3`},[s(`p`,{class:l([`eyebrow`,w.value.muted])},r(t(C)(`footer.legal.heading`)),3),s(`ul`,y,[(e(),c(o,null,n(T,e=>s(`li`,{key:e.href},[s(`a`,{href:e.href,class:l([`text-sm underline-offset-4 decoration-1 hover:underline`,w.value.link])},r(t(C)(e.key)),11,b)])),64))])],8,v)]),s(`div`,{class:l([`mt-12 pt-6 border-t flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3`,w.value.bottomRule])},[s(`p`,{class:l([`text-xs`,w.value.muted])},` © `+r(t(D))+` `+r(t(C)(`footer.copyright`)),3),s(`p`,{class:l([`text-xs`,w.value.muted])},r(t(C)(`footer.madeIn`)),3)],2)])],2))}};export{x as t};

1
dist/assets/Footer-DX-ml2dd.js vendored Normal file
View File

@@ -0,0 +1 @@
import{C as e,G as t,T as n,ft as r,m as i,o as a,r as o,s,u as c,ut as l}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as u}from"./Logo-xADhl1RY.js";import{t as d}from"./i18n-DhVF3Pwg.js";var f={class:`mx-auto w-full max-w-6xl px-6 py-12 sm:px-8 sm:py-14 md:px-12 md:py-16 lg:px-16`},p={class:`grid gap-10 md:grid-cols-[1.4fr_1fr_1fr] md:gap-12`},m={class:`flex flex-col gap-4 max-w-sm`},h=[`aria-label`],g={class:`flex flex-col gap-2`},_=[`href`],v=[`aria-label`],y={class:`flex flex-col gap-2`},b=[`href`],x={__name:`Footer`,props:{variant:{type:String,default:`cream`,validator:e=>[`cream`,`brand`,`paper`].includes(e)}},setup(x){let S=x,{t:C}=d(),w=a(()=>S.variant===`brand`?{surface:`bg-brand text-cream`,topRule:`border-cream-line`,bottomRule:`border-cream-line/40`,muted:`text-cream/80`,logo:`text-cream`,link:`hover:text-accent`}:S.variant===`paper`?{surface:`bg-paper text-brand`,topRule:`border-line`,bottomRule:`border-line/60`,muted:`text-muted`,logo:`text-brand`,link:`hover:text-brand-hover`}:{surface:`bg-cream text-brand`,topRule:`border-line`,bottomRule:`border-line/60`,muted:`text-muted`,logo:`text-brand`,link:`hover:text-brand-hover`}),T=[{key:`footer.legal.impressum`,href:`/impressum`},{key:`footer.legal.datenschutz`,href:`/datenschutz`}],E=[{key:`footer.explore.shop`,href:`/shop`},{key:`footer.explore.pflege`,href:`/pflege`},{key:`footer.explore.haushalt`,href:`/haushalt`},{key:`footer.explore.bundles`,href:`/#bundles`},{key:`footer.explore.about`,href:`/#about`}],D=new Date().getFullYear();return(a,d)=>(e(),c(`footer`,{class:l([`border-t`,w.value.surface,w.value.topRule])},[s(`div`,f,[s(`div`,p,[s(`div`,m,[i(u,{class:l([`w-20 md:w-24 h-auto`,w.value.logo])},null,8,[`class`]),s(`p`,{class:l([`text-sm leading-relaxed`,w.value.muted])},r(t(C)(`footer.tagline`)),3)]),s(`nav`,{"aria-label":t(C)(`footer.explore.heading`),class:`flex flex-col gap-3`},[s(`p`,{class:l([`eyebrow`,w.value.muted])},r(t(C)(`footer.explore.heading`)),3),s(`ul`,g,[(e(),c(o,null,n(E,e=>s(`li`,{key:e.href},[s(`a`,{href:e.href,class:l([`text-sm underline-offset-4 decoration-1 hover:underline`,w.value.link])},r(t(C)(e.key)),11,_)])),64))])],8,h),s(`nav`,{"aria-label":t(C)(`footer.legal.heading`),class:`flex flex-col gap-3`},[s(`p`,{class:l([`eyebrow`,w.value.muted])},r(t(C)(`footer.legal.heading`)),3),s(`ul`,y,[(e(),c(o,null,n(T,e=>s(`li`,{key:e.href},[s(`a`,{href:e.href,class:l([`text-sm underline-offset-4 decoration-1 hover:underline`,w.value.link])},r(t(C)(e.key)),11,b)])),64))])],8,v)]),s(`div`,{class:l([`mt-12 pt-6 border-t flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3`,w.value.bottomRule])},[s(`p`,{class:l([`text-xs`,w.value.muted])},` © `+r(t(D))+` `+r(t(C)(`footer.copyright`)),3),s(`p`,{class:l([`text-xs`,w.value.muted])},r(t(C)(`footer.madeIn`)),3)],2)])],2))}};export{x as t};

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
import{C as e,G as t,ft as n,j as r,m as i,o as a,p as o,r as s,s as c,u as l,ut as u}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{a as d}from"./vue-router-Cyqru1db.js";import{t as f}from"./i18n-BE70RUdV.js";import{t as p}from"./Navbar-lf1WjUkF.js";import{t as m}from"./Hero-DlqjknYE.js";var h={class:`italic font-light text-brand-soft`},g={class:`italic font-light text-brand-soft`},_=`/products/kaiser-natron-pulver-250-g-grosspackung.webp`,v=`/products/kaiser-natron-bad-500-g.webp`,y={__name:`HeroPreview`,setup(y){let b=d(),{t:x}=f(),S=a(()=>[`split`,`centered`].includes(b.query.variant)?b.query.variant:`split`),C=a(()=>[`cream`,`paper`,`brand`].includes(b.query.tone)?b.query.tone:`cream`),w=a(()=>b.query.reverse===`1`),T=a(()=>C.value===`brand`?`brand`:C.value),E=a(()=>w.value?v:_),D=a(()=>w.value?`Kaiser-Natron Bad 500 g`:`Kaiser-Natron Pulver 250 g Großpackung`);return(a,d)=>(e(),l(`div`,{class:u([`min-h-screen`,C.value===`brand`?`bg-brand`:`bg-surface`])},[i(p,{variant:T.value,"cart-count":0},null,8,[`variant`]),i(m,{variant:S.value,tone:C.value,reverse:w.value,eyebrow:w.value?t(x)(`home.banner.eyebrow`):t(x)(`ds.hero.eyebrow`),subheadline:w.value?t(x)(`home.banner.sub`):t(x)(`ds.hero.sub`),image:E.value,"image-alt":D.value,badge:w.value?``:t(x)(`ds.badges.featured`),"cta-label":t(x)(`ds.buttons.addToCart`),"secondary-label":t(x)(`ds.buttons.learnMore`)},{headline:r(()=>[w.value?(e(),l(s,{key:0},[o(n(t(x)(`home.banner.headline.a`))+` `,1),c(`em`,h,n(t(x)(`home.banner.headline.em`)),1),o(` `+n(t(x)(`home.banner.headline.b`)),1)],64)):(e(),l(s,{key:1},[o(n(t(x)(`ds.hero.headline.a`))+` `,1),c(`em`,g,n(t(x)(`ds.hero.headline.em`)),1),o(` `+n(t(x)(`ds.hero.headline.b`)),1)],64))]),_:1},8,[`variant`,`tone`,`reverse`,`eyebrow`,`subheadline`,`image`,`image-alt`,`badge`,`cta-label`,`secondary-label`])],2))}};export{y as default};
import{C as e,G as t,ft as n,j as r,m as i,o as a,p as o,r as s,s as c,u as l,ut as u}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{a as d}from"./vue-router-Cyqru1db.js";import{t as f}from"./i18n-DhVF3Pwg.js";import{t as p}from"./Navbar-dl8ShEaS.js";import{t as m}from"./Hero-Bag8c3o2.js";var h={class:`italic font-light text-brand-soft`},g={class:`italic font-light text-brand-soft`},_=`/products/kaiser-natron-pulver-250-g-grosspackung.webp`,v=`/products/kaiser-natron-bad-500-g.webp`,y={__name:`HeroPreview`,setup(y){let b=d(),{t:x}=f(),S=a(()=>[`split`,`centered`].includes(b.query.variant)?b.query.variant:`split`),C=a(()=>[`cream`,`paper`,`brand`].includes(b.query.tone)?b.query.tone:`cream`),w=a(()=>b.query.reverse===`1`),T=a(()=>C.value===`brand`?`brand`:C.value),E=a(()=>w.value?v:_),D=a(()=>w.value?`Kaiser-Natron Bad 500 g`:`Kaiser-Natron Pulver 250 g Großpackung`);return(a,d)=>(e(),l(`div`,{class:u([`min-h-screen`,C.value===`brand`?`bg-brand`:`bg-surface`])},[i(p,{variant:T.value,"cart-count":0},null,8,[`variant`]),i(m,{variant:S.value,tone:C.value,reverse:w.value,eyebrow:w.value?t(x)(`home.banner.eyebrow`):t(x)(`ds.hero.eyebrow`),subheadline:w.value?t(x)(`home.banner.sub`):t(x)(`ds.hero.sub`),image:E.value,"image-alt":D.value,badge:w.value?``:t(x)(`ds.badges.featured`),"cta-label":t(x)(`ds.buttons.addToCart`),"secondary-label":t(x)(`ds.buttons.learnMore`)},{headline:r(()=>[w.value?(e(),l(s,{key:0},[o(n(t(x)(`home.banner.headline.a`))+` `,1),c(`em`,h,n(t(x)(`home.banner.headline.em`)),1),o(` `+n(t(x)(`home.banner.headline.b`)),1)],64)):(e(),l(s,{key:1},[o(n(t(x)(`ds.hero.headline.a`))+` `,1),c(`em`,g,n(t(x)(`ds.hero.headline.em`)),1),o(` `+n(t(x)(`ds.hero.headline.b`)),1)],64))]),_:1},8,[`variant`,`tone`,`reverse`,`eyebrow`,`subheadline`,`image`,`image-alt`,`badge`,`cta-label`,`secondary-label`])],2))}};export{y as default};

View File

@@ -1,4 +1,4 @@
import{B as e,C as t,G as n,T as r,c as i,dt as a,ft as o,j as s,m as c,o as l,p as u,r as d,s as f,u as p,ut as m}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as h}from"./i18n-BE70RUdV.js";import{t as g}from"./SectionShell-CU6IpKMJ.js";import{t as _}from"./DevicePreview-9pDgIJdz.js";var v=[`aria-label`],y=[`aria-selected`,`onClick`],b=[`aria-label`],x=[`aria-selected`,`onClick`],S={class:`eyebrow mb-5`},C={__name:`HeroSection`,setup(C){let{t:w}=h(),T=l(()=>[{id:`split`,label:w(`ds.hero.variant.split`)},{id:`centered`,label:w(`ds.hero.variant.centered`)}]),E=l(()=>[{id:`cream`,label:w(`ds.navbar.tone.cream`),swatch:`var(--color-cream)`},{id:`paper`,label:w(`ds.navbar.tone.paper`),swatch:`#ffffff`},{id:`brand`,label:w(`ds.navbar.tone.brand`),swatch:`var(--color-brand)`}]),D=e(`split`),O=e(`cream`),k=l(()=>`/design/preview/hero?variant=${D.value}&tone=${O.value}`);return(e,l)=>(t(),i(g,{eyebrow:n(w)(`ds.eyebrow.components`),title:n(w)(`ds.hero.title`),description:n(w)(`ds.hero.description`),wide:``},{default:s(()=>[f(`section`,null,[c(_,{src:k.value,initial:`desktop`,height:760},{controls:s(()=>[f(`div`,{role:`tablist`,"aria-label":n(w)(`ds.hero.variant.label`),class:`inline-flex items-center p-1 gap-0.5 rounded-pill border border-line bg-paper`},[(t(!0),p(d,null,r(T.value,e=>(t(),p(`button`,{key:e.id,type:`button`,role:`tab`,"aria-selected":D.value===e.id,class:m([`px-3 py-1.5 text-[12px] font-semibold tracking-label rounded-pill transition-colors duration-base`,D.value===e.id?`bg-brand text-accent`:`text-muted hover:text-brand`]),onClick:t=>D.value=e.id},o(e.label),11,y))),128))],8,v),f(`div`,{role:`tablist`,"aria-label":n(w)(`ds.navbar.tone`),class:`inline-flex items-center p-1 gap-0.5 rounded-pill border border-line bg-paper`},[(t(!0),p(d,null,r(E.value,e=>(t(),p(`button`,{key:e.id,type:`button`,role:`tab`,"aria-selected":O.value===e.id,class:m([`inline-flex items-center gap-2 px-3 py-1.5 text-[12px] font-semibold tracking-label rounded-pill transition-colors duration-base`,O.value===e.id?`bg-brand text-accent`:`text-muted hover:text-brand`]),onClick:t=>O.value=e.id},[f(`span`,{class:`w-2.5 h-2.5 rounded-full border border-line-strong`,style:a({backgroundColor:e.swatch})},null,4),u(` `+o(e.label),1)],10,x))),128))],8,b)]),_:1},8,[`src`])]),f(`section`,null,[f(`h2`,S,o(n(w)(`ds.heading.usage`)),1),l[0]||=f(`div`,{class:`rounded-md border border-line bg-paper p-6 font-mono text-[12px] text-ink`},[f(`pre`,{class:`whitespace-pre-wrap`},`<Hero
import{B as e,C as t,G as n,T as r,c as i,dt as a,ft as o,j as s,m as c,o as l,p as u,r as d,s as f,u as p,ut as m}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as h}from"./i18n-DhVF3Pwg.js";import{t as g}from"./SectionShell-DGDgzO-7.js";import{t as _}from"./DevicePreview-C43cRj6M.js";var v=[`aria-label`],y=[`aria-selected`,`onClick`],b=[`aria-label`],x=[`aria-selected`,`onClick`],S={class:`eyebrow mb-5`},C={__name:`HeroSection`,setup(C){let{t:w}=h(),T=l(()=>[{id:`split`,label:w(`ds.hero.variant.split`)},{id:`centered`,label:w(`ds.hero.variant.centered`)}]),E=l(()=>[{id:`cream`,label:w(`ds.navbar.tone.cream`),swatch:`var(--color-cream)`},{id:`paper`,label:w(`ds.navbar.tone.paper`),swatch:`#ffffff`},{id:`brand`,label:w(`ds.navbar.tone.brand`),swatch:`var(--color-brand)`}]),D=e(`split`),O=e(`cream`),k=l(()=>`/design/preview/hero?variant=${D.value}&tone=${O.value}`);return(e,l)=>(t(),i(g,{eyebrow:n(w)(`ds.eyebrow.components`),title:n(w)(`ds.hero.title`),description:n(w)(`ds.hero.description`),wide:``},{default:s(()=>[f(`section`,null,[c(_,{src:k.value,initial:`desktop`,height:760},{controls:s(()=>[f(`div`,{role:`tablist`,"aria-label":n(w)(`ds.hero.variant.label`),class:`inline-flex items-center p-1 gap-0.5 rounded-pill border border-line bg-paper`},[(t(!0),p(d,null,r(T.value,e=>(t(),p(`button`,{key:e.id,type:`button`,role:`tab`,"aria-selected":D.value===e.id,class:m([`px-3 py-1.5 text-[12px] font-semibold tracking-label rounded-pill transition-colors duration-base`,D.value===e.id?`bg-brand text-accent`:`text-muted hover:text-brand`]),onClick:t=>D.value=e.id},o(e.label),11,y))),128))],8,v),f(`div`,{role:`tablist`,"aria-label":n(w)(`ds.navbar.tone`),class:`inline-flex items-center p-1 gap-0.5 rounded-pill border border-line bg-paper`},[(t(!0),p(d,null,r(E.value,e=>(t(),p(`button`,{key:e.id,type:`button`,role:`tab`,"aria-selected":O.value===e.id,class:m([`inline-flex items-center gap-2 px-3 py-1.5 text-[12px] font-semibold tracking-label rounded-pill transition-colors duration-base`,O.value===e.id?`bg-brand text-accent`:`text-muted hover:text-brand`]),onClick:t=>O.value=e.id},[f(`span`,{class:`w-2.5 h-2.5 rounded-full border border-line-strong`,style:a({backgroundColor:e.swatch})},null,4),u(` `+o(e.label),1)],10,x))),128))],8,b)]),_:1},8,[`src`])]),f(`section`,null,[f(`h2`,S,o(n(w)(`ds.heading.usage`)),1),l[0]||=f(`div`,{class:`rounded-md border border-line bg-paper p-6 font-mono text-[12px] text-ink`},[f(`pre`,{class:`whitespace-pre-wrap`},`<Hero
variant="split"
tone="cream"
eyebrow="Neu"

1
dist/assets/HomePage-DJzxxin6.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +1,2 @@
import{B as e,C as t,G as n,M as r,T as i,c as a,ft as o,j as s,l as c,m as l,o as u,r as d,s as f,u as p}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{a as m}from"./runtime-dom.esm-bundler-Bg1uJ-W7.js";import{n as h,t as g}from"./Icon-Dcq1PyHo.js";import{t as _}from"./i18n-BE70RUdV.js";import{t as v}from"./SectionShell-CU6IpKMJ.js";var y={class:`mb-5 flex items-center gap-3 flex-wrap`},b={class:`relative inline-flex items-center`},x={class:`absolute left-3 text-muted pointer-events-none`},S=[`placeholder`],C={class:`text-[12px] text-muted`},w={key:0,class:`text-[14px] text-muted`},T={class:`eyebrow mb-4`},E={class:`grid grid-cols-[repeat(auto-fill,minmax(120px,1fr))] gap-3`},D=[`onClick`],O={class:`w-10 h-10 rounded-sm bg-cream text-brand flex items-center justify-center`},k={class:`font-mono text-[11px] text-muted group-hover:text-brand transition-colors`},A={class:`eyebrow mb-5`},j={class:`grid md:grid-cols-3 gap-4`},M={class:`rounded-md border border-line bg-paper text-brand p-8 flex items-center justify-center gap-6`},N={class:`rounded-md border border-line bg-cream text-brand p-8 flex items-center justify-center gap-6`},P={class:`rounded-md bg-brand text-accent p-8 flex items-center justify-center gap-6`},F={class:`eyebrow mb-5`},I={class:`rounded-md border border-line bg-paper p-8 flex items-end gap-8 text-brand`},L={class:`flex flex-col items-center gap-3`},R={class:`flex flex-col items-center gap-3`},z={class:`font-mono text-[11px] text-muted`},B={class:`flex flex-col items-center gap-3`},V={class:`flex flex-col items-center gap-3`},H={class:`flex flex-col items-center gap-3`},U={class:`eyebrow mb-5`},W={__name:`IconsSection`,setup(W){let{t:G}=_(),K=u(()=>[{title:G(`ds.icons.group.commerce`),names:[`cart`,`bag`,`heart`,`user`,`search`]},{title:G(`ds.icons.group.navigation`),names:[`menu`,`close`,`chevron-left`,`chevron-right`,`chevron-down`,`chevron-up`,`arrow-left`,`arrow-right`]},{title:G(`ds.icons.group.actions`),names:[`plus`,`minus`,`check`]},{title:G(`ds.icons.group.contact`),names:[`mail`,`phone`,`map-pin`,`external-link`]},{title:G(`ds.icons.group.feedback`),names:[`info`,`star`]},{title:G(`ds.icons.group.devices`),names:[`mobile`,`tablet`,`desktop`]}]),q=e(``),J=u(()=>{let e=q.value.trim().toLowerCase();return e?K.value.map(t=>({...t,names:t.names.filter(t=>t.includes(e)||(h[t]?.label||``).toLowerCase().includes(e))})).filter(e=>e.names.length>0):K.value}),Y=e(``);async function X(e){let t=`<Icon name="${e}" />`;try{await navigator.clipboard.writeText(t),Y.value=e,setTimeout(()=>{Y.value===e&&(Y.value=``)},1200)}catch{}}return(e,u)=>(t(),a(v,{eyebrow:n(G)(`ds.eyebrow.components`),title:n(G)(`ds.icons.title`),description:n(G)(`ds.icons.description`)},{default:s(()=>[f(`section`,null,[f(`div`,y,[f(`label`,b,[f(`span`,x,[l(g,{name:`search`,size:16})]),r(f(`input`,{"onUpdate:modelValue":u[0]||=e=>q.value=e,type:`search`,placeholder:n(G)(`ds.icons.search`),class:`pl-9 pr-4 py-2 rounded-pill border border-line bg-paper text-[14px] text-ink placeholder:text-muted focus:outline-none focus:border-brand w-[260px]`},null,8,S),[[m,q.value]])]),f(`span`,C,o(n(G)(`ds.icons.copyHint`)),1)]),J.value.length===0?(t(),p(`div`,w,o(n(G)(`ds.icons.noMatch`))+` "`+o(q.value)+`". `,1)):c(``,!0),(t(!0),p(d,null,i(J.value,e=>(t(),p(`div`,{key:e.title,class:`mb-10 last:mb-0`},[f(`h2`,T,o(e.title),1),f(`div`,E,[(t(!0),p(d,null,i(e.names,e=>(t(),p(`button`,{key:e,type:`button`,class:`group flex flex-col items-center justify-center gap-2 p-4 rounded-md border border-line bg-paper text-ink hover:border-brand-soft hover:-translate-y-0.5 hover:shadow-sm transition-all duration-base ease-out`,onClick:t=>X(e)},[f(`span`,O,[l(g,{name:e,size:22},null,8,[`name`])]),f(`span`,k,o(Y.value===e?n(G)(`ds.icons.copied`):e),1)],8,D))),128))])]))),128))]),f(`section`,null,[f(`h2`,A,o(n(G)(`ds.heading.onDifferentSurfaces`)),1),f(`div`,j,[f(`div`,M,[l(g,{name:`cart`,size:28}),l(g,{name:`heart`,size:28}),l(g,{name:`user`,size:28})]),f(`div`,N,[l(g,{name:`cart`,size:28}),l(g,{name:`heart`,size:28}),l(g,{name:`user`,size:28})]),f(`div`,P,[l(g,{name:`cart`,size:28}),l(g,{name:`heart`,size:28}),l(g,{name:`user`,size:28})])])]),f(`section`,null,[f(`h2`,F,o(n(G)(`ds.heading.sizes`)),1),f(`div`,I,[f(`div`,L,[l(g,{name:`cart`,size:16}),u[1]||=f(`code`,{class:`font-mono text-[11px] text-muted`},`16`,-1)]),f(`div`,R,[l(g,{name:`cart`,size:20}),f(`code`,z,o(n(G)(`ds.icons.sizeDefault`)),1)]),f(`div`,B,[l(g,{name:`cart`,size:24}),u[2]||=f(`code`,{class:`font-mono text-[11px] text-muted`},`24`,-1)]),f(`div`,V,[l(g,{name:`cart`,size:32}),u[3]||=f(`code`,{class:`font-mono text-[11px] text-muted`},`32`,-1)]),f(`div`,H,[l(g,{name:`cart`,size:48}),u[4]||=f(`code`,{class:`font-mono text-[11px] text-muted`},`48`,-1)])])]),f(`section`,null,[f(`h2`,U,o(n(G)(`ds.heading.usage`)),1),u[5]||=f(`div`,{class:`rounded-md border border-line bg-paper p-6 font-mono text-[12px] text-ink`},[f(`pre`,{class:`whitespace-pre-wrap`},`<Icon name="cart" :size="20" />
import{B as e,C as t,G as n,M as r,T as i,c as a,ft as o,j as s,l as c,m as l,o as u,r as d,s as f,u as p}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{i as m}from"./runtime-dom.esm-bundler-BrsyFPJA.js";import{n as h,t as g}from"./Icon-Dcq1PyHo.js";import{t as _}from"./i18n-DhVF3Pwg.js";import{t as v}from"./SectionShell-DGDgzO-7.js";var y={class:`mb-5 flex items-center gap-3 flex-wrap`},b={class:`relative inline-flex items-center`},x={class:`absolute left-3 text-muted pointer-events-none`},S=[`placeholder`],C={class:`text-[12px] text-muted`},w={key:0,class:`text-[14px] text-muted`},T={class:`eyebrow mb-4`},E={class:`grid grid-cols-[repeat(auto-fill,minmax(120px,1fr))] gap-3`},D=[`onClick`],O={class:`w-10 h-10 rounded-sm bg-cream text-brand flex items-center justify-center`},k={class:`font-mono text-[11px] text-muted group-hover:text-brand transition-colors`},A={class:`eyebrow mb-5`},j={class:`grid md:grid-cols-3 gap-4`},M={class:`rounded-md border border-line bg-paper text-brand p-8 flex items-center justify-center gap-6`},N={class:`rounded-md border border-line bg-cream text-brand p-8 flex items-center justify-center gap-6`},P={class:`rounded-md bg-brand text-accent p-8 flex items-center justify-center gap-6`},F={class:`eyebrow mb-5`},I={class:`rounded-md border border-line bg-paper p-8 flex items-end gap-8 text-brand`},L={class:`flex flex-col items-center gap-3`},R={class:`flex flex-col items-center gap-3`},z={class:`font-mono text-[11px] text-muted`},B={class:`flex flex-col items-center gap-3`},V={class:`flex flex-col items-center gap-3`},H={class:`flex flex-col items-center gap-3`},U={class:`eyebrow mb-5`},W={__name:`IconsSection`,setup(W){let{t:G}=_(),K=u(()=>[{title:G(`ds.icons.group.commerce`),names:[`cart`,`bag`,`heart`,`user`,`search`]},{title:G(`ds.icons.group.navigation`),names:[`menu`,`close`,`chevron-left`,`chevron-right`,`chevron-down`,`chevron-up`,`arrow-left`,`arrow-right`]},{title:G(`ds.icons.group.actions`),names:[`plus`,`minus`,`check`]},{title:G(`ds.icons.group.contact`),names:[`mail`,`phone`,`map-pin`,`external-link`]},{title:G(`ds.icons.group.feedback`),names:[`info`,`star`]},{title:G(`ds.icons.group.devices`),names:[`mobile`,`tablet`,`desktop`]}]),q=e(``),J=u(()=>{let e=q.value.trim().toLowerCase();return e?K.value.map(t=>({...t,names:t.names.filter(t=>t.includes(e)||(h[t]?.label||``).toLowerCase().includes(e))})).filter(e=>e.names.length>0):K.value}),Y=e(``);async function X(e){let t=`<Icon name="${e}" />`;try{await navigator.clipboard.writeText(t),Y.value=e,setTimeout(()=>{Y.value===e&&(Y.value=``)},1200)}catch{}}return(e,u)=>(t(),a(v,{eyebrow:n(G)(`ds.eyebrow.components`),title:n(G)(`ds.icons.title`),description:n(G)(`ds.icons.description`)},{default:s(()=>[f(`section`,null,[f(`div`,y,[f(`label`,b,[f(`span`,x,[l(g,{name:`search`,size:16})]),r(f(`input`,{"onUpdate:modelValue":u[0]||=e=>q.value=e,type:`search`,placeholder:n(G)(`ds.icons.search`),class:`pl-9 pr-4 py-2 rounded-pill border border-line bg-paper text-[14px] text-ink placeholder:text-muted focus:outline-none focus:border-brand w-[260px]`},null,8,S),[[m,q.value]])]),f(`span`,C,o(n(G)(`ds.icons.copyHint`)),1)]),J.value.length===0?(t(),p(`div`,w,o(n(G)(`ds.icons.noMatch`))+` "`+o(q.value)+`". `,1)):c(``,!0),(t(!0),p(d,null,i(J.value,e=>(t(),p(`div`,{key:e.title,class:`mb-10 last:mb-0`},[f(`h2`,T,o(e.title),1),f(`div`,E,[(t(!0),p(d,null,i(e.names,e=>(t(),p(`button`,{key:e,type:`button`,class:`group flex flex-col items-center justify-center gap-2 p-4 rounded-md border border-line bg-paper text-ink hover:border-brand-soft hover:-translate-y-0.5 hover:shadow-sm transition-all duration-base ease-out`,onClick:t=>X(e)},[f(`span`,O,[l(g,{name:e,size:22},null,8,[`name`])]),f(`span`,k,o(Y.value===e?n(G)(`ds.icons.copied`):e),1)],8,D))),128))])]))),128))]),f(`section`,null,[f(`h2`,A,o(n(G)(`ds.heading.onDifferentSurfaces`)),1),f(`div`,j,[f(`div`,M,[l(g,{name:`cart`,size:28}),l(g,{name:`heart`,size:28}),l(g,{name:`user`,size:28})]),f(`div`,N,[l(g,{name:`cart`,size:28}),l(g,{name:`heart`,size:28}),l(g,{name:`user`,size:28})]),f(`div`,P,[l(g,{name:`cart`,size:28}),l(g,{name:`heart`,size:28}),l(g,{name:`user`,size:28})])])]),f(`section`,null,[f(`h2`,F,o(n(G)(`ds.heading.sizes`)),1),f(`div`,I,[f(`div`,L,[l(g,{name:`cart`,size:16}),u[1]||=f(`code`,{class:`font-mono text-[11px] text-muted`},`16`,-1)]),f(`div`,R,[l(g,{name:`cart`,size:20}),f(`code`,z,o(n(G)(`ds.icons.sizeDefault`)),1)]),f(`div`,B,[l(g,{name:`cart`,size:24}),u[2]||=f(`code`,{class:`font-mono text-[11px] text-muted`},`24`,-1)]),f(`div`,V,[l(g,{name:`cart`,size:32}),u[3]||=f(`code`,{class:`font-mono text-[11px] text-muted`},`32`,-1)]),f(`div`,H,[l(g,{name:`cart`,size:48}),u[4]||=f(`code`,{class:`font-mono text-[11px] text-muted`},`48`,-1)])])]),f(`section`,null,[f(`h2`,U,o(n(G)(`ds.heading.usage`)),1),u[5]||=f(`div`,{class:`rounded-md border border-line bg-paper p-6 font-mono text-[12px] text-ink`},[f(`pre`,{class:`whitespace-pre-wrap`},`<Icon name="cart" :size="20" />
<Icon name="arrow-right" :size="16" label="Next slide" />`)],-1)])]),_:1},8,[`eyebrow`,`title`,`description`]))}};export{W as default};

View File

@@ -1 +1 @@
import{B as e,C as t,G as n,c as r,ft as i,j as a,m as o,s}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as c}from"./i18n-BE70RUdV.js";import{t as l}from"./Input-BRj09b8b.js";import{t as u}from"./SectionShell-CU6IpKMJ.js";import{t as d}from"./Card-C-QeZ_to.js";var f={class:`eyebrow mb-5`},p={class:`grid md:grid-cols-2 gap-6 max-w-3xl`},m={class:`eyebrow mb-5`},h={class:`grid md:grid-cols-2 gap-6 max-w-3xl`},g={__name:`InputsSection`,setup(g){let{t:_}=c(),v=e(``),y=e(``),b=e(``);return(e,c)=>(t(),r(u,{eyebrow:n(_)(`ds.eyebrow.components`),title:n(_)(`ds.inputs.title`),description:n(_)(`ds.inputs.description`)},{default:a(()=>[s(`section`,null,[s(`h2`,f,i(n(_)(`ds.heading.default`)),1),o(d,{tone:`paper`},{default:a(()=>[s(`div`,p,[o(l,{modelValue:v.value,"onUpdate:modelValue":c[0]||=e=>v.value=e,label:n(_)(`ds.inputs.email`),type:`email`,placeholder:`you@example.com`,hint:n(_)(`ds.inputs.emailHint`)},null,8,[`modelValue`,`label`,`hint`]),o(l,{modelValue:y.value,"onUpdate:modelValue":c[1]||=e=>y.value=e,label:n(_)(`ds.inputs.password`),type:`password`,placeholder:`••••••••`},null,8,[`modelValue`,`label`])])]),_:1})]),s(`section`,null,[s(`h2`,m,i(n(_)(`ds.heading.states`)),1),o(d,{tone:`paper`},{default:a(()=>[s(`div`,h,[o(l,{modelValue:b.value,"onUpdate:modelValue":c[2]||=e=>b.value=e,label:n(_)(`ds.inputs.required`),required:``,error:n(_)(`ds.inputs.requiredError`)},null,8,[`modelValue`,`label`,`error`]),o(l,{label:n(_)(`ds.inputs.disabled`),placeholder:n(_)(`ds.inputs.disabledPlaceholder`),disabled:``},null,8,[`label`,`placeholder`])])]),_:1})])]),_:1},8,[`eyebrow`,`title`,`description`]))}};export{g as default};
import{B as e,C as t,G as n,c as r,ft as i,j as a,m as o,s}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as c}from"./i18n-DhVF3Pwg.js";import{t as l}from"./Input-os2-AnNh.js";import{t as u}from"./SectionShell-DGDgzO-7.js";import{t as d}from"./Card-DcusAcLZ.js";var f={class:`eyebrow mb-5`},p={class:`grid md:grid-cols-2 gap-6 max-w-3xl`},m={class:`eyebrow mb-5`},h={class:`grid md:grid-cols-2 gap-6 max-w-3xl`},g={__name:`InputsSection`,setup(g){let{t:_}=c(),v=e(``),y=e(``),b=e(``);return(e,c)=>(t(),r(u,{eyebrow:n(_)(`ds.eyebrow.components`),title:n(_)(`ds.inputs.title`),description:n(_)(`ds.inputs.description`)},{default:a(()=>[s(`section`,null,[s(`h2`,f,i(n(_)(`ds.heading.default`)),1),o(d,{tone:`paper`},{default:a(()=>[s(`div`,p,[o(l,{modelValue:v.value,"onUpdate:modelValue":c[0]||=e=>v.value=e,label:n(_)(`ds.inputs.email`),type:`email`,placeholder:`you@example.com`,hint:n(_)(`ds.inputs.emailHint`)},null,8,[`modelValue`,`label`,`hint`]),o(l,{modelValue:y.value,"onUpdate:modelValue":c[1]||=e=>y.value=e,label:n(_)(`ds.inputs.password`),type:`password`,placeholder:`••••••••`},null,8,[`modelValue`,`label`])])]),_:1})]),s(`section`,null,[s(`h2`,m,i(n(_)(`ds.heading.states`)),1),o(d,{tone:`paper`},{default:a(()=>[s(`div`,h,[o(l,{modelValue:b.value,"onUpdate:modelValue":c[2]||=e=>b.value=e,label:n(_)(`ds.inputs.required`),required:``,error:n(_)(`ds.inputs.requiredError`)},null,8,[`modelValue`,`label`,`error`]),o(l,{label:n(_)(`ds.inputs.disabled`),placeholder:n(_)(`ds.inputs.disabledPlaceholder`),disabled:``},null,8,[`label`,`placeholder`])])]),_:1})])]),_:1},8,[`eyebrow`,`title`,`description`]))}};export{g as default};

View File

@@ -1 +1 @@
import{C as e,G as t,T as n,ft as r,o as i,r as a,u as o,ut as s}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as c}from"./i18n-BE70RUdV.js";var l=[`aria-label`,`aria-pressed`,`onClick`],u={__name:`LanguageSwitcher`,props:{floating:{type:Boolean,default:!1},tone:{type:String,default:`paper`,validator:e=>[`paper`,`cream`,`brand`].includes(e)}},setup(u){let d=u,{locale:f,setLocale:p,availableLocales:m}=c(),h={paper:{container:`border border-line bg-paper`,active:`bg-brand text-accent`,inactive:`text-muted hover:text-brand`},cream:{container:`border border-line-strong bg-paper`,active:`bg-brand text-accent`,inactive:`text-muted hover:text-brand`},brand:{container:`border border-cream-line bg-cream-wash`,active:`bg-accent text-brand`,inactive:`text-cream hover:text-accent`}},g=i(()=>h[d.tone]);return(i,c)=>(e(),o(`div`,{role:`group`,"aria-label":`Language`,class:s([`inline-flex items-center h-10 px-2 gap-0.5 rounded-pill font-sans`,g.value.container,u.floating?`fixed top-6 right-6 z-[60] shadow-sm`:``])},[(e(!0),o(a,null,n(t(m),n=>(e(),o(`button`,{key:n.code,type:`button`,"aria-label":n.name,"aria-pressed":t(f)===n.code,class:s([`px-2.5 py-1 text-[11px] font-bold tracking-eyebrow rounded-pill transition-colors duration-base`,t(f)===n.code?g.value.active:g.value.inactive]),onClick:e=>t(p)(n.code)},r(n.label),11,l))),128))],2))}};export{u as t};
import{C as e,G as t,T as n,ft as r,o as i,r as a,u as o,ut as s}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as c}from"./i18n-DhVF3Pwg.js";var l=[`aria-label`,`aria-pressed`,`onClick`],u={__name:`LanguageSwitcher`,props:{floating:{type:Boolean,default:!1},tone:{type:String,default:`paper`,validator:e=>[`paper`,`cream`,`brand`].includes(e)}},setup(u){let d=u,{locale:f,setLocale:p,availableLocales:m}=c(),h={paper:{container:`border border-line bg-paper`,active:`bg-brand text-accent`,inactive:`text-muted hover:text-brand`},cream:{container:`border border-line-strong bg-paper`,active:`bg-brand text-accent`,inactive:`text-muted hover:text-brand`},brand:{container:`border border-cream-line bg-cream-wash`,active:`bg-accent text-brand`,inactive:`text-cream hover:text-accent`}},g=i(()=>h[d.tone]);return(i,c)=>(e(),o(`div`,{role:`group`,"aria-label":`Language`,class:s([`inline-flex items-center h-10 px-2 gap-0.5 rounded-pill font-sans`,g.value.container,u.floating?`fixed top-6 right-6 z-[60] shadow-sm`:``])},[(e(!0),o(a,null,n(t(m),n=>(e(),o(`button`,{key:n.code,type:`button`,"aria-label":n.name,"aria-pressed":t(f)===n.code,class:s([`px-2.5 py-1 text-[11px] font-bold tracking-eyebrow rounded-pill transition-colors duration-base`,t(f)===n.code?g.value.active:g.value.inactive]),onClick:e=>t(p)(n.code)},r(n.label),11,l))),128))],2))}};export{u as t};

View File

@@ -1,3 +1,3 @@
import{C as e,G as t,c as n,ft as r,j as i,m as a,s as o}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as s}from"./i18n-BE70RUdV.js";import{t as c}from"./LanguageSwitcher-DdisYsJ7.js";import{t as l}from"./SectionShell-CU6IpKMJ.js";import{t as u}from"./Card-C-QeZ_to.js";var d={class:`eyebrow mb-5`},f={class:`grid md:grid-cols-3 gap-4`},p={class:`eyebrow mb-4`},m={class:`text-[13px] text-muted mt-4 leading-relaxed`},h={class:`eyebrow mb-4`},g={class:`text-[13px] text-muted mt-4 leading-relaxed`},_={class:`eyebrow mb-4`},v={class:`text-[13px] opacity-80 mt-4 leading-relaxed`},y={class:`eyebrow mb-5`},b={class:`text-[13px] text-muted max-w-2xl leading-relaxed`},x={class:`eyebrow mb-5`},S={__name:`LanguageSwitcherSection`,setup(S){let{t:C}=s();return(s,S)=>(e(),n(l,{eyebrow:t(C)(`ds.eyebrow.components`),title:t(C)(`ds.language.title`),description:t(C)(`ds.language.description`)},{default:i(()=>[o(`section`,null,[o(`h2`,d,r(t(C)(`ds.heading.onDifferentSurfaces`)),1),o(`div`,f,[a(u,{tone:`paper`},{default:i(()=>[o(`p`,p,r(t(C)(`ds.cards.paper`)),1),a(c,{tone:`paper`}),o(`p`,m,r(t(C)(`ds.language.paperNote`)),1)]),_:1}),a(u,{tone:`cream`},{default:i(()=>[o(`p`,h,r(t(C)(`ds.cards.cream`)),1),a(c,{tone:`cream`}),o(`p`,g,r(t(C)(`ds.language.creamNote`)),1)]),_:1}),a(u,{tone:`brand`},{default:i(()=>[o(`p`,_,r(t(C)(`ds.cards.brand`)),1),a(c,{tone:`brand`}),o(`p`,v,r(t(C)(`ds.language.brandNote`)),1)]),_:1})])]),o(`section`,null,[o(`h2`,y,r(t(C)(`ds.language.floating`)),1),a(u,{tone:`paper`},{default:i(()=>[o(`p`,b,r(t(C)(`ds.language.floatingNote`)),1)]),_:1})]),o(`section`,null,[o(`h2`,x,r(t(C)(`ds.heading.usage`)),1),S[0]||=o(`div`,{class:`rounded-md border border-line bg-paper p-6 font-mono text-[12px] text-ink`},[o(`pre`,{class:`whitespace-pre-wrap`},`<LanguageSwitcher />
import{C as e,G as t,c as n,ft as r,j as i,m as a,s as o}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as s}from"./i18n-DhVF3Pwg.js";import{t as c}from"./LanguageSwitcher-yI_Qjz7Y.js";import{t as l}from"./SectionShell-DGDgzO-7.js";import{t as u}from"./Card-DcusAcLZ.js";var d={class:`eyebrow mb-5`},f={class:`grid md:grid-cols-3 gap-4`},p={class:`eyebrow mb-4`},m={class:`text-[13px] text-muted mt-4 leading-relaxed`},h={class:`eyebrow mb-4`},g={class:`text-[13px] text-muted mt-4 leading-relaxed`},_={class:`eyebrow mb-4`},v={class:`text-[13px] opacity-80 mt-4 leading-relaxed`},y={class:`eyebrow mb-5`},b={class:`text-[13px] text-muted max-w-2xl leading-relaxed`},x={class:`eyebrow mb-5`},S={__name:`LanguageSwitcherSection`,setup(S){let{t:C}=s();return(s,S)=>(e(),n(l,{eyebrow:t(C)(`ds.eyebrow.components`),title:t(C)(`ds.language.title`),description:t(C)(`ds.language.description`)},{default:i(()=>[o(`section`,null,[o(`h2`,d,r(t(C)(`ds.heading.onDifferentSurfaces`)),1),o(`div`,f,[a(u,{tone:`paper`},{default:i(()=>[o(`p`,p,r(t(C)(`ds.cards.paper`)),1),a(c,{tone:`paper`}),o(`p`,m,r(t(C)(`ds.language.paperNote`)),1)]),_:1}),a(u,{tone:`cream`},{default:i(()=>[o(`p`,h,r(t(C)(`ds.cards.cream`)),1),a(c,{tone:`cream`}),o(`p`,g,r(t(C)(`ds.language.creamNote`)),1)]),_:1}),a(u,{tone:`brand`},{default:i(()=>[o(`p`,_,r(t(C)(`ds.cards.brand`)),1),a(c,{tone:`brand`}),o(`p`,v,r(t(C)(`ds.language.brandNote`)),1)]),_:1})])]),o(`section`,null,[o(`h2`,y,r(t(C)(`ds.language.floating`)),1),a(u,{tone:`paper`},{default:i(()=>[o(`p`,b,r(t(C)(`ds.language.floatingNote`)),1)]),_:1})]),o(`section`,null,[o(`h2`,x,r(t(C)(`ds.heading.usage`)),1),S[0]||=o(`div`,{class:`rounded-md border border-line bg-paper p-6 font-mono text-[12px] text-ink`},[o(`pre`,{class:`whitespace-pre-wrap`},`<LanguageSwitcher />
<LanguageSwitcher tone="brand" />
<LanguageSwitcher floating />`)],-1)])]),_:1},8,[`eyebrow`,`title`,`description`]))}};export{S as default};

View File

@@ -1 +1 @@
import{B as e,C as t,G as n,S as r,T as i,ft as a,l as o,m as s,o as c,r as l,s as u,u as d,x as f}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{o as p}from"./vue-router-Cyqru1db.js";import{t as m}from"./i18n-BE70RUdV.js";import{r as h}from"./products-BqW5PUnm.js";import{c as g,i as _,l as v,o as y,s as b}from"./api-BkifONga.js";import{t as x}from"./Navbar-lf1WjUkF.js";import{t as S}from"./Footer-B0iLDEww.js";import{t as C}from"./CartDrawer-BOCuIGNH.js";var w={class:`bg-cream text-ink min-h-svh`},T={class:`mx-auto w-full max-w-3xl px-6 py-14 sm:px-8 sm:py-16 md:px-12 md:py-20 lg:px-16 lg:py-24`},E={class:`flex flex-col gap-3 mb-10 md:mb-14`},D={class:`eyebrow`},O={class:`font-display font-normal leading-[1.05] tracking-tight text-ink text-headline-md`},k={key:0,class:`text-[12px] text-muted`},A={class:`flex flex-col gap-10`},j=[`id`],M={class:`font-display text-xl md:text-2xl font-normal text-brand leading-tight`},N={class:`text-[15px] leading-relaxed text-ink whitespace-pre-line`},P={__name:`LegalPage`,props:{kind:{type:String,required:!0,validator:e=>[`impressum`,`datenschutz`].includes(e)}},setup(P){let F=P,{t:I}=m(),L=v(),R=p(),z=e(!1);function B(){z.value=!1,R.push(`/checkout`)}let V=[{key:`nav.shop`,href:`/shop`},{key:`nav.bundles`,href:`/#bundles`},{key:`nav.revitalization`,href:`/#revitalize`},{key:`nav.about`,href:`/#about`}],H={impressum:[`operator`,`contact`,`register`,`vat`,`authority`,`liability`,`copyright`],datenschutz:[`controller`,`scope`,`legalBasis`,`data`,`cookies`,`analytics`,`payments`,`rights`,`retention`,`contact`]},U=c(()=>H[F.kind].map(e=>({id:e,heading:I(`legal.${F.kind}.section.${e}.heading`),body:I(`legal.${F.kind}.section.${e}.body`)}))),W=c(()=>I(`legal.${F.kind}.updated`));async function G(e){await _(e.id,1),z.value=!0}async function K({productId:e,quantity:t}){await g(e,t)}async function q(e){await b(e)}let J=e(null),Y=null;function X(){let e=J.value,t=e&&(e.$el||e);if(!t||typeof window>`u`)return;let n=Math.round(t.getBoundingClientRect().height);document.documentElement.style.setProperty(`--nav-h`,`${n}px`)}return r(()=>{if(y(),X(),typeof ResizeObserver<`u`&&J.value){let e=J.value.$el||J.value;Y=new ResizeObserver(X),Y.observe(e)}window.addEventListener(`resize`,X)}),f(()=>{Y&&Y.disconnect(),typeof window<`u`&&window.removeEventListener(`resize`,X)}),(e,r)=>(t(),d(l,null,[s(x,{ref_key:`navRef`,ref:J,variant:`paper`,layout:`standard`,items:V,"cart-count":n(L).count,products:n(h),onCart:r[0]||=e=>z.value=!0,onSearch:G},null,8,[`cart-count`,`products`]),u(`main`,w,[u(`div`,T,[u(`header`,E,[u(`p`,D,a(n(I)(`legal.${P.kind}.eyebrow`)),1),u(`h1`,O,a(n(I)(`legal.${P.kind}.title`)),1),W.value?(t(),d(`p`,k,a(W.value),1)):o(``,!0)]),u(`article`,A,[(t(!0),d(l,null,i(U.value,e=>(t(),d(`section`,{id:e.id,key:e.id,class:`flex flex-col gap-3`},[u(`h2`,M,a(e.heading),1),u(`p`,N,a(e.body),1)],8,j))),128))])])]),s(S),r[2]||=u(`div`,{"aria-hidden":`true`,class:`md:hidden`,style:{height:`calc(64px + env(safe-area-inset-bottom))`}},null,-1),s(C,{modelValue:z.value,"onUpdate:modelValue":r[1]||=e=>z.value=e,items:n(L).items,subtotal:n(L).subtotal,count:n(L).count,onUpdateQuantity:K,onRemove:q,onCheckout:B},null,8,[`modelValue`,`items`,`subtotal`,`count`])],64))}};export{P as default};
import{B as e,C as t,G as n,S as r,T as i,ft as a,l as o,m as s,o as c,r as l,s as u,u as d,x as f}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{o as p}from"./vue-router-Cyqru1db.js";import{t as m}from"./i18n-DhVF3Pwg.js";import{r as h}from"./products-BqW5PUnm.js";import{c as g,d as _,f as v,p as y,u as b}from"./api-DlB23x7f.js";import{t as x}from"./Navbar-dl8ShEaS.js";import{t as S}from"./Footer-DX-ml2dd.js";import{t as C}from"./CartDrawer-f57jysS_.js";var w={class:`bg-cream text-ink min-h-svh`},T={class:`mx-auto w-full max-w-3xl px-6 py-14 sm:px-8 sm:py-16 md:px-12 md:py-20 lg:px-16 lg:py-24`},E={class:`flex flex-col gap-3 mb-10 md:mb-14`},D={class:`eyebrow`},O={class:`font-display font-normal leading-[1.05] tracking-tight text-ink text-headline-md`},k={key:0,class:`text-[12px] text-muted`},A={class:`flex flex-col gap-10`},j=[`id`],M={class:`font-display text-xl md:text-2xl font-normal text-brand leading-tight`},N={class:`text-[15px] leading-relaxed text-ink whitespace-pre-line`},P={__name:`LegalPage`,props:{kind:{type:String,required:!0,validator:e=>[`impressum`,`datenschutz`].includes(e)}},setup(P){let F=P,{t:I}=m(),L=y(),R=p(),z=e(!1);function B(){z.value=!1,R.push(`/checkout`)}let V=[{key:`nav.shop`,href:`/shop`},{key:`nav.bundles`,href:`/#bundles`},{key:`nav.revitalization`,href:`/#revitalize`},{key:`nav.about`,href:`/#about`}],H={impressum:[`operator`,`contact`,`register`,`vat`,`authority`,`liability`,`copyright`],datenschutz:[`controller`,`scope`,`legalBasis`,`data`,`cookies`,`analytics`,`payments`,`rights`,`retention`,`contact`]},U=c(()=>H[F.kind].map(e=>({id:e,heading:I(`legal.${F.kind}.section.${e}.heading`),body:I(`legal.${F.kind}.section.${e}.body`)}))),W=c(()=>I(`legal.${F.kind}.updated`));async function G(e){await g(e.id,1),z.value=!0}async function K({productId:e,quantity:t}){await v(e,t)}async function q(e){await _(e)}let J=e(null),Y=null;function X(){let e=J.value,t=e&&(e.$el||e);if(!t||typeof window>`u`)return;let n=Math.round(t.getBoundingClientRect().height);document.documentElement.style.setProperty(`--nav-h`,`${n}px`)}return r(()=>{if(b(),X(),typeof ResizeObserver<`u`&&J.value){let e=J.value.$el||J.value;Y=new ResizeObserver(X),Y.observe(e)}window.addEventListener(`resize`,X)}),f(()=>{Y&&Y.disconnect(),typeof window<`u`&&window.removeEventListener(`resize`,X)}),(e,r)=>(t(),d(l,null,[s(x,{ref_key:`navRef`,ref:J,variant:`cream`,layout:`standard`,items:V,"cart-count":n(L).count,products:n(h),onCart:r[0]||=e=>z.value=!0,onSearch:G},null,8,[`cart-count`,`products`]),u(`main`,w,[u(`div`,T,[u(`header`,E,[u(`p`,D,a(n(I)(`legal.${P.kind}.eyebrow`)),1),u(`h1`,O,a(n(I)(`legal.${P.kind}.title`)),1),W.value?(t(),d(`p`,k,a(W.value),1)):o(``,!0)]),u(`article`,A,[(t(!0),d(l,null,i(U.value,e=>(t(),d(`section`,{id:e.id,key:e.id,class:`flex flex-col gap-3`},[u(`h2`,M,a(e.heading),1),u(`p`,N,a(e.body),1)],8,j))),128))])])]),s(S),r[2]||=u(`div`,{"aria-hidden":`true`,class:`md:hidden`,style:{height:`calc(64px + env(safe-area-inset-bottom))`}},null,-1),s(C,{modelValue:z.value,"onUpdate:modelValue":r[1]||=e=>z.value=e,items:n(L).items,subtotal:n(L).subtotal,count:n(L).count,onUpdateQuantity:K,onRemove:q,onCheckout:B},null,8,[`modelValue`,`items`,`subtotal`,`count`])],64))}};export{P as default};

1
dist/assets/LoginPage-CK4aUIoZ.js vendored Normal file
View File

@@ -0,0 +1 @@
import{B as e,C as t,G as n,S as r,c as i,ft as a,j as o,l as s,m as c,o as l,p as u,r as d,s as f,u as p,x as m}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{s as h}from"./runtime-dom.esm-bundler-BrsyFPJA.js";import{a as g,o as _,t as v}from"./vue-router-Cyqru1db.js";import{t as ee}from"./i18n-DhVF3Pwg.js";import{r as y}from"./products-BqW5PUnm.js";import{i as b,p as x,r as S}from"./api-DlB23x7f.js";import{t as C}from"./Navbar-dl8ShEaS.js";import{t as w}from"./Button-D1Rp2Xe1.js";import{t as T}from"./Footer-DX-ml2dd.js";import{t as E}from"./Input-os2-AnNh.js";var D={class:`bg-cream text-ink min-h-svh`},O={class:`mx-auto w-full max-w-md px-6 py-14 sm:px-8 sm:py-20 md:py-24`},k={class:`flex flex-col gap-3 mb-8 text-center`},A={class:`eyebrow`},j={class:`font-display font-normal leading-[1.05] tracking-tight text-ink text-headline-md`},M={class:`text-[14px] text-muted`},N={key:1,class:`text-sm text-danger`,role:`alert`,"aria-live":`polite`},P={key:1,class:`rounded-md border border-line bg-paper p-6 md:p-8 flex flex-col gap-3 text-center`},F={class:`font-display text-xl text-brand`},I={class:`text-[14px] text-muted`},L={class:`mt-8 text-center text-[14px] text-muted`},te={__name:`LoginPage`,setup(te){let{t:R}=ee(),z=x(),B=g(),V=_(),H=[{key:`nav.shop`,href:`/shop`},{key:`nav.bundles`,href:`/#bundles`},{key:`nav.revitalization`,href:`/#revitalize`},{key:`nav.about`,href:`/#about`}],U=e(``),W=e(``),G=e(!1),K=e(``),q=e(!1),J=e(!1),Y=l(()=>U.value?q.value?!1:W.value.length<8:!0);async function X(){K.value=``,G.value=!0;try{if(q.value){await S({email:U.value}),J.value=!0;return}await b({email:U.value,password:W.value});let e=String(B.query.next||`/`);V.push(e)}catch(e){K.value=e?.message||R(`checkout.error.generic`)}finally{G.value=!1}}let Z=e(null),Q=null;function $(){let e=Z.value,t=e&&(e.$el||e);if(!t||typeof window>`u`)return;let n=Math.round(t.getBoundingClientRect().height);document.documentElement.style.setProperty(`--nav-h`,`${n}px`)}return r(()=>{if($(),typeof ResizeObserver<`u`&&Z.value){let e=Z.value.$el||Z.value;Q=new ResizeObserver($),Q.observe(e)}window.addEventListener(`resize`,$)}),m(()=>{Q&&Q.disconnect(),typeof window<`u`&&window.removeEventListener(`resize`,$)}),(e,r)=>(t(),p(d,null,[c(C,{ref_key:`navRef`,ref:Z,variant:`cream`,layout:`standard`,items:H,"cart-count":n(z).count,products:n(y)},null,8,[`cart-count`,`products`]),f(`main`,D,[f(`div`,O,[f(`header`,k,[f(`p`,A,a(n(R)(`auth.login.eyebrow`)),1),f(`h1`,j,a(q.value?n(R)(`auth.reset.title`):n(R)(`auth.login.title`)),1),f(`p`,M,a(q.value?n(R)(`auth.reset.sub`):n(R)(`auth.login.sub`)),1)]),J.value?(t(),p(`div`,P,[f(`h2`,F,a(n(R)(`auth.reset.sent.title`)),1),f(`p`,I,a(n(R)(`auth.reset.sent.body`)),1),f(`button`,{type:`button`,class:`text-[13px] text-brand hover:underline self-center mt-2`,onClick:r[3]||=e=>{q.value=!1,J.value=!1}},a(n(R)(`auth.reset.back`)),1)])):(t(),p(`form`,{key:0,class:`flex flex-col gap-5 rounded-md border border-line bg-paper p-6 md:p-8`,novalidate:``,onSubmit:h(X,[`prevent`])},[c(E,{modelValue:U.value,"onUpdate:modelValue":r[0]||=e=>U.value=e,label:n(R)(`checkout.field.email`),type:`email`,required:``,placeholder:n(R)(`checkout.placeholder.email`)},null,8,[`modelValue`,`label`,`placeholder`]),q.value?s(``,!0):(t(),i(E,{key:0,modelValue:W.value,"onUpdate:modelValue":r[1]||=e=>W.value=e,label:n(R)(`checkout.field.password`),type:`password`,required:``},null,8,[`modelValue`,`label`])),K.value?(t(),p(`p`,N,a(K.value),1)):s(``,!0),c(w,{type:`submit`,variant:`primary`,size:`lg`,block:``,loading:G.value,disabled:Y.value},{default:o(()=>[u(a(q.value?n(R)(`auth.reset.cta`):n(R)(`auth.login.cta`)),1)]),_:1},8,[`loading`,`disabled`]),f(`button`,{type:`button`,class:`text-[13px] text-brand hover:underline self-start`,onClick:r[2]||=e=>q.value=!q.value},a(q.value?n(R)(`auth.reset.back`):n(R)(`auth.login.forgot`)),1)],32)),f(`p`,L,[u(a(n(R)(`auth.login.newCustomer`))+` `,1),c(n(v),{to:`/register`,class:`text-brand hover:underline`},{default:o(()=>[u(a(n(R)(`auth.login.cta.register`)),1)]),_:1})])])]),c(T)],64))}};export{te as default};

View File

@@ -1 +1 @@
import{C as e,G as t,c as n,ft as r,j as i,m as a,s as o}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as s}from"./Logo-xADhl1RY.js";import{t as c}from"./i18n-BE70RUdV.js";import{t as l}from"./SectionShell-CU6IpKMJ.js";var u={class:`eyebrow mb-5`},d={class:`grid md:grid-cols-3 gap-4`},f={class:`rounded-md border border-line bg-paper p-10 flex flex-col items-center gap-4`},p={class:`font-mono text-[11px] text-muted`},m={class:`rounded-md border border-line bg-cream p-10 flex flex-col items-center gap-4`},h={class:`font-mono text-[11px] text-muted`},g={class:`rounded-md bg-brand p-10 flex flex-col items-center gap-4`},_={class:`font-mono text-[11px] text-cream opacity-70`},v={class:`eyebrow mb-5`},y={class:`rounded-md border border-line bg-paper p-10 flex items-end gap-10 flex-wrap`},b={class:`flex flex-col items-center gap-3`},x={class:`flex flex-col items-center gap-3`},S={class:`flex flex-col items-center gap-3`},C={class:`eyebrow mb-5`},w={class:`rounded-md border border-line bg-paper p-6 font-mono text-[12px] text-ink`},T={class:`mb-2 text-muted`},E={__name:`LogoSection`,setup(E){let{t:D}=c();return(c,E)=>(e(),n(l,{eyebrow:t(D)(`ds.eyebrow.brand`),title:t(D)(`ds.logo.title`),description:t(D)(`ds.logo.description`)},{default:i(()=>[o(`section`,null,[o(`h2`,u,r(t(D)(`ds.heading.onDifferentSurfaces`)),1),o(`div`,d,[o(`div`,f,[a(s,{class:`w-40 h-auto text-brand`}),o(`code`,p,r(t(D)(`ds.cards.paper`)),1)]),o(`div`,m,[a(s,{class:`w-40 h-auto text-brand`}),o(`code`,h,r(t(D)(`ds.cards.cream`)),1)]),o(`div`,g,[a(s,{class:`w-40 h-auto text-paper`}),o(`code`,_,r(t(D)(`ds.cards.brand`)),1)])])]),o(`section`,null,[o(`h2`,v,r(t(D)(`ds.heading.sizes`)),1),o(`div`,y,[o(`div`,b,[a(s,{class:`w-16 h-auto text-brand`}),E[0]||=o(`code`,{class:`font-mono text-[11px] text-muted`},`w-16`,-1)]),o(`div`,x,[a(s,{class:`w-32 h-auto text-brand`}),E[1]||=o(`code`,{class:`font-mono text-[11px] text-muted`},`w-32`,-1)]),o(`div`,S,[a(s,{class:`w-60 h-auto text-brand`}),E[2]||=o(`code`,{class:`font-mono text-[11px] text-muted`},`w-60`,-1)])])]),o(`section`,null,[o(`h2`,C,r(t(D)(`ds.heading.usage`)),1),o(`div`,w,[o(`p`,T,r(t(D)(`ds.logo.usageIntro`)),1),E[3]||=o(`pre`,{class:`whitespace-pre-wrap`},`<Logo class="w-60 h-auto text-brand" />`,-1)])])]),_:1},8,[`eyebrow`,`title`,`description`]))}};export{E as default};
import{C as e,G as t,c as n,ft as r,j as i,m as a,s as o}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as s}from"./Logo-xADhl1RY.js";import{t as c}from"./i18n-DhVF3Pwg.js";import{t as l}from"./SectionShell-DGDgzO-7.js";var u={class:`eyebrow mb-5`},d={class:`grid md:grid-cols-3 gap-4`},f={class:`rounded-md border border-line bg-paper p-10 flex flex-col items-center gap-4`},p={class:`font-mono text-[11px] text-muted`},m={class:`rounded-md border border-line bg-cream p-10 flex flex-col items-center gap-4`},h={class:`font-mono text-[11px] text-muted`},g={class:`rounded-md bg-brand p-10 flex flex-col items-center gap-4`},_={class:`font-mono text-[11px] text-cream opacity-70`},v={class:`eyebrow mb-5`},y={class:`rounded-md border border-line bg-paper p-10 flex items-end gap-10 flex-wrap`},b={class:`flex flex-col items-center gap-3`},x={class:`flex flex-col items-center gap-3`},S={class:`flex flex-col items-center gap-3`},C={class:`eyebrow mb-5`},w={class:`rounded-md border border-line bg-paper p-6 font-mono text-[12px] text-ink`},T={class:`mb-2 text-muted`},E={__name:`LogoSection`,setup(E){let{t:D}=c();return(c,E)=>(e(),n(l,{eyebrow:t(D)(`ds.eyebrow.brand`),title:t(D)(`ds.logo.title`),description:t(D)(`ds.logo.description`)},{default:i(()=>[o(`section`,null,[o(`h2`,u,r(t(D)(`ds.heading.onDifferentSurfaces`)),1),o(`div`,d,[o(`div`,f,[a(s,{class:`w-40 h-auto text-brand`}),o(`code`,p,r(t(D)(`ds.cards.paper`)),1)]),o(`div`,m,[a(s,{class:`w-40 h-auto text-brand`}),o(`code`,h,r(t(D)(`ds.cards.cream`)),1)]),o(`div`,g,[a(s,{class:`w-40 h-auto text-paper`}),o(`code`,_,r(t(D)(`ds.cards.brand`)),1)])])]),o(`section`,null,[o(`h2`,v,r(t(D)(`ds.heading.sizes`)),1),o(`div`,y,[o(`div`,b,[a(s,{class:`w-16 h-auto text-brand`}),E[0]||=o(`code`,{class:`font-mono text-[11px] text-muted`},`w-16`,-1)]),o(`div`,x,[a(s,{class:`w-32 h-auto text-brand`}),E[1]||=o(`code`,{class:`font-mono text-[11px] text-muted`},`w-32`,-1)]),o(`div`,S,[a(s,{class:`w-60 h-auto text-brand`}),E[2]||=o(`code`,{class:`font-mono text-[11px] text-muted`},`w-60`,-1)])])]),o(`section`,null,[o(`h2`,C,r(t(D)(`ds.heading.usage`)),1),o(`div`,w,[o(`p`,T,r(t(D)(`ds.logo.usageIntro`)),1),E[3]||=o(`pre`,{class:`whitespace-pre-wrap`},`<Logo class="w-60 h-auto text-brand" />`,-1)])])]),_:1},8,[`eyebrow`,`title`,`description`]))}};export{E as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
import{C as e,T as t,dt as n,m as r,o as i,r as a,s as o,u as s}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{a as c}from"./vue-router-Cyqru1db.js";import{t as l}from"./Navbar-lf1WjUkF.js";var u={class:`min-h-screen bg-surface`},d={class:`max-w-5xl mx-auto px-6 py-16 space-y-6`},f={__name:`NavbarPreview`,setup(f){let p=c(),m=i(()=>{let e=p.query.variant;return[`paper`,`cream`,`brand`].includes(e)?e:`paper`}),h=i(()=>p.query.layout===`floating`?`floating`:`standard`);return(i,c)=>(e(),s(`div`,u,[r(l,{variant:m.value,layout:h.value,"cart-count":2},null,8,[`variant`,`layout`]),o(`div`,d,[(e(),s(a,null,t([40,28,40,32,40,28,36,40],e=>o(`div`,{key:e,class:`rounded-md border border-line bg-paper`,style:n({height:e*4+`px`})},null,4)),64))])]))}};export{f as default};
import{C as e,T as t,dt as n,m as r,o as i,r as a,s as o,u as s}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{a as c}from"./vue-router-Cyqru1db.js";import{t as l}from"./Navbar-dl8ShEaS.js";var u={class:`min-h-screen bg-surface`},d={class:`max-w-5xl mx-auto px-6 py-16 space-y-6`},f={__name:`NavbarPreview`,setup(f){let p=c(),m=i(()=>{let e=p.query.variant;return[`paper`,`cream`,`brand`].includes(e)?e:`paper`}),h=i(()=>p.query.layout===`floating`?`floating`:`standard`);return(i,c)=>(e(),s(`div`,u,[r(l,{variant:m.value,layout:h.value,"cart-count":2},null,8,[`variant`,`layout`]),o(`div`,d,[(e(),s(a,null,t([40,28,40,32,40,28,36,40],e=>o(`div`,{key:e,class:`rounded-md border border-line bg-paper`,style:n({height:e*4+`px`})},null,4)),64))])]))}};export{f as default};

View File

@@ -1,4 +1,4 @@
import{B as e,C as t,G as n,T as r,c as i,dt as a,ft as o,j as s,m as c,o as l,p as u,r as d,s as f,u as p,ut as m}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as h}from"./i18n-BE70RUdV.js";import{t as g}from"./SectionShell-CU6IpKMJ.js";import{t as _}from"./DevicePreview-9pDgIJdz.js";var v=[`aria-label`],y=[`aria-selected`,`onClick`],b=[`aria-label`],x=[`aria-selected`,`onClick`],S={class:`eyebrow mb-5`},C={__name:`NavbarSection`,setup(C){let{t:w}=h(),T=l(()=>[{id:`paper`,label:w(`ds.navbar.tone.paper`),swatch:`#ffffff`},{id:`cream`,label:w(`ds.navbar.tone.cream`),swatch:`var(--color-cream)`},{id:`brand`,label:w(`ds.navbar.tone.brand`),swatch:`var(--color-brand)`}]),E=l(()=>[{id:`standard`,label:w(`ds.navbar.layout.standard`)},{id:`floating`,label:w(`ds.navbar.layout.floating`)}]),D=e(`paper`),O=e(`standard`),k=l(()=>`/design/preview/navbar?variant=${D.value}&layout=${O.value}`);return(e,l)=>(t(),i(g,{eyebrow:n(w)(`ds.eyebrow.components`),title:n(w)(`ds.navbar.title`),description:n(w)(`ds.navbar.description`),wide:``},{default:s(()=>[f(`section`,null,[c(_,{src:k.value,initial:`mobile`,height:720},{controls:s(()=>[f(`div`,{role:`tablist`,"aria-label":n(w)(`ds.navbar.layout`),class:`inline-flex items-center p-1 gap-0.5 rounded-pill border border-line bg-paper`},[(t(!0),p(d,null,r(E.value,e=>(t(),p(`button`,{key:e.id,type:`button`,role:`tab`,"aria-selected":O.value===e.id,class:m([`px-3 py-1.5 text-[12px] font-semibold tracking-label rounded-pill transition-colors duration-base`,O.value===e.id?`bg-brand text-accent`:`text-muted hover:text-brand`]),onClick:t=>O.value=e.id},o(e.label),11,y))),128))],8,v),f(`div`,{role:`tablist`,"aria-label":n(w)(`ds.navbar.tone`),class:`inline-flex items-center p-1 gap-0.5 rounded-pill border border-line bg-paper`},[(t(!0),p(d,null,r(T.value,e=>(t(),p(`button`,{key:e.id,type:`button`,role:`tab`,"aria-selected":D.value===e.id,class:m([`inline-flex items-center gap-2 px-3 py-1.5 text-[12px] font-semibold tracking-label rounded-pill transition-colors duration-base`,D.value===e.id?`bg-brand text-accent`:`text-muted hover:text-brand`]),onClick:t=>D.value=e.id},[f(`span`,{class:`w-2.5 h-2.5 rounded-full border border-line-strong`,style:a({backgroundColor:e.swatch})},null,4),u(` `+o(e.label),1)],10,x))),128))],8,b)]),_:1},8,[`src`])]),f(`section`,null,[f(`h2`,S,o(n(w)(`ds.heading.usage`)),1),l[0]||=f(`div`,{class:`rounded-md border border-line bg-paper p-6 font-mono text-[12px] text-ink`},[f(`pre`,{class:`whitespace-pre-wrap`},`<Navbar
import{B as e,C as t,G as n,T as r,c as i,dt as a,ft as o,j as s,m as c,o as l,p as u,r as d,s as f,u as p,ut as m}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as h}from"./i18n-DhVF3Pwg.js";import{t as g}from"./SectionShell-DGDgzO-7.js";import{t as _}from"./DevicePreview-C43cRj6M.js";var v=[`aria-label`],y=[`aria-selected`,`onClick`],b=[`aria-label`],x=[`aria-selected`,`onClick`],S={class:`eyebrow mb-5`},C={__name:`NavbarSection`,setup(C){let{t:w}=h(),T=l(()=>[{id:`paper`,label:w(`ds.navbar.tone.paper`),swatch:`#ffffff`},{id:`cream`,label:w(`ds.navbar.tone.cream`),swatch:`var(--color-cream)`},{id:`brand`,label:w(`ds.navbar.tone.brand`),swatch:`var(--color-brand)`}]),E=l(()=>[{id:`standard`,label:w(`ds.navbar.layout.standard`)},{id:`floating`,label:w(`ds.navbar.layout.floating`)}]),D=e(`paper`),O=e(`standard`),k=l(()=>`/design/preview/navbar?variant=${D.value}&layout=${O.value}`);return(e,l)=>(t(),i(g,{eyebrow:n(w)(`ds.eyebrow.components`),title:n(w)(`ds.navbar.title`),description:n(w)(`ds.navbar.description`),wide:``},{default:s(()=>[f(`section`,null,[c(_,{src:k.value,initial:`mobile`,height:720},{controls:s(()=>[f(`div`,{role:`tablist`,"aria-label":n(w)(`ds.navbar.layout`),class:`inline-flex items-center p-1 gap-0.5 rounded-pill border border-line bg-paper`},[(t(!0),p(d,null,r(E.value,e=>(t(),p(`button`,{key:e.id,type:`button`,role:`tab`,"aria-selected":O.value===e.id,class:m([`px-3 py-1.5 text-[12px] font-semibold tracking-label rounded-pill transition-colors duration-base`,O.value===e.id?`bg-brand text-accent`:`text-muted hover:text-brand`]),onClick:t=>O.value=e.id},o(e.label),11,y))),128))],8,v),f(`div`,{role:`tablist`,"aria-label":n(w)(`ds.navbar.tone`),class:`inline-flex items-center p-1 gap-0.5 rounded-pill border border-line bg-paper`},[(t(!0),p(d,null,r(T.value,e=>(t(),p(`button`,{key:e.id,type:`button`,role:`tab`,"aria-selected":D.value===e.id,class:m([`inline-flex items-center gap-2 px-3 py-1.5 text-[12px] font-semibold tracking-label rounded-pill transition-colors duration-base`,D.value===e.id?`bg-brand text-accent`:`text-muted hover:text-brand`]),onClick:t=>D.value=e.id},[f(`span`,{class:`w-2.5 h-2.5 rounded-full border border-line-strong`,style:a({backgroundColor:e.swatch})},null,4),u(` `+o(e.label),1)],10,x))),128))],8,b)]),_:1},8,[`src`])]),f(`section`,null,[f(`h2`,S,o(n(w)(`ds.heading.usage`)),1),l[0]||=f(`div`,{class:`rounded-md border border-line bg-paper p-6 font-mono text-[12px] text-ink`},[f(`pre`,{class:`whitespace-pre-wrap`},`<Navbar
variant="paper"
layout="floating"
:items="[{ key: 'nav.shop', href: '/shop' }]"

1
dist/assets/PaymentStep-tq3QRwFe.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
import{C as e,G as t,O as n,c as r,ft as i,j as a,l as o,m as s,o as c,p as l,s as u,u as d,ut as f}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as p}from"./vue-router-Cyqru1db.js";import{t as m}from"./Icon-Dcq1PyHo.js";import{t as h}from"./i18n-BE70RUdV.js";import{t as g}from"./Badge-Bu_yseK_.js";import{t as _}from"./Button-DAQ-3-Uk.js";var v=[`src`,`alt`],y=[`src`,`alt`],b={class:`flex flex-col gap-3 p-6`},x={class:`flex flex-col gap-1`},S={class:`text-sm text-muted tracking-label min-h-[1lh]`},C={class:`mt-auto flex items-center justify-between gap-3 pt-2`},w={class:`font-display text-2xl font-normal text-brand`},T={key:0,class:`text-xs font-semibold tracking-label uppercase text-danger`},E={__name:`ProductCard`,props:{title:{type:String,required:!0},size:{type:String,default:``},price:{type:[String,Number],required:!0},currency:{type:String,default:``},image:{type:String,required:!0},imageAlt:{type:String,default:``},badge:{type:String,default:``},badgeVariant:{type:String,default:`accent`,validator:e=>[`neutral`,`brand`,`accent`,`subtle`,`success`,`warning`,`danger`].includes(e)},tone:{type:String,default:`paper`,validator:e=>[`paper`,`cream`].includes(e)},inStock:{type:Boolean,default:!0},href:{type:String,default:``},ctaVariant:{type:String,default:`primary`}},emits:[`add`],setup(E){function D(e){return typeof e==`string`&&e.startsWith(`/`)&&!e.startsWith(`//`)}let O=E,{t:k}=h(),A={paper:{surface:`bg-paper`,media:`bg-paper`,border:`border-line`},cream:{surface:`bg-cream`,media:`bg-paper`,border:`border-line`}},j=c(()=>A[O.tone]),M=c(()=>typeof O.price==`number`?`${O.currency} ${O.price.toFixed(2).replace(`.`,`,`)}`:`${O.currency} ${O.price}`);return(c,h)=>(e(),d(`article`,{class:f([`group flex flex-col overflow-hidden rounded-md border transition-all duration-base ease-out`,j.value.surface,j.value.border,`hover:-translate-y-1 hover:shadow-md hover:border-brand-soft`])},[E.href&&D(E.href)?(e(),r(t(p),{key:0,to:E.href,class:f([`relative flex items-center justify-center h-40 md:h-48 overflow-hidden`,j.value.media])},{default:a(()=>[E.badge?(e(),r(g,{key:0,variant:E.badgeVariant,class:`absolute top-4 left-4 z-[1]`},{default:a(()=>[l(i(E.badge),1)]),_:1},8,[`variant`])):o(``,!0),u(`img`,{src:E.image,alt:E.imageAlt||E.title,loading:`lazy`,decoding:`async`,class:`max-w-[55%] max-h-[80%] object-contain transition-transform duration-slow ease-out group-hover:scale-105`},null,8,v)]),_:1},8,[`to`,`class`])):(e(),r(n(E.href?`a`:`div`),{key:1,href:E.href||null,class:f([`relative flex items-center justify-center h-40 md:h-48 overflow-hidden`,j.value.media])},{default:a(()=>[E.badge?(e(),r(g,{key:0,variant:E.badgeVariant,class:`absolute top-4 left-4 z-[1]`},{default:a(()=>[l(i(E.badge),1)]),_:1},8,[`variant`])):o(``,!0),u(`img`,{src:E.image,alt:E.imageAlt||E.title,loading:`lazy`,decoding:`async`,class:`max-w-[55%] max-h-[80%] object-contain transition-transform duration-slow ease-out group-hover:scale-105`},null,8,y)]),_:1},8,[`href`,`class`])),u(`div`,b,[u(`div`,x,[E.href&&D(E.href)?(e(),r(t(p),{key:0,to:E.href,class:f([`font-display text-xl font-normal leading-tight text-ink min-h-[2lh]`,`hover:text-brand transition-colors duration-base`])},{default:a(()=>[l(i(E.title),1)]),_:1},8,[`to`])):(e(),r(n(E.href?`a`:`h3`),{key:1,href:E.href||null,class:f([`font-display text-xl font-normal leading-tight text-ink min-h-[2lh]`,E.href?`hover:text-brand transition-colors duration-base`:``])},{default:a(()=>[l(i(E.title),1)]),_:1},8,[`href`,`class`])),u(`p`,S,i(E.size||`\xA0`),1)]),u(`div`,C,[u(`span`,w,i(M.value),1),E.inStock?o(``,!0):(e(),d(`span`,T,i(t(k)(`ds.product.outOfStock`)),1))]),s(_,{variant:E.ctaVariant,size:`md`,block:``,disabled:!E.inStock,onClick:h[0]||=e=>c.$emit(`add`)},{before:a(()=>[s(m,{name:`plus`,size:16})]),default:a(()=>[l(` `+i(t(k)(`ds.buttons.addToCart`)),1)]),_:1},8,[`variant`,`disabled`])])],2))}};export{E as t};
import{C as e,G as t,O as n,c as r,ft as i,j as a,l as o,m as s,o as c,p as l,s as u,u as d,ut as f}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as p}from"./vue-router-Cyqru1db.js";import{t as m}from"./Icon-Dcq1PyHo.js";import{t as h}from"./i18n-DhVF3Pwg.js";import{t as g}from"./Badge-Bu_yseK_.js";import{t as _}from"./Button-D1Rp2Xe1.js";var v=[`src`,`alt`],y=[`src`,`alt`],b={class:`flex flex-col gap-3 p-6`},x={class:`flex flex-col gap-1`},S={class:`text-sm text-muted tracking-label min-h-[1lh]`},C={class:`mt-auto flex items-center justify-between gap-3 pt-2`},w={class:`font-display text-2xl font-normal text-brand`},T={key:0,class:`text-xs font-semibold tracking-label uppercase text-danger`},E={__name:`ProductCard`,props:{title:{type:String,required:!0},size:{type:String,default:``},price:{type:[String,Number],required:!0},currency:{type:String,default:``},image:{type:String,required:!0},imageAlt:{type:String,default:``},badge:{type:String,default:``},badgeVariant:{type:String,default:`accent`,validator:e=>[`neutral`,`brand`,`accent`,`subtle`,`success`,`warning`,`danger`].includes(e)},tone:{type:String,default:`paper`,validator:e=>[`paper`,`cream`].includes(e)},inStock:{type:Boolean,default:!0},href:{type:String,default:``},ctaVariant:{type:String,default:`primary`}},emits:[`add`],setup(E){function D(e){return typeof e==`string`&&e.startsWith(`/`)&&!e.startsWith(`//`)}let O=E,{t:k}=h(),A={paper:{surface:`bg-paper`,media:`bg-paper`,border:`border-line`},cream:{surface:`bg-cream`,media:`bg-paper`,border:`border-line`}},j=c(()=>A[O.tone]),M=c(()=>typeof O.price==`number`?`${O.currency} ${O.price.toFixed(2).replace(`.`,`,`)}`:`${O.currency} ${O.price}`);return(c,h)=>(e(),d(`article`,{class:f([`group flex flex-col overflow-hidden rounded-md border transition-all duration-base ease-out`,j.value.surface,j.value.border,`hover:-translate-y-1 hover:shadow-md hover:border-brand-soft`])},[E.href&&D(E.href)?(e(),r(t(p),{key:0,to:E.href,class:f([`relative flex items-center justify-center h-40 md:h-48 overflow-hidden`,j.value.media])},{default:a(()=>[E.badge?(e(),r(g,{key:0,variant:E.badgeVariant,class:`absolute top-4 left-4 z-[1]`},{default:a(()=>[l(i(E.badge),1)]),_:1},8,[`variant`])):o(``,!0),u(`img`,{src:E.image,alt:E.imageAlt||E.title,loading:`lazy`,decoding:`async`,class:`max-w-[55%] max-h-[80%] object-contain transition-transform duration-slow ease-out group-hover:scale-105`},null,8,v)]),_:1},8,[`to`,`class`])):(e(),r(n(E.href?`a`:`div`),{key:1,href:E.href||null,class:f([`relative flex items-center justify-center h-40 md:h-48 overflow-hidden`,j.value.media])},{default:a(()=>[E.badge?(e(),r(g,{key:0,variant:E.badgeVariant,class:`absolute top-4 left-4 z-[1]`},{default:a(()=>[l(i(E.badge),1)]),_:1},8,[`variant`])):o(``,!0),u(`img`,{src:E.image,alt:E.imageAlt||E.title,loading:`lazy`,decoding:`async`,class:`max-w-[55%] max-h-[80%] object-contain transition-transform duration-slow ease-out group-hover:scale-105`},null,8,y)]),_:1},8,[`href`,`class`])),u(`div`,b,[u(`div`,x,[E.href&&D(E.href)?(e(),r(t(p),{key:0,to:E.href,class:f([`font-display text-xl font-normal leading-tight text-ink min-h-[2lh]`,`hover:text-brand transition-colors duration-base`])},{default:a(()=>[l(i(E.title),1)]),_:1},8,[`to`])):(e(),r(n(E.href?`a`:`h3`),{key:1,href:E.href||null,class:f([`font-display text-xl font-normal leading-tight text-ink min-h-[2lh]`,E.href?`hover:text-brand transition-colors duration-base`:``])},{default:a(()=>[l(i(E.title),1)]),_:1},8,[`href`,`class`])),u(`p`,S,i(E.size||`\xA0`),1)]),u(`div`,C,[u(`span`,w,i(M.value),1),E.inStock?o(``,!0):(e(),d(`span`,T,i(t(k)(`ds.product.outOfStock`)),1))]),s(_,{variant:E.ctaVariant,size:`md`,block:``,disabled:!E.inStock,onClick:h[0]||=e=>c.$emit(`add`)},{before:a(()=>[s(m,{name:`plus`,size:16})]),default:a(()=>[l(` `+i(t(k)(`ds.buttons.addToCart`)),1)]),_:1},8,[`variant`,`disabled`])])],2))}};export{E as t};

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
import{B as e,C as t,G as n,c as r,ft as i,j as a,l as o,m as s,p as c,s as l,u}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as d}from"./i18n-BE70RUdV.js";import{t as f}from"./ProductCard-Bt8OI8FF.js";import{t as p}from"./SectionShell-CU6IpKMJ.js";var m={class:`eyebrow mb-5`},h={class:`grid sm:grid-cols-2 lg:grid-cols-3 gap-6`},g={key:0,class:`mt-5 text-sm text-muted`},_={class:`font-mono text-[12px]`},v={class:`eyebrow mb-5`},y={class:`grid sm:grid-cols-2 lg:grid-cols-3 gap-6`},b={class:`eyebrow mb-5`},x=`/products/kaiser-natron-pulver-250-g-grosspackung.webp`,S={__name:`ProductsSection`,setup(S){let{t:C}=d(),w=e(``);function T(e){w.value=e,setTimeout(()=>{w.value===e&&(w.value=``)},2e3)}return(e,d)=>(t(),r(p,{eyebrow:n(C)(`ds.eyebrow.components`),title:n(C)(`ds.product.title`),description:n(C)(`ds.product.description`)},{default:a(()=>[l(`section`,null,[l(`h2`,m,i(n(C)(`ds.heading.default`)),1),l(`div`,h,[s(f,{title:`Kaiser-Natron® Pulver`,size:`250 g Großpackung`,price:4.49,image:x,"image-alt":`Kaiser-Natron Pulver 250 g Großpackung`,href:`#`,onAdd:d[0]||=e=>T(`pulver-250`)}),s(f,{title:`Kaiser-Natron® Pulver`,size:`250 g Großpackung`,price:4.49,image:x,badge:n(C)(`ds.badges.featured`),"badge-variant":`accent`,href:`#`,onAdd:d[1]||=e=>T(`pulver-250-featured`)},null,8,[`badge`]),s(f,{title:`Kaiser-Natron® Pulver`,size:`250 g Großpackung`,price:4.49,image:x,tone:`cream`,href:`#`,onAdd:d[2]||=e=>T(`pulver-250-cream`)})]),w.value?(t(),u(`p`,g,[c(i(n(C)(`ds.product.added`))+`: `,1),l(`code`,_,i(w.value),1)])):o(``,!0)]),l(`section`,null,[l(`h2`,v,i(n(C)(`ds.heading.states`)),1),l(`div`,y,[s(f,{title:`Kaiser-Natron® Pulver`,size:`250 g Großpackung`,price:4.49,image:x,"in-stock":!1}),s(f,{title:`Kaiser-Natron® Pulver`,size:`250 g Großpackung`,price:4.49,image:x,badge:n(C)(`ds.badges.newRelease`),"badge-variant":`brand`,href:`#`},null,8,[`badge`])])]),l(`section`,null,[l(`h2`,b,i(n(C)(`ds.heading.usage`)),1),d[3]||=l(`div`,{class:`rounded-md border border-line bg-paper p-6 font-mono text-[12px] text-ink`},[l(`pre`,{class:`whitespace-pre-wrap`},`<ProductCard
import{B as e,C as t,G as n,c as r,ft as i,j as a,l as o,m as s,p as c,s as l,u}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as d}from"./i18n-DhVF3Pwg.js";import{t as f}from"./ProductCard-aSVEFTCo.js";import{t as p}from"./SectionShell-DGDgzO-7.js";var m={class:`eyebrow mb-5`},h={class:`grid sm:grid-cols-2 lg:grid-cols-3 gap-6`},g={key:0,class:`mt-5 text-sm text-muted`},_={class:`font-mono text-[12px]`},v={class:`eyebrow mb-5`},y={class:`grid sm:grid-cols-2 lg:grid-cols-3 gap-6`},b={class:`eyebrow mb-5`},x=`/products/kaiser-natron-pulver-250-g-grosspackung.webp`,S={__name:`ProductsSection`,setup(S){let{t:C}=d(),w=e(``);function T(e){w.value=e,setTimeout(()=>{w.value===e&&(w.value=``)},2e3)}return(e,d)=>(t(),r(p,{eyebrow:n(C)(`ds.eyebrow.components`),title:n(C)(`ds.product.title`),description:n(C)(`ds.product.description`)},{default:a(()=>[l(`section`,null,[l(`h2`,m,i(n(C)(`ds.heading.default`)),1),l(`div`,h,[s(f,{title:`Kaiser-Natron® Pulver`,size:`250 g Großpackung`,price:4.49,image:x,"image-alt":`Kaiser-Natron Pulver 250 g Großpackung`,href:`#`,onAdd:d[0]||=e=>T(`pulver-250`)}),s(f,{title:`Kaiser-Natron® Pulver`,size:`250 g Großpackung`,price:4.49,image:x,badge:n(C)(`ds.badges.featured`),"badge-variant":`accent`,href:`#`,onAdd:d[1]||=e=>T(`pulver-250-featured`)},null,8,[`badge`]),s(f,{title:`Kaiser-Natron® Pulver`,size:`250 g Großpackung`,price:4.49,image:x,tone:`cream`,href:`#`,onAdd:d[2]||=e=>T(`pulver-250-cream`)})]),w.value?(t(),u(`p`,g,[c(i(n(C)(`ds.product.added`))+`: `,1),l(`code`,_,i(w.value),1)])):o(``,!0)]),l(`section`,null,[l(`h2`,v,i(n(C)(`ds.heading.states`)),1),l(`div`,y,[s(f,{title:`Kaiser-Natron® Pulver`,size:`250 g Großpackung`,price:4.49,image:x,"in-stock":!1}),s(f,{title:`Kaiser-Natron® Pulver`,size:`250 g Großpackung`,price:4.49,image:x,badge:n(C)(`ds.badges.newRelease`),"badge-variant":`brand`,href:`#`},null,8,[`badge`])])]),l(`section`,null,[l(`h2`,b,i(n(C)(`ds.heading.usage`)),1),d[3]||=l(`div`,{class:`rounded-md border border-line bg-paper p-6 font-mono text-[12px] text-ink`},[l(`pre`,{class:`whitespace-pre-wrap`},`<ProductCard
title="Kaiser-Natron® Pulver"
size="250 g Großpackung"
:price="4.49"

View File

@@ -1,4 +1,4 @@
import{B as e,C as t,G as n,c as r,ft as i,j as a,m as o,s}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as c}from"./i18n-BE70RUdV.js";import{t as l}from"./QuantityStepper-CBTfbpFE.js";import{t as u}from"./SectionShell-CU6IpKMJ.js";var d={class:`eyebrow mb-5`},f={class:`flex flex-wrap items-center gap-6`},p={class:`eyebrow mb-5`},m={class:`flex flex-wrap items-center gap-6`},h={class:`flex flex-col gap-2`},g={class:`eyebrow`},_={class:`flex flex-col gap-2`},v={class:`eyebrow`},y={class:`flex flex-col gap-2`},b={class:`eyebrow`},x={class:`eyebrow mb-5`},S={__name:`QuantityStepperSection`,setup(S){let{t:C}=c(),w=e(1),T=e(3),E=e(5),D=e(2);return(e,c)=>(t(),r(u,{eyebrow:n(C)(`ds.eyebrow.components`),title:n(C)(`ds.qtyStepper.title`),description:n(C)(`ds.qtyStepper.description`)},{default:a(()=>[s(`section`,null,[s(`h2`,d,i(n(C)(`ds.heading.sizes`)),1),s(`div`,f,[o(l,{modelValue:w.value,"onUpdate:modelValue":c[0]||=e=>w.value=e,size:`sm`,"decrease-label":n(C)(`cart.qty.decrease`),"increase-label":n(C)(`cart.qty.increase`)},null,8,[`modelValue`,`decrease-label`,`increase-label`]),o(l,{modelValue:T.value,"onUpdate:modelValue":c[1]||=e=>T.value=e,size:`md`,"decrease-label":n(C)(`cart.qty.decrease`),"increase-label":n(C)(`cart.qty.increase`)},null,8,[`modelValue`,`decrease-label`,`increase-label`])])]),s(`section`,null,[s(`h2`,p,i(n(C)(`ds.heading.states`)),1),s(`div`,m,[s(`div`,h,[s(`span`,g,i(n(C)(`ds.qtyStepper.minBoundary`)),1),o(l,{"model-value":0,min:0,"decrease-label":n(C)(`cart.qty.decrease`),"increase-label":n(C)(`cart.qty.increase`)},null,8,[`decrease-label`,`increase-label`])]),s(`div`,_,[s(`span`,v,i(n(C)(`ds.qtyStepper.maxBoundary`)),1),o(l,{modelValue:E.value,"onUpdate:modelValue":c[2]||=e=>E.value=e,max:5,"decrease-label":n(C)(`cart.qty.decrease`),"increase-label":n(C)(`cart.qty.increase`)},null,8,[`modelValue`,`decrease-label`,`increase-label`])]),s(`div`,y,[s(`span`,b,i(n(C)(`ds.buttons.disabled`)),1),o(l,{modelValue:D.value,"onUpdate:modelValue":c[3]||=e=>D.value=e,disabled:``,"decrease-label":n(C)(`cart.qty.decrease`),"increase-label":n(C)(`cart.qty.increase`)},null,8,[`modelValue`,`decrease-label`,`increase-label`])])])]),s(`section`,null,[s(`h2`,x,i(n(C)(`ds.heading.usage`)),1),c[4]||=s(`div`,{class:`rounded-md border border-line bg-paper p-6 font-mono text-[12px] text-ink`},[s(`pre`,{class:`whitespace-pre-wrap`},`<QuantityStepper
import{B as e,C as t,G as n,c as r,ft as i,j as a,m as o,s}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as c}from"./i18n-DhVF3Pwg.js";import{t as l}from"./QuantityStepper-DEp73rTJ.js";import{t as u}from"./SectionShell-DGDgzO-7.js";var d={class:`eyebrow mb-5`},f={class:`flex flex-wrap items-center gap-6`},p={class:`eyebrow mb-5`},m={class:`flex flex-wrap items-center gap-6`},h={class:`flex flex-col gap-2`},g={class:`eyebrow`},_={class:`flex flex-col gap-2`},v={class:`eyebrow`},y={class:`flex flex-col gap-2`},b={class:`eyebrow`},x={class:`eyebrow mb-5`},S={__name:`QuantityStepperSection`,setup(S){let{t:C}=c(),w=e(1),T=e(3),E=e(5),D=e(2);return(e,c)=>(t(),r(u,{eyebrow:n(C)(`ds.eyebrow.components`),title:n(C)(`ds.qtyStepper.title`),description:n(C)(`ds.qtyStepper.description`)},{default:a(()=>[s(`section`,null,[s(`h2`,d,i(n(C)(`ds.heading.sizes`)),1),s(`div`,f,[o(l,{modelValue:w.value,"onUpdate:modelValue":c[0]||=e=>w.value=e,size:`sm`,"decrease-label":n(C)(`cart.qty.decrease`),"increase-label":n(C)(`cart.qty.increase`)},null,8,[`modelValue`,`decrease-label`,`increase-label`]),o(l,{modelValue:T.value,"onUpdate:modelValue":c[1]||=e=>T.value=e,size:`md`,"decrease-label":n(C)(`cart.qty.decrease`),"increase-label":n(C)(`cart.qty.increase`)},null,8,[`modelValue`,`decrease-label`,`increase-label`])])]),s(`section`,null,[s(`h2`,p,i(n(C)(`ds.heading.states`)),1),s(`div`,m,[s(`div`,h,[s(`span`,g,i(n(C)(`ds.qtyStepper.minBoundary`)),1),o(l,{"model-value":0,min:0,"decrease-label":n(C)(`cart.qty.decrease`),"increase-label":n(C)(`cart.qty.increase`)},null,8,[`decrease-label`,`increase-label`])]),s(`div`,_,[s(`span`,v,i(n(C)(`ds.qtyStepper.maxBoundary`)),1),o(l,{modelValue:E.value,"onUpdate:modelValue":c[2]||=e=>E.value=e,max:5,"decrease-label":n(C)(`cart.qty.decrease`),"increase-label":n(C)(`cart.qty.increase`)},null,8,[`modelValue`,`decrease-label`,`increase-label`])]),s(`div`,y,[s(`span`,b,i(n(C)(`ds.buttons.disabled`)),1),o(l,{modelValue:D.value,"onUpdate:modelValue":c[3]||=e=>D.value=e,disabled:``,"decrease-label":n(C)(`cart.qty.decrease`),"increase-label":n(C)(`cart.qty.increase`)},null,8,[`modelValue`,`decrease-label`,`increase-label`])])])]),s(`section`,null,[s(`h2`,x,i(n(C)(`ds.heading.usage`)),1),c[4]||=s(`div`,{class:`rounded-md border border-line bg-paper p-6 font-mono text-[12px] text-ink`},[s(`pre`,{class:`whitespace-pre-wrap`},`<QuantityStepper
v-model="quantity"
:min="0"
:max="10"

View File

@@ -1 +1 @@
import{C as e,G as t,T as n,c as r,dt as i,ft as a,j as o,r as s,s as c,u as l}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as u}from"./i18n-BE70RUdV.js";import{t as d}from"./SectionShell-CU6IpKMJ.js";var f={class:`grid grid-cols-2 sm:grid-cols-3 gap-6`},p={class:`font-mono text-[12px] text-ink block`},m={class:`text-[12px] text-muted mt-1`},h={__name:`RadiiSection`,setup(h){let{t:g}=u(),_=[{name:`xs`,value:`6px`},{name:`sm`,value:`10px`},{name:`md`,value:`16px`},{name:`lg`,value:`20px`},{name:`xl`,value:`28px`},{name:`pill`,value:`100px`}];return(u,h)=>(e(),r(d,{eyebrow:t(g)(`ds.eyebrow.tokens`),title:t(g)(`ds.radii.title`),description:t(g)(`ds.radii.description`)},{default:o(()=>[c(`div`,f,[(e(),l(s,null,n(_,e=>c(`div`,{key:e.name,class:`flex flex-col items-center`},[c(`div`,{class:`h-32 w-full bg-paper border border-line mb-4 shadow-sm`,style:i({borderRadius:`var(--radius-${e.name})`})},null,4),c(`code`,p,`--radius-`+a(e.name),1),c(`span`,m,a(e.value),1)])),64))])]),_:1},8,[`eyebrow`,`title`,`description`]))}};export{h as default};
import{C as e,G as t,T as n,c as r,dt as i,ft as a,j as o,r as s,s as c,u as l}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as u}from"./i18n-DhVF3Pwg.js";import{t as d}from"./SectionShell-DGDgzO-7.js";var f={class:`grid grid-cols-2 sm:grid-cols-3 gap-6`},p={class:`font-mono text-[12px] text-ink block`},m={class:`text-[12px] text-muted mt-1`},h={__name:`RadiiSection`,setup(h){let{t:g}=u(),_=[{name:`xs`,value:`6px`},{name:`sm`,value:`10px`},{name:`md`,value:`16px`},{name:`lg`,value:`20px`},{name:`xl`,value:`28px`},{name:`pill`,value:`100px`}];return(u,h)=>(e(),r(d,{eyebrow:t(g)(`ds.eyebrow.tokens`),title:t(g)(`ds.radii.title`),description:t(g)(`ds.radii.description`)},{default:o(()=>[c(`div`,f,[(e(),l(s,null,n(_,e=>c(`div`,{key:e.name,class:`flex flex-col items-center`},[c(`div`,{class:`h-32 w-full bg-paper border border-line mb-4 shadow-sm`,style:i({borderRadius:`var(--radius-${e.name})`})},null,4),c(`code`,p,`--radius-`+a(e.name),1),c(`span`,m,a(e.value),1)])),64))])]),_:1},8,[`eyebrow`,`title`,`description`]))}};export{h as default};

1
dist/assets/RegisterPage-rTxFB_xl.js vendored Normal file
View File

@@ -0,0 +1 @@
import{B as e,C as t,G as n,M as r,S as i,ft as a,j as o,l as s,m as c,o as l,p as u,r as ee,s as d,u as f,x as p}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{r as m,s as h}from"./runtime-dom.esm-bundler-BrsyFPJA.js";import{a as g,o as _,t as v}from"./vue-router-Cyqru1db.js";import{t as y}from"./i18n-DhVF3Pwg.js";import{r as te}from"./products-BqW5PUnm.js";import{n as ne,p as b}from"./api-DlB23x7f.js";import{t as x}from"./Navbar-dl8ShEaS.js";import{t as S}from"./Button-D1Rp2Xe1.js";import{t as C}from"./Footer-DX-ml2dd.js";import{t as w}from"./Input-os2-AnNh.js";var T={class:`bg-cream text-ink min-h-svh`},E={class:`mx-auto w-full max-w-md px-6 py-14 sm:px-8 sm:py-20 md:py-24`},D={class:`flex flex-col gap-3 mb-8 text-center`},O={class:`eyebrow`},k={class:`font-display font-normal leading-[1.05] tracking-tight text-ink text-headline-md`},A={class:`text-[14px] text-muted`},j={class:`grid gap-5 md:grid-cols-2`},M={class:`inline-flex items-center gap-3 cursor-pointer select-none`},N={class:`text-sm text-ink`},P={key:0,class:`text-sm text-danger`,role:`alert`,"aria-live":`polite`},F={class:`mt-8 text-center text-[14px] text-muted`},I={__name:`RegisterPage`,setup(I){let{t:L}=y(),R=b(),re=g(),z=_(),B=[{key:`nav.shop`,href:`/shop`},{key:`nav.bundles`,href:`/#bundles`},{key:`nav.revitalization`,href:`/#revitalize`},{key:`nav.about`,href:`/#about`}],V=e(``),H=e(``),U=e(``),W=e(``),G=e(``),K=e(!1),q=e(!1),J=e(``),Y=l(()=>W.value===G.value),X=l(()=>!U.value||W.value.length<8||!Y.value);async function ie(){J.value=``,q.value=!0;try{await ne({email:U.value,password:W.value,firstName:V.value,lastName:H.value,acceptsMarketing:K.value});let e=String(re.query.next||`/`);z.push(e)}catch(e){J.value=e?.message||L(`checkout.error.generic`)}finally{q.value=!1}}let Z=e(null),Q=null;function $(){let e=Z.value,t=e&&(e.$el||e);if(!t||typeof window>`u`)return;let n=Math.round(t.getBoundingClientRect().height);document.documentElement.style.setProperty(`--nav-h`,`${n}px`)}return i(()=>{if($(),typeof ResizeObserver<`u`&&Z.value){let e=Z.value.$el||Z.value;Q=new ResizeObserver($),Q.observe(e)}window.addEventListener(`resize`,$)}),p(()=>{Q&&Q.disconnect(),typeof window<`u`&&window.removeEventListener(`resize`,$)}),(e,i)=>(t(),f(ee,null,[c(x,{ref_key:`navRef`,ref:Z,variant:`cream`,layout:`standard`,items:B,"cart-count":n(R).count,products:n(te)},null,8,[`cart-count`,`products`]),d(`main`,T,[d(`div`,E,[d(`header`,D,[d(`p`,O,a(n(L)(`auth.register.eyebrow`)),1),d(`h1`,k,a(n(L)(`auth.register.title`)),1),d(`p`,A,a(n(L)(`auth.register.sub`)),1)]),d(`form`,{class:`flex flex-col gap-5 rounded-md border border-line bg-paper p-6 md:p-8`,novalidate:``,onSubmit:h(ie,[`prevent`])},[d(`div`,j,[c(w,{modelValue:V.value,"onUpdate:modelValue":i[0]||=e=>V.value=e,label:n(L)(`checkout.field.firstName`)},null,8,[`modelValue`,`label`]),c(w,{modelValue:H.value,"onUpdate:modelValue":i[1]||=e=>H.value=e,label:n(L)(`checkout.field.lastName`)},null,8,[`modelValue`,`label`])]),c(w,{modelValue:U.value,"onUpdate:modelValue":i[2]||=e=>U.value=e,label:n(L)(`checkout.field.email`),type:`email`,required:``,placeholder:n(L)(`checkout.placeholder.email`)},null,8,[`modelValue`,`label`,`placeholder`]),c(w,{modelValue:W.value,"onUpdate:modelValue":i[3]||=e=>W.value=e,label:n(L)(`checkout.field.password`),type:`password`,required:``,hint:n(L)(`checkout.hint.password`)},null,8,[`modelValue`,`label`,`hint`]),c(w,{modelValue:G.value,"onUpdate:modelValue":i[4]||=e=>G.value=e,label:n(L)(`checkout.field.passwordConfirm`),type:`password`,required:``,error:G.value&&!Y.value?n(L)(`checkout.error.passwordMismatch`):``},null,8,[`modelValue`,`label`,`error`]),d(`label`,M,[r(d(`input`,{"onUpdate:modelValue":i[5]||=e=>K.value=e,type:`checkbox`,class:`w-5 h-5 rounded-xs border border-line accent-brand`},null,512),[[m,K.value]]),d(`span`,N,a(n(L)(`checkout.field.marketing`)),1)]),J.value?(t(),f(`p`,P,a(J.value),1)):s(``,!0),c(S,{type:`submit`,variant:`primary`,size:`lg`,block:``,loading:q.value,disabled:X.value},{default:o(()=>[u(a(n(L)(`auth.register.cta`)),1)]),_:1},8,[`loading`,`disabled`])],32),d(`p`,F,[u(a(n(L)(`auth.register.haveAccount`))+` `,1),c(n(v),{to:`/login`,class:`text-brand hover:underline`},{default:o(()=>[u(a(n(L)(`auth.register.cta.signIn`)),1)]),_:1})])])]),c(C)],64))}};export{I as default};

View File

@@ -1 +1 @@
import{C as e,T as t,c as n,dt as r,ft as i,j as a,l as o,m as s,p as c,r as l,s as u,u as d}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as f}from"./_plugin-vue_export-helper-BOai-rQB.js";import{t as p}from"./Badge-Bu_yseK_.js";import{t as m}from"./Button-DAQ-3-Uk.js";var h={id:`revitalize`,class:`bg-brand text-cream`},g={class:`mx-auto w-full max-w-6xl px-6 py-16 sm:px-8 sm:py-20 md:px-12 md:py-24 lg:px-16 lg:py-28`},_={class:`flex flex-col items-center text-center gap-6 max-w-3xl mx-auto`},v={key:1,class:`font-display font-normal leading-[1.04] tracking-tight text-headline-lg`},y={key:0,class:`italic font-light text-accent-soft`},b={key:2,class:`text-lg leading-relaxed text-cream/80 max-w-2xl`},x={key:0,class:`mt-10 md:mt-14 grid gap-6 sm:grid-cols-3 sm:gap-8 max-w-4xl mx-auto`},S={"aria-hidden":`true`,class:`rv-orbit`},C={key:0,class:`rv-orbit-center`},w={class:`font-sans text-base font-semibold text-cream leading-snug`},T={key:1,class:`mt-10 md:mt-14 flex justify-center`},E=f({__name:`Revitalization`,props:{eyebrow:{type:String,default:``},headline:{type:String,default:``},headlineEm:{type:String,default:``},sub:{type:String,default:``},features:{type:Array,default:()=>[],validator:e=>e.every(e=>typeof e==`string`||e&&typeof e==`object`&&typeof e.title==`string`)},notifyCta:{type:String,default:``}},emits:[`notify`],setup(f){return(E,D)=>(e(),d(`section`,h,[u(`div`,g,[u(`div`,_,[f.eyebrow?(e(),n(p,{key:0,variant:`accent`},{default:a(()=>[c(i(f.eyebrow),1)]),_:1})):o(``,!0),f.headline||f.headlineEm?(e(),d(`h2`,v,[c(i(f.headline)+` `,1),f.headlineEm?(e(),d(`em`,y,i(f.headlineEm),1)):o(``,!0)])):o(``,!0),f.sub?(e(),d(`p`,b,i(f.sub),1)):o(``,!0)]),f.features.length?(e(),d(`ul`,x,[(e(!0),d(l,null,t(f.features.slice(0,3),(t,n)=>(e(),d(`li`,{key:typeof t==`string`?t:t.title,class:`flex flex-col items-center gap-4 text-center`},[u(`span`,S,[u(`span`,{class:`rv-orbit-ring`,style:r({animationDelay:`${n*-5}s`})},[...D[1]||=[u(`span`,{class:`rv-orbit-dot`},null,-1)]],4),typeof t==`object`&&t.icon?(e(),d(`span`,C,i(t.icon),1)):o(``,!0)]),u(`span`,w,i(typeof t==`string`?t:t.title),1)]))),128))])):o(``,!0),f.notifyCta?(e(),d(`div`,T,[s(m,{variant:`accent`,size:`lg`,onClick:D[0]||=e=>E.$emit(`notify`)},{default:a(()=>[c(i(f.notifyCta),1)]),_:1})])):o(``,!0)])]))}},[[`__scopeId`,`data-v-04135cd6`]]);export{E as t};
import{C as e,T as t,c as n,dt as r,ft as i,j as a,l as o,m as s,p as c,r as l,s as u,u as d}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as f}from"./_plugin-vue_export-helper-BOai-rQB.js";import{t as p}from"./Badge-Bu_yseK_.js";import{t as m}from"./Button-D1Rp2Xe1.js";var h={id:`revitalize`,class:`bg-brand text-cream`},g={class:`mx-auto w-full max-w-6xl px-6 py-16 sm:px-8 sm:py-20 md:px-12 md:py-24 lg:px-16 lg:py-28`},_={class:`flex flex-col items-center text-center gap-6 max-w-3xl mx-auto`},v={key:1,class:`font-display font-normal leading-[1.04] tracking-tight text-headline-lg`},y={key:0,class:`italic font-light text-accent-soft`},b={key:2,class:`text-lg leading-relaxed text-cream/80 max-w-2xl`},x={key:0,class:`mt-10 md:mt-14 grid gap-6 sm:grid-cols-3 sm:gap-8 max-w-4xl mx-auto`},S={"aria-hidden":`true`,class:`rv-orbit`},C={key:0,class:`rv-orbit-center`},w={class:`font-sans text-base font-semibold text-cream leading-snug`},T={key:1,class:`mt-10 md:mt-14 flex justify-center`},E=f({__name:`Revitalization`,props:{eyebrow:{type:String,default:``},headline:{type:String,default:``},headlineEm:{type:String,default:``},sub:{type:String,default:``},features:{type:Array,default:()=>[],validator:e=>e.every(e=>typeof e==`string`||e&&typeof e==`object`&&typeof e.title==`string`)},notifyCta:{type:String,default:``}},emits:[`notify`],setup(f){return(E,D)=>(e(),d(`section`,h,[u(`div`,g,[u(`div`,_,[f.eyebrow?(e(),n(p,{key:0,variant:`accent`},{default:a(()=>[c(i(f.eyebrow),1)]),_:1})):o(``,!0),f.headline||f.headlineEm?(e(),d(`h2`,v,[c(i(f.headline)+` `,1),f.headlineEm?(e(),d(`em`,y,i(f.headlineEm),1)):o(``,!0)])):o(``,!0),f.sub?(e(),d(`p`,b,i(f.sub),1)):o(``,!0)]),f.features.length?(e(),d(`ul`,x,[(e(!0),d(l,null,t(f.features.slice(0,3),(t,n)=>(e(),d(`li`,{key:typeof t==`string`?t:t.title,class:`flex flex-col items-center gap-4 text-center`},[u(`span`,S,[u(`span`,{class:`rv-orbit-ring`,style:r({animationDelay:`${n*-5}s`})},[...D[1]||=[u(`span`,{class:`rv-orbit-dot`},null,-1)]],4),typeof t==`object`&&t.icon?(e(),d(`span`,C,i(t.icon),1)):o(``,!0)]),u(`span`,w,i(typeof t==`string`?t:t.title),1)]))),128))])):o(``,!0),f.notifyCta?(e(),d(`div`,T,[s(m,{variant:`accent`,size:`lg`,onClick:D[0]||=e=>E.$emit(`notify`)},{default:a(()=>[c(i(f.notifyCta),1)]),_:1})])):o(``,!0)])]))}},[[`__scopeId`,`data-v-04135cd6`]]);export{E as t};

View File

@@ -1 +1 @@
import{C as e,G as t,m as n,o as r,u as i}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as a}from"./i18n-BE70RUdV.js";import{t as o}from"./Revitalization-BBLRs3cH.js";var s={class:`min-h-screen bg-brand`},c={__name:`RevitalizationPreview`,setup(c){let{t:l}=a(),u=r(()=>[{title:l(`revit.feature.1.title`),icon:`⚗️`},{title:l(`revit.feature.2.title`),icon:`💊`},{title:l(`revit.feature.3.title`),icon:`🌿`}]);return(r,a)=>(e(),i(`div`,s,[n(o,{eyebrow:t(l)(`revit.eyebrow`),headline:t(l)(`revit.headline.a`),"headline-em":t(l)(`revit.headline.em`),sub:t(l)(`revit.sub`),features:u.value,"notify-cta":t(l)(`revit.notifyCta`)},null,8,[`eyebrow`,`headline`,`headline-em`,`sub`,`features`,`notify-cta`])]))}};export{c as default};
import{C as e,G as t,m as n,o as r,u as i}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as a}from"./i18n-DhVF3Pwg.js";import{t as o}from"./Revitalization-D0uruWe9.js";var s={class:`min-h-screen bg-brand`},c={__name:`RevitalizationPreview`,setup(c){let{t:l}=a(),u=r(()=>[{title:l(`revit.feature.1.title`),icon:`⚗️`},{title:l(`revit.feature.2.title`),icon:`💊`},{title:l(`revit.feature.3.title`),icon:`🌿`}]);return(r,a)=>(e(),i(`div`,s,[n(o,{eyebrow:t(l)(`revit.eyebrow`),headline:t(l)(`revit.headline.a`),"headline-em":t(l)(`revit.headline.em`),sub:t(l)(`revit.sub`),features:u.value,"notify-cta":t(l)(`revit.notifyCta`)},null,8,[`eyebrow`,`headline`,`headline-em`,`sub`,`features`,`notify-cta`])]))}};export{c as default};

View File

@@ -1,4 +1,4 @@
import{C as e,G as t,c as n,ft as r,j as i,m as a,s as o}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as s}from"./i18n-BE70RUdV.js";import{t as c}from"./SectionShell-CU6IpKMJ.js";import{t as l}from"./DevicePreview-9pDgIJdz.js";var u={class:`eyebrow mb-5`},d=`/design/preview/revitalization`,f={__name:`RevitalizationSection`,setup(f){let{t:p}=s();return(s,f)=>(e(),n(c,{eyebrow:t(p)(`ds.eyebrow.components`),title:t(p)(`ds.revitalization.title`),description:t(p)(`ds.revitalization.description`),wide:``},{default:i(()=>[o(`section`,null,[a(l,{src:d,initial:`desktop`,height:720})]),o(`section`,null,[o(`h2`,u,r(t(p)(`ds.heading.usage`)),1),f[0]||=o(`div`,{class:`rounded-md border border-line bg-paper p-6 font-mono text-[12px] text-ink`},[o(`pre`,{class:`whitespace-pre-wrap`},`<Revitalization
import{C as e,G as t,c as n,ft as r,j as i,m as a,s as o}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as s}from"./i18n-DhVF3Pwg.js";import{t as c}from"./SectionShell-DGDgzO-7.js";import{t as l}from"./DevicePreview-C43cRj6M.js";var u={class:`eyebrow mb-5`},d=`/design/preview/revitalization`,f={__name:`RevitalizationSection`,setup(f){let{t:p}=s();return(s,f)=>(e(),n(c,{eyebrow:t(p)(`ds.eyebrow.components`),title:t(p)(`ds.revitalization.title`),description:t(p)(`ds.revitalization.description`),wide:``},{default:i(()=>[o(`section`,null,[a(l,{src:d,initial:`desktop`,height:720})]),o(`section`,null,[o(`h2`,u,r(t(p)(`ds.heading.usage`)),1),f[0]||=o(`div`,{class:`rounded-md border border-line bg-paper p-6 font-mono text-[12px] text-ink`},[o(`pre`,{class:`whitespace-pre-wrap`},`<Revitalization
:eyebrow="t('revit.eyebrow')"
:headline="t('revit.headline.a')"
:headline-em="t('revit.headline.em')"

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
import{B as e,C as t,G as n,T as r,c as i,ft as a,j as o,m as s,p as c,r as l,s as u,u as d}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as f}from"./Icon-Dcq1PyHo.js";import{t as p}from"./i18n-BE70RUdV.js";import{t as m}from"./Search-C8nPXTO-.js";import{a as h,r as g}from"./products-BqW5PUnm.js";import{t as _}from"./Button-DAQ-3-Uk.js";import{t as v}from"./SectionShell-CU6IpKMJ.js";var y={class:`eyebrow mb-5`},b={class:`rounded-md border border-line bg-paper p-6 flex flex-col sm:flex-row sm:items-center gap-4`},x={class:`text-[13px] text-muted`},S={class:`eyebrow mb-5`},C={class:`rounded-md border border-line bg-paper divide-y divide-line overflow-hidden`},w=[`src`,`alt`],T={class:`min-w-0 flex-1`},E={class:`text-[14px] font-semibold text-ink truncate`},D={class:`text-[12px] text-muted truncate`},O={class:`shrink-0 text-[13px] font-semibold text-brand`},k={class:`eyebrow mb-5`},A=`natron pulver`,j={__name:`SearchSection`,setup(j){let{t:M}=p(),N=e(!1),P=h(A,g,4);return(e,p)=>(t(),i(v,{eyebrow:n(M)(`ds.eyebrow.components`),title:n(M)(`ds.search.title`),description:n(M)(`ds.search.description`)},{default:o(()=>[u(`section`,null,[u(`h2`,y,a(n(M)(`ds.heading.default`)),1),u(`div`,b,[s(_,{variant:`primary`,onClick:p[0]||=e=>N.value=!0},{before:o(()=>[s(f,{name:`search`,size:18})]),default:o(()=>[c(` `+a(n(M)(`ds.search.demo.label`)),1)]),_:1}),u(`p`,x,a(n(M)(`ds.search.demo.hint`)),1)])]),u(`section`,null,[u(`h2`,S,a(n(M)(`ds.heading.variants`))+` — "`+a(A)+`"`,1),u(`ul`,C,[(t(!0),d(l,null,r(n(P),e=>(t(),d(`li`,{key:e.id,class:`flex items-center gap-4 px-4 py-3`},[u(`img`,{src:e.image,alt:e.title,loading:`lazy`,decoding:`async`,class:`shrink-0 w-12 h-12 rounded-sm object-cover bg-cream`},null,8,w),u(`div`,T,[u(`p`,E,a(e.title),1),u(`p`,D,a(e.size)+` · `+a(e.category),1)]),u(`span`,O,``+a(e.price.toFixed(2).replace(`.`,`,`)),1)]))),128))])]),u(`section`,null,[u(`h2`,k,a(n(M)(`ds.heading.usage`)),1),p[2]||=u(`div`,{class:`rounded-md border border-line bg-paper p-6 font-mono text-[12px] text-ink`},[u(`pre`,{class:`whitespace-pre-wrap`},`import { products } from '@/api/products.js'
import{B as e,C as t,G as n,T as r,c as i,ft as a,j as o,m as s,p as c,r as l,s as u,u as d}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as f}from"./Icon-Dcq1PyHo.js";import{t as p}from"./i18n-DhVF3Pwg.js";import{t as m}from"./Search-CazbfuRB.js";import{a as h,r as g}from"./products-BqW5PUnm.js";import{t as _}from"./Button-D1Rp2Xe1.js";import{t as v}from"./SectionShell-DGDgzO-7.js";var y={class:`eyebrow mb-5`},b={class:`rounded-md border border-line bg-paper p-6 flex flex-col sm:flex-row sm:items-center gap-4`},x={class:`text-[13px] text-muted`},S={class:`eyebrow mb-5`},C={class:`rounded-md border border-line bg-paper divide-y divide-line overflow-hidden`},w=[`src`,`alt`],T={class:`min-w-0 flex-1`},E={class:`text-[14px] font-semibold text-ink truncate`},D={class:`text-[12px] text-muted truncate`},O={class:`shrink-0 text-[13px] font-semibold text-brand`},k={class:`eyebrow mb-5`},A=`natron pulver`,j={__name:`SearchSection`,setup(j){let{t:M}=p(),N=e(!1),P=h(A,g,4);return(e,p)=>(t(),i(v,{eyebrow:n(M)(`ds.eyebrow.components`),title:n(M)(`ds.search.title`),description:n(M)(`ds.search.description`)},{default:o(()=>[u(`section`,null,[u(`h2`,y,a(n(M)(`ds.heading.default`)),1),u(`div`,b,[s(_,{variant:`primary`,onClick:p[0]||=e=>N.value=!0},{before:o(()=>[s(f,{name:`search`,size:18})]),default:o(()=>[c(` `+a(n(M)(`ds.search.demo.label`)),1)]),_:1}),u(`p`,x,a(n(M)(`ds.search.demo.hint`)),1)])]),u(`section`,null,[u(`h2`,S,a(n(M)(`ds.heading.variants`))+` — "`+a(A)+`"`,1),u(`ul`,C,[(t(!0),d(l,null,r(n(P),e=>(t(),d(`li`,{key:e.id,class:`flex items-center gap-4 px-4 py-3`},[u(`img`,{src:e.image,alt:e.title,loading:`lazy`,decoding:`async`,class:`shrink-0 w-12 h-12 rounded-sm object-cover bg-cream`},null,8,w),u(`div`,T,[u(`p`,E,a(e.title),1),u(`p`,D,a(e.size)+` · `+a(e.category),1)]),u(`span`,O,``+a(e.price.toFixed(2).replace(`.`,`,`)),1)]))),128))])]),u(`section`,null,[u(`h2`,k,a(n(M)(`ds.heading.usage`)),1),p[2]||=u(`div`,{class:`rounded-md border border-line bg-paper p-6 font-mono text-[12px] text-ink`},[u(`pre`,{class:`whitespace-pre-wrap`},`import { products } from '@/api/products.js'
import Search from '@/design-system/components/Search.vue'
const open = ref(false)

View File

@@ -1 +1 @@
import{C as e,G as t,T as n,c as r,dt as i,ft as a,j as o,o as s,r as c,s as l,u}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as d}from"./i18n-BE70RUdV.js";import{t as f}from"./SectionShell-CU6IpKMJ.js";var p={class:`grid sm:grid-cols-3 gap-8`},m={class:`font-mono text-[12px] text-ink block`},h={class:`text-[13px] text-muted mt-1`},g={__name:`ShadowsSection`,setup(g){let{t:_}=d(),v=s(()=>[{name:`sm`,css:`var(--shadow-sm)`,note:_(`ds.shadows.sm.note`)},{name:`md`,css:`var(--shadow-md)`,note:_(`ds.shadows.md.note`)},{name:`lg`,css:`var(--shadow-lg)`,note:_(`ds.shadows.lg.note`)}]);return(s,d)=>(e(),r(f,{eyebrow:t(_)(`ds.eyebrow.tokens`),title:t(_)(`ds.shadows.title`),description:t(_)(`ds.shadows.description`)},{default:o(()=>[l(`div`,p,[(e(!0),u(c,null,n(v.value,t=>(e(),u(`div`,{key:t.name,class:`space-y-4`},[l(`div`,{class:`h-36 rounded-md bg-paper`,style:i({boxShadow:t.css})},null,4),l(`div`,null,[l(`code`,m,`--shadow-`+a(t.name),1),l(`p`,h,a(t.note),1)])]))),128))])]),_:1},8,[`eyebrow`,`title`,`description`]))}};export{g as default};
import{C as e,G as t,T as n,c as r,dt as i,ft as a,j as o,o as s,r as c,s as l,u}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as d}from"./i18n-DhVF3Pwg.js";import{t as f}from"./SectionShell-DGDgzO-7.js";var p={class:`grid sm:grid-cols-3 gap-8`},m={class:`font-mono text-[12px] text-ink block`},h={class:`text-[13px] text-muted mt-1`},g={__name:`ShadowsSection`,setup(g){let{t:_}=d(),v=s(()=>[{name:`sm`,css:`var(--shadow-sm)`,note:_(`ds.shadows.sm.note`)},{name:`md`,css:`var(--shadow-md)`,note:_(`ds.shadows.md.note`)},{name:`lg`,css:`var(--shadow-lg)`,note:_(`ds.shadows.lg.note`)}]);return(s,d)=>(e(),r(f,{eyebrow:t(_)(`ds.eyebrow.tokens`),title:t(_)(`ds.shadows.title`),description:t(_)(`ds.shadows.description`)},{default:o(()=>[l(`div`,p,[(e(!0),u(c,null,n(v.value,t=>(e(),u(`div`,{key:t.name,class:`space-y-4`},[l(`div`,{class:`h-36 rounded-md bg-paper`,style:i({boxShadow:t.css})},null,4),l(`div`,null,[l(`code`,m,`--shadow-`+a(t.name),1),l(`p`,h,a(t.note),1)])]))),128))])]),_:1},8,[`eyebrow`,`title`,`description`]))}};export{g as default};

1
dist/assets/ShippingStep-B5EDb2dg.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
import{C as e,G as t,T as n,c as r,ft as i,j as a,m as o,r as s,s as c,u as l,ut as u}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as d}from"./i18n-BE70RUdV.js";import{t as f}from"./SectionShell-CU6IpKMJ.js";import{t as p}from"./Card-C-QeZ_to.js";var m={class:`grid md:grid-cols-2 gap-6`},h={class:`eyebrow mb-3`},g={class:`text-[14px] text-muted leading-relaxed`},_={class:`eyebrow mb-3`},v={class:`text-[14px] text-muted leading-relaxed`},y={class:`eyebrow mb-6`},b={class:`divide-y divide-line rounded-md border border-line bg-paper px-6`},x={class:`font-mono text-[11px] text-muted w-24 shrink-0`},S={__name:`TypographySection`,setup(S){let{t:C}=d(),w=[`xs`,`sm`,`base`,`lg`,`xl`,`2xl`,`3xl`,`4xl`,`5xl`];return(d,S)=>(e(),r(f,{eyebrow:t(C)(`ds.eyebrow.tokens`),title:t(C)(`ds.typography.title`),description:t(C)(`ds.typography.description`)},{default:a(()=>[c(`section`,m,[o(p,{tone:`paper`},{default:a(()=>[c(`p`,h,i(t(C)(`ds.heading.display`)),1),S[0]||=c(`p`,{class:`font-display text-5xl font-normal leading-[1.05] mb-3`},`Fraunces`,-1),c(`p`,g,i(t(C)(`ds.typography.serifDesc`)),1),S[1]||=c(`code`,{class:`font-mono text-[11px] text-muted block mt-5`},`var(--font-serif)`,-1)]),_:1}),o(p,{tone:`paper`},{default:a(()=>[c(`p`,_,i(t(C)(`ds.heading.body`)),1),S[2]||=c(`p`,{class:`font-sans text-5xl font-medium leading-[1.05] mb-3`},`DM Sans`,-1),c(`p`,v,i(t(C)(`ds.typography.sansDesc`)),1),S[3]||=c(`code`,{class:`font-mono text-[11px] text-muted block mt-5`},`var(--font-sans)`,-1)]),_:1})]),c(`section`,null,[c(`h2`,y,i(t(C)(`ds.heading.scale`)),1),c(`div`,b,[(e(),l(s,null,n(w,e=>c(`div`,{key:e,class:`flex items-baseline gap-8 py-4`},[c(`code`,x,`text-`+i(e),1),c(`span`,{class:u(`text-${e}`)},i(t(C)(`ds.typography.sample`)),3)])),64))])])]),_:1},8,[`eyebrow`,`title`,`description`]))}};export{S as default};
import{C as e,G as t,T as n,c as r,ft as i,j as a,m as o,r as s,s as c,u as l,ut as u}from"./runtime-core.esm-bundler-DTXUv7Wx.js";import{t as d}from"./i18n-DhVF3Pwg.js";import{t as f}from"./SectionShell-DGDgzO-7.js";import{t as p}from"./Card-DcusAcLZ.js";var m={class:`grid md:grid-cols-2 gap-6`},h={class:`eyebrow mb-3`},g={class:`text-[14px] text-muted leading-relaxed`},_={class:`eyebrow mb-3`},v={class:`text-[14px] text-muted leading-relaxed`},y={class:`eyebrow mb-6`},b={class:`divide-y divide-line rounded-md border border-line bg-paper px-6`},x={class:`font-mono text-[11px] text-muted w-24 shrink-0`},S={__name:`TypographySection`,setup(S){let{t:C}=d(),w=[`xs`,`sm`,`base`,`lg`,`xl`,`2xl`,`3xl`,`4xl`,`5xl`];return(d,S)=>(e(),r(f,{eyebrow:t(C)(`ds.eyebrow.tokens`),title:t(C)(`ds.typography.title`),description:t(C)(`ds.typography.description`)},{default:a(()=>[c(`section`,m,[o(p,{tone:`paper`},{default:a(()=>[c(`p`,h,i(t(C)(`ds.heading.display`)),1),S[0]||=c(`p`,{class:`font-display text-5xl font-normal leading-[1.05] mb-3`},`Fraunces`,-1),c(`p`,g,i(t(C)(`ds.typography.serifDesc`)),1),S[1]||=c(`code`,{class:`font-mono text-[11px] text-muted block mt-5`},`var(--font-serif)`,-1)]),_:1}),o(p,{tone:`paper`},{default:a(()=>[c(`p`,_,i(t(C)(`ds.heading.body`)),1),S[2]||=c(`p`,{class:`font-sans text-5xl font-medium leading-[1.05] mb-3`},`DM Sans`,-1),c(`p`,v,i(t(C)(`ds.typography.sansDesc`)),1),S[3]||=c(`code`,{class:`font-mono text-[11px] text-muted block mt-5`},`var(--font-sans)`,-1)]),_:1})]),c(`section`,null,[c(`h2`,y,i(t(C)(`ds.heading.scale`)),1),c(`div`,b,[(e(),l(s,null,n(w,e=>c(`div`,{key:e,class:`flex items-baseline gap-8 py-4`},[c(`code`,x,`text-`+i(e),1),c(`span`,{class:u(`text-${e}`)},i(t(C)(`ds.typography.sample`)),3)])),64))])])]),_:1},8,[`eyebrow`,`title`,`description`]))}};export{S as default};

File diff suppressed because one or more lines are too long

1
dist/assets/checkout-stUpOmaV.js vendored Normal file
View File

@@ -0,0 +1 @@
import{n as e}from"./pinia-D94NEbtV.js";var t=`kn-checkout-v1`,n=[`guest`,`signin`,`register`];function r(){return{name:``,company:``,street:``,postalCode:``,city:``,country:`AT`,phone:``}}function i(){return{accountMode:`guest`,email:``,acceptsMarketing:!1,password:``,passwordConfirm:``,firstName:``,lastName:``,billingSame:!0,shippingAddress:r(),billingAddress:r()}}function a(){if(typeof sessionStorage>`u`)return i();try{let e=sessionStorage.getItem(t);if(!e)return i();let n=JSON.parse(e);return{...i(),...n}}catch{return i()}}function o(e){if(!(typeof sessionStorage>`u`))try{let{password:n,passwordConfirm:r,...i}=e;sessionStorage.setItem(t,JSON.stringify(i))}catch{}}function s(e){return e?[`name`,`street`,`postalCode`,`city`,`country`].every(t=>String(e[t]||``).trim().length>0):!1}var c=e(`checkout`,{state:()=>a(),getters:{accountComplete(e){return!e.email||!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e.email.trim())?!1:e.accountMode===`signin`?e.password.length>=8:e.accountMode===`register`?e.password.length>=8&&e.password===e.passwordConfirm:!0},shippingComplete(e){return s(e.shippingAddress)?e.billingSame?!0:s(e.billingAddress):!1}},actions:{setAccountMode(e){n.includes(e)&&(this.accountMode=e,this.persist())},update(e){Object.assign(this,e),this.persist()},updateShipping(e){this.shippingAddress={...this.shippingAddress,...e},this.persist()},updateBilling(e){this.billingAddress={...this.billingAddress,...e},this.persist()},persist(){o(this.$state)},reset(){Object.assign(this,i()),typeof sessionStorage<`u`&&sessionStorage.removeItem(t)}}});export{c as t};

File diff suppressed because one or more lines are too long

21
dist/assets/i18n-DhVF3Pwg.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/assets/index-CQUnoPmi.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

6
dist/index.html vendored
View File

@@ -12,14 +12,14 @@
href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,200;0,9..144,400;0,9..144,600;0,9..144,700;1,9..144,200;1,9..144,400;1,9..144,600&family=DM+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400&display=swap"
rel="stylesheet"
/>
<script type="module" crossorigin src="/assets/index-DNNOPNdg.js"></script>
<script type="module" crossorigin src="/assets/index-CduAjTXZ.js"></script>
<link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-BOai-rQB.js">
<link rel="modulepreload" crossorigin href="/assets/preload-helper-DzyYoeor.js">
<link rel="modulepreload" crossorigin href="/assets/runtime-core.esm-bundler-DTXUv7Wx.js">
<link rel="modulepreload" crossorigin href="/assets/runtime-dom.esm-bundler-Bg1uJ-W7.js">
<link rel="modulepreload" crossorigin href="/assets/runtime-dom.esm-bundler-BrsyFPJA.js">
<link rel="modulepreload" crossorigin href="/assets/pinia-D94NEbtV.js">
<link rel="modulepreload" crossorigin href="/assets/vue-router-Cyqru1db.js">
<link rel="stylesheet" crossorigin href="/assets/index-BliuvfCa.css">
<link rel="stylesheet" crossorigin href="/assets/index-CQUnoPmi.css">
</head>
<body>
<div id="app"></div>

163
src/api/auth.js Normal file
View File

@@ -0,0 +1,163 @@
// Authentication boundary. Mirrors checkout.js / cart.js: the rest of
// the app calls these functions, never the backend or session store
// directly. Today the bodies are stubs; when the PHP / MySQL backend
// lands, replace each body with a real fetch() against the documented
// endpoint — signatures and return shapes stay the same.
//
// ─── Endpoints (backend to implement) ────────────────────────────────
// POST /api/auth/login { email, password } → Session
// POST /api/auth/register { email, password, ... } → Session
// POST /api/auth/logout {} → 204
// GET /api/auth/me — → User | null
// POST /api/auth/reset { email } → 204
//
// ─── Types ───────────────────────────────────────────────────────────
// User { id, email, firstName?, lastName?, acceptsMarketing }
// Session { user: User, csrfToken }
//
// Sessions are HTTP-only cookies. The CSRF token is mirrored in a JS
// readable header / form field for state-changing calls. The frontend
// stores nothing about credentials in localStorage — `getCurrentUser()`
// is the single source of truth and reads from the server.
const STORAGE_KEY = 'kn-auth-stub-v1'
// In-memory + localStorage stub for design review. Replaced wholesale
// once the backend exposes the real endpoints. Format mirrors what
// the API will return so the UI layer stays unchanged.
function loadStub() {
if (typeof localStorage === 'undefined') return null
try {
const raw = localStorage.getItem(STORAGE_KEY)
if (!raw) return null
const parsed = JSON.parse(raw)
if (!parsed?.user?.email) return null
return parsed
} catch {
return null
}
}
function persistStub(session) {
if (typeof localStorage === 'undefined') return
try {
if (session) localStorage.setItem(STORAGE_KEY, JSON.stringify(session))
else localStorage.removeItem(STORAGE_KEY)
} catch {
/* quota or private mode — ignore */
}
}
let current = loadStub()
function emailValid(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(email || '').trim())
}
function passwordValid(password) {
return typeof password === 'string' && password.length >= 8
}
function authError(code, message) {
const err = new Error(message)
err.code = code
return err
}
/**
* Returns the current authenticated user (or null). The real endpoint
* reads from the session cookie; the stub from localStorage.
*
* Backend contract: `GET /api/auth/me`.
*
* @returns {Promise<{ user: User } | null>}
*/
export async function getCurrentUser() {
return current
}
/**
* Sign in with email + password. Real backend issues an HTTP-only
* session cookie and returns the User shape. The stub fakes both —
* any password ≥ 8 characters succeeds.
*
* Backend contract: `POST /api/auth/login`.
*
* @param {{ email: string, password: string }} credentials
* @returns {Promise<{ user: User, csrfToken: string }>}
*/
export async function signIn({ email, password }) {
if (!emailValid(email)) throw authError('auth.invalidEmail', 'Please enter a valid email.')
if (!passwordValid(password)) {
throw authError('auth.invalidPassword', 'Password must be at least 8 characters.')
}
const session = {
user: {
id: `usr_${btoa(String(email).toLowerCase()).replace(/=+$/, '').slice(0, 12)}`,
email: String(email).toLowerCase().trim(),
firstName: '',
lastName: '',
acceptsMarketing: false,
},
csrfToken: `csrf_stub_${Date.now().toString(36)}`,
}
current = session
persistStub(session)
return session
}
/**
* Create a new account. Real backend hashes the password with Argon2id,
* issues a session, and returns the new User. The stub stores the
* session locally so the UI can flow through end-to-end.
*
* Backend contract: `POST /api/auth/register`.
*
* @param {{ email: string, password: string, firstName?: string,
* lastName?: string, acceptsMarketing?: boolean }} request
* @returns {Promise<{ user: User, csrfToken: string }>}
*/
export async function register(request) {
if (!emailValid(request?.email)) {
throw authError('auth.invalidEmail', 'Please enter a valid email.')
}
if (!passwordValid(request?.password)) {
throw authError('auth.invalidPassword', 'Password must be at least 8 characters.')
}
const session = {
user: {
id: `usr_${Date.now().toString(36)}`,
email: String(request.email).toLowerCase().trim(),
firstName: String(request.firstName || '').trim(),
lastName: String(request.lastName || '').trim(),
acceptsMarketing: Boolean(request.acceptsMarketing),
},
csrfToken: `csrf_stub_${Date.now().toString(36)}`,
}
current = session
persistStub(session)
return session
}
/**
* Sign out. Real backend invalidates the session cookie server-side.
*
* Backend contract: `POST /api/auth/logout`.
*/
export async function signOut() {
current = null
persistStub(null)
}
/**
* Send a password reset email. Real backend issues a single-use,
* time-limited token via email; the stub is a no-op.
*
* Backend contract: `POST /api/auth/reset`.
*/
export async function requestPasswordReset({ email }) {
if (!emailValid(email)) throw authError('auth.invalidEmail', 'Please enter a valid email.')
return { ok: true }
}

View File

@@ -21,3 +21,10 @@ export {
createExpressIntent,
confirmCheckout,
} from './checkout.js'
export {
getCurrentUser,
signIn,
register,
signOut,
requestPasswordReset,
} from './auth.js'

View File

@@ -0,0 +1,97 @@
<script setup>
/**
* CheckoutStepper — minimal horizontal progress indicator. Each step
* is a small circle (number for upcoming/active, check for completed)
* with an inline label, joined by thin connector lines. The active
* step's circle is filled brand; completed circles carry a check;
* upcoming circles are outlined.
*
* On mobile, labels are hidden under sm to keep the bar compact —
* only circles and connectors render. On md+ the labels return.
*
* Click-to-go-back: any earlier (completed) step is navigable.
* Forward jumps are blocked unless the parent passed `completed:true`
* for that step.
*/
import { computed } from 'vue'
import { useRouter } from 'vue-router'
import Icon from './Icon.vue'
const props = defineProps({
steps: { type: Array, required: true },
activeKey: { type: String, required: true },
})
const router = useRouter()
const enriched = computed(() => {
const activeIndex = props.steps.findIndex((s) => s.key === props.activeKey)
return props.steps.map((step, i) => {
const state =
i < activeIndex ? 'completed' : i === activeIndex ? 'active' : 'upcoming'
const navigable = state === 'completed' || (state === 'upcoming' && step.completed)
return { ...step, index: i, state, navigable }
})
})
function goTo(step) {
if (!step.navigable || step.state === 'active') return
router.push(step.to)
}
</script>
<template>
<nav :aria-label="$attrs['aria-label'] || 'Checkout progress'">
<ol class="flex items-center gap-2 sm:gap-3">
<template v-for="(step, i) in enriched" :key="step.key">
<li class="flex items-center gap-2 sm:gap-3 shrink-0">
<button
type="button"
:disabled="!step.navigable && step.state !== 'active'"
:aria-current="step.state === 'active' ? 'step' : undefined"
:class="[
'group inline-flex items-center gap-2 sm:gap-2.5 transition-colors duration-base focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-brand rounded-sm',
step.navigable || step.state === 'active' ? 'cursor-pointer' : 'cursor-not-allowed',
]"
@click="goTo(step)"
>
<span
:class="[
'inline-flex items-center justify-center w-7 h-7 rounded-full text-[12px] font-bold tabular-nums transition-colors duration-base',
step.state === 'active' && 'bg-brand text-cream',
step.state === 'completed' && 'bg-brand text-cream group-hover:bg-brand-hover',
step.state === 'upcoming' && 'bg-transparent text-muted border border-line',
step.state === 'upcoming' && step.navigable && 'group-hover:border-brand group-hover:text-brand',
]"
>
<Icon
v-if="step.state === 'completed'"
name="check"
:size="13"
:stroke-width="2.6"
/>
<span v-else>{{ step.index + 1 }}</span>
</span>
<span
:class="[
'hidden md:inline whitespace-nowrap text-[13px] tracking-label transition-colors duration-base',
step.state === 'active' && 'text-brand font-semibold',
step.state === 'completed' && 'text-brand font-medium',
step.state === 'upcoming' && 'text-muted font-medium',
step.state === 'upcoming' && step.navigable && 'group-hover:text-brand',
]"
>{{ step.label }}</span>
</button>
</li>
<li
v-if="i < enriched.length - 1"
aria-hidden="true"
:class="[
'h-px w-6 sm:w-10 md:w-12 transition-colors duration-base shrink-0',
i < enriched.findIndex((s) => s.key === activeKey) ? 'bg-brand' : 'bg-line',
]"
/>
</template>
</ol>
</nav>
</template>

View File

@@ -51,6 +51,8 @@ const legalLinks = [
const exploreLinks = [
{ key: 'footer.explore.shop', href: '/shop' },
{ key: 'footer.explore.pflege', href: '/pflege' },
{ key: 'footer.explore.haushalt', href: '/haushalt' },
{ key: 'footer.explore.bundles', href: '/#bundles' },
{ key: 'footer.explore.about', href: '/#about' },
]
@@ -65,7 +67,7 @@ const year = new Date().getFullYear()
>
<div class="grid gap-10 md:grid-cols-[1.4fr_1fr_1fr] md:gap-12">
<div class="flex flex-col gap-4 max-w-sm">
<Logo :class="tone.logo" />
<Logo :class="['w-20 md:w-24 h-auto', tone.logo]" />
<p :class="['text-sm leading-relaxed', tone.muted]">
{{ t('footer.tagline') }}
</p>

View File

@@ -481,6 +481,8 @@ const de = {
'footer.tagline': 'Das Original aus Österreich. Reines Natron für Küche, Haushalt und Pflege seit Generationen.',
'footer.explore.heading': 'Entdecken',
'footer.explore.shop': 'Shop',
'footer.explore.pflege': 'Pflege',
'footer.explore.haushalt': 'Haushalt',
'footer.explore.bundles': 'Bundles',
'footer.explore.about': 'Über uns',
'footer.legal.heading': 'Rechtliches',
@@ -576,6 +578,76 @@ const de = {
'category.haushalt.cta.headline.em': 'Küche im Shop.',
'category.haushalt.cta.sub': 'Bade- und Küchenartikel, Bundles und mehr — alles auf einer Seite.',
'category.haushalt.cta.button': 'Zum Shop',
// Stepped checkout — chrome and step-level copy.
'checkout.stepper.label': 'Kassenfortschritt',
'checkout.step.cart': 'Warenkorb',
'checkout.step.account': 'Konto',
'checkout.step.shipping': 'Versand',
'checkout.step.payment': 'Bezahlung',
'checkout.back': 'Zurück',
'checkout.exit': 'Verlassen',
'checkout.backToShopping': 'Zurück zum Shop',
'checkout.home': 'Startseite',
'checkout.cta.review': 'Weiter',
'checkout.cart.heading': 'Dein Warenkorb',
'checkout.cart.title': 'Warenkorb',
'checkout.cart.itemsLabel': 'Artikel',
'checkout.cart.perItem': 'pro Stück',
'checkout.cart.shipping': 'Versand',
'checkout.cart.total': 'Gesamt',
'checkout.cart.cta.shop': 'Zum Shop',
'checkout.cart.cta.continueShopping': '← Weiter einkaufen',
'checkout.cart.cta.continue': 'Weiter',
'checkout.account.heading': 'Konto & Kontakt',
'checkout.account.sub': 'Als Gast bestellen oder mit deinem Konto fortfahren ganz wie es dir lieber ist.',
'checkout.account.tabsLabel': 'Konto-Optionen',
'checkout.account.tab.guest': 'Als Gast',
'checkout.account.tab.signin': 'Anmelden',
'checkout.account.tab.register': 'Konto erstellen',
'checkout.account.forgot': 'Passwort vergessen?',
'checkout.account.cta.continue': 'Weiter',
'checkout.shipping.sub': 'Wohin sollen wir liefern? Du kannst die Rechnungsadresse später separat angeben.',
'checkout.billing.sub': 'Falls die Rechnung an eine andere Adresse gehen soll, hier deaktivieren.',
'checkout.shipping.cta.continue': 'Weiter',
'checkout.payment.sub': 'Mit Apple Pay, Google Pay oder per Karte alles über Stripe abgewickelt.',
'checkout.section.card': 'Mit Karte bezahlen',
'checkout.field.firstName': 'Vorname',
'checkout.field.lastName': 'Nachname',
'checkout.field.password': 'Passwort',
'checkout.field.passwordConfirm': 'Passwort bestätigen',
'checkout.hint.password': 'Mindestens 8 Zeichen.',
'checkout.error.passwordMismatch': 'Die Passwörter stimmen nicht überein.',
'checkout.error.addressIncomplete': 'Bitte fülle alle Pflichtfelder der Adresse aus.',
// Auth — standalone /login and /register pages.
'auth.login.eyebrow': 'Wieder da',
'auth.login.title': 'Anmelden',
'auth.login.sub': 'Mit deinem Kaiser-Natron® Konto fortfahren.',
'auth.login.cta': 'Anmelden',
'auth.login.forgot': 'Passwort vergessen?',
'auth.login.newCustomer': 'Neu hier?',
'auth.login.cta.register': 'Konto erstellen',
'auth.reset.title': 'Passwort zurücksetzen',
'auth.reset.sub': 'Wir schicken dir einen sicheren Link an deine E-Mail-Adresse.',
'auth.reset.cta': 'Link anfordern',
'auth.reset.back': '← Zurück zur Anmeldung',
'auth.reset.sent.title': 'Mail unterwegs',
'auth.reset.sent.body': 'Wenn ein Konto zu dieser Adresse existiert, findest du den Link in deinem Postfach.',
'auth.register.eyebrow': 'Konto erstellen',
'auth.register.title': 'Willkommen',
'auth.register.sub': 'Mit einem Konto siehst du Bestellverlauf, Adressen und exklusive Bundle-Angebote.',
'auth.register.cta': 'Konto erstellen',
'auth.register.haveAccount': 'Schon ein Konto?',
'auth.register.cta.signIn': 'Anmelden',
}
// Austrian German (Österreichisches Deutsch) overrides.
@@ -1059,6 +1131,8 @@ const en = {
'footer.tagline': 'The original from Austria. Pure sodium bicarbonate for the kitchen, the home, and personal care — for generations.',
'footer.explore.heading': 'Explore',
'footer.explore.shop': 'Shop',
'footer.explore.pflege': 'Personal care',
'footer.explore.haushalt': 'Home',
'footer.explore.bundles': 'Bundles',
'footer.explore.about': 'About',
'footer.legal.heading': 'Legal',
@@ -1154,6 +1228,76 @@ const en = {
'category.haushalt.cta.headline.em': 'kitchen too in the shop.',
'category.haushalt.cta.sub': 'Bath and kitchen products, bundles and more — all on one page.',
'category.haushalt.cta.button': 'Visit the shop',
// Stepped checkout (EN).
'checkout.stepper.label': 'Checkout progress',
'checkout.step.cart': 'Cart',
'checkout.step.account': 'Account',
'checkout.step.shipping': 'Shipping',
'checkout.step.payment': 'Payment',
'checkout.back': 'Back',
'checkout.exit': 'Exit',
'checkout.backToShopping': 'Back to shopping',
'checkout.home': 'Home',
'checkout.cta.review': 'Continue',
'checkout.cart.heading': 'Your cart',
'checkout.cart.title': 'Cart',
'checkout.cart.itemsLabel': 'Items',
'checkout.cart.perItem': 'each',
'checkout.cart.shipping': 'Shipping',
'checkout.cart.total': 'Total',
'checkout.cart.cta.shop': 'Visit the shop',
'checkout.cart.cta.continueShopping': '← Continue shopping',
'checkout.cart.cta.continue': 'Continue',
'checkout.account.heading': 'Account & contact',
'checkout.account.sub': 'Check out as guest or continue with your account — whatever you prefer.',
'checkout.account.tabsLabel': 'Account options',
'checkout.account.tab.guest': 'Guest',
'checkout.account.tab.signin': 'Sign in',
'checkout.account.tab.register': 'Create account',
'checkout.account.forgot': 'Forgot password?',
'checkout.account.cta.continue': 'Continue',
'checkout.shipping.sub': 'Where shall we deliver? You can enter a separate billing address below if needed.',
'checkout.billing.sub': 'If the invoice should go to a different address, untick the box.',
'checkout.shipping.cta.continue': 'Continue',
'checkout.payment.sub': 'Pay with Apple Pay, Google Pay or card — all processed through Stripe.',
'checkout.section.card': 'Pay with card',
'checkout.field.firstName': 'First name',
'checkout.field.lastName': 'Last name',
'checkout.field.password': 'Password',
'checkout.field.passwordConfirm': 'Confirm password',
'checkout.hint.password': 'At least 8 characters.',
'checkout.error.passwordMismatch': 'The passwords don\'t match.',
'checkout.error.addressIncomplete': 'Please fill in all required address fields.',
// Auth (EN).
'auth.login.eyebrow': 'Welcome back',
'auth.login.title': 'Sign in',
'auth.login.sub': 'Continue with your Kaiser-Natron® account.',
'auth.login.cta': 'Sign in',
'auth.login.forgot': 'Forgot password?',
'auth.login.newCustomer': 'New here?',
'auth.login.cta.register': 'Create account',
'auth.reset.title': 'Reset password',
'auth.reset.sub': 'We\'ll send a secure link to your email.',
'auth.reset.cta': 'Request link',
'auth.reset.back': '← Back to sign in',
'auth.reset.sent.title': 'Mail on its way',
'auth.reset.sent.body': 'If an account exists for that address, you\'ll find the link in your inbox.',
'auth.register.eyebrow': 'Create account',
'auth.register.title': 'Welcome',
'auth.register.sub': 'An account gets you order history, saved addresses, and exclusive bundle offers.',
'auth.register.cta': 'Create account',
'auth.register.haveAccount': 'Already have an account?',
'auth.register.cta.signIn': 'Sign in',
}
export default {

View File

@@ -1,550 +1,264 @@
<script setup>
/**
* CheckoutPage — the single-page checkout. Three logical blocks:
* CheckoutPage — layout for the stepped checkout. Replaces the
* standard sitewide chrome (Navbar + Footer) with a minimal sticky
* top bar (logo + stepper + back-to-shopping). Per-step logic lives
* in `src/pages/checkout/*Step.vue`; per-step state in the
* `useCheckoutStore` Pinia store.
*
* 1. Contact — email
* 2. Shipping — address fields
* 3. Billing — same as shipping toggle, otherwise full fields
* 4. Payment — Stripe Payment Element mount (themed)
*
* Right column (lg+) is the order summary, sticky so it stays
* visible as the form scrolls. On md and below the summary stacks
* above the form so the user sees what they're paying for first.
*
* Submit flow:
* 1. User fills form, clicks Place order.
* 2. We call `createCheckoutIntent(...)` — backend returns
* `{ orderId, clientSecret, publishableKey }`.
* 3. The intent flips into the page state, which prompts the
* `<StripePaymentMount>` to load Stripe.js, instantiate
* Elements, and mount the Payment Element.
* 4. User finishes card / SEPA / Apple Pay capture in the
* Stripe iframe and clicks Place order again — the page
* calls `paymentMountRef.value.confirm()`, which triggers
* `stripe.confirmPayment(...)`. Stripe redirects the
* browser to `return_url` on success.
* 5. The return-url page polls `confirmCheckout({ orderId })`
* to fetch the final Order. (Not built here — out of scope
* for this draft.)
*
* No backend? No problem: the API stub returns a synthetic intent,
* and the Stripe component falls back to a placeholder card so
* the page is fully reviewable for design.
* Layout shape: a flex-column wrapper with min-h-svh — the sticky
* header takes its natural height; the main fills the remaining
* viewport via flex-1 and (on lg+) centers its content vertically.
*/
import { computed, nextTick, onMounted, onBeforeUnmount, ref } from 'vue'
import Navbar from '@/design-system/components/Navbar.vue'
import Footer from '@/design-system/components/Footer.vue'
import Input from '@/design-system/components/Input.vue'
import Button from '@/design-system/components/Button.vue'
import Icon from '@/design-system/components/Icon.vue'
import { computed, nextTick, onMounted, ref } from 'vue'
import { useRoute, useRouter, RouterLink } from 'vue-router'
import Logo from '@/design-system/components/Logo.vue'
import CheckoutStepper from '@/design-system/components/CheckoutStepper.vue'
import CheckoutSummary from '@/design-system/components/CheckoutSummary.vue'
import StripePaymentMount from '@/design-system/components/StripePaymentMount.vue'
import ExpressCheckoutMount from '@/design-system/components/ExpressCheckoutMount.vue'
import {
products,
fetchCart,
createCheckoutIntent,
createExpressIntent,
clearCart,
} from '@/api/index.js'
import { fetchCart, createExpressIntent, clearCart } from '@/api/index.js'
import { useCartStore } from '@/stores/cart.js'
import { useCheckoutStore } from '@/stores/checkout.js'
import { useI18n } from '@/i18n/index.js'
import { useRouter } from 'vue-router'
const { t } = useI18n()
const cart = useCartStore()
const checkout = useCheckoutStore()
const route = useRoute()
const router = useRouter()
// Site-wide nav split mirrors the rest of the app.
const navItems = [
{ key: 'nav.shop', href: '/shop' },
{ key: 'nav.bundles', href: '/#bundles' },
{ key: 'nav.revitalization', href: '/#revitalize' },
{ key: 'nav.about', href: '/#about' },
]
const navSecondaryItems = []
// `--nav-h` is defaulted in global styles; this ResizeObserver
// refines it so the sticky order summary's `top` offset matches
// the real navbar height.
const navRef = ref(null)
let navResizeObserver = null
function syncNavHeight() {
const el = navRef.value
const node = el && (el.$el || el)
if (!node || typeof window === 'undefined') return
const h = Math.round(node.getBoundingClientRect().height)
document.documentElement.style.setProperty('--nav-h', `${h}px`)
}
// ─── Form state ────────────────────────────────────────────────────
// One flat object so each field is a primitive — easier to reason
// about and to validate. Backend expects `Address` shape exactly,
// so the keys mirror the contract in `docs/api/checkout.md`.
const form = ref({
email: '',
acceptsMarketing: false,
billingSame: true,
shippingAddress: {
name: '',
company: '',
street: '',
postalCode: '',
city: '',
country: 'AT',
phone: '',
},
billingAddress: {
name: '',
company: '',
street: '',
postalCode: '',
city: '',
country: 'AT',
phone: '',
},
})
// Per-field errors. Populated when the API responds with an
// `address.invalid` style error or when the local guard catches
// a missing required field on submit.
const errors = ref({})
// CheckoutIntent (response from the API). Until the user clicks
// Place order, this is null and the StripePaymentMount renders the
// design placeholder. Setting it triggers Stripe Elements to mount.
const intent = ref(null)
const submitting = ref(false)
const submitError = ref('')
const paymentMountRef = ref(null)
const expressMountRef = ref(null)
// Separate intent + busy state for the express path so a wallet
// confirmation in flight doesn't block the regular form submit and
// vice-versa.
const expressIntent = ref(null)
const expressBusy = ref(false)
const expressError = ref('')
const expressMountRef = ref(null)
// Country options. ISO-3166 alpha-2 codes; backend validates against
// its own canonical list. Order is by frequency for our market.
const countries = [
{ code: 'AT', label: 'Österreich' },
{ code: 'DE', label: 'Deutschland' },
{ code: 'CH', label: 'Schweiz' },
{ code: 'IT', label: 'Italien' },
{ code: 'FR', label: 'Frankreich' },
]
const expressReturnUrl = computed(() => {
if (typeof window === 'undefined') return '/checkout/processing'
return `${window.location.origin}/checkout/processing?order=${expressIntent.value?.orderId || ''}`
})
// Totals: shown in the summary panel. While the backend hasn't
// confirmed yet, we mirror the stub's flat-rate calculation so the
// number on screen matches what the API will compute. The intent
// response is the source of truth once it lands.
const subtotal = computed(() => cart.subtotal)
const shipping = computed(() => (cart.items.length ? 4.9 : 0))
const tax = computed(() =>
cart.items.length ? +(((subtotal.value + shipping.value) * 0.19) / 1.19).toFixed(2) : 0,
)
const total = computed(() => +(subtotal.value + shipping.value).toFixed(2))
// Submit handler. Two-phase:
// • No intent yet → request one from the API. UI rerenders with
// the Payment Element mounted; user enters card details.
// • Intent present → hand off to Stripe.js for confirmPayment.
async function placeOrder() {
submitError.value = ''
if (!cart.items.length) {
submitError.value = t('checkout.error.empty')
return
}
submitting.value = true
try {
if (!intent.value) {
const billing = form.value.billingSame
? form.value.shippingAddress
: form.value.billingAddress
const response = await createCheckoutIntent({
email: form.value.email,
shippingAddress: form.value.shippingAddress,
billingAddress: billing,
acceptsMarketing: form.value.acceptsMarketing,
})
intent.value = response
submitting.value = false
return
}
// Second click — confirm the payment.
//
// Real Stripe path: Stripe.js redirects the browser to
// `return_url` on success and we never reach the lines below.
//
// Mock path (StripePaymentMount detects the stub clientSecret):
// resolves locally with `{ paymentIntent: { status:
// 'succeeded' } }` so we handle the post-success transitions
// ourselves — clear the cart, route to the thank-you page
// with the orderId so it can call `confirmCheckout()` for the
// final order shape.
const result = await paymentMountRef.value?.confirm()
if (result?.error) {
submitError.value = result.error.message || t('checkout.error.payment')
return
}
if (result?.paymentIntent?.status === 'succeeded') {
const orderId = intent.value.orderId
await clearCart()
router.push({ path: '/checkout/success', query: { order: orderId } })
return
}
} catch (err) {
submitError.value = err?.message || t('checkout.error.generic')
if (err?.code === 'checkout.addressInvalid') {
// The stub throws once the first missing field is found —
// surface it generically until the backend returns a fielded
// error response.
errors.value.shippingAddress = err.message
}
} finally {
submitting.value = false
}
}
// Wallet (Apple Pay / Google Pay) one-click handler. Mirrors the
// regular `placeOrder` two-phase shape: first call gets an intent
// from the API, second confirms. In real Stripe mode the Express
// Element drives confirmation itself via its own `confirm` event;
// here we re-use the mock branch so design review covers the
// success page + cart clearing end-to-end.
async function placeExpress({ wallet }) {
if (!cart.items.length) {
submitError.value = t('checkout.error.empty')
expressError.value = t('checkout.error.empty')
return
}
submitError.value = ''
expressError.value = ''
expressBusy.value = true
try {
const response = await createExpressIntent({ wallet })
expressIntent.value = response
// Wait for the mount to observe the new intent prop before
// calling confirm — mirrors the real flow where Stripe needs
// Elements bound to the clientSecret first.
await nextTick()
const result = await expressMountRef.value?.confirm()
if (result?.error) {
submitError.value = result.error.message || t('checkout.error.payment')
expressError.value = result.error.message || t('checkout.error.payment')
return
}
if (result?.paymentIntent?.status === 'succeeded') {
const orderId = response.orderId
await clearCart()
checkout.reset()
router.push({ path: '/checkout/success', query: { order: orderId } })
}
} catch (err) {
submitError.value = err?.message || t('checkout.error.generic')
expressError.value = err?.message || t('checkout.error.generic')
} finally {
expressBusy.value = false
}
}
function onExpressError(error) {
submitError.value = error?.message || t('checkout.error.payment')
function onExpressErr(err) {
expressError.value = err?.message || t('checkout.error.payment')
expressBusy.value = false
}
const ctaLabel = computed(() =>
intent.value ? t('checkout.cta.pay') : t('checkout.cta.continue'),
const SHIPPING_FLAT_EUR = 4.9
const VAT_RATE = 0.19
const subtotal = computed(() => cart.subtotal)
const shipping = computed(() => (cart.items.length ? SHIPPING_FLAT_EUR : 0))
const total = computed(() => +(subtotal.value + shipping.value).toFixed(2))
const tax = computed(() =>
cart.items.length ? +(((subtotal.value + shipping.value) * VAT_RATE) / (1 + VAT_RATE)).toFixed(2) : 0,
)
const returnUrl = computed(() => {
if (typeof window === 'undefined') return '/checkout/processing'
return `${window.location.origin}/checkout/processing?order=${intent.value?.orderId || ''}`
const steps = computed(() => [
{
key: 'cart',
label: t('checkout.step.cart'),
to: '/checkout/cart',
completed: cart.items.length > 0,
},
{
key: 'account',
label: t('checkout.step.account'),
to: '/checkout/account',
completed: checkout.accountComplete,
},
{
key: 'shipping',
label: t('checkout.step.shipping'),
to: '/checkout/shipping',
completed: checkout.shippingComplete,
},
{
key: 'payment',
label: t('checkout.step.payment'),
to: '/checkout/payment',
completed: false,
},
])
const activeKey = computed(() => {
const seg = route.path.split('/')[2] || 'cart'
return ['cart', 'account', 'shipping', 'payment'].includes(seg) ? seg : 'cart'
})
const swapCols = computed(() => activeKey.value === 'cart' && !cart.isEmpty)
// Section title for the current step. Rendered in the layout (above
// the grid) as a full-width row so the form column and summary column
// start at the same y-position on desktop.
const sectionHeader = computed(() => {
switch (activeKey.value) {
case 'cart':
return { title: t('checkout.cart.title'), sub: '' }
case 'account':
return { title: t('checkout.account.heading'), sub: t('checkout.account.sub') }
case 'shipping':
return { title: t('checkout.section.shipping'), sub: t('checkout.shipping.sub') }
case 'payment':
return { title: t('checkout.section.payment'), sub: t('checkout.payment.sub') }
default:
return { title: '', sub: '' }
}
})
onMounted(() => {
fetchCart()
syncNavHeight()
if (typeof ResizeObserver !== 'undefined' && navRef.value) {
const node = navRef.value.$el || navRef.value
navResizeObserver = new ResizeObserver(syncNavHeight)
navResizeObserver.observe(node)
}
window.addEventListener('resize', syncNavHeight)
})
onBeforeUnmount(() => {
if (navResizeObserver) navResizeObserver.disconnect()
if (typeof window !== 'undefined') window.removeEventListener('resize', syncNavHeight)
})
</script>
<template>
<Navbar
ref="navRef"
variant="paper"
layout="standard"
:items="navItems"
:secondary-items="navSecondaryItems"
:cart-count="cart.count"
:products="products"
/>
<div class="min-h-svh flex flex-col bg-cream">
<header class="sticky top-0 z-30 bg-cream border-b border-line shrink-0">
<!-- 3-column grid keeps the stepper centered at viewport midpoint
regardless of how wide the side elements are. On mobile the
right column is empty (back-to-shopping is hidden) but still
1fr, so the stepper stays centered. -->
<div
class="mx-auto w-full max-w-7xl px-4 sm:px-8 md:px-12 lg:px-16 py-3 grid grid-cols-[1fr_auto_1fr] items-center gap-3 sm:gap-6"
>
<RouterLink to="/" class="shrink-0 justify-self-start" :aria-label="t('checkout.home')">
<Logo class="w-9 md:w-12 h-auto text-brand" />
</RouterLink>
<!-- Page surface. Cream ground keeps the rhythm with home/shop;
the form/summary cards sit on `bg-paper` for separation. -->
<main class="bg-cream text-ink min-h-svh">
<div class="mx-auto w-full max-w-6xl px-6 py-12 sm:px-8 sm:py-16 md:px-12 md:py-20 lg:px-16 lg:py-24">
<!-- Page heading. Banner-style mixed font, matches
Hero/About preserves the typographic rhythm of the
rest of the site even on a transactional page. -->
<header class="flex flex-col gap-3 max-w-3xl mb-10 md:mb-14">
<p class="eyebrow">{{ t('checkout.eyebrow') }}</p>
<h1 class="font-display font-normal leading-[1.05] tracking-tight text-ink text-headline-md">
<div class="justify-self-center min-w-0">
<CheckoutStepper
:steps="steps"
:active-key="activeKey"
:aria-label="t('checkout.stepper.label')"
/>
</div>
<RouterLink
to="/shop"
class="shrink-0 justify-self-end hidden sm:inline-flex items-center text-[13px] font-semibold text-brand hover:underline whitespace-nowrap"
>{{ t('checkout.backToShopping') }}</RouterLink>
</div>
</header>
<main class="text-ink flex-1 lg:flex lg:flex-col lg:justify-center">
<div
class="mx-auto w-full max-w-7xl px-6 sm:px-8 md:px-12 lg:px-16 pt-5 pb-28 sm:pt-7 sm:pb-32 md:pt-8 md:pb-32 lg:pt-10 lg:pb-32"
>
<header
v-if="activeKey === 'cart'"
class="flex flex-col gap-2 mb-8 md:mb-10"
>
<h1
class="font-display font-normal leading-[1.05] tracking-tight text-ink text-headline-md"
>
{{ t('checkout.headline') }}
<em class="italic font-light text-brand">{{ t('checkout.headline.em') }}</em>
</h1>
<p class="text-base leading-relaxed text-muted max-w-xl">
{{ t('checkout.sub') }}
</p>
</header>
<!-- Mobile-first stack; lg+ splits into form (1.1fr) +
summary (0.9fr). The summary sits sticky so it stays
visible as the form scrolls. -->
<div class="grid gap-8 md:gap-10 lg:gap-12 lg:grid-cols-[1.1fr_0.9fr] lg:items-start">
<form
class="flex flex-col gap-8 min-w-0"
novalidate
@submit.prevent="placeOrder"
<header
v-else-if="sectionHeader.title"
class="flex flex-col gap-2 mb-8 md:mb-10"
>
<!-- Section: Express checkout. Wallet buttons up top so
returning customers can finish in one tap; the full
form below is the fallback. The "or" rule visually
separates the two paths so it doesn't read like the
buttons are part of the contact section. -->
<section class="flex flex-col gap-4 rounded-md border border-line bg-paper p-6 md:p-8">
<div class="flex items-center justify-between gap-3 flex-wrap">
<h2
class="font-display text-2xl font-normal text-ink leading-none"
>{{ sectionHeader.title }}</h2>
<p
v-if="sectionHeader.sub"
class="text-[14px] text-muted max-w-2xl"
>{{ sectionHeader.sub }}</p>
</header>
<!-- Empty cart on the cart step: single centered column. -->
<div
v-if="activeKey === 'cart' && cart.isEmpty"
class="flex justify-center"
>
<div class="w-full max-w-2xl">
<router-view />
</div>
</div>
<div
v-else
:class="[
'grid gap-8 md:gap-10 lg:gap-12 lg:items-start',
swapCols ? 'lg:grid-cols-[0.9fr_1.1fr]' : 'lg:grid-cols-[1.1fr_0.9fr]',
]"
>
<aside
v-if="swapCols"
class="flex flex-col gap-4 lg:sticky lg:top-[calc(var(--checkout-bar-h,72px)+1rem)]"
:aria-label="t('checkout.section.express')"
>
<div class="flex flex-col gap-4 rounded-md border border-line bg-paper p-6 md:p-8">
<h2 class="font-display text-xl font-normal text-brand leading-none">
{{ t('checkout.section.express') }}
</h2>
<span class="text-[12px] text-muted">
{{ t('checkout.express.sub') }}
</span>
</div>
<ExpressCheckoutMount
ref="expressMountRef"
:intent="expressIntent"
:return-url="returnUrl"
:return-url="expressReturnUrl"
:busy="expressBusy"
@pay="placeExpress"
@error="onExpressError"
/>
</section>
<!-- Visual divider between the express and form paths.
Two soft rules with a centred "or" — purely
presentational, role="presentation". -->
<div
role="presentation"
class="flex items-center gap-4 -my-2"
aria-hidden="true"
>
<span class="h-px flex-1 bg-line" />
<span class="text-[11px] font-bold uppercase tracking-eyebrow text-muted">
{{ t('checkout.express.or') }}
</span>
<span class="h-px flex-1 bg-line" />
</div>
<!-- Section: Contact -->
<section class="flex flex-col gap-5 rounded-md border border-line bg-paper p-6 md:p-8">
<h2 class="font-display text-xl font-normal text-brand leading-none">
{{ t('checkout.section.contact') }}
</h2>
<Input
v-model="form.email"
:label="t('checkout.field.email')"
type="email"
required
:placeholder="t('checkout.placeholder.email')"
:hint="t('checkout.hint.email')"
/>
<label class="inline-flex items-center gap-3 cursor-pointer select-none">
<input
v-model="form.acceptsMarketing"
type="checkbox"
class="w-5 h-5 rounded-xs border border-line accent-brand"
/>
<span class="text-sm text-ink">{{ t('checkout.field.marketing') }}</span>
</label>
</section>
<!-- Section: Shipping. Two-column grid at md+ packs the
address rows so the form doesn't read as a single
very long column. -->
<section class="flex flex-col gap-5 rounded-md border border-line bg-paper p-6 md:p-8">
<h2 class="font-display text-xl font-normal text-brand leading-none">
{{ t('checkout.section.shipping') }}
</h2>
<div class="grid gap-5 md:grid-cols-2">
<Input
v-model="form.shippingAddress.name"
:label="t('checkout.field.name')"
required
/>
<Input
v-model="form.shippingAddress.company"
:label="t('checkout.field.company')"
/>
<div class="md:col-span-2">
<Input
v-model="form.shippingAddress.street"
:label="t('checkout.field.street')"
required
@error="onExpressErr"
/>
</div>
<Input
v-model="form.shippingAddress.postalCode"
:label="t('checkout.field.postal')"
required
/>
<Input
v-model="form.shippingAddress.city"
:label="t('checkout.field.city')"
required
/>
<div class="flex flex-col gap-2">
<label class="text-[11px] font-bold uppercase tracking-eyebrow text-muted">
{{ t('checkout.field.country') }}<span class="text-danger"> *</span>
</label>
<select
v-model="form.shippingAddress.country"
required
class="w-full rounded-sm border border-line bg-paper px-4 py-3 text-[15px] text-ink transition-colors duration-base focus:outline-none focus:border-brand"
>
<option v-for="c in countries" :key="c.code" :value="c.code">
{{ c.label }}
</option>
</select>
</div>
<Input
v-model="form.shippingAddress.phone"
:label="t('checkout.field.phone')"
type="tel"
:hint="t('checkout.hint.phone')"
/>
</div>
</section>
<!-- Section: Billing. Same-as-shipping default keeps the
form short for the common case. -->
<section class="flex flex-col gap-5 rounded-md border border-line bg-paper p-6 md:p-8">
<div class="flex items-center justify-between gap-4 flex-wrap">
<h2 class="font-display text-xl font-normal text-brand leading-none">
{{ t('checkout.section.billing') }}
</h2>
<label class="inline-flex items-center gap-3 cursor-pointer select-none">
<input
v-model="form.billingSame"
type="checkbox"
class="w-5 h-5 rounded-xs border border-line accent-brand"
/>
<span class="text-sm text-ink">{{ t('checkout.field.billingSame') }}</span>
</label>
</div>
<div v-if="!form.billingSame" class="grid gap-5 md:grid-cols-2">
<Input v-model="form.billingAddress.name" :label="t('checkout.field.name')" required />
<Input v-model="form.billingAddress.company" :label="t('checkout.field.company')" />
<div class="md:col-span-2">
<Input v-model="form.billingAddress.street" :label="t('checkout.field.street')" required />
</div>
<Input v-model="form.billingAddress.postalCode" :label="t('checkout.field.postal')" required />
<Input v-model="form.billingAddress.city" :label="t('checkout.field.city')" required />
<div class="flex flex-col gap-2">
<label class="text-[11px] font-bold uppercase tracking-eyebrow text-muted">
{{ t('checkout.field.country') }}<span class="text-danger"> *</span>
</label>
<select
v-model="form.billingAddress.country"
required
class="w-full rounded-sm border border-line bg-paper px-4 py-3 text-[15px] text-ink transition-colors duration-base focus:outline-none focus:border-brand"
>
<option v-for="c in countries" :key="c.code" :value="c.code">
{{ c.label }}
</option>
</select>
</div>
<Input v-model="form.billingAddress.phone" :label="t('checkout.field.phone')" type="tel" />
</div>
</section>
<!-- Section: Payment. Hosts the Stripe mount component;
the actual <iframe> only attaches once we have a
clientSecret from the backend. -->
<section class="flex flex-col gap-5 rounded-md border border-line bg-paper p-6 md:p-8">
<div class="flex items-center justify-between gap-3 flex-wrap">
<h2 class="font-display text-xl font-normal text-brand leading-none">
{{ t('checkout.section.payment') }}
</h2>
<span class="inline-flex items-center gap-2 text-[12px] text-muted">
<Icon name="check" :size="14" :stroke-width="2.2" class="text-brand" />
{{ t('checkout.payment.secure') }}
</span>
</div>
<StripePaymentMount
ref="paymentMountRef"
:intent="intent"
:return-url="returnUrl"
/>
</section>
<!-- Inline error from the API or Stripe.js. Aria-live so
screen readers announce it when it appears. -->
<p
v-if="submitError"
class="text-sm text-danger px-2"
v-if="expressError"
class="text-sm text-danger"
role="alert"
aria-live="polite"
>{{ submitError }}</p>
>{{ expressError }}</p>
</aside>
<!-- Mobile-only submit (the lg+ summary panel renders
its own CTA so the customer always has a button in
sight). -->
<Button
type="submit"
variant="primary"
size="lg"
block
class="lg:hidden"
:loading="submitting"
:disabled="!cart.items.length"
>{{ ctaLabel }}</Button>
</form>
<div class="min-w-0">
<router-view />
</div>
<!-- Order summary. `lg:sticky` keeps it in view as the form
scrolls; `lg:top-[calc(var(--nav-h)+1rem)]` clears the
sticky navbar. CTA inline so desktop users always have
a Place order button alongside the totals. -->
<!-- Right column for non-cart steps: order summary + an action
slot that each step teleports its Back / Continue buttons
into. On mobile the column stacks below the form, so the
buttons end up at the bottom of the page naturally. -->
<CheckoutSummary
v-if="activeKey !== 'cart'"
:items="cart.items"
:subtotal="subtotal"
:shipping="shipping"
:tax="tax"
:total="total"
:heading="t('checkout.summary.heading')"
:cta-label="ctaLabel"
:cta-loading="submitting"
:cta-disabled="!cart.items.length"
class="hidden lg:flex lg:sticky lg:top-[calc(var(--nav-h)+1rem)]"
@submit="placeOrder"
class="lg:sticky lg:top-[calc(var(--checkout-bar-h,72px)+1rem)]"
/>
</div>
</div>
</main>
<Footer />
<!-- Mobile bottom clearance same pattern as Home/Shop. -->
<div
aria-hidden="true"
class="md:hidden"
style="height: calc(64px + env(safe-area-inset-bottom));"
/>
</div>
</template>

View File

@@ -2,37 +2,20 @@
/**
* CheckoutSuccessPage — landed via `?order=<id>` after a
* successful Stripe redirect or after the mock confirm flow.
*
* Calls `confirmCheckout({ orderId })` once on mount to fetch the
* canonical Order shape (status, amounts, addresses). The webhook
* is the authoritative "paid" signal on the backend; this page is
* the polite UI bridge for the user. Until the backend is wired,
* the stub returns a synthetic `paid` order so the page renders
* end-to-end.
*
* Two visual states: pending (loading skeleton while we resolve
* the order) and confirmed (thank-you headline + summary).
* canonical Order shape; the webhook is the authoritative "paid"
* signal on the backend, this page is the polite UI bridge.
*/
import { computed, onMounted, ref } from 'vue'
import { useRoute, RouterLink } from 'vue-router'
import Navbar from '@/design-system/components/Navbar.vue'
import Footer from '@/design-system/components/Footer.vue'
import Button from '@/design-system/components/Button.vue'
import Icon from '@/design-system/components/Icon.vue'
import { products, confirmCheckout } from '@/api/index.js'
import { confirmCheckout } from '@/api/index.js'
import { useI18n } from '@/i18n/index.js'
const { t } = useI18n()
const route = useRoute()
const navItems = [
{ key: 'nav.shop', href: '/shop' },
{ key: 'nav.bundles', href: '/#bundles' },
{ key: 'nav.revitalization', href: '/#revitalize' },
{ key: 'nav.about', href: '/#about' },
]
const navSecondaryItems = []
const order = ref(null)
const loadError = ref('')
const loading = ref(true)
@@ -56,26 +39,14 @@ onMounted(async () => {
</script>
<template>
<Navbar
variant="paper"
layout="standard"
:items="navItems"
:secondary-items="navSecondaryItems"
:products="products"
/>
<main class="bg-cream text-ink min-h-svh">
<div class="mx-auto w-full max-w-3xl px-6 py-16 sm:px-8 sm:py-20 md:px-12 md:py-24 lg:px-16 lg:py-28">
<!-- Confirmation card. Centered on the page so it reads as
the moment-of-truth screen no competing layout, just
the thank-you, order id, and a clear path back to
shopping. -->
<article class="rounded-md border border-line bg-paper p-8 md:p-10 flex flex-col gap-6 items-center text-center">
<!-- Success badge large brand-wash circle with a check
so the user sees an unambiguous positive signal as the
page lands. Pulse animation on the ring uses the DS's
`--animate-pulse-soft` so it feels alive without being
distracting. -->
<div class="min-h-svh flex flex-col bg-cream">
<main class="text-ink flex-1 flex flex-col justify-center">
<div
class="mx-auto w-full max-w-3xl px-6 sm:px-8 md:px-12 lg:px-16 py-10 sm:py-14 md:py-16 lg:py-20"
>
<article
class="rounded-md border border-line bg-paper p-8 md:p-10 flex flex-col gap-6 items-center text-center"
>
<div class="relative w-20 h-20">
<span
aria-hidden="true"
@@ -90,7 +61,9 @@ onMounted(async () => {
</div>
<p class="eyebrow">{{ t('checkout.success.eyebrow') }}</p>
<h1 class="font-display font-normal leading-[1.05] tracking-tight text-ink text-headline-md">
<h1
class="font-display font-normal leading-[1.05] tracking-tight text-ink text-headline-md"
>
{{ t('checkout.success.headline') }}
<em class="italic font-light text-brand">{{ t('checkout.success.headline.em') }}</em>
</h1>
@@ -113,12 +86,16 @@ onMounted(async () => {
aria-live="polite"
>{{ loadError }}</p>
<div class="mt-4 flex flex-col sm:flex-row items-stretch sm:items-center gap-3">
<RouterLink to="/shop" class="inline-flex">
<Button variant="primary" size="lg">{{ t('checkout.success.cta.shop') }}</Button>
<div class="mt-4 flex flex-col gap-3 w-full max-w-sm">
<RouterLink to="/" class="block">
<Button variant="primary" size="lg" class="w-full">
{{ t('checkout.success.cta.home') }}
</Button>
</RouterLink>
<RouterLink to="/" class="inline-flex">
<Button variant="secondary" size="lg">{{ t('checkout.success.cta.home') }}</Button>
<RouterLink to="/shop" class="block">
<Button variant="secondary" size="lg" class="w-full">
{{ t('checkout.success.cta.shop') }}
</Button>
</RouterLink>
</div>
@@ -128,6 +105,5 @@ onMounted(async () => {
</article>
</div>
</main>
<Footer />
</div>
</template>

View File

@@ -96,7 +96,7 @@ onBeforeUnmount(() => {
<template>
<Navbar
ref="navRef"
variant="paper"
variant="cream"
layout="standard"
:items="navItems"
:cart-count="cart.count"

182
src/pages/LoginPage.vue Normal file
View File

@@ -0,0 +1,182 @@
<script setup>
/**
* Standalone /login page. Returning customers arrive here from the
* navbar account link or from email password-reset flows. The form
* is intentionally narrow — single email + password pair, with a
* link over to /register for new customers.
*/
import { ref, computed, onBeforeUnmount, onMounted } from 'vue'
import { useRoute, useRouter, RouterLink } from 'vue-router'
import Navbar from '@/design-system/components/Navbar.vue'
import Footer from '@/design-system/components/Footer.vue'
import Input from '@/design-system/components/Input.vue'
import Button from '@/design-system/components/Button.vue'
import { products, signIn, requestPasswordReset } from '@/api/index.js'
import { useCartStore } from '@/stores/cart.js'
import { useI18n } from '@/i18n/index.js'
const { t } = useI18n()
const cart = useCartStore()
const route = useRoute()
const router = useRouter()
const navItems = [
{ key: 'nav.shop', href: '/shop' },
{ key: 'nav.bundles', href: '/#bundles' },
{ key: 'nav.revitalization', href: '/#revitalize' },
{ key: 'nav.about', href: '/#about' },
]
const email = ref('')
const password = ref('')
const submitting = ref(false)
const submitError = ref('')
const resetMode = ref(false)
const resetSent = ref(false)
const submitDisabled = computed(() => {
if (!email.value) return true
if (resetMode.value) return false
return password.value.length < 8
})
async function onSubmit() {
submitError.value = ''
submitting.value = true
try {
if (resetMode.value) {
await requestPasswordReset({ email: email.value })
resetSent.value = true
return
}
await signIn({ email: email.value, password: password.value })
const next = String(route.query.next || '/')
router.push(next)
} catch (err) {
submitError.value = err?.message || t('checkout.error.generic')
} finally {
submitting.value = false
}
}
const navRef = ref(null)
let navResizeObserver = null
function syncNavHeight() {
const el = navRef.value
const node = el && (el.$el || el)
if (!node || typeof window === 'undefined') return
const h = Math.round(node.getBoundingClientRect().height)
document.documentElement.style.setProperty('--nav-h', `${h}px`)
}
onMounted(() => {
syncNavHeight()
if (typeof ResizeObserver !== 'undefined' && navRef.value) {
const node = navRef.value.$el || navRef.value
navResizeObserver = new ResizeObserver(syncNavHeight)
navResizeObserver.observe(node)
}
window.addEventListener('resize', syncNavHeight)
})
onBeforeUnmount(() => {
if (navResizeObserver) navResizeObserver.disconnect()
if (typeof window !== 'undefined') window.removeEventListener('resize', syncNavHeight)
})
</script>
<template>
<Navbar
ref="navRef"
variant="cream"
layout="standard"
:items="navItems"
:cart-count="cart.count"
:products="products"
/>
<main class="bg-cream text-ink min-h-svh">
<div
class="mx-auto w-full max-w-md px-6 py-14 sm:px-8 sm:py-20 md:py-24"
>
<header class="flex flex-col gap-3 mb-8 text-center">
<p class="eyebrow">{{ t('auth.login.eyebrow') }}</p>
<h1 class="font-display font-normal leading-[1.05] tracking-tight text-ink text-headline-md">
{{ resetMode ? t('auth.reset.title') : t('auth.login.title') }}
</h1>
<p class="text-[14px] text-muted">
{{ resetMode ? t('auth.reset.sub') : t('auth.login.sub') }}
</p>
</header>
<form
v-if="!resetSent"
class="flex flex-col gap-5 rounded-md border border-line bg-paper p-6 md:p-8"
novalidate
@submit.prevent="onSubmit"
>
<Input
v-model="email"
:label="t('checkout.field.email')"
type="email"
required
:placeholder="t('checkout.placeholder.email')"
/>
<Input
v-if="!resetMode"
v-model="password"
:label="t('checkout.field.password')"
type="password"
required
/>
<p
v-if="submitError"
class="text-sm text-danger"
role="alert"
aria-live="polite"
>{{ submitError }}</p>
<Button
type="submit"
variant="primary"
size="lg"
block
:loading="submitting"
:disabled="submitDisabled"
>{{ resetMode ? t('auth.reset.cta') : t('auth.login.cta') }}</Button>
<button
type="button"
class="text-[13px] text-brand hover:underline self-start"
@click="resetMode = !resetMode"
>
{{ resetMode ? t('auth.reset.back') : t('auth.login.forgot') }}
</button>
</form>
<div
v-else
class="rounded-md border border-line bg-paper p-6 md:p-8 flex flex-col gap-3 text-center"
>
<h2 class="font-display text-xl text-brand">{{ t('auth.reset.sent.title') }}</h2>
<p class="text-[14px] text-muted">{{ t('auth.reset.sent.body') }}</p>
<button
type="button"
class="text-[13px] text-brand hover:underline self-center mt-2"
@click="resetMode = false; resetSent = false"
>
{{ t('auth.reset.back') }}
</button>
</div>
<p class="mt-8 text-center text-[14px] text-muted">
{{ t('auth.login.newCustomer') }}
<RouterLink to="/register" class="text-brand hover:underline">
{{ t('auth.login.cta.register') }}
</RouterLink>
</p>
</div>
</main>
<Footer />
</template>

187
src/pages/RegisterPage.vue Normal file
View File

@@ -0,0 +1,187 @@
<script setup>
/**
* Standalone /register page. New customers arrive here from the
* login page's "no account yet" link or from marketing CTAs.
* Mirrors the AccountStep register tab so the experience is
* consistent — same fields, same validation, same backend call.
*/
import { ref, computed, onBeforeUnmount, onMounted } from 'vue'
import { useRoute, useRouter, RouterLink } from 'vue-router'
import Navbar from '@/design-system/components/Navbar.vue'
import Footer from '@/design-system/components/Footer.vue'
import Input from '@/design-system/components/Input.vue'
import Button from '@/design-system/components/Button.vue'
import { products, register } from '@/api/index.js'
import { useCartStore } from '@/stores/cart.js'
import { useI18n } from '@/i18n/index.js'
const { t } = useI18n()
const cart = useCartStore()
const route = useRoute()
const router = useRouter()
const navItems = [
{ key: 'nav.shop', href: '/shop' },
{ key: 'nav.bundles', href: '/#bundles' },
{ key: 'nav.revitalization', href: '/#revitalize' },
{ key: 'nav.about', href: '/#about' },
]
const firstName = ref('')
const lastName = ref('')
const email = ref('')
const password = ref('')
const passwordConfirm = ref('')
const acceptsMarketing = ref(false)
const submitting = ref(false)
const submitError = ref('')
const passwordsMatch = computed(() => password.value === passwordConfirm.value)
const submitDisabled = computed(
() =>
!email.value ||
password.value.length < 8 ||
!passwordsMatch.value,
)
async function onSubmit() {
submitError.value = ''
submitting.value = true
try {
await register({
email: email.value,
password: password.value,
firstName: firstName.value,
lastName: lastName.value,
acceptsMarketing: acceptsMarketing.value,
})
const next = String(route.query.next || '/')
router.push(next)
} catch (err) {
submitError.value = err?.message || t('checkout.error.generic')
} finally {
submitting.value = false
}
}
const navRef = ref(null)
let navResizeObserver = null
function syncNavHeight() {
const el = navRef.value
const node = el && (el.$el || el)
if (!node || typeof window === 'undefined') return
const h = Math.round(node.getBoundingClientRect().height)
document.documentElement.style.setProperty('--nav-h', `${h}px`)
}
onMounted(() => {
syncNavHeight()
if (typeof ResizeObserver !== 'undefined' && navRef.value) {
const node = navRef.value.$el || navRef.value
navResizeObserver = new ResizeObserver(syncNavHeight)
navResizeObserver.observe(node)
}
window.addEventListener('resize', syncNavHeight)
})
onBeforeUnmount(() => {
if (navResizeObserver) navResizeObserver.disconnect()
if (typeof window !== 'undefined') window.removeEventListener('resize', syncNavHeight)
})
</script>
<template>
<Navbar
ref="navRef"
variant="cream"
layout="standard"
:items="navItems"
:cart-count="cart.count"
:products="products"
/>
<main class="bg-cream text-ink min-h-svh">
<div
class="mx-auto w-full max-w-md px-6 py-14 sm:px-8 sm:py-20 md:py-24"
>
<header class="flex flex-col gap-3 mb-8 text-center">
<p class="eyebrow">{{ t('auth.register.eyebrow') }}</p>
<h1 class="font-display font-normal leading-[1.05] tracking-tight text-ink text-headline-md">
{{ t('auth.register.title') }}
</h1>
<p class="text-[14px] text-muted">{{ t('auth.register.sub') }}</p>
</header>
<form
class="flex flex-col gap-5 rounded-md border border-line bg-paper p-6 md:p-8"
novalidate
@submit.prevent="onSubmit"
>
<div class="grid gap-5 md:grid-cols-2">
<Input
v-model="firstName"
:label="t('checkout.field.firstName')"
/>
<Input
v-model="lastName"
:label="t('checkout.field.lastName')"
/>
</div>
<Input
v-model="email"
:label="t('checkout.field.email')"
type="email"
required
:placeholder="t('checkout.placeholder.email')"
/>
<Input
v-model="password"
:label="t('checkout.field.password')"
type="password"
required
:hint="t('checkout.hint.password')"
/>
<Input
v-model="passwordConfirm"
:label="t('checkout.field.passwordConfirm')"
type="password"
required
:error="passwordConfirm && !passwordsMatch ? t('checkout.error.passwordMismatch') : ''"
/>
<label class="inline-flex items-center gap-3 cursor-pointer select-none">
<input
v-model="acceptsMarketing"
type="checkbox"
class="w-5 h-5 rounded-xs border border-line accent-brand"
/>
<span class="text-sm text-ink">{{ t('checkout.field.marketing') }}</span>
</label>
<p
v-if="submitError"
class="text-sm text-danger"
role="alert"
aria-live="polite"
>{{ submitError }}</p>
<Button
type="submit"
variant="primary"
size="lg"
block
:loading="submitting"
:disabled="submitDisabled"
>{{ t('auth.register.cta') }}</Button>
</form>
<p class="mt-8 text-center text-[14px] text-muted">
{{ t('auth.register.haveAccount') }}
<RouterLink to="/login" class="text-brand hover:underline">
{{ t('auth.register.cta.signIn') }}
</RouterLink>
</p>
</div>
</main>
<Footer />
</template>

View File

@@ -0,0 +1,217 @@
<script setup>
/**
* Step 2 — Account. Three-tab fork: guest checkout (default), sign
* in (returning customer), or create account. Email is the shared
* primary field; the other fields swap depending on the active tab.
*
* Sign in / register call into `src/api/auth.js`. The guest path
* just stores the email on the checkout form and continues. Either
* way, the user proceeds to /checkout/shipping next.
*/
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import Input from '@/design-system/components/Input.vue'
import Button from '@/design-system/components/Button.vue'
import { signIn, register, getCurrentUser } from '@/api/index.js'
import { useCheckoutStore } from '@/stores/checkout.js'
import { useI18n } from '@/i18n/index.js'
const { t } = useI18n()
const checkout = useCheckoutStore()
const router = useRouter()
const submitting = ref(false)
const submitError = ref('')
const tabs = [
{ key: 'guest', label: 'checkout.account.tab.guest' },
{ key: 'signin', label: 'checkout.account.tab.signin' },
{ key: 'register', label: 'checkout.account.tab.register' },
]
function setTab(key) {
submitError.value = ''
checkout.setAccountMode(key)
}
const isGuest = computed(() => checkout.accountMode === 'guest')
const isSignIn = computed(() => checkout.accountMode === 'signin')
const isRegister = computed(() => checkout.accountMode === 'register')
const passwordsMatch = computed(
() => !isRegister.value || checkout.password === checkout.passwordConfirm,
)
const submitDisabled = computed(() => {
if (!checkout.email) return true
if (isGuest.value) return false
if (!checkout.password || checkout.password.length < 8) return true
if (isRegister.value && !passwordsMatch.value) return true
return false
})
async function onSubmit() {
submitError.value = ''
submitting.value = true
try {
if (isSignIn.value) {
await signIn({ email: checkout.email, password: checkout.password })
} else if (isRegister.value) {
await register({
email: checkout.email,
password: checkout.password,
firstName: checkout.firstName,
lastName: checkout.lastName,
acceptsMarketing: checkout.acceptsMarketing,
})
}
checkout.persist()
router.push('/checkout/shipping')
} catch (err) {
submitError.value = err?.message || t('checkout.error.generic')
} finally {
submitting.value = false
}
}
onMounted(async () => {
// If the user is already signed in (returning visitor), prefill the
// email and skip ahead to shipping. Don't auto-skip if they
// explicitly chose "guest" earlier in this checkout session.
const session = await getCurrentUser()
if (session?.user?.email && !checkout.email) {
checkout.update({ email: session.user.email })
}
})
</script>
<template>
<section class="flex flex-col gap-6">
<div
role="tablist"
:aria-label="t('checkout.account.tabsLabel')"
class="flex w-full rounded-pill border border-line bg-paper p-1"
>
<button
v-for="tab in tabs"
:key="tab.key"
role="tab"
type="button"
:aria-selected="checkout.accountMode === tab.key"
:class="[
'flex-1 px-4 py-2 rounded-pill text-[13px] font-semibold tracking-label transition-colors duration-base text-center',
checkout.accountMode === tab.key
? 'bg-brand text-cream'
: 'text-brand hover:bg-cream',
]"
@click="setTab(tab.key)"
>{{ t(tab.label) }}</button>
</div>
<form
class="flex flex-col gap-5 md:min-h-[28rem]"
novalidate
@submit.prevent="onSubmit"
>
<Input
:model-value="checkout.email"
:label="t('checkout.field.email')"
type="email"
required
:placeholder="t('checkout.placeholder.email')"
:hint="isGuest ? t('checkout.hint.email') : ''"
@update:model-value="checkout.update({ email: $event })"
/>
<template v-if="isRegister">
<div class="grid gap-5 md:grid-cols-2">
<Input
:model-value="checkout.firstName"
:label="t('checkout.field.firstName')"
@update:model-value="checkout.update({ firstName: $event })"
/>
<Input
:model-value="checkout.lastName"
:label="t('checkout.field.lastName')"
@update:model-value="checkout.update({ lastName: $event })"
/>
</div>
</template>
<template v-if="isSignIn || isRegister">
<Input
:model-value="checkout.password"
:label="t('checkout.field.password')"
type="password"
required
:hint="isRegister ? t('checkout.hint.password') : ''"
@update:model-value="checkout.update({ password: $event })"
/>
</template>
<template v-if="isRegister">
<Input
:model-value="checkout.passwordConfirm"
:label="t('checkout.field.passwordConfirm')"
type="password"
required
:error="!passwordsMatch ? t('checkout.error.passwordMismatch') : ''"
@update:model-value="checkout.update({ passwordConfirm: $event })"
/>
</template>
<label
v-if="isGuest || isRegister"
class="inline-flex items-center gap-3 cursor-pointer select-none"
>
<input
:checked="checkout.acceptsMarketing"
type="checkbox"
class="w-5 h-5 rounded-xs border border-line accent-brand"
@change="checkout.update({ acceptsMarketing: $event.target.checked })"
/>
<span class="text-sm text-ink">{{ t('checkout.field.marketing') }}</span>
</label>
<p
v-if="submitError"
class="text-sm text-danger"
role="alert"
aria-live="polite"
>{{ submitError }}</p>
<p v-if="isSignIn" class="text-[13px] text-muted">
<a href="/account/reset-password" class="text-brand hover:underline">
{{ t('checkout.account.forgot') }}
</a>
</p>
</form>
</section>
<footer
class="fixed inset-x-0 bottom-0 z-30 bg-cream border-t border-line"
role="contentinfo"
>
<div
class="mx-auto w-full max-w-7xl px-6 sm:px-8 md:px-12 lg:px-16 py-3 flex gap-3 sm:justify-between"
:style="{ paddingBottom: 'max(0.75rem, env(safe-area-inset-bottom))' }"
>
<RouterLink to="/checkout/cart" class="flex-1 sm:flex-initial">
<Button variant="primary" size="lg" block type="button">
{{ t('checkout.back') }}
</Button>
</RouterLink>
<div class="flex-1 sm:flex-initial">
<Button
variant="accent"
size="lg"
block
:loading="submitting"
:disabled="submitDisabled"
@click="onSubmit"
>{{ t('checkout.account.cta.continue') }}</Button>
</div>
</div>
</footer>
</template>

View File

@@ -0,0 +1,160 @@
<script setup>
/**
* Step 1 — Cart review. Full-page version of the cart drawer:
* line items with quantity steppers, a remove control, and a
* compact totals strip. Express checkout (Apple Pay / Google Pay)
* lives in the layout's right column on this step. The Continue
* CTA sits in a fixed action footer at the bottom of the viewport.
*/
import { computed } from 'vue'
import { useRouter, RouterLink } from 'vue-router'
import Button from '@/design-system/components/Button.vue'
import QuantityStepper from '@/design-system/components/QuantityStepper.vue'
import Icon from '@/design-system/components/Icon.vue'
import {
updateCartItem,
removeFromCart,
formatPrice,
} from '@/api/index.js'
import { useCartStore } from '@/stores/cart.js'
import { useI18n } from '@/i18n/index.js'
const { t } = useI18n()
const cart = useCartStore()
const router = useRouter()
const empty = computed(() => cart.isEmpty)
const SHIPPING_FLAT_EUR = 4.9
const subtotal = computed(() => cart.subtotal)
const shipping = computed(() => (cart.items.length ? SHIPPING_FLAT_EUR : 0))
const total = computed(() => +(subtotal.value + shipping.value).toFixed(2))
async function onQty(productId, quantity) {
await updateCartItem(productId, quantity)
}
async function onRemove(productId) {
await removeFromCart(productId)
}
function continueToAccount() {
router.push('/checkout/account')
}
function exitToHome() {
router.push('/')
}
</script>
<template>
<div
v-if="empty"
class="min-h-[55svh] flex items-center justify-center"
>
<div
class="flex flex-col gap-4 items-center text-center rounded-md border border-line bg-paper p-10 max-w-md w-full"
>
<span class="inline-flex items-center justify-center w-14 h-14 rounded-full bg-cream text-brand">
<Icon name="cart" :size="24" :stroke-width="2" />
</span>
<p class="font-display text-xl text-brand">{{ t('cart.empty.title') }}</p>
<p class="text-[14px] text-muted max-w-sm">{{ t('cart.empty.subtitle') }}</p>
<RouterLink to="/shop" class="inline-flex">
<Button variant="primary" size="md">{{ t('checkout.cart.cta.shop') }}</Button>
</RouterLink>
</div>
</div>
<section v-else class="flex flex-col gap-6">
<ul class="flex flex-col rounded-md border border-line bg-paper divide-y divide-line">
<li
v-for="line in cart.items"
:key="line.productId"
class="flex flex-col sm:flex-row sm:items-center gap-4 sm:gap-5 px-5 py-5 sm:px-6"
>
<div class="shrink-0 w-20 h-20 rounded-sm bg-cream overflow-hidden flex items-center justify-center">
<img
v-if="line.product?.image"
:src="line.product.image"
:alt="line.product?.title || ''"
loading="lazy"
decoding="async"
class="w-full h-full object-contain p-2"
/>
</div>
<div class="flex-1 min-w-0">
<p class="font-display text-base text-ink leading-tight">{{ line.product?.title }}</p>
<p v-if="line.product?.size" class="text-[12px] text-muted">{{ line.product.size }}</p>
<p class="mt-2 text-[13px] text-muted">
{{ formatPrice(line.unitPrice) }} {{ t('checkout.cart.perItem') }}
</p>
</div>
<div class="flex items-center gap-4">
<QuantityStepper
:model-value="line.quantity"
:min="1"
@update:model-value="onQty(line.productId, $event)"
/>
<span class="text-[14px] font-semibold text-ink tabular-nums w-20 text-right">
{{ formatPrice(line.lineTotal) }}
</span>
<button
type="button"
class="text-muted hover:text-danger transition-colors duration-base"
:aria-label="t('cart.remove')"
@click="onRemove(line.productId)"
>
<Icon name="trash" :size="18" :stroke-width="2" />
</button>
</div>
</li>
</ul>
<dl class="rounded-md border border-line bg-paper px-6 py-5 flex flex-col gap-2">
<div class="flex items-baseline justify-between text-[14px]">
<dt class="text-muted">{{ t('cart.subtotal') }}</dt>
<dd class="text-ink tabular-nums">{{ formatPrice(subtotal) }}</dd>
</div>
<div class="flex items-baseline justify-between text-[14px]">
<dt class="text-muted">{{ t('checkout.cart.shipping') }}</dt>
<dd class="text-ink tabular-nums">{{ formatPrice(shipping) }}</dd>
</div>
<div class="mt-1 pt-3 border-t border-line flex items-baseline justify-between">
<dt class="eyebrow">{{ t('checkout.cart.total') }}</dt>
<dd class="font-display text-2xl text-brand tabular-nums">
{{ formatPrice(total) }}
</dd>
</div>
</dl>
</section>
<footer
v-if="!empty"
class="fixed inset-x-0 bottom-0 z-30 bg-cream border-t border-line"
role="contentinfo"
>
<div
class="mx-auto w-full max-w-7xl px-6 sm:px-8 md:px-12 lg:px-16 py-3 flex gap-3 sm:justify-between"
:style="{ paddingBottom: 'max(0.75rem, env(safe-area-inset-bottom))' }"
>
<div class="flex-1 sm:flex-initial">
<Button
variant="primary"
size="lg"
block
type="button"
@click="exitToHome"
>{{ t('checkout.exit') }}</Button>
</div>
<div class="flex-1 sm:flex-initial">
<Button
variant="accent"
size="lg"
block
@click="continueToAccount"
>{{ t('checkout.cart.cta.continue') }}</Button>
</div>
</div>
</footer>
</template>

View File

@@ -0,0 +1,178 @@
<script setup>
/**
* Step 4 — Payment. Express wallet buttons up top (Apple Pay /
* Google Pay) for one-tap finish, the Stripe Payment Element below
* for the regular flow. Two-phase submit — first click requests an
* intent from the API, second click confirms via Stripe.js.
*/
import { ref, computed, nextTick } from 'vue'
import { useRouter } from 'vue-router'
import Button from '@/design-system/components/Button.vue'
import StripePaymentMount from '@/design-system/components/StripePaymentMount.vue'
import ExpressCheckoutMount from '@/design-system/components/ExpressCheckoutMount.vue'
import {
createCheckoutIntent,
createExpressIntent,
clearCart,
} from '@/api/index.js'
import { useCartStore } from '@/stores/cart.js'
import { useCheckoutStore } from '@/stores/checkout.js'
import { useI18n } from '@/i18n/index.js'
const { t } = useI18n()
const cart = useCartStore()
const checkout = useCheckoutStore()
const router = useRouter()
const intent = ref(null)
const submitting = ref(false)
const submitError = ref('')
const expressIntent = ref(null)
const expressBusy = ref(false)
const paymentMountRef = ref(null)
const expressMountRef = ref(null)
const ctaLabel = computed(() =>
intent.value ? t('checkout.cta.pay') : t('checkout.cta.review'),
)
const returnUrl = computed(() => {
if (typeof window === 'undefined') return '/checkout/processing'
return `${window.location.origin}/checkout/processing?order=${intent.value?.orderId || ''}`
})
async function placeOrder() {
submitError.value = ''
if (!cart.items.length) {
submitError.value = t('checkout.error.empty')
return
}
submitting.value = true
try {
if (!intent.value) {
const billing = checkout.billingSame
? checkout.shippingAddress
: checkout.billingAddress
const response = await createCheckoutIntent({
email: checkout.email,
shippingAddress: checkout.shippingAddress,
billingAddress: billing,
acceptsMarketing: checkout.acceptsMarketing,
})
intent.value = response
submitting.value = false
return
}
const result = await paymentMountRef.value?.confirm()
if (result?.error) {
submitError.value = result.error.message || t('checkout.error.payment')
return
}
if (result?.paymentIntent?.status === 'succeeded') {
const orderId = intent.value.orderId
await clearCart()
checkout.reset()
router.push({ path: '/checkout/success', query: { order: orderId } })
return
}
} catch (err) {
submitError.value = err?.message || t('checkout.error.generic')
} finally {
submitting.value = false
}
}
async function placeExpress({ wallet }) {
if (!cart.items.length) {
submitError.value = t('checkout.error.empty')
return
}
submitError.value = ''
expressBusy.value = true
try {
const response = await createExpressIntent({ wallet })
expressIntent.value = response
await nextTick()
const result = await expressMountRef.value?.confirm()
if (result?.error) {
submitError.value = result.error.message || t('checkout.error.payment')
return
}
if (result?.paymentIntent?.status === 'succeeded') {
const orderId = response.orderId
await clearCart()
checkout.reset()
router.push({ path: '/checkout/success', query: { order: orderId } })
}
} catch (err) {
submitError.value = err?.message || t('checkout.error.generic')
} finally {
expressBusy.value = false
}
}
function onExpressError(err) {
submitError.value = err?.message || t('checkout.error.payment')
expressBusy.value = false
}
</script>
<template>
<section class="flex flex-col gap-6">
<section class="rounded-md border border-line bg-paper p-6 md:p-8">
<ExpressCheckoutMount
ref="expressMountRef"
:intent="expressIntent"
:return-url="returnUrl"
:busy="expressBusy"
@pay="placeExpress"
@error="onExpressError"
/>
</section>
<section class="rounded-md border border-line bg-paper p-6 md:p-8">
<StripePaymentMount
ref="paymentMountRef"
:intent="intent"
:return-url="returnUrl"
/>
</section>
<p
v-if="submitError"
class="text-sm text-danger"
role="alert"
aria-live="polite"
>{{ submitError }}</p>
</section>
<footer
class="fixed inset-x-0 bottom-0 z-30 bg-cream border-t border-line"
role="contentinfo"
>
<div
class="mx-auto w-full max-w-7xl px-6 sm:px-8 md:px-12 lg:px-16 py-3 flex gap-3 sm:justify-between"
:style="{ paddingBottom: 'max(0.75rem, env(safe-area-inset-bottom))' }"
>
<RouterLink to="/checkout/shipping" class="flex-1 sm:flex-initial">
<Button variant="primary" size="lg" block type="button">
{{ t('checkout.back') }}
</Button>
</RouterLink>
<div class="flex-1 sm:flex-initial">
<Button
variant="accent"
size="lg"
block
:loading="submitting"
:disabled="!cart.items.length"
@click="placeOrder"
>{{ ctaLabel }}</Button>
</div>
</div>
</footer>
</template>

View File

@@ -0,0 +1,196 @@
<script setup>
/**
* Step 3 — Shipping & billing addresses. Defaults billing to "same
* as shipping" so the form stays short for the common case;
* unchecking expands a second address block.
*/
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
import Input from '@/design-system/components/Input.vue'
import Button from '@/design-system/components/Button.vue'
import { useCheckoutStore } from '@/stores/checkout.js'
import { useI18n } from '@/i18n/index.js'
const { t } = useI18n()
const checkout = useCheckoutStore()
const router = useRouter()
const error = ref('')
const countries = [
{ code: 'AT', label: 'Österreich' },
{ code: 'DE', label: 'Deutschland' },
{ code: 'CH', label: 'Schweiz' },
{ code: 'IT', label: 'Italien' },
{ code: 'FR', label: 'Frankreich' },
]
const addressIncomplete = computed(() => !checkout.shippingComplete)
function onSubmit() {
error.value = ''
if (addressIncomplete.value) {
error.value = t('checkout.error.addressIncomplete')
return
}
checkout.persist()
router.push('/checkout/payment')
}
</script>
<template>
<form class="flex flex-col gap-8" novalidate @submit.prevent="onSubmit">
<section class="flex flex-col gap-5 rounded-md border border-line bg-paper p-6 md:p-8">
<div class="grid gap-5 md:grid-cols-2">
<Input
:model-value="checkout.shippingAddress.name"
:label="t('checkout.field.name')"
required
@update:model-value="checkout.updateShipping({ name: $event })"
/>
<Input
:model-value="checkout.shippingAddress.company"
:label="t('checkout.field.company')"
@update:model-value="checkout.updateShipping({ company: $event })"
/>
<div class="md:col-span-2">
<Input
:model-value="checkout.shippingAddress.street"
:label="t('checkout.field.street')"
required
@update:model-value="checkout.updateShipping({ street: $event })"
/>
</div>
<Input
:model-value="checkout.shippingAddress.postalCode"
:label="t('checkout.field.postal')"
required
@update:model-value="checkout.updateShipping({ postalCode: $event })"
/>
<Input
:model-value="checkout.shippingAddress.city"
:label="t('checkout.field.city')"
required
@update:model-value="checkout.updateShipping({ city: $event })"
/>
<div class="flex flex-col gap-2">
<label class="text-[11px] font-bold uppercase tracking-eyebrow text-muted">
{{ t('checkout.field.country') }}<span class="text-danger"> *</span>
</label>
<select
:value="checkout.shippingAddress.country"
required
class="w-full rounded-sm border border-line bg-paper px-4 py-3 text-[15px] text-ink transition-colors duration-base focus:outline-none focus:border-brand"
@change="checkout.updateShipping({ country: $event.target.value })"
>
<option v-for="c in countries" :key="c.code" :value="c.code">{{ c.label }}</option>
</select>
</div>
<Input
:model-value="checkout.shippingAddress.phone"
:label="t('checkout.field.phone')"
type="tel"
:hint="t('checkout.hint.phone')"
@update:model-value="checkout.updateShipping({ phone: $event })"
/>
</div>
</section>
<section class="flex flex-col gap-5 rounded-md border border-line bg-paper p-6 md:p-8">
<label class="inline-flex items-center gap-3 cursor-pointer select-none">
<input
:checked="checkout.billingSame"
type="checkbox"
class="w-5 h-5 rounded-xs border border-line accent-brand"
@change="checkout.update({ billingSame: $event.target.checked })"
/>
<span class="text-sm text-ink">{{ t('checkout.field.billingSame') }}</span>
</label>
<div v-if="!checkout.billingSame" class="grid gap-5 md:grid-cols-2">
<Input
:model-value="checkout.billingAddress.name"
:label="t('checkout.field.name')"
required
@update:model-value="checkout.updateBilling({ name: $event })"
/>
<Input
:model-value="checkout.billingAddress.company"
:label="t('checkout.field.company')"
@update:model-value="checkout.updateBilling({ company: $event })"
/>
<div class="md:col-span-2">
<Input
:model-value="checkout.billingAddress.street"
:label="t('checkout.field.street')"
required
@update:model-value="checkout.updateBilling({ street: $event })"
/>
</div>
<Input
:model-value="checkout.billingAddress.postalCode"
:label="t('checkout.field.postal')"
required
@update:model-value="checkout.updateBilling({ postalCode: $event })"
/>
<Input
:model-value="checkout.billingAddress.city"
:label="t('checkout.field.city')"
required
@update:model-value="checkout.updateBilling({ city: $event })"
/>
<div class="flex flex-col gap-2">
<label class="text-[11px] font-bold uppercase tracking-eyebrow text-muted">
{{ t('checkout.field.country') }}<span class="text-danger"> *</span>
</label>
<select
:value="checkout.billingAddress.country"
required
class="w-full rounded-sm border border-line bg-paper px-4 py-3 text-[15px] text-ink transition-colors duration-base focus:outline-none focus:border-brand"
@change="checkout.updateBilling({ country: $event.target.value })"
>
<option v-for="c in countries" :key="c.code" :value="c.code">{{ c.label }}</option>
</select>
</div>
<Input
:model-value="checkout.billingAddress.phone"
:label="t('checkout.field.phone')"
type="tel"
@update:model-value="checkout.updateBilling({ phone: $event })"
/>
</div>
</section>
<p
v-if="error"
class="text-sm text-danger"
role="alert"
aria-live="polite"
>{{ error }}</p>
</form>
<footer
class="fixed inset-x-0 bottom-0 z-30 bg-cream border-t border-line"
role="contentinfo"
>
<div
class="mx-auto w-full max-w-7xl px-6 sm:px-8 md:px-12 lg:px-16 py-3 flex gap-3 sm:justify-between"
:style="{ paddingBottom: 'max(0.75rem, env(safe-area-inset-bottom))' }"
>
<RouterLink to="/checkout/account" class="flex-1 sm:flex-initial">
<Button variant="primary" size="lg" block type="button">
{{ t('checkout.back') }}
</Button>
</RouterLink>
<div class="flex-1 sm:flex-initial">
<Button
variant="accent"
size="lg"
block
:disabled="addressIncomplete"
@click="onSubmit"
>{{ t('checkout.shipping.cta.continue') }}</Button>
</div>
</div>
</footer>
</template>

View File

@@ -19,18 +19,52 @@ const routes = [
component: () => import('@/pages/ProductPage.vue'),
meta: { layout: 'none' },
},
{
path: '/checkout',
name: 'checkout',
component: () => import('@/pages/CheckoutPage.vue'),
meta: { layout: 'none' },
},
{
path: '/checkout/success',
name: 'checkout-success',
component: () => import('@/pages/CheckoutSuccessPage.vue'),
meta: { layout: 'none' },
},
{
path: '/checkout',
component: () => import('@/pages/CheckoutPage.vue'),
meta: { layout: 'none' },
children: [
{ path: '', redirect: '/checkout/cart' },
{
path: 'cart',
name: 'checkout-cart',
component: () => import('@/pages/checkout/CartStep.vue'),
},
{
path: 'account',
name: 'checkout-account',
component: () => import('@/pages/checkout/AccountStep.vue'),
},
{
path: 'shipping',
name: 'checkout-shipping',
component: () => import('@/pages/checkout/ShippingStep.vue'),
},
{
path: 'payment',
name: 'checkout-payment',
component: () => import('@/pages/checkout/PaymentStep.vue'),
},
],
},
{
path: '/login',
name: 'login',
component: () => import('@/pages/LoginPage.vue'),
meta: { layout: 'none' },
},
{
path: '/register',
name: 'register',
component: () => import('@/pages/RegisterPage.vue'),
meta: { layout: 'none' },
},
{
path: '/pflege',
name: 'pflege',

124
src/stores/checkout.js Normal file
View File

@@ -0,0 +1,124 @@
// Checkout state — Pinia store. Single source of truth for the form
// values across the four checkout steps (cart, account, shipping,
// payment). Persists to sessionStorage so a refresh during the flow
// doesn't lose progress, but never to localStorage — the basket is
// the durable thing, not the half-filled checkout form.
//
// Step guards live here too: each step exposes a "completed" computed
// derived from the form state, and the route guards in router/index.js
// read from these to redirect users back to the first incomplete step
// if they deep-link past their progress.
import { defineStore } from 'pinia'
const STORAGE_KEY = 'kn-checkout-v1'
const ACCOUNT_MODES = ['guest', 'signin', 'register']
function emptyAddress() {
return {
name: '',
company: '',
street: '',
postalCode: '',
city: '',
country: 'AT',
phone: '',
}
}
function defaultState() {
return {
accountMode: 'guest',
email: '',
acceptsMarketing: false,
password: '',
passwordConfirm: '',
firstName: '',
lastName: '',
billingSame: true,
shippingAddress: emptyAddress(),
billingAddress: emptyAddress(),
}
}
function loadInitial() {
if (typeof sessionStorage === 'undefined') return defaultState()
try {
const raw = sessionStorage.getItem(STORAGE_KEY)
if (!raw) return defaultState()
const parsed = JSON.parse(raw)
return { ...defaultState(), ...parsed }
} catch {
return defaultState()
}
}
function persist(state) {
if (typeof sessionStorage === 'undefined') return
try {
const { password, passwordConfirm, ...safe } = state
void password
void passwordConfirm
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(safe))
} catch {
/* quota or private mode — ignore */
}
}
function addressComplete(addr) {
if (!addr) return false
return ['name', 'street', 'postalCode', 'city', 'country'].every(
(k) => String(addr[k] || '').trim().length > 0,
)
}
export const useCheckoutStore = defineStore('checkout', {
state: () => loadInitial(),
getters: {
accountComplete(state) {
if (!state.email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(state.email.trim())) return false
if (state.accountMode === 'signin') return state.password.length >= 8
if (state.accountMode === 'register') {
return (
state.password.length >= 8 &&
state.password === state.passwordConfirm
)
}
return true
},
shippingComplete(state) {
if (!addressComplete(state.shippingAddress)) return false
if (state.billingSame) return true
return addressComplete(state.billingAddress)
},
},
actions: {
setAccountMode(mode) {
if (!ACCOUNT_MODES.includes(mode)) return
this.accountMode = mode
this.persist()
},
update(patch) {
Object.assign(this, patch)
this.persist()
},
updateShipping(patch) {
this.shippingAddress = { ...this.shippingAddress, ...patch }
this.persist()
},
updateBilling(patch) {
this.billingAddress = { ...this.billingAddress, ...patch }
this.persist()
},
persist() {
persist(this.$state)
},
reset() {
Object.assign(this, defaultState())
if (typeof sessionStorage !== 'undefined') sessionStorage.removeItem(STORAGE_KEY)
},
},
})