2026-05-11 21:02:24 +12:00
< script lang = "ts" >
import { onMount } from 'svelte' ;
import Icon from '$lib/components/Icon.svelte' ;
import OnboardingSignaturePad from '$lib/components/OnboardingSignaturePad.svelte' ;
import OnboardingAuth from '$lib/components/OnboardingAuth.svelte' ;
import OnboardingFooter from '$lib/components/OnboardingFooter.svelte' ;
import logoDesktop from '$lib/images/goodwalk-auckland-dog-walking-logo.png?enhanced' ;
import type { Picture } from '@sveltejs/enhanced-img' ;
const desktop = logoDesktop as Picture ;
export let preview = false ;
const ownerEmail = 'info@goodwalk.co.nz' ;
const ownerPhone = '(022) 642 1011' ;
const services = [ 'Pack Walks' , '1:1 Walks' , 'Puppy Visits' ];
const visitStartedStorageKey = 'goodwalk_visit_started_at' ;
const draftStorageKey = 'goodwalk_contract_draft' ;
const steps = [
{ icon : 'fas fa-user' , title : 'Your details' , desc : 'Your contact information and your dog.' },
{ icon : 'fas fa-dog' , title : 'Service details' , desc : "Which service, when you'd like to start, and any notes." },
{ icon : 'fas fa-file-alt' , title : 'Terms' , desc : 'Read the service agreement before signing.' },
{ icon : 'fas fa-file-signature' , title : 'Sign and send' , desc : 'Confirm the agreement and add your signature.' },
];
let currentStep = 1 ;
// Step 1 fields
let fullName = '' ;
let email = '' ;
let phone = '' ;
let address = '' ;
let dogName = '' ;
let dogBreed = '' ;
let dogAge = '' ;
// Step 2 fields
let serviceType = '' ;
let startDate = '' ;
let walkFrequency = '' ;
let additionalNotes = '' ;
// Step 4 fields
let agreeServiceTerms = false ;
let agreeCancellation = false ;
let agreePayment = false ;
let agreeEmergency = false ;
let agreeLiability = false ;
let agreeAccuracy = false ;
let signatureDataUrl = '' ;
let website = '' ;
let formStartedAt = 0 ;
let visitStartedAt = 0 ;
let pageEnteredAt = 0 ;
let firstInteractionAt = 0 ;
let sendClickedAt = 0 ;
let submitting = false ;
let submitted = false ;
let submitError = '' ;
let signaturePad : OnboardingSignaturePad ;
let errors : Record < string , string > = {};
let authChecking = true ;
let isAuthenticated = false ;
let userEmail = '' ;
let onboardingCompleted = false ;
let contractCompleted = false ;
const today = new Date (). toISOString (). split ( 'T' )[ 0 ];
// Idle timeout
const IDLE_TIMEOUT_MS = 20 * 60 * 1000 ;
const IDLE_WARN_MS = 18 * 60 * 1000 ;
let idleWarning = false ;
let idleLastActivity = Date . now ();
let idleInterval : ReturnType < typeof setInterval > | null = null ;
function resetIdle() {
idleLastActivity = Date . now ();
idleWarning = false ;
}
function startIdleTimer() {
idleLastActivity = Date . now ();
idleInterval = setInterval (() => {
const elapsed = Date . now () - idleLastActivity ;
if ( elapsed >= IDLE_TIMEOUT_MS ) {
handleIdleLogout ();
} else if ( elapsed >= IDLE_WARN_MS ) {
idleWarning = true ;
}
}, 15 _000 );
}
function stopIdleTimer() {
if ( idleInterval ) { clearInterval ( idleInterval ); idleInterval = null ; }
idleWarning = false ;
}
async function handleIdleLogout() {
stopIdleTimer ();
await saveDraft ();
try {
const token = window . localStorage . getItem ( 'gw_onboarding_session' ) ?? '' ;
if ( token ) {
await fetch ( '/api/auth/logout' , { method : 'POST' , headers : { Authorization : `Bearer ${ token } ` } }). catch (() => {});
}
} finally {
try { window . localStorage . removeItem ( 'gw_onboarding_session' ); } catch { /* ignore */ }
isAuthenticated = false ;
userEmail = '' ;
idleWarning = false ;
}
}
async function saveDraft() {
try {
const token = window . localStorage . getItem ( 'gw_onboarding_session' );
if ( ! token ) return ;
await fetch ( '/api/save-draft' , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' , Authorization : `Bearer ${ token } ` },
body : JSON.stringify ({
form : 'contract' ,
data : {
currentStep ,
fullName , email , phone , address ,
dogName , dogBreed , dogAge ,
serviceType , startDate , walkFrequency , additionalNotes ,
agreeServiceTerms , agreeCancellation , agreePayment ,
agreeEmergency , agreeLiability , agreeAccuracy ,
},
}),
});
} catch { /* ignore */ }
}
function applyDraft ( draft : Record < string , unknown >) {
currentStep = ( draft . currentStep as number ) ?? 1 ;
fullName = ( draft . fullName as string ) ?? fullName ;
email = ( draft . email as string ) ?? email ;
phone = ( draft . phone as string ) ?? phone ;
address = ( draft . address as string ) ?? address ;
dogName = ( draft . dogName as string ) ?? dogName ;
dogBreed = ( draft . dogBreed as string ) ?? dogBreed ;
dogAge = ( draft . dogAge as string ) ?? dogAge ;
serviceType = ( draft . serviceType as string ) ?? serviceType ;
startDate = ( draft . startDate as string ) ?? startDate ;
walkFrequency = ( draft . walkFrequency as string ) ?? walkFrequency ;
additionalNotes = ( draft . additionalNotes as string ) ?? additionalNotes ;
agreeServiceTerms = ( draft . agreeServiceTerms as boolean ) ?? agreeServiceTerms ;
agreeCancellation = ( draft . agreeCancellation as boolean ) ?? agreeCancellation ;
agreePayment = ( draft . agreePayment as boolean ) ?? agreePayment ;
agreeEmergency = ( draft . agreeEmergency as boolean ) ?? agreeEmergency ;
agreeLiability = ( draft . agreeLiability as boolean ) ?? agreeLiability ;
agreeAccuracy = ( draft . agreeAccuracy as boolean ) ?? agreeAccuracy ;
}
$ : if ( typeof window !== 'undefined' ) {
try {
window . localStorage . setItem ( draftStorageKey , JSON . stringify ({
currentStep ,
fullName , email , phone , address ,
dogName , dogBreed , dogAge ,
serviceType , startDate , walkFrequency , additionalNotes ,
agreeServiceTerms , agreeCancellation , agreePayment ,
agreeEmergency , agreeLiability , agreeAccuracy ,
}));
} catch { /* storage unavailable */ }
}
function applyProfile ( serverEmail : string , profile : Record < string , string > = {}) {
if ( ! email ) email = serverEmail ;
if ( ! fullName ) fullName = profile . fullName ?? '' ;
if ( ! phone ) phone = profile . phone ?? '' ;
if ( ! address ) address = profile . address ?? '' ;
if ( ! dogName ) dogName = profile . dogName ?? '' ;
if ( ! dogBreed ) dogBreed = profile . dogBreed ?? '' ;
if ( ! dogAge ) dogAge = profile . dogAge ?? '' ;
onboardingCompleted = Boolean (( profile as Record < string , unknown >). onboardingCompleted );
contractCompleted = Boolean (( profile as Record < string , unknown >). contractCompleted );
}
async function checkAuth() {
try {
const token = window . localStorage . getItem ( 'gw_onboarding_session' );
if ( token ) {
const res = await fetch ( '/api/auth/verify' , {
headers : { Authorization : `Bearer ${ token } ` },
});
if ( res . ok ) {
const data = await res . json ();
isAuthenticated = true ;
userEmail = data . email ;
applyProfile ( data . email , data . profile ?? {});
if ( data . draft ? . contract ) applyDraft ( data . draft . contract );
} else {
window . localStorage . removeItem ( 'gw_onboarding_session' );
}
}
} catch { /* ignore */ }
authChecking = false ;
}
function handleAuthenticated ( e : CustomEvent < { email : string ; profile? : Record < string , string >; draft? : Record < string , unknown > } > ) {
isAuthenticated = true ;
userEmail = e . detail . email ;
applyProfile ( e . detail . email , e . detail . profile ?? {});
if ( e . detail . draft ? . contract ) applyDraft ( e . detail . draft . contract as Record < string , unknown >);
startIdleTimer ();
const now = Date . now ();
formStartedAt = now ;
pageEnteredAt = now ;
}
function handleLogout() {
stopIdleTimer ();
isAuthenticated = false ;
userEmail = '' ;
}
onMount ( async () => {
await checkAuth ();
if ( ! isAuthenticated ) return ;
startIdleTimer ();
const now = Date . now ();
formStartedAt = now ;
pageEnteredAt = now ;
try {
const raw = window . sessionStorage . getItem ( visitStartedStorageKey );
const parsed = raw ? Number ( raw ) : NaN ;
visitStartedAt = Number . isFinite ( parsed ) && parsed > 0 ? parsed : now ;
if ( ! Number . isFinite ( parsed ) || parsed <= 0 ) {
window . sessionStorage . setItem ( visitStartedStorageKey , String ( now ));
}
} catch {
visitStartedAt = now ;
}
try {
const raw = window . localStorage . getItem ( draftStorageKey );
const onboardingRaw = window . localStorage . getItem ( 'goodwalk_onboarding_draft' );
const ob = onboardingRaw ? JSON . parse ( onboardingRaw ) : null ;
if ( raw ) {
const d = JSON . parse ( raw );
currentStep = d . currentStep ?? 1 ;
fullName = d . fullName ?? '' ;
email = d . email ?? '' ;
phone = d . phone ?? '' ;
address = d . address ?? '' ;
dogName = d . dogName ?? '' ;
dogBreed = d . dogBreed ?? '' ;
dogAge = d . dogAge ?? '' ;
serviceType = d . serviceType ?? '' ;
startDate = d . startDate ?? '' ;
walkFrequency = d . walkFrequency ?? '' ;
additionalNotes = d . additionalNotes ?? '' ;
agreeServiceTerms = d . agreeServiceTerms ?? false ;
agreeCancellation = d . agreeCancellation ?? false ;
agreePayment = d . agreePayment ?? false ;
agreeEmergency = d . agreeEmergency ?? false ;
agreeLiability = d . agreeLiability ?? false ;
agreeAccuracy = d . agreeAccuracy ?? false ;
}
// Always back-fill empty fields from onboarding draft
if ( ob ) {
if ( ! fullName ) fullName = ob . fullName ?? '' ;
if ( ! email ) email = ob . email ?? '' ;
if ( ! phone ) phone = ob . phone ?? '' ;
if ( ! address ) address = ob . address ?? '' ;
if ( ! dogName ) dogName = ob . dogName ?? '' ;
if ( ! dogBreed ) dogBreed = ob . dogBreed ?? '' ;
if ( ! dogAge ) dogAge = ob . dogAge ?? '' ;
}
} catch { /* storage unavailable */ }
});
function noteInteraction() {
if ( ! firstInteractionAt ) firstInteractionAt = Date . now ();
}
function validateEmail ( raw : string ) : string {
const value = raw . trim ();
if ( ! value ) return 'Please enter your email address' ;
if ( ! /^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/ . test ( value )) {
return 'Please enter a valid email address' ;
}
return '' ;
}
function validateStep ( step : number ) : boolean {
const next : Record < string , string > = {};
if ( step === 1 ) {
if ( ! fullName . trim ()) next . fullName = 'Please enter your full name' ;
const emailError = validateEmail ( email );
if ( emailError ) next . email = emailError ;
if ( ! phone . trim ()) next . phone = 'Please enter your phone number' ;
if ( ! address . trim ()) next . address = 'Please enter your address' ;
if ( ! dogName . trim ()) next . dogName = "Please enter your dog's name" ;
if ( ! dogBreed . trim ()) next . dogBreed = "Please enter your dog's breed" ;
} else if ( step === 2 ) {
if ( ! serviceType ) next . serviceType = 'Please select a service' ;
if ( ! startDate ) next . startDate = 'Please select a start date' ;
} else if ( step === 4 ) {
if ( ! agreeServiceTerms ) next . agreeServiceTerms = 'Please confirm you have read the service terms' ;
if ( ! agreeCancellation ) next . agreeCancellation = 'Please confirm the cancellation policy' ;
if ( ! agreePayment ) next . agreePayment = 'Please confirm the payment terms' ;
if ( ! agreeEmergency ) next . agreeEmergency = 'Please confirm emergency consent' ;
if ( ! agreeLiability ) next . agreeLiability = 'Please confirm the liability terms' ;
if ( ! agreeAccuracy ) next . agreeAccuracy = 'Please confirm the information is accurate' ;
if ( ! signatureDataUrl ) next . signature = 'Please add your signature before sending' ;
}
errors = next ;
return Object . keys ( next ). length === 0 ;
}
function nextStep() {
noteInteraction ();
if ( ! validateStep ( currentStep )) return ;
currentStep += 1 ;
void saveDraft ();
window . scrollTo ({ top : 0 , behavior : 'smooth' });
}
function prevStep() {
errors = {};
currentStep -= 1 ;
window . scrollTo ({ top : 0 , behavior : 'smooth' });
}
async function handleSubmit ( event : SubmitEvent ) {
event . preventDefault ();
noteInteraction ();
sendClickedAt = Date . now ();
submitError = '' ;
if ( ! validateStep ( 4 )) return ;
submitting = true ;
try {
const response = await fetch ( '/api/contract-submit' , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' },
body : JSON.stringify ({
fullName , email , phone , address ,
dogName , dogBreed , dogAge ,
serviceType , startDate , walkFrequency , additionalNotes ,
agreeServiceTerms , agreeCancellation , agreePayment ,
agreeEmergency , agreeLiability , agreeAccuracy ,
signatureDataUrl , website ,
formStartedAt , visitStartedAt , pageEnteredAt , firstInteractionAt , sendClickedAt ,
referrer : document.referrer ,
page : window.location.href ,
})
});
if ( ! response . ok ) {
const payload = await response . json (). catch (() => null );
throw new Error ( payload ? . detail ? . message ?? payload ? . detail ?? 'Submission failed' );
}
submitted = true ;
try { window . localStorage . removeItem ( draftStorageKey ); } catch { /* ignore */ }
window . scrollTo ({ top : 0 , behavior : 'smooth' });
} catch ( error ) {
submitError = error instanceof Error ? error . message : 'Submission failed' ;
} finally {
submitting = false ;
}
}
</ script >
< svelte:head >
< title > Goodwalk Service Agreement</ title >
< meta
name = "description"
content = "Sign your Goodwalk service agreement online."
/>
< meta name = "robots" content = "noindex, nofollow" />
< link rel = "canonical" href = "https://onboarding.goodwalk.co.nz/contract/" />
</ svelte:head >
< main class = "contract-page" >
< div class = "contract-topbar" >
< div class = "contract-shell contract-topbar-inner" >
< div class = "contract-brand" >
< a href = "https://goodwalk.co.nz" class = "contract-logo" aria-label = "Goodwalk home" >
< picture >
{ #if desktop . sources ? . webp }
< source type = "image/webp" srcset = { desktop . sources . webp } / >
{ /if }
< img
src = { desktop . img . src }
alt="Goodwalk logo "
width = { desktop . img . w }
height= { desktop . img . h }
decoding = "async"
/>
</ picture >
</ a >
< span class = "contract-pill" > Contract</ span >
</ div >
</ div >
</ div >
{ #if authChecking }
< div class = "contract-auth-checking" >
< Icon name = "fas fa-circle-notch" className = "auth-spinner" />
</ div >
{ :else if ! isAuthenticated }
< OnboardingAuth context = "contract" on:authenticated = { handleAuthenticated } / >
{ : else }
< div class = "journey-bar" >
< div class = "contract-shell journey-bar-inner" >
< a href = "/?preview=onboarding" class = "journey-stage" class:journey-done = { onboardingCompleted } class:journey-current= { ! onboardingCompleted } >
< span class = "journey-stage-icon" >
{ #if onboardingCompleted }
< Icon name = "fas fa-check" />
{ : else }
< Icon name = "fas fa-clipboard-list" />
{ /if }
</ span >
< span class = "journey-stage-label" > Onboarding form</ span >
{ #if onboardingCompleted }
< span class = "journey-badge journey-badge-done" > Done</ span >
{ : else }
< span class = "journey-badge journey-badge-todo" > To do</ span >
{ /if }
</ a >
< div class = "journey-connector" class:journey-connector-done = { onboardingCompleted } > </div >
< div class = "journey-stage journey-stage-active" >
< span class = "journey-stage-icon" >
{ #if contractCompleted }
< Icon name = "fas fa-check" />
{ : else }
< Icon name = "fas fa-file-signature" />
{ /if }
</ span >
< span class = "journey-stage-label" > Service contract</ span >
{ #if contractCompleted }
< span class = "journey-badge journey-badge-done" > Signed</ span >
{ : else }
< span class = "journey-badge journey-badge-current" > In progress</ span >
{ /if }
</ div >
</ div >
</ div >
{ #if ! onboardingCompleted }
< div class = "onboarding-warning" >
< div class = "contract-shell onboarding-warning-inner" >
< Icon name = "fas fa-triangle-exclamation" />
< div >
< strong > You haven't completed your onboarding form yet.</ strong >
Aless will need this before your service can start.
< a href = "/?preview=onboarding" > Complete it now →</ a >
</ div >
</ div >
</ div >
{ /if }
{ #if idleWarning }
< div class = "idle-warning" role = "alert" >
< div class = "contract-shell idle-warning-inner" >
< Icon name = "fas fa-clock" />
< span > You'll be signed out in 2 minutes due to inactivity. Your progress has been saved.</ span >
< button class = "idle-stay" on:click = { resetIdle } > Stay signed in </ button >
</ div >
</ div >
{ /if }
< section class = "contract-hero" >
< div class = "contract-shell contract-hero-inner" >
< div class = "contract-hero-copy" >
< span class = "contract-eyebrow" > Service agreement</ span >
< h1 > Review the terms, fill in your details, and sign to get started.</ h1 >
< p >
This is Goodwalk's standard service agreement. Read through the terms, confirm each
section, and sign at the end. Your signed copy goes directly to Aless.
</ p >
</ div >
< div class = "contract-contact-card" >
< div class = "contract-contact-title" > Questions about the contract?</ div >
< a href = { `mailto: ${ ownerEmail } ` } class="contract-contact-link" >
< Icon name = "fas fa-envelope" />
{ ownerEmail }
</ a >
< a href = { `tel: ${ ownerPhone . replace ( /[^0-9+]/g , '' ) } ` } class="contract-contact-link" >
< Icon name = "fas fa-phone" />
{ ownerPhone }
</ a >
</ div >
</ div >
</ section >
< section class = "contract-form-section" >
< div class = "contract-shell" >
{ #if submitted }
< div class = "contract-success-card" >
< span class = "contract-success-badge" > Signed</ span >
< h2 > Thank you. Your signed agreement is with us.</ h2 >
< p >
Aless has received your signed service agreement. We'll be in touch to confirm
your start date and any final details.
</ p >
</ div >
{ : else }
<!-- Step progress -->
< div class = "step-progress" >
{ #each steps as step , i }
< button
type = "button"
class = "step-indicator"
class:active = { currentStep === i + 1 }
class:completed= { currentStep > i + 1 }
on:click = {() => { if ( currentStep > i + 1 ) { errors = {}; currentStep = i + 1 ; window . scrollTo ({ top : 0 , behavior : 'smooth' }); } }}
disabled= { currentStep <= i + 1 }
aria-label = "Go to step { i + 1 } : { step . title } "
>
< div class = "step-indicator-circle" >
{ #if currentStep > i + 1 }
< Icon name = "fas fa-check" />
{ : else }
< Icon name = { step . icon } / >
{ /if }
</ div >
< span class = "step-indicator-label" > { step . title } </ span >
</ button >
{ #if i < steps . length - 1 }
< div class = "step-connector" class:filled = { currentStep > i + 1 } > </div >
{ /if }
{ /each }
</ div >
< form class = "contract-form" novalidate on:submit = { handleSubmit } >
<!-- Step 1: Your details + dog -->
{ #if currentStep === 1 }
< section class = "contract-panel" >
< div class = "contract-panel-head" >
< div class = "contract-step-icon" >
< Icon name = "fas fa-user" />
</ div >
< div >
< h2 > { steps [ 0 ]. title } </ h2 >
< p > { steps [ 0 ]. desc } </ p >
</ div >
</ div >
< div class = "prefill-notice" >
< Icon name = "fas fa-circle-check" />
< span > We've pre-filled this from your contact form. Please check everything looks right.</ span >
</ div >
< div class = "contract-fields two-up" >
< label class = "field" >
< span > Full name < span class = "req" > *</ span ></ span >
< input bind:value = { fullName } on:input= { noteInteraction } />
{ #if errors . fullName } < small > { errors . fullName } </ small > { /if }
</ label >
< label class = "field" >
< span > Email < span class = "req" > *</ span ></ span >
< input bind:value = { email } type="email" on:input = { noteInteraction } / >
{ #if errors . email } < small > { errors . email } </ small > { /if }
</ label >
< label class = "field" >
< span > Phone < span class = "req" > *</ span ></ span >
< input bind:value = { phone } type="tel" on:input = { noteInteraction } / >
{ #if errors . phone } < small > { errors . phone } </ small > { /if }
</ label >
< label class = "field field-full" >
< span > Home address < span class = "req" > *</ span ></ span >
< input bind:value = { address } on:input= { noteInteraction } />
{ #if errors . address } < small > { errors . address } </ small > { /if }
</ label >
</ div >
< div class = "contract-section-divider" >
< Icon name = "fas fa-paw" />
< span > Dog details</ span >
</ div >
< div class = "contract-fields two-up" >
< label class = "field" >
< span > Dog's name < span class = "req" > *</ span ></ span >
< input bind:value = { dogName } on:input= { noteInteraction } />
{ #if errors . dogName } < small > { errors . dogName } </ small > { /if }
</ label >
< label class = "field" >
< span > Breed < span class = "req" > *</ span ></ span >
< input bind:value = { dogBreed } on:input= { noteInteraction } />
{ #if errors . dogBreed } < small > { errors . dogBreed } </ small > { /if }
</ label >
< label class = "field" >
< span > Age</ span >
< input bind:value = { dogAge } placeholder="Optional" on:input = { noteInteraction } / >
</ label >
</ div >
< div class = "panel-nav" >
< button class = "btn btn-yellow panel-nav-next" type = "button" on:click = { nextStep } >
Save and continue < Icon name = "fas fa-arrow-right" />
</ button >
</ div >
</ section >
{ /if }
<!-- Step 2: Service details -->
{ #if currentStep === 2 }
< section class = "contract-panel" >
< div class = "contract-panel-head" >
< div class = "contract-step-icon" >
< Icon name = "fas fa-dog" />
</ div >
< div >
< h2 > { steps [ 1 ]. title } </ h2 >
< p > { steps [ 1 ]. desc } </ p >
</ div >
</ div >
< div class = "field" >
< span > Service type</ span >
< div class = "chip-grid" >
{ #each services as service }
< label class:chip-selected = { serviceType === service } class="chip-option" >
< input
type = "radio"
name = "serviceType"
value = { service }
bind:group= { serviceType }
on:change = { noteInteraction }
/ >
< span > { service } </ span >
</ label >
{ /each }
</ div >
{ #if errors . serviceType } < small > { errors . serviceType } </ small > { /if }
</ div >
< div class = "contract-fields two-up" style = "margin-top:20px;" >
< label class = "field" >
< span > Proposed start date < span class = "req" > *</ span ></ span >
< input bind:value = { startDate } type="date" min = { today } on:input= { noteInteraction } />
{ #if errors . startDate } < small > { errors . startDate } </ small > { /if }
</ label >
< label class = "field" >
< span > Walk frequency</ span >
< input bind:value = { walkFrequency } placeholder="e.g. 3 × per week " on:input = { noteInteraction } / >
</ label >
</ div >
< label class = "field" style = "margin-top:20px;" >
< span > Additional notes</ span >
< textarea
bind:value = { additionalNotes }
rows="4"
placeholder = "Anything else Aless should know before confirming your contract."
on:input = { noteInteraction }
> </textarea >
</ label >
< div class = "panel-nav" >
< button class = "panel-nav-back" type = "button" on:click = { prevStep } >
< Icon name = "fas fa-arrow-left" /> Back
</ button >
< button class = "btn btn-yellow panel-nav-next" type = "button" on:click = { nextStep } >
Save and continue < Icon name = "fas fa-arrow-right" />
</ button >
</ div >
</ section >
{ /if }
<!-- Step 3: Terms -->
{ #if currentStep === 3 }
< section class = "contract-panel" >
< div class = "contract-panel-head" >
< div class = "contract-step-icon" >
< Icon name = "fas fa-file-alt" />
</ div >
< div >
< h2 > { steps [ 2 ]. title } </ h2 >
< p > { steps [ 2 ]. desc } </ p >
</ div >
</ div >
< div class = "terms-body" >
< div class = "terms-block" >
< h3 >< Icon name = "fas fa-walking" /> Services</ h3 >
< p > Goodwalk provides dog walking and pet care services as agreed. All dogs must be assessed for group compatibility before joining pack walks. Goodwalk reserves the right to decline or discontinue service if a dog poses a risk to others.</ p >
</ div >
< div class = "terms-block" >
< h3 >< Icon name = "fas fa-ban" /> Cancellation policy</ h3 >
< p > Cancellations must be made at least < strong > 24 hours in advance</ strong > . Late cancellations (under 24 hours) will be charged at the full rate. No-shows will be charged in full. Goodwalk will provide as much notice as possible if a walk cannot proceed due to illness or an emergency.</ p >
</ div >
< div class = "terms-block" >
< h3 >< Icon name = "fas fa-credit-card" /> Payment</ h3 >
< p > Invoices are issued weekly and are due within < strong > 7 days</ strong > of issue. Unpaid invoices may result in service being suspended. Payment is accepted by bank transfer to the details provided on the invoice.</ p >
</ div >
< div class = "terms-block" >
< h3 >< Icon name = "fas fa-heartbeat" /> Emergency care</ h3 >
< p > In the event of a medical emergency and if the owner cannot be reached, Goodwalk is authorised to seek urgent veterinary treatment. Any costs incurred will be the responsibility of the owner.</ p >
</ div >
< div class = "terms-block" >
< h3 >< Icon name = "fas fa-shield-alt" /> Liability</ h3 >
< p > Goodwalk takes every reasonable precaution for the safety of your dog. However, Goodwalk cannot be held liable for injury, illness, loss, or death caused by circumstances beyond our reasonable control, including interaction with other animals or environmental hazards. The owner confirms their dog is covered by appropriate insurance where applicable.</ p >
</ div >
< div class = "terms-block" >
< h3 >< Icon name = "fas fa-lock" /> Privacy</ h3 >
< p > Your personal information is collected solely for the purpose of providing and administering the service. It will not be shared with third parties without your consent, except where required by law.</ p >
</ div >
</ div >
< div class = "panel-nav" >
< button class = "panel-nav-back" type = "button" on:click = { prevStep } >
< Icon name = "fas fa-arrow-left" /> Back
</ button >
< button class = "btn btn-yellow panel-nav-next" type = "button" on:click = { nextStep } >
I have read the terms < Icon name = "fas fa-arrow-right" />
</ button >
</ div >
</ section >
{ /if }
<!-- Step 4: Sign and send -->
{ #if currentStep === 4 }
< section class = "contract-panel" >
< div class = "contract-panel-head" >
< div class = "contract-step-icon" >
< Icon name = "fas fa-file-signature" />
</ div >
< div >
< h2 > { steps [ 3 ]. title } </ h2 >
< p > { steps [ 3 ]. desc } </ p >
</ div >
</ div >
< div class = "contract-confirm-list" >
< label class = "confirm-row" >
< input bind:checked = { agreeServiceTerms } type="checkbox" on:change = { noteInteraction } / >
< span > I have read and understood the service terms and conditions above.</ span >
</ label >
{ #if errors . agreeServiceTerms } < small > { errors . agreeServiceTerms } </ small > { /if }
< label class = "confirm-row" >
< input bind:checked = { agreeCancellation } type="checkbox" on:change = { noteInteraction } / >
< span > I agree to the cancellation policy, including charges for late cancellations and no-shows.</ span >
</ label >
{ #if errors . agreeCancellation } < small > { errors . agreeCancellation } </ small > { /if }
< label class = "confirm-row" >
< input bind:checked = { agreePayment } type="checkbox" on:change = { noteInteraction } / >
< span > I agree to pay invoices within 7 days of issue.</ span >
</ label >
{ #if errors . agreePayment } < small > { errors . agreePayment } </ small > { /if }
< label class = "confirm-row" >
< input bind:checked = { agreeEmergency } type="checkbox" on:change = { noteInteraction } / >
< span > I authorise Goodwalk to seek emergency veterinary care if I cannot be reached, and accept responsibility for any costs.</ span >
</ label >
{ #if errors . agreeEmergency } < small > { errors . agreeEmergency } </ small > { /if }
< label class = "confirm-row" >
< input bind:checked = { agreeLiability } type="checkbox" on:change = { noteInteraction } / >
< span > I acknowledge Goodwalk's liability limitations as described in the terms.</ span >
</ label >
{ #if errors . agreeLiability } < small > { errors . agreeLiability } </ small > { /if }
< label class = "confirm-row" >
< input bind:checked = { agreeAccuracy } type="checkbox" on:change = { noteInteraction } / >
< span > I confirm the information I have provided in this agreement is accurate and complete.</ span >
</ label >
{ #if errors . agreeAccuracy } < small > { errors . agreeAccuracy } </ small > { /if }
</ div >
< div class = "signature-wrap" >
< div class = "signature-header" >
< div >
< span > Signature</ span >
< p > Draw your signature below.</ p >
</ div >
< button
class = "signature-clear"
type = "button"
on:click = {() => {
noteInteraction ();
signaturePad . clear ();
errors = { ... errors , signature : '' };
}}
>
Clear
</ button >
</ div >
< div class:signature-error = { Boolean ( errors . signature )} class="signature-frame" >
< OnboardingSignaturePad bind:this = { signaturePad } bind:value= { signatureDataUrl } />
</ div >
{ #if errors . signature } < small > { errors . signature } </ small > { /if }
</ div >
< input bind:value = { website } class="honeypot" tabindex = "-1" autocomplete = "off" />
{ #if submitError }
< div class = "submit-error" > { submitError } </ div >
{ /if }
< div class = "panel-nav" >
< button class = "panel-nav-back" type = "button" on:click = { prevStep } >
< Icon name = "fas fa-arrow-left" /> Back
</ button >
< div class = "panel-nav-submit-group" >
< button class = "btn btn-yellow contract-submit" type = "submit" disabled = { submitting } >
{ #if submitting } Sending…{ : else } Send signed contract{ /if }
</ button >
< p > This goes directly to Aless for review.</ p >
</ div >
</ div >
</ section >
{ /if }
</ form >
{ /if }
</ div >
</ section >
< OnboardingFooter email = { userEmail } on:logout= { handleLogout } />
{ /if }
</ main >
< style >
. contract-page {
min-height : 100 vh ;
background :
radial-gradient ( circle at top right , rgba ( 255 , 209 , 0 , 0.1 ), transparent 28 % ),
linear-gradient ( 180 deg , #f6f5ef 0 % , #fbfbf9 42 % , #f3f1e8 100 % );
color : var ( -- gw - green );
}
. contract-shell {
max-width : 1120 px ;
margin : 0 auto ;
padding : 0 28 px ;
}
. contract-auth-checking {
display : flex ;
align-items : center ;
justify-content : center ;
min-height : 200 px ;
color : rgba ( 33 , 48 , 33 , 0.35 );
font-size : 24 px ;
}
: global ( . auth-spinner ) {
animation : spin 0.8 s linear infinite ;
}
@ keyframes spin {
to { transform : rotate ( 360 deg ); }
}
/* ── Top nav bar ── */
. contract-topbar {
2026-05-12 00:45:02 +12:00
background : var ( -- gw - green );
2026-05-11 21:02:24 +12:00
}
. contract-topbar-inner {
display : flex ;
align-items : center ;
justify-content : space-between ;
gap : 16 px ;
height : 56 px ;
}
. contract-brand {
display : flex ;
align-items : center ;
gap : 10 px ;
flex-shrink : 0 ;
}
. contract-logo {
display : flex ;
align-items : center ;
}
. contract-logo img {
height : 28 px ;
width : auto ;
display : block ;
}
. contract-pill {
padding : 4 px 10 px ;
border-radius : 999 px ;
2026-05-12 00:45:02 +12:00
margin-left : 10 pt ;
background : var ( -- yellow );
2026-05-11 21:02:24 +12:00
font-family : var ( -- font - head );
font-size : 11 px ;
font-weight : 700 ;
letter-spacing : 0.08 em ;
text-transform : uppercase ;
2026-05-12 00:45:02 +12:00
color : var ( -- gw - green );
2026-05-11 21:02:24 +12:00
}
/* ── Journey bar ── */
. journey-bar {
background : #1a2a1a ;
border-bottom : 1 px solid rgba ( 255 , 255 , 255 , 0.06 );
}
. journey-bar-inner {
display : flex ;
align-items : center ;
gap : 0 ;
height : 48 px ;
}
. journey-stage {
display : flex ;
align-items : center ;
gap : 8 px ;
padding : 0 4 px ;
text-decoration : none ;
color : rgba ( 255 , 255 , 255 , 0.45 );
font-family : var ( -- font - head );
font-size : 13 px ;
font-weight : 700 ;
white-space : nowrap ;
transition : color 0.15 s ;
}
. journey-stage : hover {
color : rgba ( 255 , 255 , 255 , 0.7 );
}
. journey-stage-active {
color : rgba ( 255 , 255 , 255 , 0.9 );
cursor : default ;
}
. journey-stage-active : hover {
color : rgba ( 255 , 255 , 255 , 0.9 );
}
. journey-done {
color : rgba ( 255 , 255 , 255 , 0.65 );
}
. journey-stage-icon {
width : 22 px ;
height : 22 px ;
border-radius : 50 % ;
display : inline-flex ;
align-items : center ;
justify-content : center ;
font-size : 10 px ;
background : rgba ( 255 , 255 , 255 , 0.1 );
flex-shrink : 0 ;
}
. journey-done . journey-stage-icon {
background : #213021 ;
color : #7aaa7a ;
}
. journey-stage-active . journey-stage-icon {
background : linear-gradient ( 180 deg , #ffe36b 0 % , #ffd100 100 % );
color : #213021 ;
}
. journey-badge {
padding : 2 px 8 px ;
border-radius : 999 px ;
font-size : 10 px ;
font-weight : 700 ;
letter-spacing : 0.06 em ;
text-transform : uppercase ;
}
. journey-badge-done {
background : rgba ( 122 , 170 , 122 , 0.18 );
color : #7aaa7a ;
}
. journey-badge-todo {
background : rgba ( 255 , 100 , 60 , 0.15 );
color : #ff8060 ;
}
. journey-badge-current {
background : rgba ( 255 , 209 , 0 , 0.18 );
color : #ffd100 ;
}
. journey-connector {
flex : 1 ;
height : 1 px ;
background : rgba ( 255 , 255 , 255 , 0.1 );
margin : 0 12 px ;
max-width : 60 px ;
}
. journey-connector-done {
background : rgba ( 122 , 170 , 122 , 0.4 );
}
/* ── Onboarding warning ── */
. onboarding-warning {
background : rgba ( 255 , 160 , 60 , 0.1 );
border-bottom : 1 px solid rgba ( 255 , 160 , 60 , 0.2 );
}
. onboarding-warning-inner {
display : flex ;
align-items : center ;
gap : 12 px ;
padding-top : 12 px ;
padding-bottom : 12 px ;
font-size : 14 px ;
line-height : 1.5 ;
color : #8a5a20 ;
}
. onboarding-warning-inner : global ( . fa-triangle-exclamation ) {
font-size : 18 px ;
color : #e08020 ;
flex-shrink : 0 ;
}
. onboarding-warning-inner strong {
font-weight : 700 ;
color : #6b4010 ;
}
. onboarding-warning-inner a {
margin-left : 6 px ;
font-weight : 700 ;
color : #6b4010 ;
text-decoration : underline ;
}
/* ── Idle warning ── */
. idle-warning {
background : rgba ( 255 , 160 , 60 , 0.1 );
border-bottom : 1 px solid rgba ( 255 , 160 , 60 , 0.2 );
}
. idle-warning-inner {
display : flex ;
align-items : center ;
gap : 12 px ;
padding-top : 12 px ;
padding-bottom : 12 px ;
font-size : 14 px ;
color : #8a5a20 ;
}
. idle-stay {
margin-left : auto ;
padding : 8 px 16 px ;
border-radius : 999 px ;
border : 1 px solid rgba ( 138 , 90 , 32 , 0.3 );
background : #fff ;
font-family : var ( -- font - head );
font-size : 13 px ;
font-weight : 700 ;
color : #8a5a20 ;
cursor : pointer ;
white-space : nowrap ;
transition : background 0.15 s ;
}
. idle-stay : hover {
background : rgba ( 255 , 160 , 60 , 0.1 );
}
/* ── Hero ── */
. contract-hero {
padding : 28 px 0 22 px ;
}
. contract-hero-inner {
display : grid ;
gap : 20 px ;
}
. contract-hero-copy {
max-width : 760 px ;
}
. contract-eyebrow {
display : inline-block ;
margin-bottom : 10 px ;
font-family : var ( -- font - head );
font-size : 12 px ;
font-weight : 700 ;
letter-spacing : 0.12 em ;
text-transform : uppercase ;
color : #688568 ;
}
. contract-hero-copy h1 {
margin : 0 0 12 px ;
font-family : var ( -- font - head );
font-size : clamp ( 22 px , 2.6 vw , 32 px );
font-weight : 800 ;
line-height : 1.1 ;
letter-spacing : -0.03 em ;
max-width : 32 ch ;
}
. contract-hero-copy p {
margin : 0 ;
max-width : 620 px ;
font-size : 15 px ;
line-height : 1.65 ;
color : rgba ( 33 , 48 , 33 , 0.78 );
}
. contract-contact-card {
display : flex ;
align-items : center ;
gap : 14 px ;
flex-wrap : wrap ;
padding : 22 px 24 px ;
border-radius : 24 px ;
background : rgba ( 255 , 255 , 255 , 0.72 );
border : 1 px solid rgba ( 33 , 48 , 33 , 0.08 );
box-shadow : 0 18 px 40 px rgba ( 33 , 48 , 33 , 0.06 );
}
. contract-contact-title {
margin-right : auto ;
font-family : var ( -- font - head );
font-size : 18 px ;
font-weight : 700 ;
letter-spacing : -0.02 em ;
}
. contract-contact-link {
display : inline-flex ;
align-items : center ;
gap : 8 px ;
padding : 11 px 18 px ;
border-radius : 999 px ;
background : #fff ;
border : 1 px solid rgba ( 33 , 48 , 33 , 0.1 );
font-family : var ( -- font - head );
font-size : 14 px ;
font-weight : 700 ;
line-height : 1.2 ;
letter-spacing : 0.01 em ;
color : var ( -- gw - green );
}
/* ── Form section ── */
. contract-form-section {
padding : 0 0 64 px ;
}
/* ── Step progress ── */
. step-progress {
display : flex ;
align-items : center ;
margin-bottom : 24 px ;
padding : 20 px 24 px ;
border-radius : 24 px ;
background : rgba ( 255 , 255 , 255 , 0.72 );
border : 1 px solid rgba ( 33 , 48 , 33 , 0.08 );
box-shadow : 0 8 px 24 px rgba ( 33 , 48 , 33 , 0.05 );
}
. step-indicator {
display : flex ;
flex-direction : column ;
align-items : center ;
gap : 8 px ;
flex : 1 ;
background : none ;
border : none ;
padding : 0 ;
cursor : default ;
}
. step-indicator . completed {
cursor : pointer ;
}
. step-indicator-circle {
width : 44 px ;
height : 44 px ;
border-radius : 50 % ;
display : inline-flex ;
align-items : center ;
justify-content : center ;
font-size : 17 px ;
background : rgba ( 33 , 48 , 33 , 0.07 );
color : rgba ( 33 , 48 , 33 , 0.35 );
transition : background 0.2 s , color 0.2 s ;
}
. step-indicator . active . step-indicator-circle {
background : linear-gradient ( 180 deg , #ffe36b 0 % , #ffd100 100 % );
color : #213021 ;
}
. step-indicator . completed . step-indicator-circle {
background : #213021 ;
color : #fff ;
}
. step-indicator-label {
font-family : var ( -- font - head );
font-size : 12 px ;
font-weight : 700 ;
letter-spacing : 0.01 em ;
color : rgba ( 33 , 48 , 33 , 0.4 );
text-align : center ;
transition : color 0.2 s ;
}
. step-indicator . active . step-indicator-label ,
. step-indicator . completed . step-indicator-label {
color : #213021 ;
}
. step-connector {
flex : 1 ;
height : 2 px ;
background : rgba ( 33 , 48 , 33 , 0.1 );
margin-bottom : 26 px ;
transition : background 0.3 s ;
}
. step-connector . filled {
background : #213021 ;
}
/* ── Panels ── */
. contract-panel ,
. contract-success-card {
padding : 30 px ;
border-radius : 28 px ;
background : rgba ( 255 , 255 , 255 , 0.84 );
border : 1 px solid rgba ( 33 , 48 , 33 , 0.08 );
box-shadow : 0 20 px 44 px rgba ( 33 , 48 , 33 , 0.07 );
}
. contract-success-card {
max-width : 760 px ;
margin : 0 auto ;
text-align : center ;
}
. contract-success-badge {
display : inline-flex ;
align-items : center ;
justify-content : center ;
margin-bottom : 14 px ;
padding : 8 px 14 px ;
border-radius : 999 px ;
background : rgba ( 122 , 170 , 122 , 0.14 );
font-family : var ( -- font - head );
font-size : 12 px ;
font-weight : 700 ;
letter-spacing : 0.09 em ;
text-transform : uppercase ;
color : #4d6d4d ;
}
. contract-success-card h2 ,
. contract-panel-head h2 {
margin : 0 ;
font-family : var ( -- font - head );
font-size : clamp ( 26 px , 3 vw , 36 px );
font-weight : 700 ;
line-height : 1.06 ;
letter-spacing : -0.03 em ;
text-wrap : balance ;
}
. contract-success-card p ,
. contract-panel-head p {
margin : 10 px 0 0 ;
font-size : 15 px ;
line-height : 1.65 ;
color : rgba ( 33 , 48 , 33 , 0.7 );
}
. contract-panel-head {
display : flex ;
gap : 18 px ;
align-items : flex-start ;
margin-bottom : 28 px ;
}
. contract-step-icon {
flex : none ;
width : 48 px ;
height : 48 px ;
border-radius : 16 px ;
display : inline-flex ;
align-items : center ;
justify-content : center ;
font-size : 20 px ;
background : linear-gradient ( 180 deg , #ffe36b 0 % , #ffd100 100 % );
color : #213021 ;
}
/* ── Pre-fill notice ── */
. prefill-notice {
display : flex ;
align-items : center ;
gap : 10 px ;
margin-bottom : 20 px ;
padding : 12 px 16 px ;
border-radius : 14 px ;
background : rgba ( 122 , 170 , 122 , 0.1 );
border : 1 px solid rgba ( 122 , 170 , 122 , 0.2 );
font-size : 13 px ;
line-height : 1.5 ;
color : #3d6b3d ;
}
. contract-section-divider {
display : flex ;
align-items : center ;
gap : 10 px ;
margin : 28 px 0 20 px ;
font-family : var ( -- font - head );
font-size : 13 px ;
font-weight : 700 ;
letter-spacing : 0.08 em ;
text-transform : uppercase ;
color : rgba ( 33 , 48 , 33 , 0.5 );
}
. contract-section-divider :: after {
content : '' ;
flex : 1 ;
height : 1 px ;
background : rgba ( 33 , 48 , 33 , 0.1 );
}
/* ── Fields ── */
. contract-fields {
display : grid ;
gap : 16 px ;
}
. two-up {
grid-template-columns : repeat ( 2 , minmax ( 0 , 1 fr ));
}
. field {
display : grid ;
gap : 8 px ;
}
. field-full {
grid-column : 1 / -1 ;
}
. field span ,
. signature-header span {
font-family : var ( -- font - head );
font-size : 15 px ;
font-weight : 700 ;
letter-spacing : -0.01 em ;
}
. field input ,
. field textarea ,
. field select {
width : 100 % ;
padding : 15 px 16 px ;
border : 1 px solid rgba ( 33 , 48 , 33 , 0.14 );
border-radius : 18 px ;
background : #fff ;
font : inherit ;
color : var ( -- gw - green );
outline : none ;
transition : border-color 0.18 s ease , box-shadow 0.18 s ease ;
appearance : none ;
}
. field input : focus ,
. field textarea : focus ,
. field select : focus {
border-color : rgba ( 255 , 209 , 0 , 0.9 );
box-shadow : 0 0 0 4 px rgba ( 255 , 209 , 0 , 0.16 );
}
. field textarea {
resize : vertical ;
min-height : 120 px ;
}
. req {
color : #b4422f ;
font-weight : 700 ;
}
. field small ,
. contract-confirm-list small ,
. signature-wrap small {
color : #b4422f ;
font-size : 12 px ;
line-height : 1.4 ;
}
/* ── Service chips (radio) ── */
. chip-grid {
display : flex ;
flex-wrap : wrap ;
gap : 10 px ;
margin-top : 4 px ;
}
. chip-option {
position : relative ;
display : inline-flex ;
align-items : center ;
justify-content : center ;
padding : 12 px 16 px ;
border-radius : 999 px ;
border : 1 px solid rgba ( 33 , 48 , 33 , 0.12 );
background : #fff ;
font-family : var ( -- font - head );
font-size : 14 px ;
font-weight : 700 ;
cursor : pointer ;
transition : transform 0.16 s ease , border-color 0.16 s ease , background 0.16 s ease ;
}
. chip-option input {
position : absolute ;
opacity : 0 ;
pointer-events : none ;
}
. chip-option . chip-selected {
background : #213021 ;
border-color : #213021 ;
color : #ffd100 ;
transform : translateY ( -1 px );
}
/* ── Terms ── */
. terms-body {
display : grid ;
gap : 0 ;
border-radius : 18 px ;
border : 1 px solid rgba ( 33 , 48 , 33 , 0.08 );
overflow : hidden ;
}
. terms-block {
padding : 20 px 24 px ;
border-bottom : 1 px solid rgba ( 33 , 48 , 33 , 0.07 );
}
. terms-block : last-child {
border-bottom : none ;
}
. terms-block h3 {
display : flex ;
align-items : center ;
gap : 10 px ;
margin : 0 0 8 px ;
font-family : var ( -- font - head );
font-size : 15 px ;
font-weight : 700 ;
letter-spacing : -0.01 em ;
color : #213021 ;
}
. terms-block p {
margin : 0 ;
font-size : 14 px ;
line-height : 1.7 ;
color : rgba ( 33 , 48 , 33 , 0.75 );
}
. terms-block p strong {
color : #213021 ;
font-weight : 700 ;
}
/* ── Declarations ── */
. contract-confirm-list {
display : grid ;
gap : 12 px ;
}
. confirm-row {
display : grid ;
grid-template-columns : 20 px 1 fr ;
gap : 12 px ;
align-items : start ;
padding : 14 px 16 px ;
border-radius : 18 px ;
background : #fff ;
border : 1 px solid rgba ( 33 , 48 , 33 , 0.08 );
}
. confirm-row input {
margin-top : 3 px ;
}
/* ── Signature ── */
. signature-wrap {
margin-top : 24 px ;
}
. signature-header {
display : flex ;
align-items : end ;
justify-content : space-between ;
gap : 16 px ;
margin-bottom : 12 px ;
}
. signature-header p {
margin : 5 px 0 0 ;
font-size : 13 px ;
line-height : 1.5 ;
color : rgba ( 33 , 48 , 33 , 0.65 );
}
. signature-frame {
padding : 6 px ;
border-radius : 24 px ;
background : rgba ( 33 , 48 , 33 , 0.05 );
border : 1 px solid rgba ( 33 , 48 , 33 , 0.1 );
}
. signature-frame . signature-error {
border-color : rgba ( 180 , 66 , 47 , 0.5 );
}
. signature-clear {
padding : 10 px 14 px ;
border-radius : 999 px ;
border : 1 px solid rgba ( 33 , 48 , 33 , 0.12 );
background : #fff ;
font-family : var ( -- font - head );
font-size : 13 px ;
font-weight : 700 ;
color : var ( -- gw - green );
cursor : pointer ;
}
/* ── Submit error ── */
. submit-error {
margin-top : 18 px ;
padding : 14 px 16 px ;
border-radius : 16 px ;
background : #fff3ef ;
color : #a43f2c ;
font-size : 14 px ;
line-height : 1.55 ;
}
/* ── Panel navigation ── */
. panel-nav {
display : flex ;
align-items : center ;
gap : 12 px ;
margin-top : 28 px ;
padding-top : 24 px ;
border-top : 1 px solid rgba ( 33 , 48 , 33 , 0.07 );
}
. panel-nav-back {
display : inline-flex ;
align-items : center ;
gap : 7 px ;
padding : 12 px 18 px ;
border-radius : 999 px ;
border : 1 px solid rgba ( 33 , 48 , 33 , 0.14 );
background : #fff ;
font-family : var ( -- font - head );
font-size : 14 px ;
font-weight : 700 ;
color : var ( -- gw - green );
cursor : pointer ;
transition : background 0.15 s , border-color 0.15 s ;
}
. panel-nav-back : hover {
background : rgba ( 33 , 48 , 33 , 0.05 );
border-color : rgba ( 33 , 48 , 33 , 0.22 );
}
. panel-nav-next {
margin-left : auto ;
display : inline-flex ;
align-items : center ;
gap : 8 px ;
}
. panel-nav-submit-group {
margin-left : auto ;
display : flex ;
flex-direction : column ;
align-items : flex-end ;
gap : 8 px ;
}
. panel-nav-submit-group p {
margin : 0 ;
font-size : 13 px ;
line-height : 1.5 ;
color : rgba ( 33 , 48 , 33 , 0.6 );
}
. contract-submit {
min-width : 220 px ;
}
. honeypot {
position : absolute ;
left : -9999 px ;
opacity : 0 ;
pointer-events : none ;
}
/* ── Mobile ── */
@ media ( max-width : 768px ) {
. contract-shell {
padding : 0 18 px ;
}
. journey-bar-inner {
height : auto ;
padding : 12 px 0 ;
}
. journey-stage-label {
display : none ;
}
. onboarding-warning-inner {
padding-top : 14 px ;
padding-bottom : 14 px ;
gap : 14 px ;
font-size : 13 px ;
}
. contract-topbar-inner {
height : 50 px ;
}
. contract-hero {
padding : 20 px 0 16 px ;
}
. contract-contact-card {
align-items : stretch ;
}
. contract-contact-title {
width : 100 % ;
margin-right : 0 ;
}
. contract-contact-link {
justify-content : center ;
width : 100 % ;
}
. step-progress {
padding : 16 px 12 px ;
}
. step-indicator-label {
font-size : 10 px ;
}
. step-indicator-circle {
width : 36 px ;
height : 36 px ;
font-size : 14 px ;
}
. step-connector {
margin-bottom : 22 px ;
}
. two-up {
grid-template-columns : 1 fr ;
}
. contract-panel ,
. contract-success-card {
padding : 22 px 18 px ;
border-radius : 22 px ;
}
. contract-panel-head {
gap : 14 px ;
margin-bottom : 20 px ;
}
. signature-header {
align-items : start ;
flex-direction : column ;
}
. panel-nav {
flex-wrap : wrap ;
}
. panel-nav-next ,
. contract-submit {
width : 100 % ;
justify-content : center ;
}
. panel-nav-submit-group {
width : 100 % ;
align-items : stretch ;
}
}
</ style >