Skip to content

Latest commit

 

History

History
496 lines (353 loc) · 46.1 KB

File metadata and controls

496 lines (353 loc) · 46.1 KB

Руководство по работе с CSS

Это руководство по написанию поддерживаемого, производительного и соответствующего стандартам CSS.

1. Основы

Избегайте дублирования. Используйте переменные вместо повторения, но, по-возможности, используйте встроенные вещи, такие как:

  • currentColor, вместо определения переменной и установке ей color;
  • ключевое слово inherit, вместо определения переменной у родителя, и ее использования для родителя и потомка;
  • единицы измерения em, вместо font-size: var(--size);
  • единицы измерения cqw/cqh (или их логические версии - cqi/cqb), вместо повторения значений блочной модели;
  • дублирование кода - это не дублирование знаний. Цель - надежность и поддерживаемость, а не экономия символов;
  • используйте логические свойства и значения, вместо физических (например, margin-inline-start, вместо margin-left) для адаптирования стилей к разным направлениям письма и ориентациям. Даже если автор не планировал локализацию, внешние инструменты перевода часто отображают переведенный текст в контексте;
  • не используйте логические свойства без разбора - спрашивайте себя "хочу ли я, чтобы это переворачивалось в RTL?" - если ответ "нет", используйте физическое свойство;
  • учитывайте разные режимы отображения (темный, высококонтрастный режим), разные размеры области просмотра и разные режимы ввода (касания, клавиатура, указатель).

2. Наследование и каскад

Не используйте методологию BEM (Block, Element, Modifier - блок, элемент, модификатор) для управления специфичностью. Используйте современные возможности CSS, такие как каскадные слои (cascade layers) и :where() для обеспечения большей предсказуемости каскадного поведения и следования намерениям автора.

Используйте каскадные слои (@layer) для явного определения приоритетных зон (например, reset, base, theme, components, utilities), и определения их порядка (например, @layer reset, base, theme, components, utilities;).

Внутри каждого слоя используйте :where(), чтобы селекторы конкурировали только на основе значимых сигналов, а не случайных фильтров (граничные случаи :not(), далекие предки и т.д.), или для одноразовых легко переопределяемых значений по умолчанию.

Используйте такие ключевые слова, как inherit, initial, unset или revert, вместо явных значений для улучшения поддерживаемости и лучшего выражения намерений. Примеры:

  • при определении перехода потомка, который должен совпадать с родительскими свойствами transition-*, вместо повторения свойств переходов у потомка, используйте transition: inherit (уменьшает дублирование, улучшает поддерживаемость);
  • используйте initial для сброса свойства к его начальному значению, вместо явного определения значения (более четкое выражение намерений).

3. Селекторы и область применения

Современные нативные браузерные селекторы уменьшают необходимость в препроцессорах и отслеживании состояния с помощью JS.

Используйте селекторы CSS вместо JS для сложного поиска элементов

  • Используйте :has() для стилизации предков на основе состояния потомков, вместо управления классами с помощью JS (например, label:has(:checked) вместо класса label.has-checked).
  • Не используйте вложенные :has() или псевдоэлементы в них (ограничение браузерного API).
  • Используйте :nth-child(<An+B> of <selector>) для стилизации каждого n-го элемента определенного типа. Например, details:nth-child(1 of [open]) будет применяться к первому найденному открытому элементу <details>, а details[open]:first-child будет применяться только к первому потомку, если он открыт.

Используйте :is() (или :where()), вместо дублирования правил CSS, для резервных случаев

Не дублируйте правила CSS в качестве резерва для псевдоклассов, которые могут не поддерживаться - используйте :is() или :where() и получите преимущество их мягких правил парсинга.

/* ПЛОХО: дублирование правил, вместо использования `:where()` */
[popover]:popover-open {
  /* стили нативных поповеров */
}
[popover].\:popover-open {
  /* те же стили для полифиллов */
}

/* ХОРОШО */
[popover]:where(:popover-open, .\:popover-open) {
  /* все стили в одном правиле */
}

Не используйте это для псевдоэлементов, поскольку они не поддерживаются в :is() и :where().

Избегайте чрезмерного соответствия

Пишите селекторы способом, явно выражающим ваши намерения.

Используйте :not(), вместо перезаписи, для исключения нерелевантных состояний/целей

