Skip to content

Commit 156afc6

Browse files
Improve compatibility with Safari 15 (#17435)
This PR improves the compatibility with Tailwind CSS v4 with unsupported browsers with the goal to greatly improve compatibility with Safari 15. To make this work, this PR makes the following changes to all code - Change `oklab(…)` default theme values to use a percentage in the first place (so instead of `--color-red-500: oklch(0.637 0.237 25.331);` we now define it as `--color-red-500: oklch(63.7% 0.237 25.331);` since this syntax has much broader support on Safari). - Polyfill `@property` with a `@supports` query targeting older versions of Safari and Firefox * - Create fallbacks for the `color-mix(…)` function that use _inlined color values from your theme_ so that they can be computed a compile time by `lightningcss`. These fallbacks will convert to srgb to increase compatibility. - Create fallbacks for the _relative color_ feature used in the new shadow utilities and using `color-mix(…)` in case _relative color_ is applied on `currentcolor` (due to limited browser support) - Create fallbacks for gradient interpolation methods (e.g. to support `bg-linear-to-r/oklab`) - Polyfill `@media` queries range syntax. ## A simplified example Given this example CSS input: ```css @import 'tailwindcss'; @source inline('from-cyan-500/50 bg-linear-45'); ``` Here's the updated output CSS including the newly added polyfills and updated `oklab` values: ```css .bg-linear-45 { --tw-gradient-position: 45deg; background-image: linear-gradient(var(--tw-gradient-stops)); } @supports (background-image: linear-gradient(in lab, red, red)) { .bg-linear-45 { --tw-gradient-position: 45deg in oklab; } } .from-cyan-500\\/50 { --tw-gradient-from: oklab(71.5% -.11682 -.08247 / .5); --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); } @supports (color: color-mix(in lab, red, red)) { .from-cyan-500\\/50 { --tw-gradient-from: color-mix(in oklab, var(--color-cyan-500) 50%, transparent); } } :root, :host { --color-cyan-500: oklch(71.5% .143 215.221); } @supports (((-webkit-hyphens: none)) and (not (margin-trim: 1lh))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { @layer base { *, :before, :after, ::backdrop { --tw-gradient-position: initial; --tw-gradient-from: #0000; --tw-gradient-via: #0000; --tw-gradient-to: #0000; --tw-gradient-stops: initial; --tw-gradient-via-stops: initial; --tw-gradient-from-position: 0%; --tw-gradient-via-position: 50%; --tw-gradient-to-position: 100%; } } } @Property --tw-gradient-position { syntax: "*"; inherits: false } @Property --tw-gradient-from { syntax: "<color>"; inherits: false; initial-value: #0000; } @Property --tw-gradient-via { syntax: "<color>"; inherits: false; initial-value: #0000; } @Property --tw-gradient-to { syntax: "<color>"; inherits: false; initial-value: #0000; } @Property --tw-gradient-stops { syntax: "*"; inherits: false } @Property --tw-gradient-via-stops { syntax: "*"; inherits: false } @Property --tw-gradient-from-position { syntax: "<length-percentage>"; inherits: false; initial-value: 0%; } @Property --tw-gradient-via-position { syntax: "<length-percentage>"; inherits: false; initial-value: 50%; } @Property --tw-gradient-to-position { syntax: "<length-percentage>"; inherits: false; initial-value: 100%; } ``` ## \* A note on `@property` polyfills and CSS modules On Next.js, CSS module files are required to be _pure_, meaning that all selectors must either be scoped to a class or an ID. Fortunatnyl for us, this does not apply to `@property` rules which we've been using before to initialize CSS variables. However, since we're now bringing back the `@property` polyfills, that would cause unexpected rules to be exported from the CSS file as this: ```css @reference "tailwindcss"; .skew { @apply skew-7; } ``` Would turn to the following file: ```css .skew { /* … */ } @supports (/*…*/) { @layer base { *, :before, :after, ::backdrop { --tw-gradient-position: initial; } } } @Property /* … */ ``` Notice that this adds a `*` selector which is not considered pure. Unfortunately there is no way for us to silence this warning or work around it, as the dependency causing this errors ([`postcss-modules-local-by-default`](https://github.com/css-modules/postcss-modules-local-by-default)) is bundled into Next.js. To work around crashes, these polyfills will not apply to CSS modules processed by the PostCSS extension for now. ## Testing on tailwindcss.com To see the changes in effect, take a look at this screencast that compares tailwindcss.com on iOS 15.5 with a version that has the patches of this PR applied: https://github.com/user-attachments/assets/1279d6f5-3c63-4f30-839c-198a789f4292 ## Test plan - Tested on tailwindcss.com via a preview build: https://tailwindcss-com-git-legacy-browsers-tailwindlabs.vercel.app/ - Updated tests - Ensure we also test on Chrome 111, Safari 16.4, Firefox 128 to make sure we have no regressions. Also tested on Safari 16.4, 15.5, 18.0
1 parent 80017eb commit 156afc6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+4372
-1670
lines changed

integrations/cli/index.test.ts

+24-3
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,13 @@ test(
713713
expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(`
714714
"
715715
--- ./dist/out.css ---
716+
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
717+
@layer base {
718+
*, ::before, ::after, ::backdrop {
719+
--tw-content: "";
720+
}
721+
}
722+
}
716723
.content-\\[\\"components\\/my-component\\.tsx\\"\\] {
717724
--tw-content: "components/my-component.tsx";
718725
content: var(--tw-content);
@@ -944,6 +951,13 @@ test(
944951
expect(await fs.dumpFiles('./project-a/dist/*.css')).toMatchInlineSnapshot(`
945952
"
946953
--- ./project-a/dist/out.css ---
954+
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
955+
@layer base {
956+
*, ::before, ::after, ::backdrop {
957+
--tw-content: "";
958+
}
959+
}
960+
}
947961
.content-\\[\\'project-a\\/node_modules\\/my-lib-1\\/src\\/index\\.html\\'\\] {
948962
--tw-content: 'project-a/node modules/my-lib-1/src/index.html';
949963
content: var(--tw-content);
@@ -1181,6 +1195,13 @@ test(
11811195
expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(`
11821196
"
11831197
--- ./dist/out.css ---
1198+
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
1199+
@layer base {
1200+
*, ::before, ::after, ::backdrop {
1201+
--tw-content: "";
1202+
}
1203+
}
1204+
}
11841205
.content-\\[\\"pages\\/foo\\.html\\"\\] {
11851206
--tw-content: "pages/foo.html";
11861207
content: var(--tw-content);
@@ -1508,7 +1529,7 @@ test(
15081529
"
15091530
--- ./dist/out.css ---
15101531
:root, :host {
1511-
--color-blue-500: oklch(0.623 0.214 259.815);
1532+
--color-blue-500: oklch(62.3% 0.214 259.815);
15121533
}
15131534
.flex {
15141535
display: flex;
@@ -1531,8 +1552,8 @@ test(
15311552
"
15321553
--- ./dist/out.css ---
15331554
:root, :host {
1534-
--color-red-500: oklch(0.637 0.237 25.331);
1535-
--color-blue-500: oklch(0.623 0.214 259.815);
1555+
--color-red-500: oklch(63.7% 0.237 25.331);
1556+
--color-blue-500: oklch(62.3% 0.214 259.815);
15361557
}
15371558
.flex {
15381559
display: flex;

integrations/postcss/next.test.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ test(
4848
'app/page.module.css': css`
4949
@reference './globals.css';
5050
.heading {
51-
@apply text-red-500 animate-ping;
51+
@apply text-red-500 animate-ping skew-7;
5252
}
5353
`,
5454
'app/globals.css': css`
@@ -77,9 +77,10 @@ test(
7777
])
7878

7979
await fs.expectFileToContain(moduleCss!, [
80-
'color:var(--color-red-500,oklch(.637 .237 25.331)',
80+
'color:var(--color-red-500,oklch(63.7% .237 25.331)',
8181
'animation:var(--animate-ping,ping 1s cubic-bezier(0,0,.2,1) infinite)',
8282
/@keyframes page_ping.*{75%,to{transform:scale\(2\);opacity:0}/,
83+
'--tw-skew-x:skewX(7deg);',
8384
])
8485
},
8586
)
@@ -130,7 +131,7 @@ describe.each(['turbo', 'webpack'])('%s', (bundler) => {
130131
'app/page.module.css': css`
131132
@reference './globals.css';
132133
.heading {
133-
@apply text-red-500 animate-ping content-['module'];
134+
@apply text-red-500 animate-ping skew-7 content-['module'];
134135
}
135136
`,
136137
'app/globals.css': css`
@@ -173,6 +174,7 @@ describe.each(['turbo', 'webpack'])('%s', (bundler) => {
173174
let css = await fetchStyles(url)
174175
expect(css).toContain(candidate`underline`)
175176
expect(css).toContain(candidate`bg-red-500`)
177+
expect(css).toContain('--tw-skew-x: skewX(7deg);')
176178
expect(css).toContain('content: var(--tw-content)')
177179
expect(css).toContain('@keyframes')
178180
})

integrations/postcss/source.test.ts

+28
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,13 @@ test(
9494
expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(`
9595
"
9696
--- ./dist/out.css ---
97+
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
98+
@layer base {
99+
*, ::before, ::after, ::backdrop {
100+
--tw-content: "";
101+
}
102+
}
103+
}
97104
.content-\\[\\"components\\/my-component\\.tsx\\"\\] {
98105
--tw-content: "components/my-component.tsx";
99106
content: var(--tw-content);
@@ -324,6 +331,13 @@ test(
324331
expect(await fs.dumpFiles('./project-a/dist/*.css')).toMatchInlineSnapshot(`
325332
"
326333
--- ./project-a/dist/out.css ---
334+
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
335+
@layer base {
336+
*, ::before, ::after, ::backdrop {
337+
--tw-content: "";
338+
}
339+
}
340+
}
327341
.content-\\[\\'project-a\\/node_modules\\/my-lib-1\\/src\\/index\\.html\\'\\] {
328342
--tw-content: 'project-a/node modules/my-lib-1/src/index.html';
329343
content: var(--tw-content);
@@ -579,6 +593,13 @@ test(
579593
expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(`
580594
"
581595
--- ./dist/out.css ---
596+
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
597+
@layer base {
598+
*, ::before, ::after, ::backdrop {
599+
--tw-content: "";
600+
}
601+
}
602+
}
582603
.content-\\[\\"pages\\/foo\\.html\\"\\] {
583604
--tw-content: "pages/foo.html";
584605
content: var(--tw-content);
@@ -683,6 +704,13 @@ test(
683704
expect(await fs.dumpFiles('./project-a/dist/*.css')).toMatchInlineSnapshot(`
684705
"
685706
--- ./project-a/dist/out.css ---
707+
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
708+
@layer base {
709+
*, ::before, ::after, ::backdrop {
710+
--tw-content: "";
711+
}
712+
}
713+
}
686714
.content-\\[\\'keep-me\\.html\\'\\] {
687715
--tw-content: 'keep-me.html';
688716
content: var(--tw-content);

integrations/upgrade/js-config.test.ts

+12-12
Original file line numberDiff line numberDiff line change
@@ -472,17 +472,17 @@ test(
472472
@import 'tailwindcss';
473473
474474
@theme {
475-
--color-gray-50: oklch(0.985 0 0);
476-
--color-gray-100: oklch(0.97 0 0);
477-
--color-gray-200: oklch(0.922 0 0);
478-
--color-gray-300: oklch(0.87 0 0);
479-
--color-gray-400: oklch(0.708 0 0);
480-
--color-gray-500: oklch(0.556 0 0);
481-
--color-gray-600: oklch(0.439 0 0);
482-
--color-gray-700: oklch(0.371 0 0);
483-
--color-gray-800: oklch(0.269 0 0);
484-
--color-gray-900: oklch(0.205 0 0);
485-
--color-gray-950: oklch(0.145 0 0);
475+
--color-gray-50: oklch(98.5% 0 0);
476+
--color-gray-100: oklch(97% 0 0);
477+
--color-gray-200: oklch(92.2% 0 0);
478+
--color-gray-300: oklch(87% 0 0);
479+
--color-gray-400: oklch(70.8% 0 0);
480+
--color-gray-500: oklch(55.6% 0 0);
481+
--color-gray-600: oklch(43.9% 0 0);
482+
--color-gray-700: oklch(37.1% 0 0);
483+
--color-gray-800: oklch(26.9% 0 0);
484+
--color-gray-900: oklch(20.5% 0 0);
485+
--color-gray-950: oklch(14.5% 0 0);
486486
}
487487
488488
/*
@@ -1061,7 +1061,7 @@ describe('border compatibility', () => {
10611061
::before,
10621062
::backdrop,
10631063
::file-selector-button {
1064-
border-color: oklch(0.623 0.214 259.815);
1064+
border-color: oklch(62.3% 0.214 259.815);
10651065
}
10661066
}
10671067
"

integrations/vite/svelte.test.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ test(
100100

101101
await fs.expectFileToContain(files[0][0], [
102102
candidate`underline`,
103-
'.global{color:var(--color-green-500,oklch(.723 .219 149.579));animation:2s ease-in-out infinite globalKeyframes}',
104-
/\.local.svelte-.*\{color:var\(--color-red-500\,oklch\(\.637 \.237 25\.331\)\);animation:2s ease-in-out infinite svelte-.*-localKeyframes\}/,
103+
'.global{color:var(--color-green-500,oklch(72.3% .219 149.579));animation:2s ease-in-out infinite globalKeyframes}',
104+
/\.local.svelte-.*\{color:var\(--color-red-500\,oklch\(63\.7% \.237 25\.331\)\);animation:2s ease-in-out infinite svelte-.*-localKeyframes\}/,
105105
/@keyframes globalKeyframes\{/,
106106
/@keyframes svelte-.*-localKeyframes\{/,
107107
])
@@ -213,10 +213,10 @@ test(
213213
let [, css] = files[0]
214214
expect(css).toContain(candidate`underline`)
215215
expect(css).toContain(
216-
'.global{color:var(--color-green-500,oklch(.723 .219 149.579));animation:2s ease-in-out infinite globalKeyframes}',
216+
'.global{color:var(--color-green-500,oklch(72.3% .219 149.579));animation:2s ease-in-out infinite globalKeyframes}',
217217
)
218218
expect(css).toMatch(
219-
/\.local.svelte-.*\{color:var\(--color-red-500,oklch\(\.637 \.237 25\.331\)\);animation:2s ease-in-out infinite svelte-.*-localKeyframes\}/,
219+
/\.local.svelte-.*\{color:var\(--color-red-500,oklch\(63\.7% \.237 25\.331\)\);animation:2s ease-in-out infinite svelte-.*-localKeyframes\}/,
220220
)
221221
expect(css).toMatch(/@keyframes globalKeyframes\{/)
222222
expect(css).toMatch(/@keyframes svelte-.*-localKeyframes\{/)
@@ -238,15 +238,15 @@ test(
238238
let [, css] = files[0]
239239
expect(css).toContain(candidate`font-bold`)
240240
expect(css).toContain(
241-
'.global{color:var(--color-green-500,oklch(.723 .219 149.579));animation:2s ease-in-out infinite globalKeyframes}',
241+
'.global{color:var(--color-green-500,oklch(72.3% .219 149.579));animation:2s ease-in-out infinite globalKeyframes}',
242242
)
243243
expect(css).toMatch(
244-
/\.local.svelte-.*\{color:var\(--color-red-500,oklch\(\.637 \.237 25\.331\)\);animation:2s ease-in-out infinite svelte-.*-localKeyframes\}/,
244+
/\.local.svelte-.*\{color:var\(--color-red-500,oklch\(63\.7% \.237 25\.331\)\);animation:2s ease-in-out infinite svelte-.*-localKeyframes\}/,
245245
)
246246
expect(css).toMatch(/@keyframes globalKeyframes\{/)
247247
expect(css).toMatch(/@keyframes svelte-.*-localKeyframes\{/)
248248
expect(css).toMatch(
249-
/\.bar.svelte-.*\{color:var\(--color-pink-500,oklch\(\.656 \.241 354\.308\)\)\}/,
249+
/\.bar.svelte-.*\{color:var\(--color-pink-500,oklch\(65\.6% \.241 354\.308\)\)\}/,
250250
)
251251
})
252252
},

integrations/webpack/index.test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ test(
7575
"
7676
--- ./dist/main.css ---
7777
:root, :host {
78-
--color-blue-500: oklch(0.623 0.214 259.815);
78+
--color-blue-500: oklch(62.3% 0.214 259.815);
7979
}
8080
.flex {
8181
display: flex;
@@ -98,8 +98,8 @@ test(
9898
"
9999
--- ./dist/main.css ---
100100
:root, :host {
101-
--color-red-500: oklch(0.637 0.237 25.331);
102-
--color-blue-500: oklch(0.623 0.214 259.815);
101+
--color-red-500: oklch(63.7% 0.237 25.331);
102+
--color-blue-500: oklch(62.3% 0.214 259.815);
103103
}
104104
.flex {
105105
display: flex;

packages/@tailwindcss-cli/package.json

-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
"@tailwindcss/node": "workspace:*",
3434
"@tailwindcss/oxide": "workspace:*",
3535
"enhanced-resolve": "^5.18.1",
36-
"lightningcss": "catalog:",
3736
"mri": "^1.2.0",
3837
"picocolors": "^1.1.1",
3938
"tailwindcss": "workspace:*"

packages/@tailwindcss-cli/src/commands/build/index.ts

+2-35
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import watcher from '@parcel/watcher'
22
import { compile, env, Instrumentation } from '@tailwindcss/node'
33
import { clearRequireCache } from '@tailwindcss/node/require-cache'
44
import { Scanner, type ChangedContent } from '@tailwindcss/oxide'
5-
import { Features, transform } from 'lightningcss'
65
import { existsSync, type Stats } from 'node:fs'
76
import fs from 'node:fs/promises'
87
import path from 'node:path'
8+
import { optimize } from '../../../../@tailwindcss-node/src'
99
import type { Arg, Result } from '../../utils/args'
1010
import { Disposables } from '../../utils/disposables'
1111
import {
@@ -128,7 +128,7 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
128128
if (args['--minify'] || args['--optimize']) {
129129
if (css !== previous.css) {
130130
DEBUG && I.start('Optimize CSS')
131-
let optimizedCss = optimizeCss(css, {
131+
let optimizedCss = optimize(css, {
132132
file: args['--input'] ?? 'input.css',
133133
minify: args['--minify'] ?? false,
134134
})
@@ -430,39 +430,6 @@ async function createWatchers(dirs: string[], cb: (files: string[]) => void) {
430430
}
431431
}
432432

433-
function optimizeCss(
434-
input: string,
435-
{ file = 'input.css', minify = false }: { file?: string; minify?: boolean } = {},
436-
) {
437-
function optimize(code: Buffer | Uint8Array) {
438-
return transform({
439-
filename: file,
440-
code,
441-
minify,
442-
sourceMap: false,
443-
drafts: {
444-
customMedia: true,
445-
},
446-
nonStandard: {
447-
deepSelectorCombinator: true,
448-
},
449-
include: Features.Nesting,
450-
exclude: Features.LogicalProperties | Features.DirSelector | Features.LightDark,
451-
targets: {
452-
safari: (16 << 16) | (4 << 8),
453-
ios_saf: (16 << 16) | (4 << 8),
454-
firefox: 128 << 16,
455-
chrome: 111 << 16,
456-
},
457-
errorRecovery: true,
458-
}).code
459-
}
460-
461-
// Running Lightning CSS twice to ensure that adjacent rules are merged after
462-
// nesting is applied. This creates a more optimized output.
463-
return optimize(optimize(Buffer.from(input))).toString()
464-
}
465-
466433
function watchDirectories(scanner: Scanner) {
467434
return [...new Set(scanner.normalizedSources.flatMap((globEntry) => globEntry.base))]
468435
}

packages/@tailwindcss-node/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"dependencies": {
4040
"enhanced-resolve": "^5.18.1",
4141
"jiti": "^2.4.2",
42-
"tailwindcss": "workspace:*"
42+
"tailwindcss": "workspace:*",
43+
"lightningcss": "catalog:"
4344
}
4445
}

packages/@tailwindcss-node/src/compile.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,29 @@ import {
99
compile as _compile,
1010
compileAst as _compileAst,
1111
Features,
12+
Polyfills,
1213
} from 'tailwindcss'
1314
import type { AstNode } from '../../tailwindcss/src/ast'
1415
import { getModuleDependencies } from './get-module-dependencies'
1516
import { rewriteUrls } from './urls'
1617

17-
export { Features }
18+
export { Features, Polyfills }
1819

1920
export type Resolver = (id: string, base: string) => Promise<string | false | undefined>
2021

2122
export interface CompileOptions {
2223
base: string
2324
onDependency: (path: string) => void
2425
shouldRewriteUrls?: boolean
26+
polyfills?: Polyfills
2527

2628
customCssResolver?: Resolver
2729
customJsResolver?: Resolver
2830
}
2931

3032
function createCompileOptions({
3133
base,
34+
polyfills,
3235
onDependency,
3336
shouldRewriteUrls,
3437

@@ -37,6 +40,7 @@ function createCompileOptions({
3740
}: CompileOptions) {
3841
return {
3942
base,
43+
polyfills,
4044
async loadModule(id: string, base: string) {
4145
return loadModule(id, base, onDependency, customJsResolver)
4246
},

packages/@tailwindcss-node/src/index.cts

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as env from './env'
44
export * from './compile'
55
export * from './instrumentation'
66
export * from './normalize-path'
7+
export * from './optimize'
78
export { env }
89

910
// In Bun, ESM modules will also populate `require.cache`, so the module hook is

packages/@tailwindcss-node/src/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import * as Module from 'node:module'
22
import { pathToFileURL } from 'node:url'
33
import * as env from './env'
4-
export { __unstable__loadDesignSystem, compile, compileAst, Features } from './compile'
4+
export * from './compile'
55
export * from './instrumentation'
66
export * from './normalize-path'
7+
export * from './optimize'
78
export { env }
89

910
// In Bun, ESM modules will also populate `require.cache`, so the module hook is

0 commit comments

Comments
 (0)