diff --git a/.changeset/card-ring-overflow-clip.md b/.changeset/card-ring-overflow-clip.md
new file mode 100644
index 00000000000..bb490fa39d8
--- /dev/null
+++ b/.changeset/card-ring-overflow-clip.md
@@ -0,0 +1,8 @@
+---
+"@chakra-ui/react": patch
+"@chakra-ui/panda-preset": patch
+---
+
+Fix issue where the checked ring of `RadioCard` and `CheckboxCard` (outline
+variant) gets clipped when a parent has `overflow: hidden|auto|scroll`. The ring
+is now drawn with an inset shadow instead of an outer shadow.
diff --git a/.changeset/chore-cli-migrate-get-tsconfig.md b/.changeset/chore-cli-migrate-get-tsconfig.md
new file mode 100644
index 00000000000..c666640b4b3
--- /dev/null
+++ b/.changeset/chore-cli-migrate-get-tsconfig.md
@@ -0,0 +1,6 @@
+---
+"@chakra-ui/cli": patch
+---
+
+Migrate from deprecated `tsconfck` to `get-tsconfig`. Enables installation next
+to TypeScript version 6 and up.
diff --git a/.changeset/cli-windows-paths.md b/.changeset/cli-windows-paths.md
new file mode 100644
index 00000000000..1ae801d0c4c
--- /dev/null
+++ b/.changeset/cli-windows-paths.md
@@ -0,0 +1,5 @@
+---
+"@chakra-ui/cli": patch
+---
+
+Normalize resolved tsconfig paths to native separators on Windows
diff --git a/.changeset/panda-preset-sync.md b/.changeset/panda-preset-sync.md
new file mode 100644
index 00000000000..b3da8828a08
--- /dev/null
+++ b/.changeset/panda-preset-sync.md
@@ -0,0 +1,6 @@
+---
+"@chakra-ui/panda-preset": minor
+---
+
+Sync theme from `@chakra-ui/react`: register the missing `floatingPanel` slot
+recipe and update the date picker range-selection styles
diff --git a/apps/www/content/docs/components/concepts/animation.mdx b/apps/www/content/docs/components/concepts/animation.mdx
index 5a1d1a9ac50..2b76c8a941c 100644
--- a/apps/www/content/docs/components/concepts/animation.mdx
+++ b/apps/www/content/docs/components/concepts/animation.mdx
@@ -89,3 +89,54 @@ it easy to create complex animations with multiple keyframes.
This is a composed animation
```
+
+## Reduced motion
+
+The built-in component animations play regardless of the user's
+`prefers-reduced-motion` setting. To disable the enter/exit animations for users
+who prefer reduced motion, add this rule to your `globalCss` config:
+
+```tsx
+import { createSystem, defaultConfig, defineConfig } from "@chakra-ui/react"
+
+const config = defineConfig({
+ globalCss: {
+ '[data-state="open"], [data-state="closed"]': {
+ _motionReduce: {
+ animationDuration: "1ms !important",
+ },
+ },
+ },
+})
+
+export const system = createSystem(defaultConfig, config)
+```
+
+This collapses the enter/exit animations of every disclosure component (dialog,
+drawer, menu, popover, tooltip, etc.) to `1ms` when reduced motion is enabled.
+
+A few details worth knowing:
+
+- **Scope to `data-state="open"` and `data-state="closed"`** rather than
+ targeting all elements. A blanket rule also collapses looping animations
+ (`Spinner`, `Skeleton`, indeterminate `Progress`), turning them into a
+ flicker. Loading indicators are considered essential motion and are best left
+ running.
+- **`animation: none` also works.** The presence logic detects it and unmounts
+ exiting components immediately. A `1ms` duration is suggested because it keeps
+ `animationend` events firing for any custom code that listens for them.
+- **The `!important` is required** because the rule lives in a lower CSS layer
+ than component recipes.
+
+When building custom animations, use the `_motionReduce` condition to provide a
+reduced-motion alternative:
+
+```tsx
+
+ Slides on screen, but only fades for reduced motion users
+
+```
diff --git a/packages/cli/__tests__/resolve-tsconfig.test.ts b/packages/cli/__tests__/resolve-tsconfig.test.ts
index 00af8866cf3..6f7dbfdef30 100644
--- a/packages/cli/__tests__/resolve-tsconfig.test.ts
+++ b/packages/cli/__tests__/resolve-tsconfig.test.ts
@@ -85,7 +85,7 @@ describe("resolveTsconfig", () => {
expect(result).toBe(appTsconfigPath)
})
- it("falls back to first reference when no paths found", async () => {
+ it("picks the reference that includes the source file when no paths exist", async () => {
writeFileSync(
join(testDir, "tsconfig.json"),
JSON.stringify({
@@ -157,7 +157,7 @@ describe("resolveTsconfig", () => {
expect(result).toBe(tsconfigPath)
})
- it("picks the reference with paths when it is not the first one", async () => {
+ it("picks the matching reference regardless of reference order", async () => {
writeFileSync(
join(testDir, "tsconfig.json"),
JSON.stringify({
@@ -229,14 +229,14 @@ describe("resolveTsconfig", () => {
writeFileSync(join(testDir, "src/index.ts"), "export default {}")
const result = await resolveTsconfig(join(testDir, "src/index.ts"))
- // tsconfck merges extends into the parsed tsconfig, so paths should be visible
+ // get-tsconfig merges extends into the parsed tsconfig, so paths should be visible
expect(result).toBe(appTsconfigPath)
})
it("picks non-first reference with inherited paths over first reference without", async () => {
- // This test disambiguates: does tsconfck merge `extends` in referenced
- // configs so our `paths` check finds inherited paths? If not, the fallback
- // would wrongly pick tsconfig.node.json (listed first).
+ // The matched config inherits its `paths` via `extends`. parseTsconfig
+ // resolves the extends chain, so the returned config still exposes the
+ // inherited paths to downstream consumers (e.g. esbuild).
writeFileSync(
join(testDir, "tsconfig.json"),
JSON.stringify({
@@ -284,6 +284,97 @@ describe("resolveTsconfig", () => {
expect(result).toBe(appTsconfigPath)
})
+ it("falls back to the root config when no reference includes the source file", async () => {
+ const rootTsconfigPath = join(testDir, "tsconfig.json")
+ writeFileSync(
+ rootTsconfigPath,
+ JSON.stringify({
+ files: [],
+ references: [{ path: "./tsconfig.node.json" }],
+ }),
+ )
+
+ writeFileSync(
+ join(testDir, "tsconfig.node.json"),
+ JSON.stringify({
+ compilerOptions: {},
+ include: ["vite.config.ts"],
+ }),
+ )
+
+ writeFileSync(join(testDir, "src/index.ts"), "export default {}")
+
+ const result = await resolveTsconfig(join(testDir, "src/index.ts"))
+ expect(result).toBe(rootTsconfigPath)
+ })
+
+ it("resolves nested project references", async () => {
+ writeFileSync(
+ join(testDir, "tsconfig.json"),
+ JSON.stringify({
+ files: [],
+ references: [{ path: "./tsconfig.solution.json" }],
+ }),
+ )
+
+ // intermediate solution-style config referencing the app config
+ writeFileSync(
+ join(testDir, "tsconfig.solution.json"),
+ JSON.stringify({
+ files: [],
+ references: [{ path: "./tsconfig.app.json" }],
+ }),
+ )
+
+ const appTsconfigPath = join(testDir, "tsconfig.app.json")
+ writeFileSync(
+ appTsconfigPath,
+ JSON.stringify({
+ compilerOptions: {
+ paths: { "@/*": ["./src/*"] },
+ },
+ include: ["src"],
+ }),
+ )
+
+ writeFileSync(join(testDir, "src/index.ts"), "export default {}")
+
+ const result = await resolveTsconfig(join(testDir, "src/index.ts"))
+ expect(result).toBe(appTsconfigPath)
+ })
+
+ it("tolerates circular and missing references", async () => {
+ writeFileSync(
+ join(testDir, "tsconfig.json"),
+ JSON.stringify({
+ files: [],
+ references: [
+ // circular: points back to the root config
+ { path: "./tsconfig.json" },
+ // missing: file does not exist
+ { path: "./tsconfig.missing.json" },
+ { path: "./tsconfig.app.json" },
+ ],
+ }),
+ )
+
+ const appTsconfigPath = join(testDir, "tsconfig.app.json")
+ writeFileSync(
+ appTsconfigPath,
+ JSON.stringify({
+ compilerOptions: {
+ paths: { "@/*": ["./src/*"] },
+ },
+ include: ["src"],
+ }),
+ )
+
+ writeFileSync(join(testDir, "src/index.ts"), "export default {}")
+
+ const result = await resolveTsconfig(join(testDir, "src/index.ts"))
+ expect(result).toBe(appTsconfigPath)
+ })
+
it("finds tsconfig from deeply nested source file", async () => {
const tsconfigPath = join(testDir, "tsconfig.json")
writeFileSync(
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 1b194a4ad47..e5be0fcd989 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -61,6 +61,7 @@
"debug": "^4.4.3",
"dotenv": "^17.2.3",
"esbuild": "^0.27.3",
+ "get-tsconfig": "^4.14.0",
"globby": "16.1.1",
"https-proxy-agent": "^7.0.6",
"look-it-up": "2.1.0",
@@ -69,7 +70,6 @@
"prettier": "3.8.1",
"recast": "^0.23.0",
"scule": "1.3.0",
- "tsconfck": "^3.1.6",
"zod": "^3.25.76"
},
"peerDependencies": {
diff --git a/packages/cli/src/utils/resolve-tsconfig.ts b/packages/cli/src/utils/resolve-tsconfig.ts
index ffe6b46c5f6..4490f25c143 100644
--- a/packages/cli/src/utils/resolve-tsconfig.ts
+++ b/packages/cli/src/utils/resolve-tsconfig.ts
@@ -1,9 +1,112 @@
import createDebug from "debug"
-import { resolve } from "node:path"
-import { parse } from "tsconfck"
+import { createFilesMatcher, getTsconfig, parseTsconfig } from "get-tsconfig"
+import type { TsConfigJsonResolved } from "get-tsconfig"
+import { realpathSync, statSync } from "node:fs"
+import { dirname, join, normalize, resolve } from "node:path"
const debug = createDebug("chakra:tsconfig")
+type ConfigNode = {
+ path: string
+ config: TsConfigJsonResolved
+ references?: ConfigNode[]
+}
+
+// resolve relative path to referenced tsconfig
+function resolveReferencePath(configPath: string, refPath: string): string {
+ const base = resolve(dirname(configPath), refPath)
+
+ try {
+ if (statSync(base).isFile()) return base
+ } catch {}
+
+ return join(base, "tsconfig.json")
+}
+
+// recursively load referenced tsconfigs
+function loadReference(
+ parent: ConfigNode,
+ refPath: string,
+ seen: Set,
+): ConfigNode {
+ const configPath = resolveReferencePath(parent.path, refPath)
+
+ const real = realpathSync(configPath)
+ if (seen.has(real)) {
+ throw new Error(`Circular tsconfig reference: ${configPath}`)
+ }
+
+ const config = parseTsconfig(configPath)
+ const node: ConfigNode = {
+ path: configPath,
+ config,
+ }
+
+ // follow nested references
+ seen.add(real)
+ resolveReferences(node, seen)
+ seen.delete(real)
+
+ return node
+}
+
+// recursively resolve referenced tsconfigs
+function resolveReferences(
+ node: ConfigNode,
+ seen = new Set(),
+): ConfigNode {
+ const { references } = node.config
+ if (!references?.length) return node
+
+ debug("solution-style tsconfig detected, checking references...")
+ node.references = references.flatMap((ref) => {
+ // Skip references that are missing, malformed, or circular so a single
+ // broken entry doesn't fail resolution for the whole project
+ if (!ref?.path) {
+ debug("invalid tsconfig reference in", node.path)
+ return []
+ }
+ try {
+ return [loadReference(node, ref.path, seen)]
+ } catch (error) {
+ debug("skipping unresolvable reference:", ref.path, error)
+ return []
+ }
+ })
+
+ return node
+}
+
+function findConfigForFile(root: ConfigNode, sourceFile: string): ConfigNode {
+ function visit(node: ConfigNode): ConfigNode | undefined {
+ // Explore children first so the first matching descendant wins.
+ for (const ref of node.references ?? []) {
+ const match = visit(ref)
+ if (match) return match
+ }
+
+ const matcher = createFilesMatcher(node)
+ return matcher(sourceFile) ? node : undefined
+ }
+
+ return visit(root) ?? root
+}
+
+function loadResolvedConfig(sourceFile: string): ConfigNode | undefined {
+ const root = getTsconfig(sourceFile)
+ if (!root) return undefined
+
+ const real = realpathSync(root.path)
+ const node: ConfigNode = {
+ path: root.path,
+ config: root.config,
+ }
+
+ const seen = new Set([real])
+ const resolved = resolveReferences(node, seen)
+ return findConfigForFile(resolved, sourceFile)
+}
+
/**
* Resolves the correct tsconfig file for a given source file.
*
@@ -22,33 +125,18 @@ export async function resolveTsconfig(
}
try {
- const result = await parse(sourceFile)
+ const nearest = loadResolvedConfig(sourceFile)
- if (!result.tsconfigFile) {
+ if (!nearest) {
debug("no tsconfig found for", sourceFile)
return undefined
}
- debug("found tsconfig:", result.tsconfigFile)
-
- // If this is a solution-style tsconfig (has references, no real files),
- // find the referenced config that contains `paths`
- if (result.referenced && result.referenced.length > 0) {
- debug("solution-style tsconfig detected, checking references...")
-
- for (const ref of result.referenced) {
- if (ref.tsconfig?.compilerOptions?.paths) {
- debug("found paths in referenced tsconfig:", ref.tsconfigFile)
- return ref.tsconfigFile
- }
- }
-
- // Fall back to the first referenced tsconfig
- debug("no paths found in references, using first reference")
- return result.referenced[0].tsconfigFile
- }
-
- return result.tsconfigFile
+ // get-tsconfig returns forward-slash paths while reference resolution
+ // produces native paths. Normalize so output is native on every platform.
+ const configPath = normalize(nearest.path)
+ debug("found tsconfig:", configPath)
+ return configPath
} catch (error) {
debug("tsconfig resolution failed:", error)
return undefined
diff --git a/packages/panda-preset/scripts/sync.ts b/packages/panda-preset/scripts/sync.ts
index ac4e80c7ef6..cc1df37bf9c 100644
--- a/packages/panda-preset/scripts/sync.ts
+++ b/packages/panda-preset/scripts/sync.ts
@@ -1,6 +1,6 @@
import { globby } from "globby"
import { readFile, rm, writeFile } from "node:fs/promises"
-import { dirname, join, normalize, relative, resolve } from "node:path"
+import { dirname, join, normalize, relative, resolve, sep } from "node:path"
import { format } from "prettier"
import { cleanFiles } from "./shared"
@@ -56,7 +56,8 @@ async function main() {
const promises = files.map(async (file) => {
const content = await readFile(file, "utf8")
- let relativePath = relative(dirname(file), defFile)
+ // generated import specifiers must use posix separators
+ let relativePath = relative(dirname(file), defFile).split(sep).join("/")
relativePath = relativePath === "def.ts" ? "./def.ts" : relativePath
const fileFromSrc = relative("src", file)
diff --git a/packages/panda-preset/src/slot-recipes/checkbox-card.ts b/packages/panda-preset/src/slot-recipes/checkbox-card.ts
index adab9bd0d4e..cdcbb295954 100644
--- a/packages/panda-preset/src/slot-recipes/checkbox-card.ts
+++ b/packages/panda-preset/src/slot-recipes/checkbox-card.ts
@@ -190,7 +190,7 @@ export const checkboxCardSlotRecipe = defineSlotRecipe({
borderWidth: "1px",
borderColor: "border",
_checked: {
- boxShadow: "0 0 0 1px var(--shadow-color)",
+ boxShadow: "inset 0 0 0 1px var(--shadow-color)",
boxShadowColor: "colorPalette.solid",
borderColor: "colorPalette.solid",
},
diff --git a/packages/panda-preset/src/slot-recipes/date-picker.ts b/packages/panda-preset/src/slot-recipes/date-picker.ts
index 2ef242deae1..2aff308aec6 100644
--- a/packages/panda-preset/src/slot-recipes/date-picker.ts
+++ b/packages/panda-preset/src/slot-recipes/date-picker.ts
@@ -247,7 +247,7 @@ export const datePickerSlotRecipe = defineSlotRecipe({
textUnderlineOffset: "3px",
textDecorationThickness: "2px",
},
- _selected: {
+ "&[data-selected]": {
bg: "colorPalette.solid",
color: "colorPalette.contrast",
_hover: {
@@ -262,33 +262,23 @@ export const datePickerSlotRecipe = defineSlotRecipe({
bg: "colorPalette.subtle",
},
},
- "&[data-range-start]": {
+ "&[data-in-range][data-selected]": {
bg: "colorPalette.solid",
color: "colorPalette.contrast",
borderRadius: "0",
- borderStartRadius: "l2",
_hover: {
bg: "colorPalette.solid",
},
- },
- "&[data-range-end]": {
- bg: "colorPalette.solid",
- color: "colorPalette.contrast",
- borderRadius: "0",
- borderEndRadius: "l2",
- _hover: {
- bg: "colorPalette.solid",
+ "&[data-range-start][data-range-end]": {
+ borderRadius: "l2",
},
- },
- "&[data-range-start][data-range-end]": {
- borderRadius: "l2",
- },
- "&[data-selected][data-in-range]": {
- bg: "colorPalette.solid",
- color: "colorPalette.contrast",
- borderRadius: "l2",
- _hover: {
- bg: "colorPalette.solid",
+ "&[data-range-start]:not([data-range-end])": {
+ borderStartRadius: "l2",
+ borderEndRadius: "0",
+ },
+ "&[data-range-end]:not([data-range-start])": {
+ borderEndRadius: "l2",
+ borderStartRadius: "0",
},
},
_disabled: {
diff --git a/packages/panda-preset/src/slot-recipes/index.ts b/packages/panda-preset/src/slot-recipes/index.ts
index d3e49e9efdb..4702bc97716 100644
--- a/packages/panda-preset/src/slot-recipes/index.ts
+++ b/packages/panda-preset/src/slot-recipes/index.ts
@@ -21,6 +21,7 @@ import { emptyStateSlotRecipe } from "./empty-state"
import { fieldSlotRecipe } from "./field"
import { fieldsetSlotRecipe } from "./fieldset"
import { fileUploadSlotRecipe } from "./file-upload"
+import { floatingPanelSlotRecipe } from "./floating-panel"
import { hoverCardSlotRecipe } from "./hover-card"
import { listSlotRecipe } from "./list"
import { listboxSlotRecipe } from "./listbox"
@@ -110,4 +111,5 @@ export const slotRecipes = {
qrCode: qrCodeSlotRecipe,
treeView: treeViewSlotRecipe,
marquee: marqueeSlotRecipe,
+ floatingPanel: floatingPanelSlotRecipe,
}
diff --git a/packages/panda-preset/src/slot-recipes/radio-card.ts b/packages/panda-preset/src/slot-recipes/radio-card.ts
index d5b1dd806e5..3071777dd99 100644
--- a/packages/panda-preset/src/slot-recipes/radio-card.ts
+++ b/packages/panda-preset/src/slot-recipes/radio-card.ts
@@ -212,7 +212,7 @@ export const radioCardSlotRecipe = defineSlotRecipe({
item: {
borderWidth: "1px",
_checked: {
- boxShadow: "0 0 0 1px var(--shadow-color)",
+ boxShadow: "inset 0 0 0 1px var(--shadow-color)",
boxShadowColor: "colorPalette.solid",
borderColor: "colorPalette.solid",
},
diff --git a/packages/react/src/theme/recipes/checkbox-card.ts b/packages/react/src/theme/recipes/checkbox-card.ts
index c581535acf0..fadb3c05301 100644
--- a/packages/react/src/theme/recipes/checkbox-card.ts
+++ b/packages/react/src/theme/recipes/checkbox-card.ts
@@ -147,7 +147,7 @@ export const checkboxCardSlotRecipe = defineSlotRecipe({
borderWidth: "1px",
borderColor: "border",
_checked: {
- boxShadow: "0 0 0 1px var(--shadow-color)",
+ boxShadow: "inset 0 0 0 1px var(--shadow-color)",
boxShadowColor: "colorPalette.solid",
borderColor: "colorPalette.solid",
},
diff --git a/packages/react/src/theme/recipes/radio-card.ts b/packages/react/src/theme/recipes/radio-card.ts
index 64e8de578ed..6f5c20eddf9 100644
--- a/packages/react/src/theme/recipes/radio-card.ts
+++ b/packages/react/src/theme/recipes/radio-card.ts
@@ -152,7 +152,7 @@ export const radioCardSlotRecipe = defineSlotRecipe({
item: {
borderWidth: "1px",
_checked: {
- boxShadow: "0 0 0 1px var(--shadow-color)",
+ boxShadow: "inset 0 0 0 1px var(--shadow-color)",
boxShadowColor: "colorPalette.solid",
borderColor: "colorPalette.solid",
},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0f1192d3e28..cf73d5df8f4 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -598,6 +598,9 @@ importers:
esbuild:
specifier: ^0.27.3
version: 0.27.3
+ get-tsconfig:
+ specifier: ^4.14.0
+ version: 4.14.0
globby:
specifier: 16.1.1
version: 16.1.1
@@ -622,9 +625,6 @@ importers:
scule:
specifier: 1.3.0
version: 1.3.0
- tsconfck:
- specifier: ^3.1.6
- version: 3.1.6(typescript@5.9.3)
zod:
specifier: ^3.25.76
version: 3.25.76
diff --git a/scripts/build/build.ts b/scripts/build/build.ts
index 96e5006eb67..2f9058f425f 100644
--- a/scripts/build/build.ts
+++ b/scripts/build/build.ts
@@ -1,6 +1,6 @@
import { Alias } from "@rollup/plugin-alias"
import { rmSync } from "fs"
-import { join } from "path/posix"
+import { join } from "node:path"
import * as rollup from "rollup"
import { getConfig } from "./config.js"
import { generateTypes } from "./tsc.js"
diff --git a/scripts/build/config.ts b/scripts/build/config.ts
index 5b58fa0a30a..4dde6fefd56 100644
--- a/scripts/build/config.ts
+++ b/scripts/build/config.ts
@@ -7,6 +7,7 @@ import { resolve } from "node:path"
import { Plugin, RollupOptions } from "rollup"
import esbuild from "rollup-plugin-esbuild"
import { preserveDirectives } from "rollup-plugin-preserve-directives"
+import { readPackageJson } from "./read-package-json.js"
interface Options {
dir: string
@@ -16,7 +17,7 @@ interface Options {
export async function getConfig(options: Options): Promise {
const { dir, aliases } = options
- const packageJson = await import(resolve(dir, "package.json"))
+ const packageJson = readPackageJson(dir)
const isCli =
packageJson.bin !== undefined || packageJson.name.includes("docgen")
diff --git a/scripts/build/main.ts b/scripts/build/main.ts
index 1d1b331767d..b91e75edf17 100644
--- a/scripts/build/main.ts
+++ b/scripts/build/main.ts
@@ -1,5 +1,5 @@
-import { resolve } from "path/posix"
import { buildProject } from "./build.js"
+import { readPackageJson } from "./read-package-json.js"
async function main() {
const cwd = process.cwd()
@@ -8,7 +8,7 @@ async function main() {
const clean = flags.includes("--clean")
const dts = flags.includes("--dts")
- const packageJson = await import(resolve(cwd, "package.json"))
+ const packageJson = readPackageJson(cwd)
await buildProject({
dir: cwd,
diff --git a/scripts/build/read-package-json.ts b/scripts/build/read-package-json.ts
new file mode 100644
index 00000000000..cadea336992
--- /dev/null
+++ b/scripts/build/read-package-json.ts
@@ -0,0 +1,6 @@
+import { readFileSync } from "node:fs"
+import { join } from "node:path"
+
+export function readPackageJson(dir: string) {
+ return JSON.parse(readFileSync(join(dir, "package.json"), "utf8"))
+}
diff --git a/scripts/build/tsc.ts b/scripts/build/tsc.ts
index 5cc45c55fe0..c592243ce2d 100644
--- a/scripts/build/tsc.ts
+++ b/scripts/build/tsc.ts
@@ -1,5 +1,5 @@
import { cpSync } from "node:fs"
-import { join } from "node:path/posix"
+import { join } from "node:path"
export async function generateTypes(dir: string) {
const { execa } = await import("execa")