Когда намерение состоит в исключении определенных состояний или элементов, которые фундаментально нерелевантны, используйте :not().

Например, для применения нижних границ между элементами списка не делайте так:

.fancy-list li {
  border-bottom: 1px solid silver;
}

.fancy-list li:last-child {
  border-bottom: none;
}

Это может случайно переписать желаемый набор border-bottom из другого правила. Реальное намерение состоит в неприменении нижней границы к последнему li. Приведенный выше код плохо выражает это намерение. Следующий код делает это более явно:

.fancy-list li:not(:last-child) {
  border-bottom: 1px solid silver;
}

Аналогично, не делайте так:

button:hover {
  background: var(--color-blue);
}

button:disabled {
  background: var(--color-neutral);
}

Если мы поменяем порядок двух правил, то получим изменение цвета фона при наведении у заблокированных кнопок! Лучше делайте так:

button:hover:not(:disabled) {
  background: var(--color-blue);
}

button:disabled {
  background: var(--color-neutral);
}

Это работает независимо от порядка, поскольку первое правило не является чрезмерным.

Используйте @scope, вместо :not(), для исключения (потенциально глубоко вложенных) поддеревьев

Хотя :not() + селекторы потомков могут исключить поддеревья, это плохо работает для глубоко вложенных структур. Например, .card :not(.content *) не будет работать ожидаемо для вложенных карточек. @scope исправляет это, поскольку учитывает иерархическую близость:

@scope (.card) to (.content) {
  /* стили для элементов внутри .card, но не внутри .content */
}

Это будет работать ожидаемо даже для вложенных карточек.

Перезапись допустима для специализации

Такое допустимо:

button {
  background: var(--color-neutral);
}

button.primary {
  background: var(--color-blue);
}

Оба правила выражают легитимное намерение: все кнопки изначально нейтральные, но первичные синие.

Избегайте глобальных сбросов

Не используйте глобальные сбросы (стили на *), поскольку они не могут быть перезаписаны веб-компонентами или низкоприоритетными каскадными слоями (без !important). Вместо этого, применяйте стили сброса к определенным типам элементов и/или условиям.

Вложенность и область применения

Используйте нативную вложенность CSS для группировки связанных стилей с целью улучшения их поддерживаемости и читаемости.

Используйте @scope, вместо вложенности, когда близость имеет большее значение, чем чистая специфичность. Это распространено в селекторах, которые могут быть вложены в любом порядке, но ближайший подходящий (в элементе -> порядок потомков) должен побеждать, например, классы темы.

Например, это не будет работать ожидаемо:

.dark .invert { color-scheme: light }
.light .invert { color-scheme: dark }

Если .invert вложен и в .dark, и в .light, он будет всегда разрешаться в темный режим, поскольку оба правила имеют одинаковую специфичность. Использование @scope исправляет это:

@scope (.dark) {
  .invert { color-scheme: light }
}

@scope (.light) {
  .invert { color-scheme: dark }
}

4. Интерактивность

Управление фокусом

  • Используйте :focus-visible для определения кастомных колец фокуса, а не :focus.
  • Не удаляйте дефолтные браузерные кольца фокуса (с помощью outline: none) без предоставления альтернативных видимых стилей фокуса.
  • Используйте outline вместо других свойств (например, box-shadow) для колец фокуса. При использовании box-shadow для колец фокуса, предоставьте основанный на outline резерв для высококонтрастного режима с помощью медиа-запроса forced-colors.
  • Используйте outline-offset для визуального отделения кольца от элемента.

Цели касаний

  • Интерактивные элементы должны быть, как минимум, 24×24 пикселей CSS (WCAG 2.5.8 AA). Используйте min-block-size / min-inline-size или паддинг, вместо width / height, чтобы контент мог расширять цель, но не сужать ее.
  • Не используйте touch-action: none для кастомных жестов - это отключает прокрутку страницы сквозь элемент. Ограничивайтесь только необходимой осью: pan-y для горизонтальных свайпов (страница по-прежнему прокручивается вертикально), pan-x - для вертикальных. Используйте none только для элементов, для которых нативные касания не имеют смысла (например, холст для рисования).

5. Дизайн-токены и тематизация

Используйте кастомные свойства CSS на :root для определения основных переменных дизайна (цвета, шрифты, размеры и т.д.), которые используются во всем дизайне, для визуальной согласованности и для распределения UI между командами.

