학습 목표
- WebAssembly가 무엇이고 언제 쓰는지 이해한다
- Web Components와 Shadow DOM의 원리를 학습한다
- 접근성(a11y)을 WAI-ARIA와 키보드·스크린리더 관점에서 이해한다
- PWA / Service Worker / Offline 패턴을 익힌다
- i18n, 타입 안전 라우팅 등 실무에서 자주 마주치는 보조 주제를 정리한다
0. 이 장은 왜 "기타"인가 — 초심자용
0-1. 각자 중요하지만 단독 챕터로 하기엔 작은 주제들
5단계의 다른 장들이 "모든 FE가 매일 쓰는" 주제(JS 엔진·번들러·렌더링)였다면, 이 장은 "특정 상황에서 결정적으로 필요한" 주제들을 모은 것.
| 주제 | 언제 필요한가 |
|---|---|
| WebAssembly | CPU 집약적 작업을 브라우저에서 할 때 (이미지 편집, 게임) |
| Web Components | 프레임워크 독립적인 UI 컴포넌트 배포 시 |
| 접근성(a11y) | 모든 서비스가 갖춰야 할 기본 (법적 의무인 경우도) |
| PWA | 오프라인 지원, 설치 가능한 웹앱 만들 때 |
| i18n | 다국어 서비스 |
| 타입 안전 라우팅 | 대형 프로젝트에서 URL 버그 방지 |
0-2. "기타"지만 이거 하나는 꼭 — 접근성
위 주제 중 a11y (접근성) 만큼은 "언젠가 필요한" 게 아니라 지금 필요한 기본기.
- 한국: 공공기관 웹은 법적 의무 (장애인차별금지법)
- 해외: 미국 ADA, 유럽 EAA — 소송 사례 존재
- 키보드만 쓰는 사용자, 스크린리더 사용자 가 실제로 많음
- 많은 접근성 개선이 일반 UX도 향상시킴 (focus 관리, 시맨틱 HTML)
"시간 남으면 하자" 로 미루면 나중에 전체 UI 뜯어고쳐야 함. 처음부터 신경 써야 한다.
0-3. WebAssembly의 의의
"JS가 아닌 언어로 브라우저에서 네이티브 급 속도 실행" 이 가능해진 것.
실제 사례:
- Figma: C++ 로 작성된 렌더링 엔진을 Wasm으로 → 브라우저에서 데스크톱급 속도
- Google Earth: Unity/C++ → Wasm
- Adobe Photoshop Web: Wasm
"JS만 알면 된다" 시대에서 "C/Rust 같은 언어로 웹에 배포 가능" 시대로.
0-4. PWA와 Service Worker
Progressive Web App = 웹인데 네이티브 앱처럼:
- 홈 화면에 설치 가능
- 오프라인에서도 일부 기능 동작
- 푸시 알림
- 백그라운드 동기화
핵심은 Service Worker — 브라우저와 네트워크 사이에 끼어들어 요청을 가로채는 JS 스크립트. 2-9 캐싱에서 본 캐시 전략을 클라이언트에서 직접 구현 할 수 있게 해줌.
0-5. Web Components는 왜 안 뜨나
표준 기술로 컴포넌트를 만들 수 있는 방법. 그런데 실무에선 React/Vue가 압도적. 이유:
- 상태 관리, 라우팅 등 생태계 부재
- 서버 렌더링이 까다로움
- 프레임워크만큼 DX가 매끄럽지 않음
하지만 디자인 시스템을 여러 프레임워크에 배포해야 하는 대형 조직(Adobe Spectrum, Shoelace 등)에서는 꾸준히 쓰임.
0-6. 이 장을 다 읽은 뒤 할 수 있는 것
- "이 작업은 Wasm이 필요한가 JS로 충분한가" 판단
- 접근성 기본기로 기존 컴포넌트 개선
- PWA 기능을 점진적으로 추가
- i18n 전략 설계 (URL 구조, 폴백, 번들 분할)
"꼭 몰라도 되는 것들" 이 아니라 "특정 상황에서 모르면 끝장나는 것들" 의 모음이다.
1. WebAssembly (Wasm)
1-1. 정의
브라우저에서 네이티브에 가까운 속도로 실행되는, 스택 머신 기반의 바이너리 명령 포맷.
- C/C++/Rust/Go/AssemblyScript 등에서 컴파일
- JS와 같은 가상 머신에서 실행되지만 파싱·최적화 단계가 거의 불필요 → 빠른 startup
- 샌드박스에서 실행되어 안전
1-2. 언제 쓰는가
| 사용 사례 | 예시 |
|---|---|
| CPU 집약적 계산 | 이미지/비디오 처리, 게임 엔진, 물리 시뮬레이션 |
| 기존 C/C++ 라이브러리 재사용 | FFmpeg, SQLite, OpenCV |
| 보안이 중요한 로직 | 암호화, DRM |
| 결정론적 실행 | 블록체인 스마트 컨트랙트 |
대표 사례: Figma (C++ → Wasm), Google Earth (Unity → Wasm), Photoshop Web (C++ → Wasm), AutoCAD Web.
1-3. 기본 사용법
// Rust → wasm-bindgen
import init, { fibonacci } from './pkg/my_lib.js';
await init();
console.log(fibonacci(40)); // Wasm 함수 호출
1-4. Wasm이 만능은 아니다
- DOM 직접 접근 불가 (JS를 거쳐야 함) → 가벼운 UI 조작엔 오버헤드
- JS ↔ Wasm 경계 비용 — 데이터 복사·변환 필요
- 번들 크기가 클 수 있음
- 디버깅 도구가 JS만큼 성숙하지 않음
⚠️ 간단한 숫자 계산 정도는 오히려 JS가 빠를 수 있다. 크로스 오버가 있는 경계에서만 이득. 프로파일링 필수.
1-5. WASI
브라우저 밖(서버, CLI)에서도 Wasm을 실행하는 표준 인터페이스. Cloudflare Workers, Fastly Compute@Edge 등이 Wasm을 서버리스 런타임으로 사용.
2. Web Components
2-1. 정의
브라우저 표준 기반의 커스텀 재사용 컴포넌트. 프레임워크 독립적. 3가지 기술로 구성.
- Custom Elements — 새 HTML 태그 정의
- Shadow DOM — 스타일·마크업 캡슐화
- HTML Templates —
<template>,<slot>
2-2. Custom Element
class MyButton extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
button { padding: 8px 16px; background: #06f; color: white; }
</style>
<button><slot></slot></button>
`;
}
connectedCallback() { /* DOM에 부착되었을 때 */ }
disconnectedCallback() { /* 제거되었을 때 */ }
attributeChangedCallback(name, oldVal, newVal) { /* 속성 변경 */ }
static get observedAttributes() { return ['disabled']; }
}
customElements.define('my-button', MyButton);
<my-button>Click</my-button>
2-3. Shadow DOM
캡슐화된 DOM 서브트리. 외부 CSS가 뚫고 들어오지 않고, 내부 스타일이 밖으로 새지 않는다.
this.attachShadow({ mode: 'open' }); // 외부 JS에서 shadowRoot 접근 가능
// 'closed' → 접근 불가 (실무에선 거의 안 씀)
Shadow DOM 경계:
- 외부
querySelector는 Shadow 내부에 접근 불가 (의도적) :host선택자로 컴포넌트 자신에 스타일::part(button)→ 외부에서 내부 일부에 스타일 허용- CSS 변수는 Shadow 경계를 넘는다 (테마 전파)
2-4. 왜 덜 쓰일까
- 프레임워크(React/Vue)와 궁합이 미묘함 — form·이벤트 연계
- SSR 지원 부족 (Declarative Shadow DOM으로 개선 중)
- 접근성 구현이 수작업 (스크린리더가 Shadow 경계를 잘 타는지 테스트 필요)
2-5. 실무 활용
- 디자인 시스템 배포 — 여러 프레임워크 환경에 뿌리는 공용 컴포넌트 (Ionic, Shoelace, Adobe Spectrum)
- 마이크로 프론트엔드 — 서로 다른 팀의 앱을 안전하게 조합
- 제품 임베드 — 고객 사이트에 삽입해도 스타일 충돌 없음 (위젯, 챗봇)
3. 접근성 (Accessibility, a11y)
3-1. 왜 중요한가
- 법적 요구 — 미국 ADA, EU EAA, 한국 웹접근성 표준
- 사용자 경험 — 키보드/스크린리더 사용자, 저시력, 색맹, 운동장애
- SEO — 시맨틱 HTML은 검색엔진에도 이득
3-2. 시맨틱 HTML이 기본
<!-- ❌ -->
<div onclick="submit()">제출</div>
<!-- ✅ -->
<button type="submit">제출</button>
<button>은 기본적으로:
- 키보드 포커스 가능
- Enter/Space로 활성화
- 스크린리더에 "버튼"으로 읽힘
- 비활성 상태 기본 제공
3-3. WAI-ARIA
시맨틱 HTML로 표현할 수 없을 때 보조 속성.
<div role="button" tabindex="0" aria-pressed="false">토글</div>
<div role="alert" aria-live="assertive">저장 실패!</div>
<button aria-expanded="true" aria-controls="menu">메뉴</button>
<ul id="menu" hidden>...</ul>
ARIA의 철칙 (No ARIA is better than Bad ARIA):
- 가능하면 네이티브 HTML 요소 사용
- 네이티브 시맨틱을 덮어쓰지 말 것 (
<h1 role="button">금지) - 모든 인터랙티브 요소는 키보드로 조작 가능해야 함
- 포커스 가능한 요소에 접근 가능한 이름(aria-label 등) 제공
- 숨긴 콘텐츠(
display: none)는 스크린리더도 못 읽음 — 의도한 대로인지 확인
3-4. 키보드 접근성
- Tab: 포커스 이동 (tabindex 순서)
- Shift+Tab: 역방향
- Enter/Space: 활성화
- Esc: 모달/메뉴 닫기
- 화살표: 라디오·탭 패널·메뉴 내부 이동
Focus Trap (모달에서 포커스가 밖으로 나가지 않게):
// focus-trap 라이브러리 추천, 직접 구현 시 Tab/Shift+Tab 이벤트로 순환
⚠️ outline: none 금지. 포커스 링이 없으면 키보드 사용자가 "지금 어디 있는지" 알 수 없다. 제거하려면 대체 스타일 필수(:focus-visible).
button:focus { outline: none; } /* ❌ */
button:focus-visible { outline: 2px solid #06f; outline-offset: 2px; } /* ✅ */
3-5. 이미지 대체 텍스트
<img src="chart.png" alt="2024년 분기별 매출: 1분기 100, 2분기 120 ...">
<!-- 장식용 이미지 -->
<img src="decoration.svg" alt="" role="presentation">
3-6. 폼 레이블
<!-- ✅ -->
<label for="email">이메일</label>
<input id="email" type="email">
<!-- 또는 -->
<label>
이메일
<input type="email">
</label>
<!-- placeholder는 레이블이 아님 -->
<input placeholder="이메일"> <!-- ❌ 단독 사용 -->
3-7. 색 대비 (Color Contrast)
- WCAG AA: 본문 4.5:1, 큰 글자 3:1
- WCAG AAA: 본문 7:1, 큰 글자 4.5:1
색 하나만으로 정보를 전달하지 말 것 (색맹 고려). 아이콘·텍스트·패턴 병행.
3-8. 스크린리더 테스트
- macOS: VoiceOver (Cmd+F5)
- Windows: NVDA (무료), JAWS (상용)
- iOS: VoiceOver
- Android: TalkBack
3-9. 자동화 도구
- axe DevTools — Chrome 확장, 80% 이슈 자동 감지
- Lighthouse Accessibility
- eslint-plugin-jsx-a11y — 컴파일 타임 검사
한계: 자동화로 잡을 수 있는 건 약 30-40%. 나머지는 수동 테스트(키보드 only 주행, 스크린리더 리딩)로 검증.
4. PWA (Progressive Web App)
4-1. 정의
웹을 앱처럼 만드는 기술 조합. 단일 기술이 아니라 패턴.
- Installable (홈 화면 추가) — Web App Manifest
- Offline — Service Worker
- Reliable — 캐시 전략
- Engageable — Push Notification (iOS도 16.4부터 지원)
4-2. Web App Manifest
{
"name": "My App",
"short_name": "MyApp",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#06f",
"icons": [
{ "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" }
]
}
4-3. Service Worker
브라우저와 네트워크 사이의 프로그래머블 프록시.
// sw.js
self.addEventListener('install', (e) => {
e.waitUntil(
caches.open('v1').then(cache => cache.addAll(['/', '/style.css', '/app.js']))
);
});
self.addEventListener('fetch', (e) => {
e.respondWith(
caches.match(e.request).then(res => res || fetch(e.request))
);
});
중요 특성:
- HTTPS 필수 (localhost 예외)
- 별도 스레드 (UI 블로킹 없음)
- DOM 접근 불가
- 라이프사이클: install → activate → fetch/message 대기
- 업데이트는
skipWaiting+clients.claim조합으로 제어
4-4. 캐시 전략
- Cache First — 캐시 우선, 없으면 네트워크. 정적 자산에.
- Network First — 네트워크 우선, 실패 시 캐시. API 응답에.
- Stale While Revalidate — 캐시 즉시 반환 + 백그라운드 업데이트. 반 동적 콘텐츠에.
- Network Only / Cache Only — 상황별
Workbox 라이브러리로 이 전략들을 쉽게 적용 가능.
4-5. iOS의 한계와 변화
- 예전: 푸시/백그라운드/웹뷰 강제 등 제약
- iOS 16.4+: Web Push Notification 지원, PWA 설치 시 홈 화면 추가
- 여전히 일부 API는 Safari에서 제한적 — 실무에서는 크로스 브라우저 매트릭스로 검증
5. 국제화 (i18n) / 지역화 (l10n)
5-1. i18n의 실제 난이도
- 단순 번역 != i18n — 복수형, 성별, 시간/숫자/통화 포맷, RTL(오른쪽→왼쪽), 날짜 체계
- 텍스트 확장: 독일어는 영어보다 길어지기 쉬움 → 레이아웃 망가짐
5-2. 도구
- ICU MessageFormat — 복수형/성별 등 표현
{count, plural, =0 {No items} one {# item} other {# items}} - Intl API (네이티브):
new Intl.NumberFormat('ko-KR', { style: 'currency', currency: 'KRW' }).format(12345); // "₩12,345" new Intl.DateTimeFormat('en-US', { dateStyle: 'long' }).format(new Date()); // "April 19, 2026" new Intl.RelativeTimeFormat('ko').format(-1, 'day'); // "1일 전" new Intl.PluralRules('ko').select(1); // "other" new Intl.PluralRules('en').select(1); // "one" - 라이브러리:
react-intl(FormatJS),next-intl,i18next,vue-i18n
5-3. RTL 지원
아랍어·히브리어는 오른쪽→왼쪽.
<html dir="rtl" lang="ar">
CSS Logical Properties로 대응:
/* ❌ 방향 고정 */
.box { margin-left: 8px; }
/* ✅ 방향 독립 */
.box { margin-inline-start: 8px; }
6. 타입 안전 라우팅
6-1. 왜 중요한가
- 라우트 URL과 params를 코드 전역에서 문자열로 쓰면 오타/누락/리팩토링 지옥
- 타입 안전 라우팅 = URL 구조를 타입 시스템이 보증
6-2. 예시 (Next.js App Router, TanStack Router 등)
// TanStack Router 예
const userRoute = new Route({
getParentRoute: () => rootRoute,
path: '/users/$userId',
validateSearch: z.object({ tab: z.enum(['posts', 'likes']).optional() }),
});
// 사용
<Link to="/users/$userId" params={{ userId: '1' }} search={{ tab: 'posts' }} />
// params, search 모두 타입 체크됨
6-3. 일반 패턴 (자체 구현 시)
const ROUTES = {
user: (id: string) => `/users/${id}` as const,
post: (id: string) => `/posts/${id}` as const,
} as const;
router.push(ROUTES.user('123')); // 문자열 리터럴로 안전
7. 기타 알아두면 좋은 주제
7-1. CSS Container Queries
부모 크기에 반응하는 스타일 (미디어 쿼리는 뷰포트 기준).
.card { container-type: inline-size; }
@container (min-width: 400px) {
.card-body { display: grid; grid-template-columns: 1fr 2fr; }
}
7-2. View Transitions API
SPA 전환을 네이티브 애니메이션으로.
document.startViewTransition(() => {
updateDOM();
});
페이지 간 "같은" 요소에 view-transition-name을 부여하면 morph 애니메이션.
7-3. Popover API
<dialog>와 유사한 네이티브 팝오버.
<button popovertarget="menu">열기</button>
<div id="menu" popover>...</div>
포커스 트랩·light dismiss·ESC 닫기가 기본 제공.
7-4. WebRTC / WebSocket / Server-Sent Events
| 기술 | 용도 | 특성 |
|---|---|---|
| WebSocket | 양방향 실시간 | 채팅, 알림 |
| SSE | 서버→클라 스트림 | 단방향, HTTP 기반, 재접속 쉬움, AI 스트리밍 응답 |
| WebRTC | P2P 미디어 | 화상통화, 파일 전송 |
7-5. File System Access API
사용자 허락 받아 로컬 파일 읽기/쓰기.
const [fileHandle] = await window.showOpenFilePicker();
const file = await fileHandle.getFile();
VS Code Web, Figma 같은 도구가 사용.
7-6. Web Crypto API
암호화/해시를 네이티브로.
const encoder = new TextEncoder();
const data = encoder.encode('hello');
const hash = await crypto.subtle.digest('SHA-256', data);
7-7. Feature Detection
라이브러리 추가 전에 네이티브 API가 있는지 확인:
if ('share' in navigator) { await navigator.share({...}); }
if (CSS.supports('container-type: inline-size')) { /* ... */ }
8. 실무 체크리스트
- Wasm이 필요한 영역을 프로파일링으로 식별했는가 (JS로 충분한 걸 억지로 Wasm 쓰지 않는가)
- 키보드만으로 전체 페이지 주요 기능을 사용할 수 있는가
-
:focus-visible이 모든 인터랙티브 요소에 적용되는가 - 색 대비 WCAG AA 이상인가 (axe로 검사)
- 폼의 모든 입력에 연결된
<label>이 있는가 - Service Worker가 배포 시 제대로 갱신되는가 (
skipWaiting정책) -
IntlAPI로 날짜/숫자/통화 포맷팅하고 있는가 (문자열 직접 조립 금지) - RTL 대응이 필요한 언어를 지원한다면 Logical Properties를 쓰는가
- 라우트 URL을 타입 안전하게 관리하는가
9. 연습 문제
Q1. WebAssembly가 브라우저에서 JS보다 빠를 수 있는 이유와, 실제로 느릴 수 있는 상황을 각각 설명하라.
정답
빠른 이유:
- 바이너리 포맷이라 파싱·구문 분석이 거의 없음 → startup 빠름
- 정적 타입 + 미리 정의된 명령 집합 → JIT 최적화 초기부터 가능, deopt 위험 적음
- 메모리 레이아웃을 제어할 수 있어 SIMD/멀티스레드(Worker + SharedArrayBuffer) 활용 용이
느릴 수 있는 상황:
- JS ↔ Wasm 경계 비용 — 데이터 복사/변환이 잦으면 오히려 손해
- DOM 접근은 JS 브릿지 필요 → UI 조작 위주라면 메리트 없음
- 작은 계산은 V8 JIT 최적화된 JS가 비등하거나 빠름
Q2. Shadow DOM의 캡슐화가 제공하는 실용적 이점을 2가지 설명하라.
정답
- 스타일 충돌 방지: 외부 CSS가 Shadow 내부에 영향을 주지 않고, 내부 스타일도 밖으로 새지 않는다. 여러 팀이 만든 컴포넌트를 한 페이지에 조합할 때 CSS 충돌을 걱정할 필요가 없다. 마이크로프론트엔드·임베드 위젯에 매우 유용.
- 마크업 캡슐화: 외부
document.querySelector가 내부 구조에 의존하지 못하게 해, 내부 구현을 자유롭게 리팩토링 가능. 공용 컴포넌트 라이브러리의 API 안정성 확보.
Q3. 다음 코드의 접근성 문제를 모두 지적하라.
<div onclick="submit()">제출</div>
<input placeholder="이메일">
<img src="chart.png">
정답
<div onclick>대신<button>필요: 키보드 포커스 안 됨, Enter/Space 안 됨, 스크린리더에 버튼으로 안 읽힘.<input>에 레이블 없음: placeholder는 레이블 아님. 포커스되면 사라지고 스크린리더가 name을 못 찾음.<label for>또는 감싼<label>필요.<img>에 alt 없음: 스크린리더가 파일명을 읽거나 무시. 내용이면alt="2024년 분기별 매출 차트", 장식이면alt="".
Q4. Service Worker의 "Cache First"와 "Network First" 전략을 각각 어떤 자원에 쓰는 게 적절한지 설명하라.
정답
- Cache First: 변경이 거의 없는 정적 자산(해시 붙은 JS/CSS 번들, 폰트, 아이콘). 오프라인·빠른 로딩이 중요하고 stale 데이터 걱정 없음.
- Network First: 실시간성이 중요한 API 응답(뉴스, 피드, 가격). 네트워크에서 최신 데이터를 받고, 오프라인일 때만 캐시로 폴백.
추가: 반 동적 콘텐츠(블로그 글, 사용자 프로필)에는 Stale While Revalidate가 적합.
Q5. Intl.NumberFormat과 Intl.DateTimeFormat을 써서 다음을 출력하는 코드를 작성하라.
12345.67을 미국 달러로 (예:$12,345.67)- 현재 날짜를 한국어 긴 포맷으로 (예:
2026년 4월 19일 일요일)
정답
new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(12345.67);
// "$12,345.67"
new Intl.DateTimeFormat('ko-KR', { dateStyle: 'full' }).format(new Date());
// "2026년 4월 19일 일요일"Q6. WebSocket과 SSE(Server-Sent Events)의 차이를 설명하고, AI 챗봇의 "답변 스트리밍"에는 어느 것이 더 적합한지 이유를 들어라.
정답
- WebSocket: 양방향, 바이너리/텍스트, 별도 프로토콜(
ws://), HTTP/2 multiplex 못 탐. - SSE: 단방향(서버→클라), 텍스트 전용, HTTP 기반, 자동 재접속 내장, 방화벽/프록시 친화적.
AI 챗봇은 클라→서버는 한 번 요청이면 충분하고(프롬프트 전송), 서버→클라는 토큰 스트림을 길게 흘리면 된다. 단방향 스트림에 SSE가 더 단순·안정적이며 구현 복잡도가 낮다. 실제로 OpenAI, Anthropic 등 대부분의 LLM API가 SSE 기반 스트리밍을 사용한다.
Q7. PWA에서 Service Worker를 배포했는데, 사용자 브라우저에 구버전 SW가 계속 살아있어 새 코드가 반영되지 않는다. 어떻게 해결하는가?
정답
Service Worker는 기본적으로 기존 탭이 모두 닫힌 후 다음 방문에서 활성화된다. 강제 업데이트는:
install이벤트에서self.skipWaiting()호출 → 대기 없이 즉시 활성화activate이벤트에서self.clients.claim()→ 열려 있는 모든 페이지를 새 SW가 즉시 제어- 클라이언트 코드에서
registration.waiting을 감지해 사용자에게 "새 버전이 있습니다, 새로고침" 배너 노출 (Workboxworkbox-window제공) - 캐시 이름에 버전 문자열 포함 →
activate에서 구 캐시 삭제
주의: 무조건 skipWaiting은 페이지 간 자산 버전 불일치를 만들 수 있어, 상황에 따라 사용자 동의 후 갱신이 안전하다.
10. 정리
- WebAssembly는 CPU 집약 작업과 레거시 C/C++ 재사용에 강하지만, DOM·JS 경계 비용을 고려해야 한다.
- Web Components는 프레임워크 독립적 캡슐화 단위로, 디자인 시스템·마이크로 프론트엔드에 유용하다.
- 접근성은 시맨틱 HTML이 기본. ARIA는 보조. 키보드/스크린리더 테스트가 필수다.
- PWA는 Manifest + Service Worker + 캐시 전략의 조합이다.
- Intl API로 i18n의 숫자·날짜·복수형을 표준적으로 처리한다.
- Container Queries, View Transitions, Popover, File System Access 등 최신 플랫폼 API는 라이브러리로 해오던 일을 네이티브로 대체하고 있다. 항상 Feature Detection 먼저.