Design language tweaks v3

This commit is contained in:
2026-05-15 16:27:39 +12:00
parent 580d600c47
commit b950229003
14 changed files with 2746 additions and 246 deletions
@@ -0,0 +1,694 @@
<script lang="ts">
export let idPrefix: string;
export let lead = '';
export let submitLabel = 'Book a free Meet & Greet';
export let tone: 'default' | 'conversational' | 'minimal' = 'default';
export let layout: 'stacked' | 'carded' = 'stacked';
export let eyebrow = 'Free Meet & Greet';
export let title = 'Tell us about your dog';
export let subtitle =
'A guided first step so we can recommend the right walk, visit, or calmer starting point.';
let submitted = false;
let step = 1;
let ownerStepUnlocked = false;
function handleSubmit() {
if (step === 1) {
ownerStepUnlocked = true;
step = 2;
return;
}
submitted = true;
}
function goToStep(nextStep: number) {
if (nextStep === 2 && !ownerStepUnlocked) {
return;
}
step = nextStep;
}
</script>
<form
class={`vf-form vf-form--${tone} vf-form--${layout}`}
on:submit|preventDefault={handleSubmit}
novalidate
>
<div class="vf-header">
<span class="vf-eyebrow">{eyebrow}</span>
<h3 class="vf-title">{title}</h3>
<p class="vf-subtitle">{subtitle}</p>
<div class="vf-stepper" aria-label="Application steps">
<button
type="button"
class:active={step === 1}
class="vf-step"
on:click={() => goToStep(1)}
>
<span class="vf-step-number">1</span>
<span class="vf-step-label">Dog details</span>
</button>
<span class="vf-step-divider" aria-hidden="true"></span>
<button
type="button"
class:active={step === 2}
class="vf-step"
disabled={!ownerStepUnlocked}
aria-disabled={!ownerStepUnlocked}
on:click={() => goToStep(2)}
>
<span class="vf-step-number">2</span>
<span class="vf-step-label">Owner details</span>
</button>
</div>
</div>
{#if lead}
<p class="vf-lead">{lead}</p>
{/if}
{#if step === 1}
<section class="vf-group" aria-labelledby={`${idPrefix}-dog-group`}>
<div class="vf-group-head">
<p class="vf-kicker" id={`${idPrefix}-dog-group`}>Dog details</p>
<p class="vf-group-copy">
A few calm details first so we can judge fit, pace, and the right kind of outing.
</p>
</div>
<div class="vf-grid vf-grid--two">
<label class="vf-field">
<span class="vf-label">Dog name</span>
<input id={`${idPrefix}-dog-name`} name="dogName" type="text" placeholder="Maya" />
</label>
<label class="vf-field">
<span class="vf-label">Suburb / location</span>
<input
id={`${idPrefix}-location`}
name="location"
type="text"
placeholder="Mt Eden, Grey Lynn, Ponsonby…"
/>
</label>
</div>
<fieldset class="vf-choice-group">
<legend class="vf-label">What feels closest right now?</legend>
<p class="vf-helper">
Not sure is completely fine. We can sort that out at the Meet &amp; Greet.
</p>
<div class="vf-choices">
<label class="vf-choice">
<input type="radio" name={`${idPrefix}-service-fit`} value="tiny-gang" />
<span>Tiny Gang Pack Walks</span>
</label>
<label class="vf-choice">
<input type="radio" name={`${idPrefix}-service-fit`} value="one-to-one" />
<span>1:1 Walks</span>
</label>
<label class="vf-choice">
<input type="radio" name={`${idPrefix}-service-fit`} value="puppy-visits" />
<span>Puppy Visits</span>
</label>
<label class="vf-choice">
<input type="radio" name={`${idPrefix}-service-fit`} value="not-sure" checked />
<span>Not sure yet</span>
</label>
</div>
</fieldset>
<label class="vf-field vf-field--full">
<span class="vf-label">About your dog</span>
<textarea
id={`${idPrefix}-about-dog`}
name="aboutDog"
rows="5"
placeholder="Tell us about age, size, confidence, leash manners, how they feel around other dogs, and anything that helps us make the right recommendation."
></textarea>
</label>
<div class="vf-note-row" aria-label="Helpful notes">
<article class="vf-note">
<span class="vf-note-title">Nervous dogs</span>
<p>
If your dog is shy, easily overwhelmed, or still building confidence, mention it here.
That often points us toward a calmer first step.
</p>
</article>
<article class="vf-note">
<span class="vf-note-title">Tiny Gang suitability</span>
<p>
Our small-group walks work best for dogs who enjoy the right company, gentle
structure, and a routine they can settle into.
</p>
</article>
</div>
</section>
{/if}
{#if step === 2}
<section class="vf-group" aria-labelledby={`${idPrefix}-owner-group`}>
<div class="vf-group-head">
<p class="vf-kicker" id={`${idPrefix}-owner-group`}>Owner details</p>
<p class="vf-group-copy">
So Aless can reply properly and arrange a free Meet &amp; Greet without back-and-forth.
</p>
</div>
<div class="vf-grid vf-grid--three">
<label class="vf-field">
<span class="vf-label">Owner name</span>
<input id={`${idPrefix}-owner-name`} name="ownerName" type="text" placeholder="Your full name" />
</label>
<label class="vf-field">
<span class="vf-label">Email</span>
<input id={`${idPrefix}-email`} name="email" type="email" placeholder="you@example.com" />
</label>
<label class="vf-field">
<span class="vf-label">Phone</span>
<input id={`${idPrefix}-phone`} name="phone" type="tel" placeholder="021 123 4567" />
</label>
</div>
<article class="vf-owner-note">
<span class="vf-note-title">What happens next</span>
<p>
Well reply personally, talk through fit, and arrange the free Meet &amp; Greet if it
feels like the right next step.
</p>
</article>
</section>
{/if}
<div class="vf-footer">
<p class="vf-reassurance">
Free Meet &amp; Greet. No pressure. Just the right starting point for your dog.
</p>
<div class="vf-actions">
{#if step === 2}
<button type="button" class="vf-secondary" on:click={() => goToStep(1)}>Back</button>
{/if}
<button type="submit" class="vf-submit">
{step === 1 ? 'Next: owner details' : submitLabel}
</button>
</div>
</div>
{#if submitted}
<p class="vf-success" role="status">
Demo only: this variant would hand the enquiry to Aless and prompt a free Meet &amp; Greet.
</p>
{/if}
</form>
<style>
.vf-form {
display: grid;
gap: 24px;
}
.vf-form--carded {
gap: 18px;
}
.vf-header {
display: grid;
gap: 12px;
}
.vf-eyebrow {
display: inline-flex;
align-items: center;
width: fit-content;
min-height: 30px;
padding: 0 12px;
border-radius: 999px;
background: rgba(33, 48, 33, 0.07);
box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.05);
color: var(--gw-green);
font-family: var(--font-head);
font-size: 11px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.vf-title {
margin: 0;
color: #11171b;
font-family: var(--font-head);
font-size: clamp(26px, 3vw, 34px);
line-height: 1.06;
letter-spacing: -0.04em;
}
.vf-subtitle {
margin: 0;
color: #59606d;
font-size: 15px;
line-height: 1.6;
}
.vf-stepper {
display: flex;
align-items: center;
gap: 10px;
margin-top: 4px;
}
.vf-step {
display: inline-flex;
align-items: center;
gap: 10px;
min-height: 44px;
padding: 0 14px 0 10px;
border: none;
border-radius: 999px;
background: rgba(255, 255, 255, 0.84);
box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.06);
color: #50555c;
cursor: pointer;
transition:
background 0.18s ease,
color 0.18s ease,
transform 0.18s cubic-bezier(0.22, 1, 0.36, 1);
}
.vf-step.active {
background: rgba(33, 48, 33, 0.08);
color: #11171b;
}
.vf-step:disabled {
cursor: default;
opacity: 0.55;
}
.vf-step-number {
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: 10px;
background: var(--gw-green);
color: var(--yellow);
font-family: var(--font-head);
font-size: 12px;
font-weight: 700;
}
.vf-step-label {
font-family: var(--font-head);
font-size: 12px;
font-weight: 700;
line-height: 1.2;
}
.vf-step-divider {
width: 26px;
height: 1px;
background: rgba(17, 20, 24, 0.1);
}
.vf-lead {
margin: 0;
color: #4c5056;
font-size: 15px;
line-height: 1.65;
}
.vf-group {
display: grid;
gap: 18px;
}
.vf-form--carded .vf-group {
padding: 22px;
border-radius: 24px;
background: linear-gradient(180deg, rgba(251, 251, 251, 0.98) 0%, rgba(247, 248, 246, 1) 100%);
box-shadow:
inset 0 0 0 1px rgba(17, 20, 24, 0.06),
0 12px 28px rgba(17, 20, 24, 0.06);
}
.vf-group-head {
display: grid;
gap: 6px;
}
.vf-kicker {
margin: 0;
color: var(--gw-green);
font-family: var(--font-head);
font-size: 11px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.vf-group-copy {
margin: 0;
color: #59606d;
font-size: 14px;
line-height: 1.55;
}
.vf-grid {
display: grid;
gap: 14px;
}
.vf-grid--two {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.vf-grid--three {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.vf-field {
display: grid;
gap: 8px;
}
.vf-field--full {
width: 100%;
}
.vf-label,
.vf-choice-group legend {
color: #16181d;
font-size: 14px;
font-weight: 700;
line-height: 1.35;
}
.vf-field input,
.vf-field textarea {
width: 100%;
border: none;
border-radius: 18px;
background: rgba(255, 255, 255, 0.96);
box-shadow:
inset 0 0 0 1px rgba(17, 20, 24, 0.08),
0 1px 0 rgba(255, 255, 255, 0.3);
color: #1f2421;
padding: 15px 16px;
font-size: 15px;
line-height: 1.45;
outline: none;
transition:
box-shadow 0.18s ease,
transform 0.18s cubic-bezier(0.22, 1, 0.36, 1),
background 0.18s ease;
}
.vf-field textarea {
resize: vertical;
min-height: 132px;
}
.vf-field input:focus-visible,
.vf-field textarea:focus-visible,
.vf-choice input:focus-visible + span,
.vf-step:focus-visible,
.vf-secondary:focus-visible {
box-shadow:
inset 0 0 0 2px rgba(255, 209, 0, 0.7),
0 0 0 4px rgba(255, 209, 0, 0.14);
background: #fff;
outline: none;
}
.vf-choice-group {
display: grid;
gap: 10px;
min-width: 0;
margin: 0;
padding: 0;
border: none;
}
.vf-helper {
margin: 0;
color: #687076;
font-size: 13px;
line-height: 1.5;
}
.vf-choices {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.vf-choice {
position: relative;
}
.vf-choice input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.vf-choice span {
display: inline-flex;
align-items: center;
min-height: 42px;
padding: 0 14px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.9);
box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.08);
color: #2f3134;
font-size: 13px;
font-weight: 600;
line-height: 1.2;
transition:
box-shadow 0.18s ease,
background 0.18s ease,
color 0.18s ease,
transform 0.18s cubic-bezier(0.22, 1, 0.36, 1);
}
.vf-choice input:checked + span {
background: rgba(255, 209, 0, 0.18);
box-shadow: inset 0 0 0 1px rgba(255, 209, 0, 0.34);
color: #16181d;
}
.vf-note-row {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
.vf-note,
.vf-owner-note {
padding: 16px 16px 15px;
border-radius: 20px;
}
.vf-note {
background: rgba(33, 48, 33, 0.05);
box-shadow: inset 0 0 0 1px rgba(33, 48, 33, 0.06);
}
.vf-owner-note {
background: rgba(255, 209, 0, 0.12);
box-shadow: inset 0 0 0 1px rgba(255, 209, 0, 0.16);
}
.vf-note-title {
display: block;
margin-bottom: 6px;
color: var(--gw-green);
font-family: var(--font-head);
font-size: 11px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.vf-note p,
.vf-owner-note p {
margin: 0;
color: #4c5056;
font-size: 13px;
line-height: 1.55;
}
.vf-footer {
display: grid;
gap: 14px;
}
.vf-actions {
display: flex;
align-items: center;
gap: 12px;
}
.vf-reassurance {
margin: 0;
color: #59606d;
font-size: 13px;
line-height: 1.5;
}
.vf-secondary {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 52px;
padding: 0 18px;
border: none;
border-radius: 999px;
background: rgba(33, 48, 33, 0.07);
box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.06);
color: var(--gw-green);
font-family: var(--font-head);
font-size: 14px;
font-weight: 700;
line-height: 1.2;
cursor: pointer;
}
.vf-submit {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 52px;
width: fit-content;
padding: 0 22px;
border: none;
border-radius: 999px;
background: linear-gradient(135deg, #ffd54a, var(--yellow-soft));
box-shadow:
inset 0 -2px 0 rgba(0, 0, 0, 0.08),
0 12px 24px rgba(17, 20, 24, 0.1);
color: #111;
font-family: var(--font-head);
font-size: 14px;
font-weight: 700;
line-height: 1.2;
cursor: pointer;
transition:
transform 0.18s cubic-bezier(0.22, 1, 0.36, 1),
box-shadow 0.18s ease,
filter 0.18s ease;
}
.vf-submit:focus-visible {
outline: 3px solid rgba(255, 209, 0, 0.28);
outline-offset: 3px;
}
.vf-success {
margin: 0;
padding: 14px 16px;
border-radius: 18px;
background: rgba(33, 48, 33, 0.07);
box-shadow: inset 0 0 0 1px rgba(33, 48, 33, 0.08);
color: var(--gw-green);
font-size: 14px;
line-height: 1.55;
}
.vf-form--conversational .vf-label,
.vf-form--conversational .vf-choice-group legend {
font-family: var(--font-head);
font-size: 16px;
letter-spacing: -0.02em;
}
.vf-form--conversational .vf-field input,
.vf-form--conversational .vf-field textarea {
border-radius: 22px;
padding: 17px 18px;
font-size: 16px;
}
.vf-form--minimal .vf-group-copy,
.vf-form--minimal .vf-helper,
.vf-form--minimal .vf-reassurance,
.vf-form--minimal .vf-subtitle {
color: #6b7077;
}
.vf-form--minimal .vf-field input,
.vf-form--minimal .vf-field textarea,
.vf-form--minimal .vf-choice span {
box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.06);
}
@media (hover: hover) {
.vf-step:hover,
.vf-secondary:hover,
.vf-choice:hover span,
.vf-submit:hover {
transform: translateY(-1px);
}
.vf-submit:hover {
box-shadow:
inset 0 -2px 0 rgba(0, 0, 0, 0.08),
0 16px 28px rgba(17, 20, 24, 0.12);
filter: brightness(1.02);
}
}
@media (max-width: 768px) {
.vf-grid--two,
.vf-grid--three,
.vf-note-row {
grid-template-columns: 1fr;
}
.vf-stepper {
align-items: stretch;
gap: 8px;
}
.vf-step {
flex: 1 1 0;
min-width: 0;
}
.vf-step-divider {
display: none;
}
.vf-form {
gap: 20px;
}
.vf-form--carded .vf-group {
padding: 18px;
border-radius: 22px;
}
.vf-field input,
.vf-field textarea {
font-size: 16px;
}
.vf-actions {
flex-direction: column-reverse;
align-items: stretch;
}
.vf-secondary,
.vf-submit {
width: 100%;
}
}
</style>