Не определяйте нетривиальные стилевые значения инлайн. Например, background: transparent или padding: 0 - это нормально, а background: #f06 или padding: .3em - нет. Исключением являются случаи, когда сохранение малого объема кода и его простоты важнее долгосрочной поддержки и эволюции, как в случае с тестами.

Как правило, переменные организуются по уровням, когда каждый следующий уровень основан на предыдущем. Например:

  1. Уровень 1: литеральные дизайн-токены (например, --color-blue-10, --color-gray-90, --font-sans-serif, --size-xl и т.д.).
  2. Уровень 2: семантические дизайн-токены (например, --color-accent, --color-neutral, --font-body, --font-heading и т.д.).
  3. Уровень 3: общие дизайн-токены UI (например, --ui-border, --surface-bg-subtle и т.д.).
  4. Уровень 4: дизайн-токены компонентов (например, --button-bg-primary-hover, --button-border-color-secondary и т.д).

Чем меньше масштаб случая, тем меньше уровней ему требуется. Например, быстрое демо или игрушечное приложение может обойтись одним уровнем. Не переусердствуйте. Проверяйте существующие соглашения о наименованиях и уровнях перед изобретением собственных.

Темный режим

  • Используйте color-scheme: light dark на :root для поддержки темного режима, который автоматически адаптируется к настройкам системы. Вы также можете определить color-scheme на отдельных элементах для применения другого значения для конкретного поддерева.
  • Используйте light-dark() для альтернатив, которые автоматически применяются на основе color-scheme элемента. Обычно, это делается на уровне токенов 2 или 3.
  • Важно: при использовании light-dark() на унаследованном свойстве <color>, оно разрешится в определенный цвет на основе color-scheme этого элемента и наследуется как этот разрешенный цвет, а не как значение light-dark(). Оно не будет адаптироваться к перезаписям color-scheme на уровне потомков. Для сохранения динамичности цветовых токенов light-dark() разрешайте их как можно позже, передавая их как незарегистрированные кастомные свойства, и не полагайтесь на наследование значений цвета в границах color-scheme.

Режим принудительных цветов

В режиме принудительных цветов (высокий контраст в Windows), браузер перезаписывает цвета автора системными ключевыми словами и удаляет background-image, box-shadow и border-image.

  • Определяйте системные цветовые резервы для токенов цвета с помощью @media (forced-colors: active).
  • Не полагайтесь на background-image, box-shadow или border-image для определения границ, разделителей или состояния - они исчезнут в режиме принудительных цветов (и часто при печати). В противном случае, убедитесь в наличии альтернатив, таких как outline или border с системными цветовыми ключевыми словами (CanvasText, LinkText, ButtonText, Highlight, GrayText и т.д.).
  • Используйте forced-color-adjust: none, когда цвет является существенной информацией (подсветка синтаксиса, инпут выбора цвета). Не используйте forced-color-adjust: none просто для сохранения эстетичности.

Генерация оттенков

Перед динамической генерацией оттенков, попробуйте использовать существующий, предопределенный дизайн-токен. Это обеспечивает больший дизайнерский контроль и согласованность.

При динамической генерации более светлых и темных цветов:

  • Не ограничивайтесь регулировкой канала яркости в oklch/oklab или lch/lab, например, oklab(from var(--primary) 0.9 a b). Хотя это теоретически верный способ, браузеры пока не реализовали отображение гаммы (gamut mapping), поэтому результирующий цвет непредсказуем.
  • Вы можете использовать color-mix() для смешивания с белым или черным (предпочтительно в oklab). Это сохраняет цвет в гамме, но иногда приводит к чрезмерному обесцвечиванию цветов и появлению размытых оттенков и теней.
  • Вы можете комбинировать регулировку яркости с любыми другими методами (например, color-mix(in oklab, oklch(from var(--primary) 0.9 c h), white 30%)) для баланса между двумя цветами, но с 30%-м ограничением регулировки яркости.

Тематизация генерируемого браузером UI

Большая часть генерируемого браузером UI может быть кастомизирована в той или иной степени с помощью CSS. Даже если это требует современных возможностей, в старых браузерах происходит изящная деградация, и часто не нужен ни полифилл, ни резерв.

