diff --git a/W1/app.js b/W1/app.js
new file mode 100644
index 0000000..02e41e0
--- /dev/null
+++ b/W1/app.js
@@ -0,0 +1,106 @@
+// Phone format
+const PHONE_RE = /^\+?\d[\d\s()-]{7,}$/;
+
+const form = document.getElementById('signup-form');
+const fields = {
+ name: {
+ el: document.getElementById('name'),
+ err: document.getElementById('err-name'),
+ validate: (v) => v.trim() ? null : 'Name is required.',
+ },
+ phone: {
+ el: document.getElementById('phone'),
+ err: document.getElementById('err-phone'),
+ validate: (v) => {
+ const t = v.trim();
+ if (!t) return 'Phone number is required.';
+ if (!PHONE_RE.test(t)) return 'Enter a valid phone number (e.g. +61 123 456 789).';
+ return null;
+ },
+ },
+ dulux: {
+ el: document.getElementById('dulux'),
+ err: document.getElementById('err-dulux'),
+ validate: () => null,
+ },
+};
+
+function debounce(fn, wait = 250){
+ let t;
+ return function debounced(...args){
+ clearTimeout(t);
+ t = setTimeout(() => fn.apply(this, args), wait);
+ };
+}
+
+/*Error*/
+function setError(el, errEl, message){
+ const bad = Boolean(message);
+ el.classList.toggle('is-error', bad);
+ el.setAttribute('aria-invalid', bad ? 'true' : 'false');
+
+ if (bad) {
+ el.setAttribute('aria-describedby', errEl.id);
+ errEl.textContent = message;
+ errEl.classList.add('is-visible');
+ } else {
+ el.removeAttribute('aria-describedby');
+ errEl.classList.remove('is-visible');
+ errEl.textContent = '';
+ }
+}
+
+function validateField(key){
+ const { el, err, validate } = fields[key];
+ const msg = validate(el.value);
+ setError(el, err, msg);
+ return !msg;
+}
+
+function validateAll(){
+ let firstInvalid = null;
+ const ok = Object.keys(fields).every((k) => {
+ const valid = validateField(k);
+ if (!valid && !firstInvalid) firstInvalid = fields[k].el;
+ return valid;
+ });
+ return { ok, firstInvalid };
+}
+
+/*Toast*/
+function showToast(message, type = 'info', duration = 4200){
+ const root = document.getElementById('toast-root');
+ if (!root) return;
+ const item = document.createElement('div');
+ item.className = `toast toast--${type}`;
+ item.role = 'status';
+ item.textContent = message;
+ root.appendChild(item);
+
+ const remove = () => item.parentNode && root.removeChild(item);
+ const timer = setTimeout(remove, duration + 220);
+ item.addEventListener('click', () => { clearTimeout(timer); remove(); });
+}
+
+
+Object.values(fields).forEach(({ el }) => {
+ el.addEventListener('blur', () => validateField(el.id));
+ el.addEventListener(
+ 'input',
+ debounce(() => {
+ if (el.classList.contains('is-error')) validateField(el.id);
+ }, 250)
+ );
+});
+
+form.addEventListener('submit', (e) => {
+ e.preventDefault();
+ const { ok, firstInvalid } = validateAll();
+ if (!ok) {
+ firstInvalid?.focus();
+ showToast('Please fix the highlighted fields.', 'error');
+ return;
+ }
+ // Mockup submit successful
+ showToast('Signed up successfully!', 'success');
+});
\ No newline at end of file
diff --git a/W1/bg.png b/W1/bg.png
new file mode 100644
index 0000000..4212c30
Binary files /dev/null and b/W1/bg.png differ
diff --git a/W1/index.html b/W1/index.html
new file mode 100644
index 0000000..602ef2e
--- /dev/null
+++ b/W1/index.html
@@ -0,0 +1,60 @@
+
+
+
+
+
+ Sign Up • Paint Quote System
+
+
+
+
+
+ Welcome to Paint Quote System
+
+ Consequat adipisicing ea do labore irure adipisicing occaecat cupidatat
+ excepteur duis mo
+
+
+
+
+
+
Sign Up
+
Enter details to create your account
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/W1/styles.css b/W1/styles.css
new file mode 100644
index 0000000..32c7c89
--- /dev/null
+++ b/W1/styles.css
@@ -0,0 +1,207 @@
+:root{
+ --bg: #f5f3ef;
+ --ink: #0f172a;
+ --muted: #64748b;
+ --brand: #0d6ea8;
+ --brand-weak: #e0f2ff;
+ --panel: #ffffff;
+ --border: #e5e7eb;
+ --danger: #dc2626;
+ --danger-weak: #fee2e2;
+ --radius: 16px;
+ --shadow: 0 14px 40px rgba(2, 8, 23, .15);
+}
+
+*,
+*::before,
+*::after{ box-sizing: border-box; }
+
+html, body{
+ height: 100%;
+ margin: 0;
+ color: var(--ink);
+ background: var(--bg);
+ font: 16px/1.5 ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI",
+ Roboto, "Helvetica Neue", Arial, "Noto Sans", "Apple Color Emoji",
+ "Segoe UI Emoji", "Segoe UI Symbol";
+}
+
+.bg-photo{
+ background-image: url("bg.png");
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+}
+
+.shell{
+ min-height: 100dvh;
+ display: grid;
+ grid-template-columns: 1.15fr 0.8fr;
+ align-items: center;
+ gap: 5px;
+ padding: 48px clamp(30px, 6vw, 64px);
+ backdrop-filter: saturate(1.05);
+}
+
+.hero{
+ max-width: 620px;
+ justify-self: start;
+}
+.hero-title{
+ font-weight: 600;
+ font-size: clamp(28px, 2.2vw, 42px);
+ letter-spacing: .2px;
+ position: relative;
+ margin: 0;
+ display: inline-block;
+ color: #173b47;
+}
+.hero-title::after {
+ content: "";
+ display: block;
+ height: 1.8px;
+ margin: 15px 0 15px;
+ width: 100%;
+ background: currentColor;
+ opacity: .35;
+ border-radius: 2px;
+}
+.hero-desc{
+ margin: 0;
+ color: #173b47;
+ max-width: 46ch;
+}
+
+.card{
+ justify-self: end;
+ width: min(560px, 100%);
+ background: var(--panel);
+ border-radius: var(--radius);
+ box-shadow: var(--shadow);
+ border: 1px solid rgba(15,23,42,.05);
+ margin-right: 6vw;
+}
+.card-inner{
+ padding: 36px clamp(24px, 4vw, 44px);
+}
+.card-title{
+ margin: 0 0 6px;
+ text-align: center;
+ font-size: clamp(22px, 2.2vw, 30px);
+ font-weight: 600;
+}
+.card-subtitle{
+ margin: 0 0 24px;
+ text-align: center;
+ color: var(--muted);
+}
+
+.form{ display: grid; gap: 16px; }
+.field{ display: grid; gap: 8px; }
+.field label{ font-size: 14px; color: #374151; }
+
+.field input{
+ width: 100%;
+ padding: 12px 14px;
+ border-radius: 10px;
+ border: 1px solid var(--border);
+ outline: none;
+ background: #fff;
+ font-size: 15px;
+ transition: border-color .15s ease, box-shadow .15s ease, background-color .15s ease;
+}
+.field input::placeholder{ color: #9aa4b2; }
+.field input:focus-visible{
+ border-color: var(--brand);
+ box-shadow: 0 0 0 3px var(--brand-weak);
+}
+
+.error{
+ display: none;
+ margin-top: 6px;
+ font-size: 13px;
+ color: var(--danger);
+}
+.error.is-visible{ display: block; }
+
+.field input.is-error{
+ border-color: var(--danger);
+ box-shadow: 0 0 0 3px var(--danger-weak);
+}
+
+.btn{
+ appearance: none;
+ border: 0;
+ border-radius: 10px;
+ padding: 14px 18px;
+ font-weight: 600;
+ font-size: 16px;
+ cursor: pointer;
+ margin-top: 2vh;
+}
+.btn-primary{
+ background: var(--brand);
+ color: #fff;
+ box-shadow: 0 10px 22px rgba(13,110,168,.25);
+ transition: filter .15s ease, transform .02s ease, box-shadow .15s ease;
+}
+.btn-primary:hover{ filter: brightness(1.03); }
+.btn-primary:active{ transform: translateY(1px); }
+
+.footnote{
+ margin: 12px 0 0;
+ text-align: center;
+ color: var(--muted);
+ font-size: 14px;
+}
+.link{ color: var(--brand); text-decoration: none; }
+.link:hover{ text-decoration: underline; }
+
+/* 响应式 */
+@media (max-width: 900px){
+ .shell{
+ grid-template-columns: 1fr;
+ gap: 24px;
+ padding: 28px 18px 40px;
+ backdrop-filter: none;
+ }
+ .hero{ order: 1; max-width: 680px; justify-self: center; }
+ .card{ order: 2; justify-self: center; }
+}
+
+/* Toast */
+#toast-root{
+ position: fixed;
+ right: 20px;
+ bottom: 20px;
+ display: grid;
+ gap: 10px;
+ z-index: 9999;
+}
+.toast{
+ min-width: 260px;
+ max-width: 360px;
+ padding: 12px 14px;
+ border-radius: 10px;
+ color: #0f172a;
+ background: #ffffff;
+ border: 1px solid rgba(15,23,42,.1);
+ box-shadow: 0 10px 24px rgba(2,8,23,.16);
+ font: 14px/1.4 ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Arial;
+ opacity: 0;
+ transform: translateY(8px);
+ animation: toast-in .18s ease-out forwards, toast-out .2s ease-in forwards 4.2s;
+}
+.toast--success{ border-color: #05966933; box-shadow: 0 10px 24px rgba(5,150,105,.18); }
+.toast--error{ border-color: #dc262633; box-shadow: 0 10px 24px rgba(220,38,38,.18); }
+
+@keyframes toast-in{
+ to{ opacity:1; transform: translateY(0); }
+}
+@keyframes toast-out{
+ to{ opacity:0; transform: translateY(8px); }
+}
+
+@media (prefers-reduced-motion: reduce){
+ .toast{ animation: none; opacity:1; transform:none; }
+}
\ No newline at end of file