Перед воссозданием браузерного UI (контролы формы, скроллбары, селекторы, сообщения об ошибках и т.д), сначала проверьте, что:

  1. Браузерный UI не может быть кастомизирован в достаточной степени для ваших нужд, даже с помощью современного CSS.
  2. Желаемая кастомизация достаточно важна, чтобы оправдать компромиссы при воссоздании встроенного UI - потеря семантики доступности, обработки клавиатуры, интеграции с IME (Input Method Editor - редактор метода ввода) и AT (Assistive Technology - вспомогательные технологии), которые нативный UI предоставляет бесплатно.

Примеры допустимых кастомизаций:

  • используйте ::selection для кастомизации цветов выделенного текста;
  • не применяйте user-select: none для текстового контента - это ломает копирование-вставку, инструменты перевода и жесты AT "читать отсюда"; Применяйте его только при крайней необходимости (маркеры перетаскивания, панели инструментов, лишние подписи к кнопкам);
  • используйте accent-color для применения акцентного цвета страницы к любому генерируемому браузером UI;
  • используйте color-scheme, чтобы UI браузера адаптировался к светлому/темному режиму;
  • используйте scrollbar-color для кастомизации цветов полосы прокрутки и scrollbar-width для управления шириной скроллбара - визуально отделяйте бегунок от дорожки (≥3:1) и не устанавливайте scrollbar-width: none прокручиваемым областям (можно устанавливать только в случае полной замены прокрутки другим решением);
  • используйте :user-invalid / :user-valid для стилей валидации - они применяются только после взаимодействия пользователя с полем (в отличие от :invalid / :valid), что, например, позволяет избежать отображения ошибок незаполненности обязательных полей при загрузке страницы;
  • кнопки и текстовые поля (включая <textarea>), как правило, могут стилизоваться как обычные элементы.
  • используйте font-size для масштабирования и другие свойства текста для управления типографикой.

Стилизация текстовых полей (<input> и <textarea>)

Для большинства целей стилизации (например, цвета, границы, фоны, типографика и т.д.) элементы могут рассматриваться, как обычные текстовые контейнеры.

  • Используйте :placeholder-shown и ::placeholder для стилизации плейсхолдеров инпутов.
  • Используйте field-sizing: content для растягивания текстовых полей по ширине контента.
  • Для элементов <textarea>, используйте resize: vertical для отключения горизонтального масштабирования или resize: none для отключения масштабирования по обеим осям.

Контролы множественного выбора (селекты, радио-кнопки, чекбоксы)

  • Для выбора одного из нескольких вариантов в выпадающем списке: используйте <select> + appearance: base-select + ::picker(select).
  • Для выбора одного или нескольких вариантов из списка, отображаемого на странице: используйте <input type=checkbox> или <input type=radio> внутри <label> для каждого варианта. Стилизуйте его с помощью label:has(:checked).
  • Стилизуйте чекбоксы, радио-кнопки и переключатели с помощью appearance: none + генерируемого контента (::before/::after) или фоновых изображений для стилизации выбранного состояния.

Нетекстовые <input> (кнопки, слайдеры, инпуты для выбора файлов и т.д.)

  • Файловые инпуты: используйте ::file-selector-button для стилизации кнопки.
  • Не используйте <input> с атрибутом type со значением button, submit или reset. Вместо этого, используйте <button> и стилизуйте его как обычный элемент.
  • Слайдеры: используйте appearance: none + псевдоэлементы бегунка (::-webkit-slider-thumb, ::-moz-range-thumb и т.д.) и псевдоэлементы дорожки (::-webkit-slider-runnable-track, ::-moz-range-track и т.д.) для более точечного управления.

6. Отзывчивый дизайн

  • Используйте запросы @container для создания отзывчивых макетов на основе компонентов, которые адаптируются под размеры их родительского контейнера, а не под размеры области просмотра.
  • Используйте динамические единицы области просмотра (dvh, dvw) вместо vh/vw для предотвращения поломки макета при появлении/исчезновении элементов UI мобильных браузеров (таких как строка поиска).
  • Используйте aspect-ratio для медиа-элементов (таких как <img> и <video>) для резервирования пространства во время загрузки страницы для предотвращения совокупного сдвига макета (CLS).

Отзывчивая типографика

  • Комбинируйте основанные на области просмотра и основанные на шрифте единицы измерения в clamp() для размера шрифта, которые увеличиваются по мере увеличения области просмотра в пределах установленного диапазона. Например, clamp(2rem, 1rem + 5vw, 4rem). Меняйте пропорции этих единиц измерения для управления скоростью изменения размера шрифта.
  • Не используйте только vw для размера шрифта без clamp(), поскольку это сделает шрифт слишком мелким или слишком большим на "экстремальных" экранах.

7. Типографика

  • Используйте числа без единиц измерения для line-height (например, 1.5) для обеспечения относительного масштабирования в процессе наследования размера шрифта.
  • Используйте overflow-wrap: break-word (или anywhere) для длинных URL.
  • Не используйте px для font-size. Используйте rem для учета настроек браузера пользователя (корневой размер шрифта) или em для контекстного определения размера.

Перенос текста

  • Используйте text-wrap: balance для балансировки заголовков или похожего контента (например, <th>).
  • Используйте text-wrap: pretty для длинного основного текста (параграфы, цитаты и т.д.).
  • Используйте text-wrap: balance или text-wrap: pretty осознанно, не применяйте их к *, поскольку это снизит производительность.
  • Не применяйте text-wrap: balance к элементам с видимым блоком (фон, границы, тени и т.д.), поскольку это свойство не меняет ширину контейнера, оно лишь определяет, как переносится текст в пределах этой ширины. Это может привести к образования пустого пространства в конце контейнера, что обычно нежелательно.

8. Визуальные эффекты

Глубина и текстура

  • Наслаивайте несколько теней для создания реалистичных эффектов мягкой глубины.
  • Используйте filter: drop-shadow(), вместо box-shadow, для непрямоугольных фигур или прозрачных PNG.
  • Используйте mix-blend-mode и background-blend-mode для подсветки оверлеев (ограничивайте их область применения с помощью isolation: isolate)
.hero {
  background-image: url('texture.png'), linear-gradient(to bottom, #fff, #eee);
  background-blend-mode: soft-light;
}

Фигуры

  • Используйте corner-shape: squircle для более эстетичных изгибов в качестве прогрессивного улучшения вместо обычных скругленных углов.
  • Используйте эллиптические border-radius (например, 10px / 20px) для пропорциональных изгибов без дополнительных элементов.

Градиенты и color-mix()

Используйте in oklch или in oklab для явного определения интерполяции цветового пространства для градиентов или color-mix().

  • in oklch лучше сохраняет цветность, но легче выходит за пределы гаммы устройства, особенно, в случае большой разницы между цветами.
  • in oklab лучше вписывается в гамму (при условии, что конечные точки находятся внутри гаммы), но может создавать размытые бледные цвета в середине, особенно, при интерполяции между противоположными оттенками.
  • Не используйте in srgb, пока у вас не будет для этого веской причины (например, вы разрабатываете инпут выбора цвета с функционалом интерполяции в srgb).

Поддержка и страховка

Некоторые версии браузеров, вышедшие до 2024 года, не поддерживают пространство интерполяции цвета градиента. Для поддержки этих браузеров используйте токен, только когда его использование безопасно, путем определения переменной:

:root {
  --in-oklab: ;
  --in-oklch: ;
}

@supports (linear-gradient(in oklab, white, black)) {
  :root {
    --in-oklab: in oklab;
    --in-oklch: in oklch;
  }
}

Пример использования:

.card {
  background: linear-gradient(to bottom var(--in-oklab), var(--accent-color), var(--darker));
}
  • Это не требуется для color-mix(). Если браузер поддерживает color-mix(), он также поддерживает его аргумент in <color-space>.

Паттерны

С помощью градиентов CSS + жестких контрольных точек можно создать много интересных паттернов, которые могут быть более гибкими и производительными, чем SVG или внешние изображения, поскольку они имеют доступ к переменным CSS и длинам из окружающего контекста. Не нужно дублировать положение - просто используйте 0 или 0% и исправление градиента автоматически отрегулирует его.

Примеры.

Вертикальные полосы шириной 1em:

background: linear-gradient(to right, var(--color-1) 50%, var(--color-2) 0) 0 / 2em;

Диагональные полосы шириной 1em:

background: repeating-linear-gradient(-45deg, var(--color-1) 0 1em, var(--color-2) 0 2em);

Паттерн шахмат с квадратами размером 1em:

background: repeating-conic-gradient(var(--color-1) 0 25%, var(--color-2) 0 50%) 0 / 2em 2em;

Паттерн горошка с радиусом точек .5em, расположенными на расстоянии 2em друг от друга (по горизонтали/вертикали - умножьте на sqrt(2) для расстояния по диагонали):

--distance: 2em;
--radius: .5em;
--polka: radial-gradient(circle, var(--color-1) var(--radius), transparent calc(var(--radius) + 1px));
background: var(--polka) 0 0, var(--polka) var(--distance) var(--distance) var(--color-2);
background-size: calc(var(--distance) * 2) calc(var(--distance) * 2);

Простая круговая диаграмма:

.pie {
  --p: 80%;
  width: 60px;
  aspect-ratio: 1;
  border-radius: 50%;
  background: conic-gradient(var(--color-1) var(--p), transparent 0%) var(--color-2);
}

Важно: при использовании градиентов для рендеринга диаграмм, убедитесь в наличии текстового резерва для устройств чтения с экрана.

9. Переходы и анимация

  • Используйте clip-path и mask-image для кастомных геометрических проявлений и плавных затуханий.
  • Используйте анимацию, управляемую прокруткой (Scroll-Driven Animations) (animation-timeline: scroll()) для несущественных эффектов прокрутки, вместо обработчиков JS.
  • Используйте переходы отображения (View Transitions) для плавной анимации между сложными состояниями макета.

Производительность

Производительность рендеринга является критичной для положительного пользовательского опыта, особенно, для тяжелых деревьев DOM.

  • Анимируйте opacity и transform (включая индивидуальные свойства трансформации, например, translate, вместо left/right/top/bottom), поскольку такие анимации выполняются в потоке компоновщика, а не в основном потоке.
  • Используйте transition-behavior: allow-discrete + @starting-style для нативного анимирования таких свойств макета, как display, или состояния <dialog>.
  • Всегда используйте content-visibility совместно с contain-intrinsic-size для предотвращения прыжков скроллбара (CLS).
  • При установке contain-intrinsic-size, используйте ключевое слово auto и значение, которое вычисляется на основе знаний о контенте (например, размер текста, доступное пространство, размер графики, количество символов). Используйте такие единицы измерения, как rem, lh, cap или ch, которые соответствуют значениям, используемым для элементов внутри содержимого, а не px. Если элементы группы имеют разные размеры, используйте средний размер.
  • Используйте contain: layout style paint для изоляции рендеринга обновлений компонента.

Пример оптимизации рендеринга

.large-section {
  content-visibility: auto;
  contain-intrinsic-block-size: auto 800px;
}

.row {
  --row-gap: .4rem;
  --title-height: 1lh;
  --description-height: 0.85lh;

  display: grid;
  row-gap: var(--row-gap);
  content-visibility: auto;
  /* Сумма высоты заголовка, отступа между строками и высоты описания должна быть размером контента при пропуске рендеринга. */
  contain-intrinsic-block-size: auto calc(var(--title-height) + var(--row-gap) + var(--description-height));
}

.popover-reveal {
  /* Разрешаем дискретную анимацию для переходов display */
  transition: display 0.2s allow-discrete;
}

10. Генерируемый контент

  • Не используйте content для передачи осмысленного текста (подписи, состояние, инструкции) - он должен содержаться в DOM (WCAG F87).
  • Используйте content для предоставления альтернативного текста для устройств чтения с экрана, например, content: url(cloud.svg) / "Save";
  • Используйте content: "text" / ""; для предотвращения озвучки чисто декоративного текста.
  • Не используйте пустой альтернативный текст для изображений - они являются презентабельными по умолчанию. Например, это неправильно: content: url(cloud.svg) / "";.
  • Не используйте альтернативный текст для эмодзи, если описание не отличается от официального названия эмодзи. Например, не делайте content: "🎉" / "celebration";, но content: "🎉" / "Yay!"; допустимо.

Используйте альтернативный текст только в случае, когда он отличается от основного значения и отсутствует в DOM. Например, это неправильно:

HTML:

<button class="save">Save</button>

CSS:

button.save::before {
  content: url(cloud.svg) / "Save";
}

Устройство чтения с экрана прочитает это как "Save save".