diff --git a/frontend/apps/artcraft/app/index.html b/frontend/apps/artcraft/app/index.html
--- a/frontend/apps/artcraft/app/index.html
+++ b/frontend/apps/artcraft/app/index.html
@@ -1,5 +1,5 @@
 <!doctype html>
-<html lang="en">
+<html lang="en" dir="ltr">
 
 <head>
   <title>ArtCraft</title>
diff --git a/frontend/apps/artcraft/app/src/components/experiences/ImageTo3DExperience.tsx b/frontend/apps/artcraft/app/src/components/experiences/ImageTo3DExperience.tsx
--- a/frontend/apps/artcraft/app/src/components/experiences/ImageTo3DExperience.tsx
+++ b/frontend/apps/artcraft/app/src/components/experiences/ImageTo3DExperience.tsx
@@ -527,7 +527,7 @@ export const ImageTo3DExperience = ({
       delay={100}
       zIndex={50}
       content={
-        <div className="flex flex-col gap-1.5 text-left">
+        <div className="flex flex-col gap-1.5 text-start">
           <Button
             variant="primary"
             icon={UploadIcon}
@@ -621,7 +621,7 @@ export const ImageTo3DExperience = ({
           {!isUploading && (
             <button
               type="button"
-              className="absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-[3px] bg-black/60 text-white opacity-0 transition-all hover:bg-black group-hover:opacity-100"
+              className="absolute end-2 top-2 flex h-6 w-6 items-center justify-center rounded-[3px] bg-black/60 text-white opacity-0 transition-all hover:bg-black group-hover:opacity-100"
               onClick={(event) => {
                 event.stopPropagation();
                 setUploadedPreview(null);
@@ -644,10 +644,11 @@ export const ImageTo3DExperience = ({
   const renderTextMode = () => (
     <div>
       <textarea
+        dir="auto"
         ref={textareaRef}
         id={promptInputId}
         rows={1}
-        className="text-md max-h-[5.5em] w-full resize-none overflow-y-auto rounded-[3px] bg-transparent pr-2 pt-1 text-base-fg placeholder-base-fg/60 focus:outline-none"
+        className="text-md max-h-[5.5em] w-full resize-none overflow-y-auto rounded-[3px] bg-transparent pe-2 pt-1 text-base-fg placeholder-base-fg/60 focus:outline-none"
         value={prompt}
         placeholder="Describe any object you want to generate from scratch..."
         onChange={(event) => setPrompt(event.target.value)}
@@ -669,7 +670,7 @@ export const ImageTo3DExperience = ({
               delay={100}
               zIndex={50}
               content={
-                <div className="flex flex-col gap-1.5 text-left">
+                <div className="flex flex-col gap-1.5 text-start">
                   <Button
                     variant="primary"
                     icon={UploadIcon}
@@ -740,6 +741,7 @@ export const ImageTo3DExperience = ({
             </Tooltip>
           </div>
           <textarea
+            dir="auto"
             ref={worldTextareaRef}
             rows={2}
             className="w-full resize-none overflow-y-auto rounded-[3px] border border-white/15 bg-ui-controls px-3 py-2.5 text-base text-base-fg placeholder-base-fg/60 transition-colors focus:border-white/60 focus:outline-none"
@@ -780,7 +782,7 @@ export const ImageTo3DExperience = ({
         {!img.isUploading && (
           <button
             type="button"
-            className="absolute right-[2px] top-[2px] flex h-4 w-4 cursor-pointer items-center justify-center rounded-[3px] bg-black/60 text-white opacity-0 transition-colors hover:bg-red/70 group-hover:opacity-100"
+            className="absolute end-[2px] top-[2px] flex h-4 w-4 cursor-pointer items-center justify-center rounded-[3px] bg-black/60 text-white opacity-0 transition-colors hover:bg-red/70 group-hover:opacity-100"
             onClick={(e) => {
               e.stopPropagation();
               removeWorldImage(img.id);
@@ -799,7 +801,7 @@ export const ImageTo3DExperience = ({
         delay={100}
         zIndex={50}
         content={
-          <div className="flex flex-col gap-1.5 text-left">
+          <div className="flex flex-col gap-1.5 text-start">
             <Button
               variant="primary"
               icon={UploadIcon}
@@ -865,6 +867,7 @@ export const ImageTo3DExperience = ({
               {addButton}
             </div>
             <textarea
+              dir="auto"
               ref={worldTextareaRef}
               rows={1}
               className="flex-1 resize-none overflow-y-auto rounded-[3px] border border-white/15 bg-ui-controls px-3 py-2 text-sm text-base-fg placeholder-base-fg/60 transition-colors focus:border-white/60 focus:outline-none"
@@ -887,6 +890,7 @@ export const ImageTo3DExperience = ({
         </div>
         {imageCountRow}
         <textarea
+          dir="auto"
           ref={worldTextareaRef}
           rows={2}
           className="w-full resize-none overflow-y-auto rounded-[3px] border border-white/15 bg-ui-controls px-3 py-2.5 text-base text-base-fg placeholder-base-fg/60 transition-colors focus:border-white/60 focus:outline-none"
@@ -930,7 +934,7 @@ export const ImageTo3DExperience = ({
 
       <div className="relative z-10 h-full w-full p-8">
         {!hasResults && (
-          <div className="pointer-events-none absolute left-0 top-[calc(50%-280px)] flex w-full justify-center">
+          <div className="pointer-events-none absolute start-0 top-[calc(50%-280px)] flex w-full justify-center">
             <CreateEmptyState
               title={title}
               subtitle={subtitle}
@@ -958,7 +962,7 @@ export const ImageTo3DExperience = ({
                 className="h-full"
               />
               {activeResult?.modelUrl && activeResult?.mediaToken && (
-                <div className="absolute right-4 top-4 z-10 flex gap-2">
+                <div className="absolute end-4 top-4 z-10 flex gap-2">
                   <Button
                     variant="primary"
                     className="min-w-[120px]"
@@ -1031,7 +1035,7 @@ export const ImageTo3DExperience = ({
                         key={result.id}
                         onClick={() => setSelectedResultId(result.id)}
                         className={twMerge(
-                          "group flex w-full items-center gap-3 rounded-none border p-2 text-left transition-all hover:bg-white/5",
+                          "group flex w-full items-center gap-3 rounded-none border p-2 text-start transition-all hover:bg-white/5",
                           isSelected
                             ? "border-white bg-white/10"
                             : "border-white/10 bg-transparent hover:border-white/30",
@@ -1152,7 +1156,7 @@ export const ImageTo3DExperience = ({
           </div>
         </animated.div>
 
-        <div className="absolute bottom-4 right-4 z-20 flex items-center gap-2">
+        <div className="absolute bottom-4 end-4 z-20 flex items-center gap-2">
           <CostCalculatorButton
             modelPage={
               variant === "world" ? WORLD_MODEL_PAGE : OBJECT_MODEL_PAGE
@@ -1207,7 +1211,7 @@ export const ImageTo3DExperience = ({
         >
           <button
             type="button"
-            className="absolute right-4 top-4 flex h-8 w-8 items-center justify-center rounded-[3px] border border-white/15 bg-black/60 text-white transition-colors hover:border-white/30 hover:bg-black"
+            className="absolute end-4 top-4 flex h-8 w-8 items-center justify-center rounded-[3px] border border-white/15 bg-black/60 text-white transition-colors hover:border-white/30 hover:bg-black"
             onClick={() => setPreviewImage(null)}
           >
             <XIcon  className="h-4 w-4" />
diff --git a/frontend/apps/artcraft/app/src/components/generation-feed/CreateEmptyState.tsx b/frontend/apps/artcraft/app/src/components/generation-feed/CreateEmptyState.tsx
--- a/frontend/apps/artcraft/app/src/components/generation-feed/CreateEmptyState.tsx
+++ b/frontend/apps/artcraft/app/src/components/generation-feed/CreateEmptyState.tsx
@@ -2,10 +2,10 @@ import { type ReactNode } from "react";
 import { twMerge } from "tailwind-merge";
 
 const CORNERS = [
-  "top-0 left-0",
-  "top-0 right-0",
-  "bottom-0 left-0",
-  "bottom-0 right-0",
+  "top-0 start-0",
+  "top-0 end-0",
+  "bottom-0 start-0",
+  "bottom-0 end-0",
 ];
 
 interface CreateEmptyStateProps {
@@ -27,7 +27,7 @@ export function CreateEmptyState({
   return (
     <section
       className={twMerge(
-        "create-empty-section relative w-full max-w-3xl px-6 text-left sm:px-10",
+        "create-empty-section relative w-full max-w-3xl px-6 text-start sm:px-10",
         compact ? "py-8" : "py-10 sm:py-14",
         className,
       )}
diff --git a/frontend/apps/artcraft/app/src/components/generation-feed/DesktopCreatePageShell.tsx b/frontend/apps/artcraft/app/src/components/generation-feed/DesktopCreatePageShell.tsx
--- a/frontend/apps/artcraft/app/src/components/generation-feed/DesktopCreatePageShell.tsx
+++ b/frontend/apps/artcraft/app/src/components/generation-feed/DesktopCreatePageShell.tsx
@@ -100,7 +100,7 @@ export function DesktopCreatePageShell({
         {promptBox}
 
         {bottomRight && (
-          <div className="absolute bottom-4 right-4 z-20 flex items-center gap-2">
+          <div className="absolute bottom-4 end-4 z-20 flex items-center gap-2">
             {bottomRight}
           </div>
         )}
diff --git a/frontend/apps/artcraft/app/src/components/reusable/ConfirmationModal.tsx b/frontend/apps/artcraft/app/src/components/reusable/ConfirmationModal.tsx
--- a/frontend/apps/artcraft/app/src/components/reusable/ConfirmationModal.tsx
+++ b/frontend/apps/artcraft/app/src/components/reusable/ConfirmationModal.tsx
@@ -64,7 +64,7 @@ export const ConfirmationModal = ({
             leaveFrom="opacity-100 scale-100"
             leaveTo="opacity-0 scale-95"
           >
-            <Dialog.Panel className="w-full max-w-md transform overflow-hidden border border-white/15 bg-ui-modal p-5 text-left align-middle transition-all">
+            <Dialog.Panel className="w-full max-w-md transform overflow-hidden border border-white/15 bg-ui-modal p-5 text-start align-middle transition-all">
               <Dialog.Title
                 as="h4"
                 className="mb-4 font-display text-xl tracking-tight text-white"
diff --git a/frontend/apps/artcraft/app/src/components/reusable/CostModal.tsx b/frontend/apps/artcraft/app/src/components/reusable/CostModal.tsx
--- a/frontend/apps/artcraft/app/src/components/reusable/CostModal.tsx
+++ b/frontend/apps/artcraft/app/src/components/reusable/CostModal.tsx
@@ -60,7 +60,7 @@ export const CostModal = ({ credits = 1, onClose }: CostModalProps) => {
                 {credits} Credits
               </span>
             </div>
-            <div className="text-right text-xs text-base-fg/60">
+            <div className="text-end text-xs text-base-fg/60">
               ≈ {formattedPrice} {currencyOption.value}
             </div>
           </div>
diff --git a/frontend/apps/artcraft/app/src/components/reusable/UploadModalMedia/UploadFilesMedia.tsx b/frontend/apps/artcraft/app/src/components/reusable/UploadModalMedia/UploadFilesMedia.tsx
--- a/frontend/apps/artcraft/app/src/components/reusable/UploadModalMedia/UploadFilesMedia.tsx
+++ b/frontend/apps/artcraft/app/src/components/reusable/UploadModalMedia/UploadFilesMedia.tsx
@@ -86,7 +86,7 @@ export const UploadFilesMedia = ({
 
         <div className="relative m-auto aspect-square w-full overflow-hidden border border-white/15 bg-ui-controls">
           {!assetFile.value && (
-            <h6 className="absolute left-0 top-1/2 -mt-5 w-full text-center">
+            <h6 className="absolute start-0 top-1/2 -mt-5 w-full text-center">
               File Preview
             </h6>
           )}
diff --git a/frontend/apps/artcraft/app/src/components/reusable/UploadModalMedia/UploadImageMedia.tsx b/frontend/apps/artcraft/app/src/components/reusable/UploadModalMedia/UploadImageMedia.tsx
--- a/frontend/apps/artcraft/app/src/components/reusable/UploadModalMedia/UploadImageMedia.tsx
+++ b/frontend/apps/artcraft/app/src/components/reusable/UploadModalMedia/UploadImageMedia.tsx
@@ -87,7 +87,7 @@ export const UploadImageMedia = ({
 
         <div className="relative m-auto aspect-square w-full overflow-hidden border border-white/15 bg-ui-controls">
           {!assetFile.value && (
-            <H6 className="absolute left-0 top-1/2 -mt-5 w-full text-center">
+            <H6 className="absolute start-0 top-1/2 -mt-5 w-full text-center">
               File Preview
             </H6>
           )}
diff --git a/frontend/apps/artcraft/app/src/components/signaled/TopBar/AppsQuickMenu.tsx b/frontend/apps/artcraft/app/src/components/signaled/TopBar/AppsQuickMenu.tsx
--- a/frontend/apps/artcraft/app/src/components/signaled/TopBar/AppsQuickMenu.tsx
+++ b/frontend/apps/artcraft/app/src/components/signaled/TopBar/AppsQuickMenu.tsx
@@ -38,7 +38,7 @@ const AppsQuickMenuSection = ({
             onClick={() => goToApp(app.action)}
             disabled={!app.action}
             className={twMerge(
-              "group flex w-full items-center gap-3 rounded-[3px] px-2 py-2 text-left transition-colors",
+              "group flex w-full items-center gap-3 rounded-[3px] px-2 py-2 text-start transition-colors",
               app.action
                 ? "cursor-pointer hover:bg-white/[0.08]"
                 : "cursor-default opacity-60",
diff --git a/frontend/apps/artcraft/app/src/components/signaled/TopBar/ProfileDropdown.tsx b/frontend/apps/artcraft/app/src/components/signaled/TopBar/ProfileDropdown.tsx
--- a/frontend/apps/artcraft/app/src/components/signaled/TopBar/ProfileDropdown.tsx
+++ b/frontend/apps/artcraft/app/src/components/signaled/TopBar/ProfileDropdown.tsx
@@ -56,7 +56,7 @@ export default function ProfileDropdown() {
       >
         <Menu.Items
           static
-          className="absolute right-0 z-50 mt-2 w-48 origin-top-right overflow-hidden rounded-[3px] border border-white/15 bg-[#101014] focus:outline-none"
+          className="absolute end-0 z-50 mt-2 w-48 origin-top-right overflow-hidden rounded-[3px] border border-white/15 bg-[#101014] focus:outline-none rtl:origin-top-left"
         >
           <Menu.Item key={0}>
             {({ active }) => (
diff --git a/frontend/apps/artcraft/app/src/components/signaled/TopBar/SceneTitleInput.tsx b/frontend/apps/artcraft/app/src/components/signaled/TopBar/SceneTitleInput.tsx
--- a/frontend/apps/artcraft/app/src/components/signaled/TopBar/SceneTitleInput.tsx
+++ b/frontend/apps/artcraft/app/src/components/signaled/TopBar/SceneTitleInput.tsx
@@ -89,7 +89,7 @@ export const SceneTitleInput = ({ pageName }: Props) => {
     <div
       className={twMerge(
         "flex w-full items-center justify-center gap-1.5",
-        isSaving && "ml-3",
+        isSaving && "ms-3",
       )}
       data-tauri-drag-region
     >
@@ -112,11 +112,16 @@ export const SceneTitleInput = ({ pageName }: Props) => {
               onClick={handleShowInput}
               data-tauri-drag-region="false"
             >
-              <span className="truncate">{scene.value.title || ""}</span>
-              <PencilIcon className="ml-2 shrink-0 text-sm opacity-50" />
+              <span dir="auto" className="truncate">
+                {scene.value.title || ""}
+              </span>
+              <PencilIcon className="ms-2 shrink-0 text-sm opacity-50" />
             </button>
           ) : (
-            <div className="max-w-[280px] truncate border border-transparent px-3 py-1.5 text-sm font-semibold text-white/80">
+            <div
+              dir="auto"
+              className="max-w-[280px] truncate border border-transparent px-3 py-1.5 text-sm font-semibold text-white/80"
+            >
               {scene.value.title || ""}
             </div>
           )}
@@ -133,6 +138,7 @@ export const SceneTitleInput = ({ pageName }: Props) => {
               isSaving && "outline-white/30",
             )}
             isError={!isValid}
+            dir="auto"
             value={scene.value.title || ""}
             onChange={handleChangeSceneTitle}
             onBlur={validateSceneTitle}
diff --git a/frontend/apps/artcraft/app/src/components/signaled/TopBar/TaskQueue.tsx b/frontend/apps/artcraft/app/src/components/signaled/TopBar/TaskQueue.tsx
--- a/frontend/apps/artcraft/app/src/components/signaled/TopBar/TaskQueue.tsx
+++ b/frontend/apps/artcraft/app/src/components/signaled/TopBar/TaskQueue.tsx
@@ -141,7 +141,7 @@ const PromptLine = ({
               : undefined
           }
         >
-          {prompt}
+          <bdi>{prompt}</bdi>
         </div>
       </div>
     </Tooltip>
@@ -213,7 +213,7 @@ const InProgressCard = ({
           size="lg" />
       </div>
       {task.refImageUrls!.length > 1 && (
-        <div className="absolute bottom-0.5 right-0.5 bg-black/60 px-1 text-[9px] text-white/80">
+        <div className="absolute bottom-0.5 end-0.5 bg-black/60 px-1 text-[9px] text-white/80">
           +{task.refImageUrls!.length - 1}
         </div>
       )}
@@ -250,12 +250,12 @@ const InProgressCard = ({
                 </Tooltip>
               )}
             </div>
-            <div className="ml-2 shrink-0 text-xs tabular-nums text-base-fg/60">
+            <div className="ms-2 shrink-0 text-xs tabular-nums text-base-fg/60">
               {progressPercent}%
             </div>
           </div>
           {task.subtitle && (
-            <div className="mt-0.5 truncate text-xs text-base-fg opacity-60">
+            <div dir="auto" className="mt-0.5 truncate text-xs text-base-fg opacity-60">
               {task.subtitle}
             </div>
           )}
@@ -274,7 +274,7 @@ const InProgressCard = ({
           )}
           {task.prompt && <PromptLine prompt={task.prompt} className="mt-0" />}
         </div>
-        <div className="ml-auto flex shrink-0 items-center gap-1">
+        <div className="ms-auto flex shrink-0 items-center gap-1">
           {task.prompt && <CopyPromptButton prompt={task.prompt} />}
           {onDismiss && (
             <button
@@ -337,7 +337,7 @@ const CompletedCard = ({
           {task.title}
         </div>
         {task.subtitle && (
-          <div className="mt-0.5 truncate text-xs text-base-fg opacity-60">
+          <div dir="auto" className="mt-0.5 truncate text-xs text-base-fg opacity-60">
             {task.subtitle}
           </div>
         )}
@@ -348,7 +348,7 @@ const CompletedCard = ({
         )}
         {task.prompt && <PromptLine prompt={task.prompt} />}
       </div>
-      <div className="ml-auto flex shrink-0 items-center gap-1">
+      <div className="ms-auto flex shrink-0 items-center gap-1">
         {task.prompt && <CopyPromptButton prompt={task.prompt} />}
         {onDismiss && (
           <button
@@ -416,7 +416,7 @@ const FailedCard = ({
           size="lg" />
       </div>
       {task.refImageUrls!.length > 1 && (
-        <div className="absolute bottom-0.5 right-0.5 bg-black/60 px-1 text-[9px] text-white/80">
+        <div className="absolute bottom-0.5 end-0.5 bg-black/60 px-1 text-[9px] text-white/80">
           +{task.refImageUrls!.length - 1}
         </div>
       )}
@@ -441,7 +441,7 @@ const FailedCard = ({
             </div>
           </div>
           {task.subtitle && (
-            <div className="mt-0.5 truncate text-xs text-base-fg opacity-60">
+            <div dir="auto" className="mt-0.5 truncate text-xs text-base-fg opacity-60">
               {task.subtitle}
             </div>
           )}
@@ -486,7 +486,7 @@ const FailedCard = ({
           )}
           {task.prompt && <PromptLine prompt={task.prompt} />}
         </div>
-        <div className="ml-auto flex shrink-0 items-center gap-1">
+        <div className="ms-auto flex shrink-0 items-center gap-1">
           {task.prompt && <CopyPromptButton prompt={task.prompt} />}
           {onDismiss && (
             <button
@@ -1008,7 +1008,7 @@ export const TaskQueue = () => {
       <Tooltip content="Task Queue" position="bottom" closeOnClick={true}>
         <div className="relative">
           {badgeCount > 0 && (
-            <div className="absolute -right-1 -top-1 z-20 flex h-[15px] min-w-[15px] items-center justify-center bg-white px-1 font-mono text-[10px] font-semibold text-black ring-2 ring-[#0b0b0c]">
+            <div className="absolute -end-1 -top-1 z-20 flex h-[15px] min-w-[15px] items-center justify-center bg-white px-1 font-mono text-[10px] font-semibold text-black ring-2 ring-[#0b0b0c]">
               {badgeCount}
             </div>
           )}
@@ -1069,7 +1069,7 @@ export const TaskQueue = () => {
                               >
                                 <XIcon
                                   
-                                  className="mr-1" />
+                                  className="me-1" />
                                 Clear failed
                               </button>
                             </div>
@@ -1094,7 +1094,7 @@ export const TaskQueue = () => {
                               >
                                 <XIcon
                                   
-                                  className="mr-1" />
+                                  className="me-1" />
                                 Clear completed
                               </button>
                             </div>
@@ -1171,31 +1171,31 @@ export const TaskQueue = () => {
                   className="flex h-9 items-center justify-center bg-green-500/10 px-3 text-green-500 hover:bg-green-500/20"
                   onClick={() => handleClearCompleted()}
                 >
-                  <BrushIcon  className="mr-1.5" />
+                  <BrushIcon  className="me-1.5" />
                   Clear completed
                 </Button>
                 <Button
                   className="flex h-9 items-center justify-center bg-orange-500/10 px-3 text-orange-500 hover:bg-orange-500/20"
                   onClick={() => handleClearStale()}
                 >
-                  <Trash2Icon  className="mr-1.5" />
+                  <Trash2Icon  className="me-1.5" />
                   Clear stale
                 </Button>
                 <Button
                   className="flex h-9 items-center justify-center bg-red-500/10 px-3 text-red-400 hover:bg-red-500/20"
                   onClick={() => handleClearFailed()}
                 >
-                  <Trash2Icon  className="mr-1.5" />
+                  <Trash2Icon  className="me-1.5" />
                   Clear failed
                 </Button>
                 <Button
                   className="flex h-9 items-center justify-center bg-red-500/10 px-3 text-red-500 hover:bg-red-500/20"
                   onClick={() => handleRemoveAll()}
                 >
-                  <BombIcon  className="mr-1.5" />
+                  <BombIcon  className="me-1.5" />
                   Remove all
                 </Button>
-                <div className="mr-2 h-4 w-[1px] bg-base-fg/10" />
+                <div className="me-2 h-4 w-[1px] bg-base-fg/10" />
                 <CloseButton onClick={() => setModalOpen(false)} />
               </div>
             </div>
diff --git a/frontend/apps/artcraft/app/src/components/signaled/TopBar/TopBar.tsx b/frontend/apps/artcraft/app/src/components/signaled/TopBar/TopBar.tsx
--- a/frontend/apps/artcraft/app/src/components/signaled/TopBar/TopBar.tsx
+++ b/frontend/apps/artcraft/app/src/components/signaled/TopBar/TopBar.tsx
@@ -169,7 +169,7 @@ const CreditsCoinWithStatus = ({
         <CoinsIcon className="text-[11px] text-primary" />
         {showBadge && (
           <span
-            className={`absolute -right-1.5 -top-1.5 flex h-3 w-3 items-center justify-center ring-1 ring-ui-panel ${badgeColorClass}`}
+            className={`absolute -end-1.5 -top-1.5 flex h-3 w-3 items-center justify-center ring-1 ring-ui-panel ${badgeColorClass}`}
           >
             <DynamicIcon icon={badgeIconDef} className="text-[7px]" />
           </span>
@@ -427,7 +427,7 @@ export const TopBar = ({ pageName }: Props) => {
   return (
     <>
       <header
-        className="fixed left-0 top-0 z-[60] w-full border-b border-ui-border bg-ui-panel"
+        className="fixed start-0 top-0 z-[60] w-full border-b border-ui-border bg-ui-panel"
         data-tauri-drag-region
       >
         <nav
@@ -439,7 +439,7 @@ export const TopBar = ({ pageName }: Props) => {
             className={`flex items-center gap-3 ${platform === "macos" ? "ml-14" : ""}`}
             data-tauri-drag-region
           >
-            {/* <div className="mr-2" data-tauri-drag-region>
+            {/* <div className="me-2" data-tauri-drag-region>
               <span className="sr-only" data-tauri-drag-region>
                 ArtCraft
               </span>
diff --git a/frontend/apps/artcraft/app/src/index.tsx b/frontend/apps/artcraft/app/src/index.tsx
--- a/frontend/apps/artcraft/app/src/index.tsx
+++ b/frontend/apps/artcraft/app/src/index.tsx
@@ -13,6 +13,7 @@ import { SyncStorytellerApiConfig } from "./api/SyncStorytellerApiConfig";
 import { posthog } from "posthog-js";
 import { SoundManager } from "@storyteller/soundboard";
 import { useModelsStore } from "@storyteller/tauri-api";
+import { setDocumentLanguage } from "@storyteller/common";
 
 // TODO(bt,2025-04-19): Make these configurable
 const ENV = {
@@ -97,6 +98,8 @@ const GlobalSettingsManager = ({ env }: { env: Record<string, string> }) => {
 };
 
 // TODO: Replace environment variables from `root.tsx`
+setDocumentLanguage();
+
 createRoot(document.getElementById("root")!).render(
   <>
     <StrictMode>
diff --git a/frontend/apps/artcraft/app/src/pages/PageAngles/Angles.tsx b/frontend/apps/artcraft/app/src/pages/PageAngles/Angles.tsx
--- a/frontend/apps/artcraft/app/src/pages/PageAngles/Angles.tsx
+++ b/frontend/apps/artcraft/app/src/pages/PageAngles/Angles.tsx
@@ -569,7 +569,7 @@ export const Angles = () => {
             </div>
 
             {/* ── History stack (right side, like PageEdit) ── */}
-            <div className="absolute right-4 top-1/2 z-10 -translate-y-1/2">
+            <div className="absolute end-4 top-1/2 z-10 -translate-y-1/2">
               <HistoryStack
                 onClear={handleHistoryClear}
                 imageBundles={allBundles}
@@ -645,7 +645,7 @@ export const Angles = () => {
                         suffix="°"
                       />
                     </div>
-                    <span className="w-10 shrink-0 text-left font-mono text-xs tabular-nums text-base-fg/70">
+                    <span className="w-10 shrink-0 text-start font-mono text-xs tabular-nums text-base-fg/70">
                       {rotationToDisplay(angleConfig.rotation)}°
                     </span>
                   </div>
@@ -664,7 +664,7 @@ export const Angles = () => {
                         suffix="°"
                       />
                     </div>
-                    <span className="w-9 shrink-0 text-left font-mono text-xs tabular-nums text-base-fg/70">
+                    <span className="w-9 shrink-0 text-start font-mono text-xs tabular-nums text-base-fg/70">
                       {angleConfig.tilt}°
                     </span>
                   </div>
@@ -682,7 +682,7 @@ export const Angles = () => {
                         onChange={handleZoomSlider}
                       />
                     </div>
-                    <span className="w-9 shrink-0 text-left font-mono text-xs tabular-nums text-base-fg/70">
+                    <span className="w-9 shrink-0 text-start font-mono text-xs tabular-nums text-base-fg/70">
                       {angleConfig.zoom}
                     </span>
                   </div>
@@ -710,8 +710,8 @@ export const Angles = () => {
                           className={twMerge(
                             "size-3.5 rounded-full bg-white transition",
                             generateFromBestAngles
-                              ? "translate-x-[18px]"
-                              : "translate-x-1",
+                              ? "translate-x-[18px] rtl:-translate-x-[18px]"
+                              : "translate-x-1 rtl:-translate-x-1",
                           )}
                         />
                       </Switch>
@@ -737,7 +737,7 @@ export const Angles = () => {
             </div>
 
             {/* ── Cost calculator + Help (bottom-right) ── */}
-            <div className="absolute bottom-4 right-4 z-20 flex items-center gap-2">
+            <div className="absolute bottom-4 end-4 z-20 flex items-center gap-2">
               <CostCalculatorButton modelPage={ANGLES_MODEL_PAGE} />
               <HelpMenuButton />
             </div>
diff --git a/frontend/apps/artcraft/app/src/pages/PageApps/AppsIndexPage.tsx b/frontend/apps/artcraft/app/src/pages/PageApps/AppsIndexPage.tsx
--- a/frontend/apps/artcraft/app/src/pages/PageApps/AppsIndexPage.tsx
+++ b/frontend/apps/artcraft/app/src/pages/PageApps/AppsIndexPage.tsx
@@ -66,7 +66,7 @@ function AppCard({ app }: { app: FullAppItem }) {
       onClick={() => goToApp(app.action)}
       disabled={!enabled}
       className={twMerge(
-        "group relative flex h-full rounded-[3px] border border-ui-border bg-white/5 p-5 text-left transition-colors duration-150 focus-visible:border-primary",
+        "group relative flex h-full rounded-[3px] border border-ui-border bg-white/5 p-5 text-start transition-colors duration-150 focus-visible:border-primary",
         enabled ? twMerge("cursor-pointer", palette.hoverStyle) : "cursor-default opacity-60",
       )}
     >
diff --git a/frontend/apps/artcraft/app/src/pages/PageBackgroundChange/PageBackgroundChange.tsx b/frontend/apps/artcraft/app/src/pages/PageBackgroundChange/PageBackgroundChange.tsx
--- a/frontend/apps/artcraft/app/src/pages/PageBackgroundChange/PageBackgroundChange.tsx
+++ b/frontend/apps/artcraft/app/src/pages/PageBackgroundChange/PageBackgroundChange.tsx
@@ -146,7 +146,7 @@ export const PageBackgroundChange = () => {
 
       <div
         aria-hidden
-        className="pointer-events-none fixed bottom-0 left-0 right-0 z-20 h-72 bg-gradient-to-t from-ui-background via-ui-background/85 to-transparent"
+        className="pointer-events-none fixed bottom-0 start-0 end-0 z-20 h-72 bg-gradient-to-t from-ui-background via-ui-background/85 to-transparent"
       />
 
       <div
diff --git a/frontend/apps/artcraft/app/src/pages/PageDraw/BaseImageSelector.tsx b/frontend/apps/artcraft/app/src/pages/PageDraw/BaseImageSelector.tsx
--- a/frontend/apps/artcraft/app/src/pages/PageDraw/BaseImageSelector.tsx
+++ b/frontend/apps/artcraft/app/src/pages/PageDraw/BaseImageSelector.tsx
@@ -152,7 +152,7 @@ export const BaseImageSelector = ({
           />
         </div>
       </div>
-      <div className="fixed bottom-6 right-6 z-20 flex items-center gap-2">
+      <div className="fixed bottom-6 end-6 z-20 flex items-center gap-2">
         <CostCalculatorButton modelPage={ModelPage.ImageEditor} />
         <HelpMenuButton />
       </div>
diff --git a/frontend/apps/artcraft/app/src/pages/PageImageWatermarkRemover/ImageWatermarkRemover.tsx b/frontend/apps/artcraft/app/src/pages/PageImageWatermarkRemover/ImageWatermarkRemover.tsx
--- a/frontend/apps/artcraft/app/src/pages/PageImageWatermarkRemover/ImageWatermarkRemover.tsx
+++ b/frontend/apps/artcraft/app/src/pages/PageImageWatermarkRemover/ImageWatermarkRemover.tsx
@@ -142,7 +142,7 @@ export const ImageWatermarkRemover = () => {
                         setImageUrl("");
                         setImageDimensions(null);
                       }}
-                      className="absolute right-3 top-3 z-10 border border-red/50 px-3 py-1.5 text-sm hover:border-red/80 hover:bg-red/80"
+                      className="absolute end-3 top-3 z-10 border border-red/50 px-3 py-1.5 text-sm hover:border-red/80 hover:bg-red/80"
                     >
                       Switch Image
                     </Button>
diff --git a/frontend/apps/artcraft/app/src/pages/PageStoryboard/Storyboard.tsx b/frontend/apps/artcraft/app/src/pages/PageStoryboard/Storyboard.tsx
--- a/frontend/apps/artcraft/app/src/pages/PageStoryboard/Storyboard.tsx
+++ b/frontend/apps/artcraft/app/src/pages/PageStoryboard/Storyboard.tsx
@@ -65,7 +65,7 @@ interface MetadataEditorProps {
 }
 
 const MetadataEditor = ({ board, onUpdate, onDelete, onUpload, onSketch }: MetadataEditorProps) => (
-  <aside className="flex w-[280px] shrink-0 flex-col border-r border-ui-panel-border bg-ui-panel">
+  <aside className="flex w-[280px] shrink-0 flex-col border-e border-ui-panel-border bg-ui-panel">
     <div className="flex-1 overflow-y-auto p-4">
       <div className="flex flex-col gap-3">
         <div className="hud-label text-base-fg/40">
@@ -86,6 +86,7 @@ const MetadataEditor = ({ board, onUpdate, onDelete, onUpload, onSketch }: Metad
         <div>
           <label className="mb-1 block font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-base-fg/60">Dialogue</label>
           <textarea
+            dir="auto"
             value={board.dialogue}
             onChange={(e) => onUpdate({ dialogue: e.target.value })}
             placeholder="Character dialogue..."
@@ -97,6 +98,7 @@ const MetadataEditor = ({ board, onUpdate, onDelete, onUpload, onSketch }: Metad
         <div>
           <label className="mb-1 block font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-base-fg/60">Action</label>
           <textarea
+            dir="auto"
             value={board.action}
             onChange={(e) => onUpdate({ action: e.target.value })}
             placeholder="On-screen action..."
@@ -108,6 +110,7 @@ const MetadataEditor = ({ board, onUpdate, onDelete, onUpload, onSketch }: Metad
         <div>
           <label className="mb-1 block font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-base-fg/60">Notes</label>
           <textarea
+            dir="auto"
             value={board.notes}
             onChange={(e) => onUpdate({ notes: e.target.value })}
             placeholder="Director notes..."
@@ -401,7 +404,7 @@ const SketchCanvas = ({ initialImageDataUrl, onSave, onExit }: SketchCanvasProps
         <button
           onClick={onExit}
           title="Done sketching"
-          className="ml-auto flex h-8 items-center gap-1.5 rounded-[3px] bg-white px-3 text-xs font-medium text-black transition-colors hover:bg-white/85"
+          className="ms-auto flex h-8 items-center gap-1.5 rounded-[3px] bg-white px-3 text-xs font-medium text-black transition-colors hover:bg-white/85"
         >
           <CheckIcon  className="text-xs" />
           Done
@@ -481,7 +484,7 @@ const ThumbnailItem = ({
         <ImagesIcon  className="text-lg text-base-fg/20" />
       </div>
     )}
-    <div className="absolute bottom-0 left-0 right-0 bg-black/60 px-1.5 py-0.5">
+    <div className="absolute bottom-0 start-0 end-0 bg-black/60 px-1.5 py-0.5">
       <span className="font-mono text-[10px] tabular-nums text-white/85">{board.shotNumber}</span>
     </div>
   </div>
@@ -769,11 +772,16 @@ const Timeline = ({
                 <div className="flex min-w-0 flex-1 flex-col justify-center gap-0.5 px-1.5">
                   <span className="truncate text-[10px] font-semibold text-base-fg/80">
                     {board.shotNumber}
-                    {board.title ? ` · ${board.title}` : ""}
+                    {board.title && (
+                      <>
+                        {" · "}
+                        <bdi>{board.title}</bdi>
+                      </>
+                    )}
                   </span>
                   {board.dialogue && (
                     <span className="truncate text-[9px] italic text-base-fg/40">
-                      "{board.dialogue}"
+                      "<bdi>{board.dialogue}</bdi>"
                     </span>
                   )}
                   <span className="font-mono text-[9px] tabular-nums text-base-fg/30">{board.duration.toFixed(1)}s</span>
@@ -1203,7 +1211,7 @@ export const Storyboard = () => {
             onSketch={handleSketchOpen}
           />
         ) : (
-          <div className="w-[280px] shrink-0 border-r border-ui-panel-border bg-ui-panel" />
+          <div className="w-[280px] shrink-0 border-e border-ui-panel-border bg-ui-panel" />
         )}
         {isSketchMode && selectedBoard ? (
           <SketchCanvas
diff --git a/frontend/apps/artcraft/app/src/pages/PageVideoFrameExtractor/VideoFrameExtractor.tsx b/frontend/apps/artcraft/app/src/pages/PageVideoFrameExtractor/VideoFrameExtractor.tsx
--- a/frontend/apps/artcraft/app/src/pages/PageVideoFrameExtractor/VideoFrameExtractor.tsx
+++ b/frontend/apps/artcraft/app/src/pages/PageVideoFrameExtractor/VideoFrameExtractor.tsx
@@ -500,7 +500,7 @@ export const VideoFrameExtractor = () => {
                         setSavedFrames(new Set());
                         setConvertingFrames(new Set());
                       }}
-                      className="absolute right-3 top-3 z-10 border border-red/50 px-3 py-1.5 text-sm hover:border-red/80 hover:bg-red/80"
+                      className="absolute end-3 top-3 z-10 border border-red/50 px-3 py-1.5 text-sm hover:border-red/80 hover:bg-red/80"
                     >
                       Switch Video
                     </Button>
@@ -513,7 +513,7 @@ export const VideoFrameExtractor = () => {
                       playsInline
                       crossOrigin="anonymous"
                     />
-                    <div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/90 to-transparent p-4">
+                    <div className="absolute bottom-0 start-0 end-0 bg-gradient-to-t from-black/90 to-transparent p-4">
                       <div
                         ref={progressBarRef}
                         className="group relative mb-3 h-3 cursor-pointer bg-white/15"
diff --git a/frontend/apps/artcraft/app/src/pages/PageVideoWatermarkRemover/VideoWatermarkRemover.tsx b/frontend/apps/artcraft/app/src/pages/PageVideoWatermarkRemover/VideoWatermarkRemover.tsx
--- a/frontend/apps/artcraft/app/src/pages/PageVideoWatermarkRemover/VideoWatermarkRemover.tsx
+++ b/frontend/apps/artcraft/app/src/pages/PageVideoWatermarkRemover/VideoWatermarkRemover.tsx
@@ -241,7 +241,7 @@ export const VideoWatermarkRemover = () => {
                         setVideoUrl("");
                         setCurrentTime(0);
                       }}
-                      className="absolute right-3 top-3 z-10 border border-red/50 px-3 py-1.5 text-sm hover:border-red/80 hover:bg-red/80"
+                      className="absolute end-3 top-3 z-10 border border-red/50 px-3 py-1.5 text-sm hover:border-red/80 hover:bg-red/80"
                     >
                       Switch Video
                     </Button>
@@ -253,7 +253,7 @@ export const VideoWatermarkRemover = () => {
                       preload="metadata"
                       playsInline
                     />
-                    <div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/90 to-transparent p-4">
+                    <div className="absolute bottom-0 start-0 end-0 bg-gradient-to-t from-black/90 to-transparent p-4">
                       <div
                         ref={progressBarRef}
                         className="group relative mb-3 h-3 cursor-pointer bg-white/15"
diff --git a/frontend/apps/artcraft/app/src/styles/base.css b/frontend/apps/artcraft/app/src/styles/base.css
--- a/frontend/apps/artcraft/app/src/styles/base.css
+++ b/frontend/apps/artcraft/app/src/styles/base.css
@@ -463,6 +463,14 @@ img {
   );
 }
 
+/* The browser puts the resize corner on the side of the text direction, which
+   follows the typed text (dir="auto"), not the document. */
+.promptbox-resize-wrap:has(:is(textarea, [contenteditable]):dir(rtl))::after {
+  right: auto;
+  left: 2px;
+  transform: scaleX(-1);
+}
+
 .scrollbar-hidden {
   -ms-overflow-style: none;
   /* IE and Edge */
diff --git a/frontend/libs/common/pnpm-lock.yaml b/frontend/libs/common/pnpm-lock.yaml
new file mode 100644
--- /dev/null
+++ b/frontend/libs/common/pnpm-lock.yaml
@@ -0,0 +1,9 @@
+lockfileVersion: '9.0'
+
+settings:
+  autoInstallPeers: true
+  excludeLinksFromLockfile: false
+
+importers:
+
+  .: {}
diff --git a/frontend/libs/common/src/lib/utils/document-language.spec.ts b/frontend/libs/common/src/lib/utils/document-language.spec.ts
new file mode 100644
--- /dev/null
+++ b/frontend/libs/common/src/lib/utils/document-language.spec.ts
@@ -0,0 +1,47 @@
+import { afterEach, describe, expect, it, vi } from "vitest";
+import { directionOfLanguage, setDocumentLanguage } from "./document-language";
+
+describe("directionOfLanguage", () => {
+  it("finds right-to-left languages and scripts", () => {
+    expect(directionOfLanguage("ar")).toBe("rtl");
+    expect(directionOfLanguage("he-IL")).toBe("rtl");
+    expect(directionOfLanguage("pa-Arab")).toBe("rtl");
+    expect(directionOfLanguage("en")).toBe("ltr");
+    expect(directionOfLanguage("az-Latn")).toBe("ltr");
+    expect(directionOfLanguage("not a tag")).toBe("ltr");
+  });
+
+  describe("without Intl.Locale text info", () => {
+    afterEach(() => vi.unstubAllGlobals());
+
+    it("falls back to the language and script lists", () => {
+      vi.stubGlobal("Intl", { ...Intl, Locale: class {} });
+      expect(directionOfLanguage("ur-PK")).toBe("rtl");
+      expect(directionOfLanguage("uz-Arab")).toBe("rtl");
+      expect(directionOfLanguage("ks-Deva")).toBe("ltr");
+      expect(directionOfLanguage("de")).toBe("ltr");
+    });
+
+    it("ignores extension and private-use subtags", () => {
+      vi.stubGlobal("Intl", { ...Intl, Locale: class {} });
+      expect(directionOfLanguage("fa-u-nu-latn")).toBe("rtl");
+      expect(directionOfLanguage("ur-PK-u-nu-latn")).toBe("rtl");
+      expect(directionOfLanguage("uz-Arab-u-nu-latn")).toBe("rtl");
+      expect(directionOfLanguage("en-x-arab")).toBe("ltr");
+    });
+  });
+});
+
+describe("setDocumentLanguage", () => {
+  it("defaults to English, left to right", () => {
+    setDocumentLanguage();
+    expect(document.documentElement.lang).toBe("en");
+    expect(document.documentElement.dir).toBe("ltr");
+  });
+
+  it("sets the direction from the language", () => {
+    setDocumentLanguage("fa");
+    expect(document.documentElement.lang).toBe("fa");
+    expect(document.documentElement.dir).toBe("rtl");
+  });
+});
diff --git a/frontend/libs/common/src/lib/utils/document-language.ts b/frontend/libs/common/src/lib/utils/document-language.ts
new file mode 100644
--- /dev/null
+++ b/frontend/libs/common/src/lib/utils/document-language.ts
@@ -0,0 +1,52 @@
+export type TextDirection = "ltr" | "rtl";
+
+export const DEFAULT_UI_LANGUAGE = "en";
+
+// Fallback for webviews without Intl.Locale text info. Languages whose usual
+// script is written right to left, and scripts that are.
+const RTL_LANGUAGES = new Set([
+  "ar", "arc", "ckb", "dv", "fa", "he", "iw", "ks", "lrc", "mzn", "nqo",
+  "ps", "sd", "syr", "ug", "ur", "yi",
+]);
+const RTL_SCRIPTS = new Set([
+  "adlm", "arab", "hebr", "mand", "nkoo", "rohg", "syrc", "thaa",
+]);
+
+/** Writing direction of a BCP 47 language tag such as `ar`, `he-IL` or `pa-Arab`. */
+export function directionOfLanguage(tag: string): TextDirection {
+  try {
+    const locale = new Intl.Locale(tag) as Intl.Locale & {
+      getTextInfo?: () => { direction?: string };
+      textInfo?: { direction?: string };
+    };
+    const info = locale.getTextInfo?.() ?? locale.textInfo;
+    if (info?.direction === "rtl" || info?.direction === "ltr") {
+      return info.direction;
+    }
+  } catch {
+    return "ltr";
+  }
+  const [language, ...rest] = tag.toLowerCase().split(/[-_]/);
+  // Stop at the first singleton (`-u-` Unicode extensions, `-x-` private use):
+  // their keys and values, such as `nu-latn`, are not scripts.
+  const singleton = rest.findIndex((subtag) => subtag.length === 1);
+  const subtags = singleton < 0 ? rest : rest.slice(0, singleton);
+  const script = subtags.find((subtag) => /^[a-z]{4}$/.test(subtag));
+  if (script) {
+    return RTL_SCRIPTS.has(script) ? "rtl" : "ltr";
+  }
+  return RTL_LANGUAGES.has(language) ? "rtl" : "ltr";
+}
+
+/**
+ * Sets `lang` and `dir` on the document root from the UI language. Tailwind's
+ * `rtl:` variant and the logical (start/end) classes follow `dir`.
+ */
+export function setDocumentLanguage(
+  tag: string = DEFAULT_UI_LANGUAGE,
+  dir: TextDirection = directionOfLanguage(tag),
+  root: HTMLElement = document.documentElement,
+): void {
+  root.lang = tag;
+  root.dir = dir;
+}
diff --git a/frontend/libs/common/src/lib/utils/index.ts b/frontend/libs/common/src/lib/utils/index.ts
--- a/frontend/libs/common/src/lib/utils/index.ts
+++ b/frontend/libs/common/src/lib/utils/index.ts
@@ -1,3 +1,4 @@
 export * from "./thumbnail-utils";
 export * from "./media-format";
 export * from "./referral";
+export * from "./document-language";
diff --git a/frontend/libs/components/audio-player/src/lib/WaveformAudioPlayer.tsx b/frontend/libs/components/audio-player/src/lib/WaveformAudioPlayer.tsx
--- a/frontend/libs/components/audio-player/src/lib/WaveformAudioPlayer.tsx
+++ b/frontend/libs/components/audio-player/src/lib/WaveformAudioPlayer.tsx
@@ -263,7 +263,7 @@ export function WaveformAudioPlayer({
       >
         <DynamicIcon
           icon={isPlaying ? PauseIcon : PlayIcon}
-          className={`${compact ? "h-3 w-3" : "h-3.5 w-3.5"} ${isPlaying ? "" : "ml-0.5"}`}
+          className={`${compact ? "h-3 w-3" : "h-3.5 w-3.5"} ${isPlaying ? "" : "ms-0.5"}`}
         />
       </button>
 
@@ -315,7 +315,7 @@ export function WaveformAudioPlayer({
           />
         </button>
         {showVolume && (
-          <div className="absolute bottom-full right-0 z-20 mb-2 w-28 rounded-lg border border-white/10 bg-neutral-900/95 px-3 py-2.5 shadow-xl backdrop-blur">
+          <div className="absolute bottom-full end-0 z-20 mb-2 w-28 rounded-lg border border-white/10 bg-neutral-900/95 px-3 py-2.5 shadow-xl backdrop-blur">
             <SliderV2
               min={0}
               max={100}
diff --git a/frontend/libs/components/button-dropdown/src/lib/button-dropdown.tsx b/frontend/libs/components/button-dropdown/src/lib/button-dropdown.tsx
--- a/frontend/libs/components/button-dropdown/src/lib/button-dropdown.tsx
+++ b/frontend/libs/components/button-dropdown/src/lib/button-dropdown.tsx
@@ -77,7 +77,7 @@ export const ButtonDropdown = ({
 
   return (
     <div className="relative">
-      <Menu as="div" className="inline-block text-left">
+      <Menu as="div" className="inline-block text-start">
         <Menu.Button as="div">
           <Button
             className={className}
@@ -102,7 +102,7 @@ export const ButtonDropdown = ({
             static
             className={twMerge(
               "absolute z-20 mt-2 w-max min-w-48 overflow-hidden rounded-[3px] border border-ui-panel-border bg-ui-controls p-1.5 text-base-fg focus:outline-none",
-              align === "left" ? "left-0" : "right-0",
+              align === "left" ? "start-0" : "end-0",
             )}
           >
             <div>
@@ -130,11 +130,11 @@ export const ButtonDropdown = ({
                           {option.icon && (
                             <DynamicIcon
                               icon={option.icon}
-                              className="mr-2 h-4 w-4 shrink-0"
+                              className="me-2 h-4 w-4 shrink-0"
                             />
                           )}
                           <div className="grow text-start">{option.label}</div>
-                          <div className="ml-6 font-mono text-[11px] font-normal text-white/45">
+                          <div className="ms-6 font-mono text-[11px] font-normal text-white/45">
                             {option.description && option.description}
                           </div>
                           {showSelected && (
@@ -143,7 +143,7 @@ export const ButtonDropdown = ({
                                 <svg
                                   xmlns="http://www.w3.org/2000/svg"
                                   viewBox="0 0 512 512"
-                                  className="ml-3 flex h-5"
+                                  className="ms-3 flex h-5"
                                 >
                                   <path
                                     opacity="1"
diff --git a/frontend/libs/components/button-icon-select/src/lib/button-icon-select.tsx b/frontend/libs/components/button-icon-select/src/lib/button-icon-select.tsx
--- a/frontend/libs/components/button-icon-select/src/lib/button-icon-select.tsx
+++ b/frontend/libs/components/button-icon-select/src/lib/button-icon-select.tsx
@@ -40,7 +40,7 @@ export function ButtonIconSelect({
   };
 
   return (
-    <div className="flex space-x-1">
+    <div className="flex space-x-1 rtl:space-x-reverse">
       {options.map(({ value, icon, text, tooltip }) =>
         tooltip ? (
           <Tooltip
diff --git a/frontend/libs/components/camera-settings-modal/src/lib/camera-settings-modal.tsx b/frontend/libs/components/camera-settings-modal/src/lib/camera-settings-modal.tsx
--- a/frontend/libs/components/camera-settings-modal/src/lib/camera-settings-modal.tsx
+++ b/frontend/libs/components/camera-settings-modal/src/lib/camera-settings-modal.tsx
@@ -115,7 +115,7 @@ export const CameraSettingsModal = ({
       showClose={false}
     >
       <div className="grid h-full grid-cols-12 gap-3">
-        <div className="relative col-span-4 p-3 pt-2 after:absolute after:right-0 after:top-0 after:h-full after:w-px after:bg-white/15">
+        <div className="relative col-span-4 p-3 pt-2 after:absolute after:end-0 after:top-0 after:h-full after:w-px after:bg-white/15">
           <div className="flex items-center justify-between gap-2.5 py-0.5">
             <h2 className="hud-label text-base-fg/60">Camera</h2>
             <Tooltip content="Add camera" position="top" delay={200}>
@@ -132,7 +132,7 @@ export const CameraSettingsModal = ({
             {cameras.map((camera) => (
               <button
                 key={camera.id}
-                className={`h-9 w-full rounded-[3px] border-l-2 p-2 text-left transition-colors duration-100 ${
+                className={`h-9 w-full rounded-[3px] border-s-2 p-2 text-start transition-colors duration-100 ${
                   camera.id === selectedCameraId
                     ? "border-white bg-white/10"
                     : "border-transparent hover:bg-white/5"
diff --git a/frontend/libs/components/file-uploader/src/lib/drag-and-drop-zone.tsx b/frontend/libs/components/file-uploader/src/lib/drag-and-drop-zone.tsx
--- a/frontend/libs/components/file-uploader/src/lib/drag-and-drop-zone.tsx
+++ b/frontend/libs/components/file-uploader/src/lib/drag-and-drop-zone.tsx
@@ -40,12 +40,13 @@ export const DragAndDropZone = ({
       <div className="flex cursor-pointer items-center justify-between gap-3.5 rounded-none border border-dashed border-white/20 bg-white/[0.02] p-3 transition-colors hover:border-white/40 hover:bg-white/[0.05]">
         <DynamicIcon icon={fileIcon} className="text-4xl" />
         <div className="flex grow flex-col gap-0">
-          <p className="font-medium">
+          <p dir="auto" className="font-medium">
             {file.name.slice(0, file.name.lastIndexOf("."))}
           </p>
           <p className="flex items-center gap-2 text-sm font-normal">
             <span className="opacity-50">
-              {`${fileName} file size: ${fileSize} `}
+              <bdi>{fileName}</bdi>
+              {` file size: ${fileSize} `}
             </span>
             <u className="transition-all hover:text-white/80">Change File</u>
           </p>
diff --git a/frontend/libs/components/gallery-modal/src/lib/DragGhost.tsx b/frontend/libs/components/gallery-modal/src/lib/DragGhost.tsx
--- a/frontend/libs/components/gallery-modal/src/lib/DragGhost.tsx
+++ b/frontend/libs/components/gallery-modal/src/lib/DragGhost.tsx
@@ -143,7 +143,7 @@ export function DragGhost({
       />
       {state.indicator && (
         <div
-          className={`absolute left-1.5 top-1.5 flex h-5 w-5 items-center justify-center shadow-md ring-1 ring-black/30 ${
+          className={`absolute start-1.5 top-1.5 flex h-5 w-5 items-center justify-center shadow-md ring-1 ring-black/30 ${
             state.indicator === "ok" ? "bg-emerald-500" : "bg-red-500"
           }`}
         >
diff --git a/frontend/libs/components/gallery-modal/src/lib/GalleryDraggableItem.tsx b/frontend/libs/components/gallery-modal/src/lib/GalleryDraggableItem.tsx
--- a/frontend/libs/components/gallery-modal/src/lib/GalleryDraggableItem.tsx
+++ b/frontend/libs/components/gallery-modal/src/lib/GalleryDraggableItem.tsx
@@ -271,7 +271,10 @@ export const GalleryDraggableItem: React.FC<GalleryDraggableItemProps> = ({
                 <MusicIcon className="text-lg text-white/70" />
               </div>
             </div>
-            <p className="line-clamp-2 shrink-0 px-2.5 pb-2.5 text-left text-xs leading-snug text-white/75">
+            <p
+              dir="auto"
+              className="line-clamp-2 shrink-0 px-2.5 pb-2.5 text-start text-xs leading-snug text-white/75"
+            >
               {audioPromptText || item.label}
             </p>
           </div>
@@ -309,12 +312,12 @@ export const GalleryDraggableItem: React.FC<GalleryDraggableItemProps> = ({
           </>
         )}
         {selected && (
-          <div className="absolute left-2 top-2 flex h-6 w-6 items-center justify-center rounded-[3px] bg-primary">
+          <div className="absolute start-2 top-2 flex h-6 w-6 items-center justify-center rounded-[3px] bg-primary">
             <CheckIcon className="text-sm" />
           </div>
         )}
         {disabled && (
-          <div className="absolute left-2 top-2 bg-black/70 px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-white/80">
+          <div className="absolute start-2 top-2 bg-black/70 px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-white/80">
             Added
           </div>
         )}
@@ -339,7 +342,7 @@ export const GalleryDraggableItem: React.FC<GalleryDraggableItemProps> = ({
           // Tile chrome stays under sticky page headers (e.g. the library
           // filter bar at z-10): z-[2]/z-[1] keep the in-tile ordering
           // (menu/checkbox above badges) without escaping the page layers.
-          className="absolute right-2 top-2 z-[2] opacity-0 group-hover:opacity-100 [@media(pointer:coarse)]:opacity-100 transition-opacity duration-75"
+          className="absolute end-2 top-2 z-[2] opacity-0 group-hover:opacity-100 [@media(pointer:coarse)]:opacity-100 transition-opacity duration-75"
           onPointerDown={(e) => e.stopPropagation()}
           onClick={(e) => {
             e.stopPropagation();
@@ -409,7 +412,7 @@ export const GalleryDraggableItem: React.FC<GalleryDraggableItemProps> = ({
       {mode !== "select" && onBulkSelectToggle && (
         <div
           className={twMerge(
-            "absolute left-2 top-2 z-[2] flex h-5 w-5 items-center justify-center rounded-[3px] border-2 cursor-pointer transition-all duration-100",
+            "absolute start-2 top-2 z-[2] flex h-5 w-5 items-center justify-center rounded-[3px] border-2 cursor-pointer transition-all duration-100",
             bulkSelected
               ? "bg-primary border-primary"
               : "border-white/60 bg-black/40 hover:border-white",
@@ -428,7 +431,7 @@ export const GalleryDraggableItem: React.FC<GalleryDraggableItemProps> = ({
       )}
       {/* Media class badge on hover — bottom-left */}
       {!disableTooltipAndBadge && item.mediaClass && (
-        <div className="pointer-events-none absolute left-2 bottom-2 z-[1] bg-black/50 backdrop-blur-lg px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide text-white opacity-0 group-hover:opacity-100 transition-opacity duration-150">
+        <div className="pointer-events-none absolute start-2 bottom-2 z-[1] bg-black/50 backdrop-blur-lg px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide text-white opacity-0 group-hover:opacity-100 transition-opacity duration-150">
           {item.mediaClass === "dimensional" ||
           item.mediaClass === "mesh" ||
           item.mediaClass === "splat"
@@ -438,7 +441,7 @@ export const GalleryDraggableItem: React.FC<GalleryDraggableItemProps> = ({
       )}
       {/* Upload badge — bottom-right (always visible, even in select mode) */}
       {item.isUpload && (
-        <div className="pointer-events-none absolute right-2 bottom-2 z-[1] flex h-5 w-5 items-center justify-center bg-black/50 backdrop-blur-lg text-white">
+        <div className="pointer-events-none absolute end-2 bottom-2 z-[1] flex h-5 w-5 items-center justify-center bg-black/50 backdrop-blur-lg text-white">
           <UploadIcon className="text-[10px]" />
         </div>
       )}
diff --git a/frontend/libs/components/gallery-modal/src/lib/GalleryFolderChip.tsx b/frontend/libs/components/gallery-modal/src/lib/GalleryFolderChip.tsx
--- a/frontend/libs/components/gallery-modal/src/lib/GalleryFolderChip.tsx
+++ b/frontend/libs/components/gallery-modal/src/lib/GalleryFolderChip.tsx
@@ -117,7 +117,7 @@ export const GalleryFolderChip: React.FC<GalleryFolderChipProps> = ({
       {hasStar && (
         <StarIcon
           
-          className="absolute left-2 top-2 text-sm text-amber-400 drop-shadow-[0_1px_2px_rgba(0,0,0,0.6)]" />
+          className="absolute start-2 top-2 text-sm text-amber-400 drop-shadow-[0_1px_2px_rgba(0,0,0,0.6)]" />
       )}
 
       {/* Name + subfolder count */}
@@ -135,8 +135,9 @@ export const GalleryFolderChip: React.FC<GalleryFolderChipProps> = ({
               style={colorCode ? { color: colorCode } : undefined} />
           )}
           <span
+            dir="auto"
             className={twMerge(
-              "line-clamp-2 break-words text-left text-sm font-medium",
+              "line-clamp-2 break-words text-start text-sm font-medium",
               hasArt
                 ? "text-white drop-shadow-[0_1px_2px_rgba(0,0,0,0.7)]"
                 : "text-base-fg/90",
@@ -148,7 +149,7 @@ export const GalleryFolderChip: React.FC<GalleryFolderChipProps> = ({
         {childCount > 0 && (
           <span
             className={twMerge(
-              "text-left text-[10px]",
+              "text-start text-[10px]",
               hasArt ? "text-white/70" : "text-base-fg/40",
             )}
           >
@@ -168,7 +169,7 @@ export const GalleryFolderChip: React.FC<GalleryFolderChipProps> = ({
             e.stopPropagation();
             openMenuAt(e.currentTarget as HTMLElement);
           }}
-          className="absolute right-1.5 top-1.5 flex h-7 w-7 items-center justify-center rounded-[3px] bg-black/40 text-white opacity-0 transition-opacity hover:bg-black/60 group-hover/chip:opacity-100 [@media(pointer:coarse)]:opacity-100"
+          className="absolute end-1.5 top-1.5 flex h-7 w-7 items-center justify-center rounded-[3px] bg-black/40 text-white opacity-0 transition-opacity hover:bg-black/60 group-hover/chip:opacity-100 [@media(pointer:coarse)]:opacity-100"
         >
           <EllipsisIcon  className="text-sm" />
         </span>
diff --git a/frontend/libs/components/gallery-modal/src/lib/GalleryItemMenuItems.tsx b/frontend/libs/components/gallery-modal/src/lib/GalleryItemMenuItems.tsx
--- a/frontend/libs/components/gallery-modal/src/lib/GalleryItemMenuItems.tsx
+++ b/frontend/libs/components/gallery-modal/src/lib/GalleryItemMenuItems.tsx
@@ -146,7 +146,7 @@ export const GalleryItemMenuItems: React.FC<GalleryItemMenuItemsProps> = ({
                 />
               </div>
             ) : (
-              <div className="absolute left-full top-0 -ml-1 pl-2 z-50">
+              <div className="absolute start-full top-0 -ms-1 ps-2 z-50">
                 <div className="max-h-64 overflow-y-auto w-max min-w-36 rounded-[3px] border border-ui-panel-border bg-ui-panel p-1 shadow-xl">
                   <FolderList
                     item={item}
diff --git a/frontend/libs/components/gallery-modal/src/lib/gallery-modal.tsx b/frontend/libs/components/gallery-modal/src/lib/gallery-modal.tsx
--- a/frontend/libs/components/gallery-modal/src/lib/gallery-modal.tsx
+++ b/frontend/libs/components/gallery-modal/src/lib/gallery-modal.tsx
@@ -438,6 +438,7 @@ function TagCloudView({
       <div className="flex max-w-3xl flex-wrap items-baseline justify-center gap-x-5 gap-y-3 select-none">
         {words.map(({ tag, weight }) => (
           <button
+            dir="auto"
             key={tag.tag_token}
             type="button"
             onClick={() => onOpen(tag.tag_token)}
@@ -2593,7 +2594,7 @@ export const GalleryModal = React.memo(
         >
           {mode === "view" && (
             <Modal.DragHandle>
-              <div className="absolute left-0 top-0 z-[50] h-[60px] w-full cursor-move" />
+              <div className="absolute start-0 top-0 z-[50] h-[60px] w-full cursor-move" />
             </Modal.DragHandle>
           )}
           <div className="relative flex h-full flex-col">
@@ -2643,7 +2644,7 @@ export const GalleryModal = React.memo(
                       </button>
                       <span className="text-base-fg/30">/</span>
                       <TagIcon className="text-sm text-violet-400" />
-                      <h2 className="text-lg font-semibold truncate max-w-[16rem]">
+                      <h2 dir="auto" className="text-lg font-semibold truncate max-w-[16rem]">
                         {activeTag.tag_value}
                       </h2>
                       <span className="text-base-fg/40 text-sm">
@@ -2688,6 +2689,7 @@ export const GalleryModal = React.memo(
                         <React.Fragment key={crumb.id}>
                           <span className="text-base-fg/30">/</span>
                           <button
+                            dir="auto"
                             type="button"
                             onClick={() => handleOpenFolder(crumb.id)}
                             className="text-base-fg/50 hover:text-base-fg text-sm transition-colors truncate max-w-[10rem]"
@@ -2714,6 +2716,7 @@ export const GalleryModal = React.memo(
                         />
                       ) : (
                         <h2
+                          dir="auto"
                           className="text-lg font-semibold cursor-pointer rounded-[3px] hover:bg-ui-controls/30 px-1.5 py-0 transition-colors truncate max-w-[20rem]"
                           onClick={() => handleStartRename(activeFolderId!)}
                         >
@@ -2743,7 +2746,7 @@ export const GalleryModal = React.memo(
                               onClick={() => setFolderMenuOpen(false)}
                             />
                             <FolderContextMenuItems
-                              className="absolute left-0 top-full mt-1"
+                              className="absolute start-0 top-full mt-1"
                               folderId={activeFolderId!}
                               hasStar={activeFolder?.hasStar}
                               colorCode={activeFolder?.colorCode}
@@ -2819,7 +2822,7 @@ export const GalleryModal = React.memo(
                       </div>
                     )}
                 </div>
-                <div className="flex justify-end gap-2 items-center flex-wrap ml-auto">
+                <div className="flex justify-end gap-2 items-center flex-wrap ms-auto">
                   {/* Refresh button */}
                   <Tooltip
                     position="top"
@@ -2899,7 +2902,7 @@ export const GalleryModal = React.memo(
               {/* ── Filter sidebar ── shown on both tabs; clicking a filter
                   jumps back to the library, clicking a folder into the browser */}
               {!hideFilter && (
-                <div className="w-52 min-w-[13rem] border-r border-ui-panel-border bg-ui-panel flex flex-col overflow-hidden">
+                <div className="w-52 min-w-[13rem] border-e border-ui-panel-border bg-ui-panel flex flex-col overflow-hidden">
                   {/* Filter items — pinned; they never scroll out of view */}
                   <div className="flex flex-col px-1.5 pt-2 pb-1 flex-shrink-0">
                     {SIDEBAR_FILTERS.map((f) => {
@@ -3001,7 +3004,7 @@ export const GalleryModal = React.memo(
                           />
                           <span className="truncate">{folder.name}</span>
                           {folder.hasStar && (
-                            <StarIcon className="ml-auto text-[10px] text-amber-400" />
+                            <StarIcon className="ms-auto text-[10px] text-amber-400" />
                           )}
                         </button>
                       ))
@@ -3033,7 +3036,7 @@ export const GalleryModal = React.memo(
                           >
                             <TagIcon className="text-xs w-4 text-violet-400" />
                             <span className="truncate">{tag.tag_value}</span>
-                            <span className="ml-auto text-[10px] text-base-fg/40">
+                            <span className="ms-auto text-[10px] text-base-fg/40">
                               {tag.use_count}
                             </span>
                           </button>
@@ -3050,7 +3053,7 @@ export const GalleryModal = React.memo(
                         >
                           <EllipsisIcon className="text-xs w-4" />
                           <span>All tags</span>
-                          <span className="ml-auto text-[10px] text-base-fg/40">
+                          <span className="ms-auto text-[10px] text-base-fg/40">
                             {sortedTags.length}
                           </span>
                         </button>
@@ -3576,7 +3579,7 @@ export const GalleryModal = React.memo(
                           className="fixed inset-0 z-[59]"
                           onClick={() => setBulkFolderPopoverOpen(false)}
                         />
-                        <div className="absolute bottom-full mb-2 right-0 w-56 rounded-[3px] border border-ui-panel-border bg-ui-panel p-2 shadow-xl z-[60]">
+                        <div className="absolute bottom-full mb-2 end-0 w-56 rounded-[3px] border border-ui-panel-border bg-ui-panel p-2 shadow-xl z-[60]">
                           {/* Folders */}
                           <div className="text-[11px] font-semibold uppercase tracking-wider text-base-fg/40 px-2 py-1">
                             Folders
diff --git a/frontend/libs/components/generation-list/src/lib/FailedRow.tsx b/frontend/libs/components/generation-list/src/lib/FailedRow.tsx
--- a/frontend/libs/components/generation-list/src/lib/FailedRow.tsx
+++ b/frontend/libs/components/generation-list/src/lib/FailedRow.tsx
@@ -61,7 +61,9 @@ export const FailedRow = memo(function FailedRow({
           {recreateSlot}
         </div>
         {prompt && (
-          <p className="mt-0.5 truncate text-xs text-white/45">{prompt}</p>
+          <p dir="auto" className="mt-0.5 truncate text-xs text-white/45">
+            {prompt}
+          </p>
         )}
         <div className="mt-1 flex items-center gap-1.5 text-xs text-white/40">
           {iconPath && (
@@ -71,7 +73,9 @@ export const FailedRow = memo(function FailedRow({
               className="h-3 w-3 shrink-0 icon-auto-contrast"
             />
           )}
-          <span className="truncate">{modelLabel}</span>
+          <span dir="auto" className="truncate">
+            {modelLabel}
+          </span>
           {failureMessage && failureMessage !== failureReason && (
             <>
               <span className="text-white/20">·</span>
diff --git a/frontend/libs/components/generation-list/src/lib/GalleryCard.tsx b/frontend/libs/components/generation-list/src/lib/GalleryCard.tsx
--- a/frontend/libs/components/generation-list/src/lib/GalleryCard.tsx
+++ b/frontend/libs/components/generation-list/src/lib/GalleryCard.tsx
@@ -189,7 +189,10 @@ export const GalleryCard = memo(function GalleryCard({
       >
         {isAudio ? (
           <div className="flex h-full flex-col bg-gradient-to-br from-white/[0.08] via-white/[0.04] to-white/[0.02] p-3">
-            <p className="line-clamp-2 shrink-0 text-sm leading-snug text-white/85">
+            <p
+              dir="auto"
+              className="line-clamp-2 shrink-0 text-sm leading-snug text-white/85"
+            >
               {title || item.label}
             </p>
             <div className="flex min-h-0 flex-1 items-center justify-center">
@@ -221,7 +224,7 @@ export const GalleryCard = memo(function GalleryCard({
       {/* Selection checkbox chip (select mode only) */}
       {selectMode && item.fullImage && (
         <div
-          className={`pointer-events-none absolute left-2 top-2 z-10 flex h-5 w-5 items-center justify-center rounded-[3px] border transition-colors ${
+          className={`pointer-events-none absolute start-2 top-2 z-10 flex h-5 w-5 items-center justify-center rounded-[3px] border transition-colors ${
             selected
               ? "border-primary-400 bg-primary-400 text-white"
               : "border-white/60 bg-black/40 text-transparent"
@@ -235,7 +238,7 @@ export const GalleryCard = memo(function GalleryCard({
           in the lightbox). Top-right so it never collides with the selection
           checkbox chip at top-left. */}
       {lastViewed && (
-        <div className="pointer-events-none absolute right-2 top-2 z-10 flex items-center gap-1 rounded-[3px] bg-black/70 px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-white/80">
+        <div className="pointer-events-none absolute end-2 top-2 z-10 flex items-center gap-1 rounded-[3px] bg-black/70 px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-white/80">
           <EyeIcon />
           Last viewed
         </div>
diff --git a/frontend/libs/components/generation-list/src/lib/GalleryRow.tsx b/frontend/libs/components/generation-list/src/lib/GalleryRow.tsx
--- a/frontend/libs/components/generation-list/src/lib/GalleryRow.tsx
+++ b/frontend/libs/components/generation-list/src/lib/GalleryRow.tsx
@@ -133,14 +133,14 @@ export const GalleryRow = memo(function GalleryRow({
         {isVideo && item.thumbnail && (
           <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
             <span className="flex h-6 w-6 items-center justify-center rounded-full bg-black/55 backdrop-blur-sm">
-              <PlayIcon className="ml-0.5 text-[9px] text-white/90" />
+              <PlayIcon className="ms-0.5 text-[9px] text-white/90" />
             </span>
           </div>
         )}
         {/* Persistent "Last viewed" badge (stays until another item is
             opened in the lightbox). */}
         {lastViewed && (
-          <div className="pointer-events-none absolute left-1 top-1 z-10 flex items-center gap-1 rounded-full bg-black/70 px-1.5 py-0.5 text-[9px] font-medium uppercase tracking-wide text-white/80">
+          <div className="pointer-events-none absolute start-1 top-1 z-10 flex items-center gap-1 rounded-full bg-black/70 px-1.5 py-0.5 text-[9px] font-medium uppercase tracking-wide text-white/80">
             <EyeIcon />
             Last viewed
           </div>
@@ -155,7 +155,7 @@ export const GalleryRow = memo(function GalleryRow({
           </div>
         ) : (
           <div className="flex items-start gap-2">
-            <p className="line-clamp-3 min-w-0 flex-1 text-sm leading-snug text-white/90">
+            <p dir="auto" className="line-clamp-3 min-w-0 flex-1 text-sm leading-snug text-white/90">
               {title || item.label}
             </p>
             {title && (
@@ -183,7 +183,7 @@ export const GalleryRow = memo(function GalleryRow({
               className="h-3 w-3 shrink-0 icon-auto-contrast"
             />
           )}
-          <span className="max-w-[40vw] truncate sm:max-w-none">
+          <span dir="auto" className="max-w-[40vw] truncate sm:max-w-none">
             {modelDisplayName ?? mediaLabel}
           </span>
           {modelDisplayName && (
@@ -201,7 +201,7 @@ export const GalleryRow = memo(function GalleryRow({
               {actionsSlot}
             </div>
           )}
-          <span className="ml-auto shrink-0 whitespace-nowrap text-white/40">
+          <span className="ms-auto shrink-0 whitespace-nowrap text-white/40">
             {timeAgo}
           </span>
         </div>
diff --git a/frontend/libs/components/generation-list/src/lib/GenerationGridView.tsx b/frontend/libs/components/generation-list/src/lib/GenerationGridView.tsx
--- a/frontend/libs/components/generation-list/src/lib/GenerationGridView.tsx
+++ b/frontend/libs/components/generation-list/src/lib/GenerationGridView.tsx
@@ -23,9 +23,9 @@ const BREAKPOINT_COLS = {
 };
 
 // 12px gap on both axes (≈ Tailwind gap-3).
-// ml-[-12px] on container offsets the first column's pl-[8px].
-const MASONRY_CLASS = "flex w-auto ml-[-12px]";
-const COLUMN_CLASS = "pl-[8px]";
+// ms-[-12px] on container offsets the first column's ps-[8px].
+const MASONRY_CLASS = "flex w-auto ms-[-12px]";
+const COLUMN_CLASS = "ps-[8px]";
 
 // ── Component ──────────────────────────────────────────────────────────────
 
diff --git a/frontend/libs/components/generation-list/src/lib/PendingCard.tsx b/frontend/libs/components/generation-list/src/lib/PendingCard.tsx
--- a/frontend/libs/components/generation-list/src/lib/PendingCard.tsx
+++ b/frontend/libs/components/generation-list/src/lib/PendingCard.tsx
@@ -39,7 +39,7 @@ export const PendingCard = memo(function PendingCard({
     <div className="group relative aspect-square w-full overflow-hidden rounded-[3px] bg-white/[0.03]">
       <div className="animate-shimmer h-full w-full" />
       {batchCount != null && batchCount > 1 && (
-        <div className="absolute left-2 right-2 top-2 z-10 bg-black/60 px-2.5 py-1.5 text-center text-[10px] leading-snug text-white/70 backdrop-blur-sm">
+        <div className="absolute start-2 end-2 top-2 z-10 bg-black/60 px-2.5 py-1.5 text-center text-[10px] leading-snug text-white/70 backdrop-blur-sm">
           Generating {batchCount} {batchNoun(mediaClass)} · Results may appear
           one at a time
         </div>
@@ -57,7 +57,10 @@ export const PendingCard = memo(function PendingCard({
       </div>
       <div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/70 via-black/40 to-transparent px-3 pb-2.5 pt-8">
         <div className="flex items-start gap-2">
-          <p className="line-clamp-3 min-w-0 flex-1 text-xs leading-relaxed text-white/80">
+          <p
+            dir="auto"
+            className="line-clamp-3 min-w-0 flex-1 text-xs leading-relaxed text-white/80"
+          >
             {prompt}
           </p>
           {recreateSlot}
diff --git a/frontend/libs/components/generation-list/src/lib/PendingRow.tsx b/frontend/libs/components/generation-list/src/lib/PendingRow.tsx
--- a/frontend/libs/components/generation-list/src/lib/PendingRow.tsx
+++ b/frontend/libs/components/generation-list/src/lib/PendingRow.tsx
@@ -53,7 +53,7 @@ export const PendingRow = memo(function PendingRow({
       <div className="min-w-0 flex-1">
         <div className="flex items-start gap-2">
           {prompt.trim() ? (
-            <p className="line-clamp-3 min-w-0 flex-1 text-sm leading-snug text-white/90">
+            <p dir="auto" className="line-clamp-3 min-w-0 flex-1 text-sm leading-snug text-white/90">
               {prompt}
             </p>
           ) : (
diff --git a/frontend/libs/components/help-menu/src/lib/help-menu.tsx b/frontend/libs/components/help-menu/src/lib/help-menu.tsx
--- a/frontend/libs/components/help-menu/src/lib/help-menu.tsx
+++ b/frontend/libs/components/help-menu/src/lib/help-menu.tsx
@@ -57,7 +57,7 @@ const NewsView = ({ onBack }: { onBack: () => void }) => {
 
   if (selectedPost) {
     return (
-      <div className="flex flex-col h-[500px] overflow-y-auto pr-2 custom-scrollbar">
+      <div className="flex flex-col h-[500px] overflow-y-auto pe-2 custom-scrollbar">
         <button
           onClick={() => setSelectedSlug(null)}
           className="self-start mb-4 text-sm flex items-center gap-2 text-white/60 hover:text-white transition-colors"
@@ -106,7 +106,7 @@ const NewsView = ({ onBack }: { onBack: () => void }) => {
   }
 
   return (
-    <div className="flex flex-col gap-4 mt-2 h-[500px] overflow-y-auto pr-2 custom-scrollbar">
+    <div className="flex flex-col gap-4 mt-2 h-[500px] overflow-y-auto pe-2 custom-scrollbar">
       {items.map((item) => (
         <div
           key={item.slug}
@@ -122,7 +122,7 @@ const NewsView = ({ onBack }: { onBack: () => void }) => {
                 className="opacity-0 -translate-x-2 group-hover:opacity-100 group-hover:translate-x-0 transition-all text-white" />
             </h3>
             {item.date && (
-              <span className="font-mono text-[11px] uppercase tracking-[0.12em] text-white/40 whitespace-nowrap ml-2">
+              <span className="font-mono text-[11px] uppercase tracking-[0.12em] text-white/40 whitespace-nowrap ms-2">
                 {item.date}
               </span>
             )}
@@ -372,7 +372,7 @@ export function HelpMenuButton({
                     key={item.id}
                     type="button"
                     onClick={() => viewTutorial(item)}
-                    className="group block overflow-hidden rounded-none border border-white/15 bg-white/5 transition-colors hover:border-white/25 hover:bg-white/[0.07] text-left"
+                    className="group block overflow-hidden rounded-none border border-white/15 bg-white/5 transition-colors hover:border-white/25 hover:bg-white/[0.07] text-start"
                   >
                     <div className="aspect-video w-full overflow-hidden">
                       <img
diff --git a/frontend/libs/components/input/src/lib/input.tsx b/frontend/libs/components/input/src/lib/input.tsx
--- a/frontend/libs/components/input/src/lib/input.tsx
+++ b/frontend/libs/components/input/src/lib/input.tsx
@@ -38,7 +38,7 @@ export const Input = React.forwardRef(
           {icon && (
             <DynamicIcon
               icon={icon}
-              className={twMerge("text-md absolute pl-3 pt-3", iconClassName)}
+              className={twMerge("text-md absolute ps-3 pt-3", iconClassName)}
             />
           )}
           <input
@@ -49,7 +49,7 @@ export const Input = React.forwardRef(
               "bg-ui-panel text-base-fg placeholder-base-fg/40",
               "border border-ui-panel-border transition-colors duration-150 ease-in-out hover:border-white/40 focus:border-white focus:!outline-none",
               "disabled:cursor-not-allowed disabled:opacity-60 disabled:hover:border-ui-panel-border",
-              icon && "pl-10",
+              icon && "ps-10",
               isError && "border-red-500 focus:border-red-500",
               inputClassName,
             )}
diff --git a/frontend/libs/components/keybinds/src/lib/settings/KeybindsSettings.tsx b/frontend/libs/components/keybinds/src/lib/settings/KeybindsSettings.tsx
--- a/frontend/libs/components/keybinds/src/lib/settings/KeybindsSettings.tsx
+++ b/frontend/libs/components/keybinds/src/lib/settings/KeybindsSettings.tsx
@@ -357,7 +357,7 @@ function PresetDropdown({
         aria-haspopup="listbox"
         aria-expanded={open}
         className={twMerge(
-          "flex w-full items-center gap-3 rounded-none bg-white/[0.04] p-1.5 text-left ring-1 ring-white/15 transition-all hover:bg-white/[0.06] active:scale-[0.995]",
+          "flex w-full items-center gap-3 rounded-none bg-white/[0.04] p-1.5 text-start ring-1 ring-white/15 transition-all hover:bg-white/[0.06] active:scale-[0.995]",
           EASE,
           open && "ring-primary/40",
         )}
@@ -370,7 +370,7 @@ function PresetDropdown({
         </div>
         <ChevronIcon
           className={twMerge(
-            "mr-3 h-4 w-4 shrink-0 text-base-fg/40 transition-transform duration-300",
+            "me-3 h-4 w-4 shrink-0 text-base-fg/40 transition-transform duration-300",
             EASE,
             open ? "-rotate-90" : "rotate-90",
           )}
@@ -381,7 +381,7 @@ function PresetDropdown({
         role="listbox"
         aria-label="Preset"
         className={twMerge(
-          "absolute left-0 right-0 top-full z-20 mt-2 origin-top rounded-none bg-ui-modal shadow-[0_20px_50px_-16px_rgba(0,0,0,0.7)] ring-1 ring-white/10 transition-all duration-200 overflow-hidden",
+          "absolute start-0 end-0 top-full z-20 mt-2 origin-top rounded-none bg-ui-modal shadow-[0_20px_50px_-16px_rgba(0,0,0,0.7)] ring-1 ring-white/10 transition-all duration-200 overflow-hidden",
           EASE,
           open
             ? "scale-100 opacity-100"
@@ -402,7 +402,7 @@ function PresetDropdown({
                 setOpen(false);
               }}
               className={twMerge(
-                "flex w-full items-start gap-3 px-3 py-2.5 text-left transition-colors",
+                "flex w-full items-start gap-3 px-3 py-2.5 text-start transition-colors",
                 EASE,
                 selected ? "bg-primary/[0.08]" : "hover:bg-white/[0.04]",
               )}
@@ -494,7 +494,7 @@ function NameSearchField({
         "focus-within:bg-ui-modal/80 focus-within:ring-primary/50",
       )}
     >
-      <SearchIcon className="ml-3 h-4 w-4 shrink-0 text-base-fg/35 transition-colors group-focus-within:text-primary" />
+      <SearchIcon className="ms-3 h-4 w-4 shrink-0 text-base-fg/35 transition-colors group-focus-within:text-primary" />
       <input
         type="search"
         value={value}
@@ -508,7 +508,7 @@ function NameSearchField({
           onClick={() => onChange("")}
           aria-label="Clear search"
           className={twMerge(
-            "mr-1.5 grid h-7 w-7 shrink-0 place-items-center rounded-[3px] text-base-fg/40 transition-all hover:bg-white/10 hover:text-base-fg active:scale-90",
+            "me-1.5 grid h-7 w-7 shrink-0 place-items-center rounded-[3px] text-base-fg/40 transition-all hover:bg-white/10 hover:text-base-fg active:scale-90",
             EASE,
           )}
         >
@@ -541,7 +541,7 @@ function KeySearchField({
       <button
         type="button"
         onClick={onArm}
-        className="flex h-10 grow items-center gap-2 px-3 text-left"
+        className="flex h-10 grow items-center gap-2 px-3 text-start"
       >
         <KeyIcon
           className={twMerge(
@@ -570,7 +570,7 @@ function KeySearchField({
           onClick={onClear}
           aria-label="Clear key filter"
           className={twMerge(
-            "mr-1.5 grid h-7 w-7 shrink-0 place-items-center rounded-[3px] text-base-fg/40 transition-all hover:bg-white/10 hover:text-base-fg active:scale-90",
+            "me-1.5 grid h-7 w-7 shrink-0 place-items-center rounded-[3px] text-base-fg/40 transition-all hover:bg-white/10 hover:text-base-fg active:scale-90",
             EASE,
           )}
         >
@@ -610,14 +610,14 @@ function CollapsibleSurface({
         {/* The toggle and the (destructive) section reset are sibling buttons —
             a nested button is invalid HTML and a mis-click on "reset" must
             never also collapse the section. */}
-        <div className="flex w-full items-center gap-3 pr-4">
+        <div className="flex w-full items-center gap-3 pe-4">
           <button
             type="button"
             onClick={locked ? undefined : onToggle}
             aria-expanded={open}
             disabled={locked}
             className={twMerge(
-              "flex min-w-0 grow items-center gap-3 py-3 pl-4 text-left transition-colors",
+              "flex min-w-0 grow items-center gap-3 py-3 ps-4 text-start transition-colors",
               EASE,
               locked ? "cursor-default" : "hover:bg-white/[0.03]",
             )}
@@ -796,7 +796,7 @@ function TimedFill({
     <span
       aria-hidden="true"
       className={twMerge(
-        "pointer-events-none absolute inset-0 origin-left",
+        "pointer-events-none absolute inset-0 origin-left rtl:origin-right",
         className,
       )}
       style={{
diff --git a/frontend/libs/components/label/src/lib/label.tsx b/frontend/libs/components/label/src/lib/label.tsx
--- a/frontend/libs/components/label/src/lib/label.tsx
+++ b/frontend/libs/components/label/src/lib/label.tsx
@@ -21,7 +21,7 @@ export const Label = ({
     {...rest}
   >
     {children}
-    {required && <span className="ml-0.5 text-red-400">*</span>}
+    {required && <span className="ms-0.5 text-red-400">*</span>}
   </label>
 );
 
diff --git a/frontend/libs/components/lightbox-modal/src/lib/lightbox-modal.tsx b/frontend/libs/components/lightbox-modal/src/lib/lightbox-modal.tsx
--- a/frontend/libs/components/lightbox-modal/src/lib/lightbox-modal.tsx
+++ b/frontend/libs/components/lightbox-modal/src/lib/lightbox-modal.tsx
@@ -555,7 +555,7 @@ export function LightboxModal({
       >
         {/* Invisible drag handle strip at the very top for moving */}
         <Modal.DragHandle>
-          <div className="absolute left-0 top-0 z-20 h-12 w-full cursor-move" />
+          <div className="absolute start-0 top-0 z-20 h-12 w-full cursor-move" />
         </Modal.DragHandle>
 
         {/* content grid */}
@@ -737,7 +737,7 @@ export function LightboxModal({
           <div className="flex h-full w-[280px] shrink-0 flex-col">
             <div className="flex-1 overflow-y-auto space-y-5 text-base-fg min-h-0 pb-2">
               {creator ? (
-                <div className="sticky top-0 z-10 flex items-center gap-3 bg-ui-modal pb-3 pr-20 border-b border-white/10">
+                <div className="sticky top-0 z-10 flex items-center gap-3 bg-ui-modal pb-3 pe-20 border-b border-white/10">
                   {creator.core_info ? (
                     <Gravatar
                       size={36}
@@ -755,7 +755,7 @@ export function LightboxModal({
                     </div>
                   )}
                   <div className="flex flex-col gap-1 min-w-0">
-                    <span className="text-base-fg text-sm font-semibold leading-none truncate">
+                    <span dir="auto" className="text-base-fg text-sm font-semibold leading-none truncate">
                       {creator.display_name}
                     </span>
                     <span className="font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-base-fg/60">
@@ -764,7 +764,7 @@ export function LightboxModal({
                   </div>
                 </div>
               ) : promptLoading ? (
-                <div className="sticky top-0 z-10 flex items-center gap-3 bg-ui-modal pb-3 pr-20 border-b border-white/10 animate-pulse">
+                <div className="sticky top-0 z-10 flex items-center gap-3 bg-ui-modal pb-3 pe-20 border-b border-white/10 animate-pulse">
                   <div className="h-9 w-9 shrink-0 bg-white/10" />
                   <div className="flex flex-col gap-1.5 min-w-0 flex-1">
                     <div className="h-3.5 w-24 bg-white/10" />
@@ -826,6 +826,7 @@ export function LightboxModal({
                       ) : (
                         <div
                           ref={promptRef}
+                          dir="auto"
                           className={twMerge(
                             !isPromptExpanded && "line-clamp-4",
                           )}
@@ -930,7 +931,7 @@ export function LightboxModal({
                                     <div className="flex flex-col gap-1.5 min-w-[100px]">
                                       {!isAudio && !isVideoRef && (
                                         <button
-                                          className="text-xs text-left text-base-fg/80 hover:text-base-fg transition-colors py-1 px-1 rounded-[3px] hover:bg-white/5"
+                                          className="text-xs text-start text-base-fg/80 hover:text-base-fg transition-colors py-1 px-1 rounded-[3px] hover:bg-white/5"
                                           onClick={(e) => {
                                             e.stopPropagation();
                                             setRefPreviewUrl(fullSize);
@@ -938,13 +939,13 @@ export function LightboxModal({
                                         >
                                           <SearchIcon
                                             
-                                            className="mr-1.5" />
+                                            className="me-1.5" />
                                           Preview image
                                         </button>
                                       )}
                                       {onNavigateToMedia && (
                                         <button
-                                          className="text-xs text-left text-base-fg/80 hover:text-base-fg transition-colors py-1 px-1 rounded-[3px] hover:bg-white/5"
+                                          className="text-xs text-start text-base-fg/80 hover:text-base-fg transition-colors py-1 px-1 rounded-[3px] hover:bg-white/5"
                                           onClick={(e) => {
                                             e.stopPropagation();
                                             onNavigateToMedia(
@@ -954,7 +955,7 @@ export function LightboxModal({
                                         >
                                           <LogOutIcon
                                             
-                                            className="mr-1.5" />
+                                            className="me-1.5" />
                                           View as media
                                         </button>
                                       )}
@@ -1266,7 +1267,7 @@ export function LightboxModal({
           expandable={false}
         >
           <Modal.DragHandle>
-            <div className="absolute left-0 top-0 z-20 h-12 w-full cursor-move" />
+            <div className="absolute start-0 top-0 z-20 h-12 w-full cursor-move" />
           </Modal.DragHandle>
           <div className="relative flex items-center justify-center overflow-hidden bg-black">
             <img
@@ -1333,7 +1334,7 @@ function InfoSection({
                   alt="Model creator logo"
                   className="h-4 w-4 invert"
                 />
-                <span>{getModelDisplayName(modelType)}</span>
+                <span dir="auto">{getModelDisplayName(modelType)}</span>
               </>
             }
           />
@@ -1384,7 +1385,7 @@ function InfoRow({ label, value }: { label: string; value: ReactNode }) {
       <span className="shrink-0 pt-0.5 font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-base-fg/60">
         {label}
       </span>
-      <span className="min-w-0 text-right text-sm text-base-fg font-medium flex items-center justify-end gap-2">
+      <span className="min-w-0 text-end text-sm text-base-fg font-medium flex items-center justify-end gap-2">
         {value}
       </span>
     </div>
diff --git a/frontend/libs/components/lightbox-modal/src/lib/tags/TagChipInput.tsx b/frontend/libs/components/lightbox-modal/src/lib/tags/TagChipInput.tsx
--- a/frontend/libs/components/lightbox-modal/src/lib/tags/TagChipInput.tsx
+++ b/frontend/libs/components/lightbox-modal/src/lib/tags/TagChipInput.tsx
@@ -134,7 +134,7 @@ export function TagChipInput({
   };
 
   const chipClass = (value: string) =>
-    `flex items-center gap-1 break-all text-left rounded-[3px] border border-white/15 bg-white/5 px-2.5 py-1 text-[11px] font-medium text-base-fg/80 transition-all ${
+    `flex items-center gap-1 break-all text-start rounded-[3px] border border-white/15 bg-white/5 px-2.5 py-1 text-[11px] font-medium text-base-fg/80 transition-all ${
       flashValue === value ? "ring-2 ring-primary" : ""
     }`;
 
@@ -150,7 +150,7 @@ export function TagChipInput({
           disabled ? (
             <span key={value.toLowerCase()} className={chipClass(value)}>
               <TagIcon  className="h-2.5 w-2.5 text-base-fg/40" />
-              {value}
+              <bdi>{value}</bdi>
             </span>
           ) : (
             <button
@@ -163,7 +163,7 @@ export function TagChipInput({
               title={`Remove "${value}"`}
               className={`group/tag ${chipClass(value)} hover:bg-red/15 hover:text-red focus:outline-none focus-visible:ring-2 focus-visible:ring-red`}
             >
-              {value}
+              <bdi>{value}</bdi>
               <XIcon  className="h-2.5 w-2.5" />
             </button>
           ),
@@ -171,6 +171,7 @@ export function TagChipInput({
         {!disabled && (
           <input
             ref={inputRef}
+            dir="auto"
             value={draft}
             onChange={(e) => {
               setDraft(e.target.value);
@@ -192,7 +193,7 @@ export function TagChipInput({
 
       {dropdownOpen && (
         <div
-          className={`absolute left-0 right-0 z-30 max-h-48 overflow-y-auto rounded-none border border-ui-panel-border bg-ui-panel p-1 shadow-xl ${
+          className={`absolute start-0 end-0 z-30 max-h-48 overflow-y-auto rounded-none border border-ui-panel-border bg-ui-panel p-1 shadow-xl ${
             dropUp ? "bottom-full mb-1" : "top-full mt-1"
           }`}
         >
@@ -209,8 +210,8 @@ export function TagChipInput({
               }`}
             >
               <TagIcon  className="text-[10px] text-base-fg/40" />
-              <span className="truncate">{suggestion.value}</span>
-              <span className="ml-auto text-[11px] text-base-fg/40">
+              <span dir="auto" className="truncate">{suggestion.value}</span>
+              <span className="ms-auto text-[11px] text-base-fg/40">
                 {suggestion.useCount}
               </span>
             </button>
diff --git a/frontend/libs/components/list-dropdown/src/lib/ListDropdown.tsx b/frontend/libs/components/list-dropdown/src/lib/ListDropdown.tsx
--- a/frontend/libs/components/list-dropdown/src/lib/ListDropdown.tsx
+++ b/frontend/libs/components/list-dropdown/src/lib/ListDropdown.tsx
@@ -16,9 +16,9 @@ export const ListDropdown = ({ list, onSelect }: ListDropdownProps) => {
   return (
     <Listbox value={selected} onChange={setSelected}>
       <div className="relative mt-1">
-        <Listbox.Button className="relative h-10 w-full cursor-pointer rounded-[3px] border border-ui-controls-border bg-ui-controls py-2 pl-3 pr-10 text-left text-base-fg outline-none outline-offset-0 transition-colors duration-150 ease-in-out hover:border-white/40 focus:!outline-none sm:text-sm">
+        <Listbox.Button className="relative h-10 w-full cursor-pointer rounded-[3px] border border-ui-controls-border bg-ui-controls py-2 ps-3 pe-10 text-start text-base-fg outline-none outline-offset-0 transition-colors duration-150 ease-in-out hover:border-white/40 focus:!outline-none sm:text-sm">
           <span className="block truncate">{Object.keys(selected)[0]}</span>
-          <span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
+          <span className="pointer-events-none absolute inset-y-0 end-0 flex items-center pe-2">
             <ChevronDownIcon  aria-hidden="true" />
           </span>
         </Listbox.Button>
@@ -33,7 +33,7 @@ export const ListDropdown = ({ list, onSelect }: ListDropdownProps) => {
               <Listbox.Option
                 key={itemIdx}
                 className={({ active, selected }) =>
-                  `relative cursor-pointer select-none py-2 pl-10 pr-4 transition-colors duration-150 ${
+                  `relative cursor-pointer select-none py-2 ps-10 pe-4 transition-colors duration-150 ${
                     active
                       ? "bg-white text-black"
                       : selected
@@ -52,7 +52,7 @@ export const ListDropdown = ({ list, onSelect }: ListDropdownProps) => {
                       {Object.values(item)[0]}
                     </span>
                     {selected ? (
-                      <span className="absolute inset-y-0 left-0 flex items-center pl-3">
+                      <span className="absolute inset-y-0 start-0 flex items-center ps-3">
                         <CheckIcon  aria-hidden="true" />
                       </span>
                     ) : null}
diff --git a/frontend/libs/components/login-modal/src/lib/DesktopLoginBridge.tsx b/frontend/libs/components/login-modal/src/lib/DesktopLoginBridge.tsx
--- a/frontend/libs/components/login-modal/src/lib/DesktopLoginBridge.tsx
+++ b/frontend/libs/components/login-modal/src/lib/DesktopLoginBridge.tsx
@@ -113,7 +113,7 @@ export function DesktopLoginBridge({ onSuccess, onStart, onActiveChange }: { onS
 
   const remaining = challenge ? Math.max(0, Math.ceil((Date.parse(challenge.expires_at) - now) / 1000)) : 0;
   return <section aria-label="Website login" className="mb-6 border border-white/15 bg-white/[0.03] p-4 text-center">
-    {isActive && <div className="mb-4 text-left">
+    {isActive && <div className="mb-4 text-start">
       <button type="button" className="inline-flex items-center gap-2 font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-white/60 transition-colors hover:text-white" onClick={back}>
         <ArrowLeftIcon size={16} aria-hidden="true" /> Back
       </button>
diff --git a/frontend/libs/components/login-modal/src/lib/artcraft-signup.tsx b/frontend/libs/components/login-modal/src/lib/artcraft-signup.tsx
--- a/frontend/libs/components/login-modal/src/lib/artcraft-signup.tsx
+++ b/frontend/libs/components/login-modal/src/lib/artcraft-signup.tsx
@@ -132,12 +132,12 @@ export const ArtCraftSignUp = ({
             placeholder="Min. 8 characters"
             required
             autoComplete="off"
-            inputClassName={`${FIELD_INPUT} pr-12`}
+            inputClassName={`${FIELD_INPUT} pe-12`}
           />
           <button
             type="button"
             onClick={() => setShowPassword((v) => !v)}
-            className="absolute right-4 top-1/2 -translate-y-1/2 text-white/30 transition-colors hover:text-white/60"
+            className="absolute end-4 top-1/2 -translate-y-1/2 text-white/30 transition-colors hover:text-white/60"
             tabIndex={-1}
           >
             <DynamicIcon icon={showPassword ? EyeOffIcon : EyeIcon} />
@@ -155,12 +155,12 @@ export const ArtCraftSignUp = ({
               placeholder="Re-enter password"
               required
               autoComplete="off"
-              inputClassName={`${FIELD_INPUT} pr-12`}
+              inputClassName={`${FIELD_INPUT} pe-12`}
             />
             <button
               type="button"
               onClick={() => setShowConfirm((v) => !v)}
-              className="absolute right-4 top-1/2 -translate-y-1/2 text-white/30 transition-colors hover:text-white/60"
+              className="absolute end-4 top-1/2 -translate-y-1/2 text-white/30 transition-colors hover:text-white/60"
               tabIndex={-1}
             >
               <DynamicIcon icon={showConfirm ? EyeOffIcon : EyeIcon} />
diff --git a/frontend/libs/components/login-modal/src/lib/login-modal.tsx b/frontend/libs/components/login-modal/src/lib/login-modal.tsx
--- a/frontend/libs/components/login-modal/src/lib/login-modal.tsx
+++ b/frontend/libs/components/login-modal/src/lib/login-modal.tsx
@@ -15,10 +15,10 @@ const DEFAULT_SHOWCASE_VIDEO =
   "https://frontend-cdn.fakeyou.com/videos/knight-video.mp4";
 const LOGIN_SUCCESS_DURATION_MS = 3000;
 const FRAME_CORNERS = [
-  "top-0 left-0",
-  "top-0 right-0",
-  "bottom-0 left-0",
-  "bottom-0 right-0",
+  "top-0 start-0",
+  "top-0 end-0",
+  "bottom-0 start-0",
+  "bottom-0 end-0",
 ];
 
 interface LoginModalProps {
@@ -245,7 +245,7 @@ export function LoginModal({
                     <div className="relative flex w-full flex-col lg:w-1/2">
                       <div className="flex flex-1 flex-col justify-center px-6 py-10 sm:px-10 sm:py-12">
                         <div className="w-full">
-                          <div className="mb-8 text-left">
+                          <div className="mb-8 text-start">
                             <img
                               src="/resources/logo/artcraft-icon.png"
                               alt="ArtCraft"
@@ -294,7 +294,7 @@ export function LoginModal({
                     </div>
 
                     {/* ── Showcase pane (desktop only) ── */}
-                    <div className="relative hidden border-l border-white/15 lg:block lg:w-1/2">
+                    <div className="relative hidden border-s border-white/15 lg:block lg:w-1/2">
                       <LoginShowcase videoUrl={videoUrl} />
                     </div>
                   </>
diff --git a/frontend/libs/components/modal/src/lib/modal.tsx b/frontend/libs/components/modal/src/lib/modal.tsx
--- a/frontend/libs/components/modal/src/lib/modal.tsx
+++ b/frontend/libs/components/modal/src/lib/modal.tsx
@@ -1076,7 +1076,7 @@ export const Modal = ({
                   >
                     <animated.div
                       className={twMerge(
-                        "w-full max-w-lg rounded-none relative border border-ui-panel-border bg-ui-modal text-left align-middle z-[70]",
+                        "w-full max-w-lg rounded-none relative border border-ui-panel-border bg-ui-modal text-start align-middle z-[70]",
                         childPadding && !expanded ? "p-4" : "",
                         className,
                         "!transition-none", // Always disable CSS transitions for spring animations
@@ -1170,7 +1170,7 @@ export const Modal = ({
                         {renderResizeHandles()}
                       </div>
                       {(showClose || expandable) && (
-                        <div className="absolute top-0 right-0 m-2.5 z-[80] flex items-center gap-2">
+                        <div className="absolute top-0 end-0 m-2.5 z-[80] flex items-center gap-2">
                           {expandable && <Modal.ExpandButton />}
                           {showClose && <CloseButton onClick={onClose} />}
                         </div>
diff --git a/frontend/libs/components/model-selector/src/lib/classy-model-selector.tsx b/frontend/libs/components/model-selector/src/lib/classy-model-selector.tsx
--- a/frontend/libs/components/model-selector/src/lib/classy-model-selector.tsx
+++ b/frontend/libs/components/model-selector/src/lib/classy-model-selector.tsx
@@ -109,7 +109,7 @@ function ProviderTooltipContent({
             type="button"
             className={`group flex cursor-pointer items-center justify-between rounded-[3px] px-2 py-2 transition-all ${
               selectedProvider === p
-                ? "bg-ui-controls/70 border-l-4 border-primary"
+                ? "bg-ui-controls/70 border-s-4 border-primary"
                 : "hover:bg-ui-controls/50"
             }`}
           >
@@ -118,7 +118,7 @@ function ProviderTooltipContent({
               {getProviderDisplayName(p)}
             </span>
             {selectedProvider === p && (
-              <span className="ml-2 flex h-5 w-5 shrink-0 items-center justify-center text-white">
+              <span className="ms-2 flex h-5 w-5 shrink-0 items-center justify-center text-white">
                 <CheckIcon aria-hidden="true" className="h-4 w-4" strokeWidth={2.5} />
               </span>
             )}
@@ -229,7 +229,7 @@ export function ClassyModelSelector({
                     : undefined;
                   const iconProvider = prov ?? allowedProviders[0];
                   return iconProvider ? (
-                    <div className="mr-1 p-1.5 bg-ui-controls/60 group-hover:bg-ui-controls/80 transition-colors">
+                    <div className="me-1 p-1.5 bg-ui-controls/60 group-hover:bg-ui-controls/80 transition-colors">
                       <span className="text-base-fg/70 group-hover:text-base-fg/90 text-lg">
                         {getProviderIcon(iconProvider)}
                       </span>
@@ -241,7 +241,7 @@ export function ClassyModelSelector({
             isSameModel(item.model as Model | undefined, selectedModel) &&
             selectedProvider &&
             hasMultipleProviders ? (
-              <div className="mr-1 p-1.5 bg-primary/60 group-hover:bg-primary/80 transition-colors">
+              <div className="me-1 p-1.5 bg-primary/60 group-hover:bg-primary/80 transition-colors">
                 <span className="text-base-fg/70 group-hover:text-base-fg/90 text-lg">
                   {getProviderIcon(selectedProvider)}
                 </span>
@@ -313,7 +313,7 @@ export function ClassyModelSelector({
         mode="hoverSelect"
         maxListHeight={maxListHeight}
         {...popoverProps}
-        buttonClassName="rounded-[3px] bg-ui-controls text-left shadow-sm px-3 py-1 gap-3 border border-ui-controls-border"
+        buttonClassName="rounded-[3px] bg-ui-controls text-start shadow-sm px-3 py-1 gap-3 border border-ui-controls-border"
         renderTrigger={(selectedItem) => {
           const modelTitle =
             selectedItem?.label ?? selectedModel?.selectorName ?? "";
diff --git a/frontend/libs/components/moodboard/src/lib/BoardPicker.tsx b/frontend/libs/components/moodboard/src/lib/BoardPicker.tsx
--- a/frontend/libs/components/moodboard/src/lib/BoardPicker.tsx
+++ b/frontend/libs/components/moodboard/src/lib/BoardPicker.tsx
@@ -79,7 +79,7 @@ export const BoardPicker = () => {
       </button>
 
       {open && (
-        <div className="glass absolute left-0 top-full z-50 mt-1.5 w-64 rounded-2xl border border-ui-divider p-1.5 shadow-[0_12px_32px_-12px_rgba(0,0,0,0.6)]">
+        <div className="glass absolute start-0 top-full z-50 mt-1.5 w-64 rounded-2xl border border-ui-divider p-1.5 shadow-[0_12px_32px_-12px_rgba(0,0,0,0.6)]">
           <div className="max-h-72 overflow-y-auto">
             {orderedBoards.map((board) => (
               <div
@@ -92,9 +92,10 @@ export const BoardPicker = () => {
                 ].join(" ")}
               >
                 <button
+                  dir="auto"
                   type="button"
                   onClick={() => handleSwitch(board.id)}
-                  className="min-w-0 flex-1 truncate px-3 py-2 text-left text-sm text-base-fg focus:outline-none"
+                  className="min-w-0 flex-1 truncate px-3 py-2 text-start text-sm text-base-fg focus:outline-none"
                 >
                   {board.name}
                 </button>
@@ -103,7 +104,7 @@ export const BoardPicker = () => {
                   title={`Delete ${board.name}`}
                   aria-label={`Delete ${board.name}`}
                   onClick={() => void handleDelete(board.id)}
-                  className="mr-1 flex h-7 w-7 shrink-0 items-center justify-center rounded-lg text-base-fg/40 opacity-0 transition-opacity hover:bg-red-500/20 hover:text-red-400 focus:opacity-100 group-hover:opacity-100"
+                  className="me-1 flex h-7 w-7 shrink-0 items-center justify-center rounded-lg text-base-fg/40 opacity-0 transition-opacity hover:bg-red-500/20 hover:text-red-400 focus:opacity-100 group-hover:opacity-100"
                 >
                   <Trash2Icon  className="h-3 w-3" />
                 </button>
diff --git a/frontend/libs/components/moodboard/src/lib/MoodboardWorkspace.tsx b/frontend/libs/components/moodboard/src/lib/MoodboardWorkspace.tsx
--- a/frontend/libs/components/moodboard/src/lib/MoodboardWorkspace.tsx
+++ b/frontend/libs/components/moodboard/src/lib/MoodboardWorkspace.tsx
@@ -96,7 +96,7 @@ const TopLeftCluster = ({
   mode: ViewMode;
   onChange: (m: ViewMode) => void;
 }) => (
-  <div className="glass absolute left-3 top-3 z-40 flex items-center gap-0.5 rounded-2xl border border-ui-divider p-1 shadow-[0_8px_24px_-12px_rgba(0,0,0,0.45)]">
+  <div className="glass absolute start-3 top-3 z-40 flex items-center gap-0.5 rounded-2xl border border-ui-divider p-1 shadow-[0_8px_24px_-12px_rgba(0,0,0,0.45)]">
     <BoardPicker />
     <div className="mx-0.5 h-5 w-px bg-ui-divider" />
     {OPTIONS.map((opt) => {
@@ -190,7 +190,7 @@ const TopRightCluster = ({
   onSave?: () => void;
   endSlot?: ReactNode;
 }) => (
-  <div className="pointer-events-none absolute right-3 top-3 z-40 flex items-center gap-2">
+  <div className="pointer-events-none absolute end-3 top-3 z-40 flex items-center gap-2">
     <div className="glass pointer-events-auto flex items-center rounded-xl border border-ui-divider p-0.5 shadow-[0_8px_24px_-12px_rgba(0,0,0,0.45)]">
       {saveStatus !== null && onSave && (
         <SaveButton status={saveStatus} onSave={onSave} />
diff --git a/frontend/libs/components/moodboard/src/lib/canvas/Moodboard.tsx b/frontend/libs/components/moodboard/src/lib/canvas/Moodboard.tsx
--- a/frontend/libs/components/moodboard/src/lib/canvas/Moodboard.tsx
+++ b/frontend/libs/components/moodboard/src/lib/canvas/Moodboard.tsx
@@ -44,7 +44,7 @@ export const Moodboard = ({ adapter }: Props) => {
           onGalleryClick={triggerGallery}
         />
       )} */}
-      <div className="absolute left-0 right-0 top-0 z-10">
+      <div className="absolute start-0 end-0 top-0 z-10">
         <MoodboardToolbar
           onUploadClick={triggerUpload}
           onGalleryClick={triggerGallery}
diff --git a/frontend/libs/components/moodboard/src/lib/canvas/overlays/RecenterIndicator.tsx b/frontend/libs/components/moodboard/src/lib/canvas/overlays/RecenterIndicator.tsx
--- a/frontend/libs/components/moodboard/src/lib/canvas/overlays/RecenterIndicator.tsx
+++ b/frontend/libs/components/moodboard/src/lib/canvas/overlays/RecenterIndicator.tsx
@@ -22,7 +22,7 @@ export const RecenterIndicator = () => {
       type="button"
       onClick={() => fitToContent()}
       title="Zoom to fit (Shift+1)"
-      className="pointer-events-auto absolute bottom-4 right-4 z-20 flex items-center gap-2 rounded-full border border-white/20 bg-black/70 px-3 py-2 text-sm text-white/90 shadow-lg backdrop-blur-sm transition-colors hover:bg-black/80"
+      className="pointer-events-auto absolute bottom-4 end-4 z-20 flex items-center gap-2 rounded-full border border-white/20 bg-black/70 px-3 py-2 text-sm text-white/90 shadow-lg backdrop-blur-sm transition-colors hover:bg-black/80"
     >
       <MinimizeIcon />
       <span>Content off-screen — recenter</span>
diff --git a/frontend/libs/components/moodboard/src/lib/grid/BoardCard.tsx b/frontend/libs/components/moodboard/src/lib/grid/BoardCard.tsx
--- a/frontend/libs/components/moodboard/src/lib/grid/BoardCard.tsx
+++ b/frontend/libs/components/moodboard/src/lib/grid/BoardCard.tsx
@@ -147,7 +147,7 @@ const BoardCardInner = ({
               container, where blur filters trigger GPU repaints on every
               scroll frame. A solid scrim keeps the icons legible instead. */}
           {item.rating > 0 && (
-            <div className="absolute bottom-2 left-2 flex items-center gap-0.5 rounded-full bg-black/65 px-2 py-1">
+            <div className="absolute bottom-2 start-2 flex items-center gap-0.5 rounded-full bg-black/65 px-2 py-1">
               {Array.from({ length: item.rating }).map((_, i) => (
                 <StarIcon
                   key={i}
@@ -160,7 +160,7 @@ const BoardCardInner = ({
           {/* Selection check — visible when selected or on hover. */}
           <div
             className={[
-              "absolute left-2 top-2 flex h-6 w-6 items-center justify-center rounded-full",
+              "absolute start-2 top-2 flex h-6 w-6 items-center justify-center rounded-full",
               "border transition-all duration-150",
               selected
                 ? "border-primary bg-primary text-white opacity-100"
@@ -172,7 +172,7 @@ const BoardCardInner = ({
 
           {/* Action rail — solid scrim pill (no blur in the scroll container),
               fades up on hover. */}
-          <div className="absolute right-2 top-2 flex translate-y-1 items-center gap-1 rounded-full border border-white/15 bg-black/65 p-0.5 opacity-0 transition-all duration-200 group-hover:translate-y-0 group-hover:opacity-100">
+          <div className="absolute end-2 top-2 flex translate-y-1 items-center gap-1 rounded-full border border-white/15 bg-black/65 p-0.5 opacity-0 transition-all duration-200 group-hover:translate-y-0 group-hover:opacity-100">
             <RailButton
               label="Open"
               icon={Maximize2Icon}
@@ -255,7 +255,7 @@ const CardContent = ({
     case "color":
       return (
         <div className="h-full w-full" style={{ background: item.color }}>
-          <span className="absolute bottom-2 left-2 rounded-full bg-black/40 px-2 py-0.5 text-[10px] font-medium uppercase tracking-wider text-white backdrop-blur">
+          <span className="absolute bottom-2 start-2 rounded-full bg-black/40 px-2 py-0.5 text-[10px] font-medium uppercase tracking-wider text-white backdrop-blur">
             {item.color}
           </span>
         </div>
@@ -303,7 +303,7 @@ const CardContent = ({
               <Favicon url={item.url} className="h-4 w-4" />
             </span>
             <span className="flex min-w-0 flex-col">
-              <span className="truncate text-xs font-medium text-base-fg/90">
+              <span dir="auto" className="truncate text-xs font-medium text-base-fg/90">
                 {item.title || hostnameOf(item.url)}
               </span>
               <span className="truncate text-[10px] text-base-fg/45">
@@ -346,6 +346,7 @@ const NoteEditor = ({
       onDoubleClick={(e) => e.stopPropagation()}
     >
       <textarea
+        dir="auto"
         ref={ref}
         value={draft}
         onChange={(e) => setDraft(e.target.value)}
diff --git a/frontend/libs/components/moodboard/src/lib/grid/BoardGridToolbar.tsx b/frontend/libs/components/moodboard/src/lib/grid/BoardGridToolbar.tsx
--- a/frontend/libs/components/moodboard/src/lib/grid/BoardGridToolbar.tsx
+++ b/frontend/libs/components/moodboard/src/lib/grid/BoardGridToolbar.tsx
@@ -66,9 +66,9 @@ export const BoardGridToolbar = ({
   return (
     <div className="pointer-events-none absolute inset-x-0 top-0 z-20 flex justify-center px-3 pt-3">
       <ToolbarShell className="max-w-full">
-        <div className="flex min-w-0 flex-col pl-1 pr-2">
+        <div className="flex min-w-0 flex-col ps-1 pe-2">
           <BoardTitle name={boardName} onRename={onRenameBoard} />
-          <span className="pl-1.5 text-[11px] leading-tight text-base-fg/45">
+          <span className="ps-1.5 text-[11px] leading-tight text-base-fg/45">
             {itemCount} {itemCount === 1 ? "item" : "items"}
           </span>
         </div>
@@ -216,13 +216,14 @@ const BoardTitle = ({
 
   return (
     <button
+      dir="auto"
       type="button"
       title="Rename board"
       onClick={() => {
         setDraft(name);
         setEditing(true);
       }}
-      className="truncate rounded-md px-1.5 py-0.5 text-left text-sm font-semibold leading-tight text-base-fg transition-colors hover:bg-base-fg/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary"
+      className="truncate rounded-md px-1.5 py-0.5 text-start text-sm font-semibold leading-tight text-base-fg transition-colors hover:bg-base-fg/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary"
     >
       {name}
     </button>
diff --git a/frontend/libs/components/moodboard/src/lib/grid/ItemInspector.tsx b/frontend/libs/components/moodboard/src/lib/grid/ItemInspector.tsx
--- a/frontend/libs/components/moodboard/src/lib/grid/ItemInspector.tsx
+++ b/frontend/libs/components/moodboard/src/lib/grid/ItemInspector.tsx
@@ -109,12 +109,12 @@ export const ItemInspector = ({
     >
       {/* Invisible drag strip along the top, matching the shared lightbox. */}
       <Modal.DragHandle>
-        <div className="absolute left-0 top-0 z-20 h-12 w-full cursor-move rounded-t-xl" />
+        <div className="absolute start-0 top-0 z-20 h-12 w-full cursor-move rounded-t-xl" />
       </Modal.DragHandle>
 
       <div className="flex h-full gap-4">
         {/* Media panel — flexible width, dark plate, contained media. */}
-        <div className="group/nav relative flex h-full flex-1 items-center justify-center overflow-hidden rounded-l-xl bg-black/30">
+        <div className="group/nav relative flex h-full flex-1 items-center justify-center overflow-hidden rounded-s-xl bg-black/30">
           <Media item={item} />
 
           {hasPrev && <NavArrow side="left" onClick={onPrev} />}
@@ -124,7 +124,7 @@ export const ItemInspector = ({
         {/* Info + actions — fixed width, scrollable body, buttons pinned bottom. */}
         <div className="flex h-full w-[280px] shrink-0 flex-col">
           <div className="min-h-0 flex-1 space-y-5 overflow-y-auto pb-2 text-base-fg">
-            <div className="flex items-center gap-2.5 border-b border-white/5 pb-3 pr-10">
+            <div className="flex items-center gap-2.5 border-b border-white/5 pb-3 pe-10">
               <span className="rounded-full border border-ui-panel-border px-2.5 py-1 text-[10px] font-medium uppercase tracking-[0.18em] text-base-fg/55">
                 {KIND_LABEL[item.kind]}
               </span>
@@ -161,7 +161,7 @@ export const ItemInspector = ({
                     onClick={() => onRemoveTag(tag)}
                     className="group/tag flex items-center gap-1 rounded-full bg-base-fg/10 px-2.5 py-1 text-[11px] font-medium text-base-fg/80 transition-colors hover:bg-red/15 hover:text-red focus:outline-none focus-visible:ring-2 focus-visible:ring-red"
                   >
-                    {tag}
+                    <bdi>{tag}</bdi>
                     <XIcon  className="h-2.5 w-2.5" />
                   </button>
                 ))}
@@ -304,7 +304,7 @@ const Media = ({ item }: { item: BoardItem }) => {
               <Favicon url={item.url} className="h-5 w-5" />
             </span>
             <div className="min-w-0">
-              <p className="text-base font-medium text-base-fg">
+              <p dir="auto" className="text-base font-medium text-base-fg">
                 {item.title || hostnameOf(item.url)}
               </p>
               <p className="mt-1 truncate text-sm text-base-fg/55">{item.url}</p>
diff --git a/frontend/libs/components/moodboard/src/lib/grid/PresentationView.tsx b/frontend/libs/components/moodboard/src/lib/grid/PresentationView.tsx
--- a/frontend/libs/components/moodboard/src/lib/grid/PresentationView.tsx
+++ b/frontend/libs/components/moodboard/src/lib/grid/PresentationView.tsx
@@ -40,7 +40,7 @@ export const PresentationView = ({ items, onClose }: Props) => {
         type="button"
         aria-label="Exit presentation"
         onClick={onClose}
-        className="absolute right-5 top-5 z-20 flex h-10 w-10 items-center justify-center rounded-full border border-white/15 bg-white/5 text-white/80 backdrop-blur-md transition-colors hover:bg-white/15 hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-white/70"
+        className="absolute end-5 top-5 z-20 flex h-10 w-10 items-center justify-center rounded-full border border-white/15 bg-white/5 text-white/80 backdrop-blur-md transition-colors hover:bg-white/15 hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-white/70"
       >
         <XIcon  className="h-4 w-4" />
       </button>
@@ -130,7 +130,7 @@ const Stage = ({ item }: { item: BoardItem }) => {
               className="max-h-[60vh] rounded-2xl object-contain"
             />
           )}
-          <span className="text-xl font-medium text-white">
+          <span dir="auto" className="text-xl font-medium text-white">
             {item.title || item.url}
           </span>
         </a>
diff --git a/frontend/libs/components/moodboard/src/lib/grid/SectionHeader.tsx b/frontend/libs/components/moodboard/src/lib/grid/SectionHeader.tsx
--- a/frontend/libs/components/moodboard/src/lib/grid/SectionHeader.tsx
+++ b/frontend/libs/components/moodboard/src/lib/grid/SectionHeader.tsx
@@ -81,6 +81,7 @@ export const SectionHeader = ({
         />
       ) : (
         <button
+          dir="auto"
           type="button"
           onClick={startEdit}
           disabled={!editable}
diff --git a/frontend/libs/components/moodboard/src/lib/grid/SelectionBar.tsx b/frontend/libs/components/moodboard/src/lib/grid/SelectionBar.tsx
--- a/frontend/libs/components/moodboard/src/lib/grid/SelectionBar.tsx
+++ b/frontend/libs/components/moodboard/src/lib/grid/SelectionBar.tsx
@@ -175,7 +175,7 @@ const MenuItem = ({
     role="menuitem"
     onClick={onClick}
     className={[
-      "flex w-full items-center gap-2.5 rounded-md px-2 py-1.5 text-left text-sm transition-colors",
+      "flex w-full items-center gap-2.5 rounded-md px-2 py-1.5 text-start text-sm transition-colors",
       "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary",
       accent
         ? "text-primary hover:bg-ui-controls/50"
diff --git a/frontend/libs/components/moodboard/src/lib/grid/SmartSearchBar.tsx b/frontend/libs/components/moodboard/src/lib/grid/SmartSearchBar.tsx
--- a/frontend/libs/components/moodboard/src/lib/grid/SmartSearchBar.tsx
+++ b/frontend/libs/components/moodboard/src/lib/grid/SmartSearchBar.tsx
@@ -47,6 +47,7 @@ export const SmartSearchBar = ({
             const active = activeTags.includes(tag);
             return (
               <button
+                dir="auto"
                 key={tag}
                 type="button"
                 onClick={() => onToggleTag(tag)}
diff --git a/frontend/libs/components/pagedraw/src/lib/HistoryStack.tsx b/frontend/libs/components/pagedraw/src/lib/HistoryStack.tsx
--- a/frontend/libs/components/pagedraw/src/lib/HistoryStack.tsx
+++ b/frontend/libs/components/pagedraw/src/lib/HistoryStack.tsx
@@ -308,7 +308,7 @@ export const HistoryStack = ({
                       />
                     )}
                     <div
-                      className="absolute -right-0 -top-0 flex h-5 w-5 items-center justify-center rounded-bl-lg bg-red/50 opacity-0 transition-opacity hover:bg-red/80 group-hover:opacity-100"
+                      className="absolute -end-0 -top-0 flex h-5 w-5 items-center justify-center rounded-es-lg bg-red/50 opacity-0 transition-opacity hover:bg-red/80 group-hover:opacity-100"
                       onClick={(e) => {
                         e.stopPropagation();
                         showActionReminder({
diff --git a/frontend/libs/components/pagedraw/src/lib/PageDraw.tsx b/frontend/libs/components/pagedraw/src/lib/PageDraw.tsx
--- a/frontend/libs/components/pagedraw/src/lib/PageDraw.tsx
+++ b/frontend/libs/components/pagedraw/src/lib/PageDraw.tsx
@@ -1229,7 +1229,7 @@ const PageDraw = ({
     <>
       <div className={`fixed inset-0 -z-10 ${backgroundClassName}`} />
       <div
-        className={`preserve-aspect-ratio fixed right-4 top-[calc(50%_-_var(--pd-bottom-reserve,0px))] z-10 -translate-y-1/2 transform ${
+        className={`preserve-aspect-ratio fixed end-4 top-[calc(50%_-_var(--pd-bottom-reserve,0px))] z-10 -translate-y-1/2 transform ${
           isSelecting ? "pointer-events-none" : "pointer-events-auto"
         }`}
       >
@@ -1292,7 +1292,7 @@ const PageDraw = ({
         />
       </div>
       <SideToolbar
-        className="fixed left-0 top-[calc(50%_-_var(--pd-bottom-reserve,0px))] z-10 -translate-y-1/2 transform"
+        className="fixed start-0 top-[calc(50%_-_var(--pd-bottom-reserve,0px))] z-10 -translate-y-1/2 transform"
         onSelect={handleSelectTool}
         onActivateShapeTool={handleActivateShapeTool}
         onPaintBrush={handlePaintBrush}
@@ -1351,7 +1351,7 @@ const PageDraw = ({
         </ContextMenuContainer>
       </div>
       {showBottomRightControls && (
-        <div className="absolute bottom-4 right-4 z-20 flex items-center gap-2">
+        <div className="absolute bottom-4 end-4 z-20 flex items-center gap-2">
           <CostCalculatorButton modelPage={PAGE_ID} />
           <HelpMenuButton />
         </div>
diff --git a/frontend/libs/components/pagedraw/src/lib/PromptEditor/ImagePreview.tsx b/frontend/libs/components/pagedraw/src/lib/PromptEditor/ImagePreview.tsx
--- a/frontend/libs/components/pagedraw/src/lib/PromptEditor/ImagePreview.tsx
+++ b/frontend/libs/components/pagedraw/src/lib/PromptEditor/ImagePreview.tsx
@@ -55,7 +55,7 @@ const ImagePreview: React.FC<ImagePreviewProps> = ({ image, onUpdate, onRemove }
       {showControls && (
         <>
           <button 
-            className="absolute top-1 right-1 bg-black bg-opacity-50 rounded-full p-0.5 text-white hover:bg-opacity-70 transition-colors"
+            className="absolute top-1 end-1 bg-black bg-opacity-50 rounded-full p-0.5 text-white hover:bg-opacity-70 transition-colors"
             onClick={() => onRemove(image.id)}
           >
             <X size={16} />
@@ -67,10 +67,10 @@ const ImagePreview: React.FC<ImagePreviewProps> = ({ image, onUpdate, onRemove }
             onMouseDown={handleMouseDown}
           >
             <div 
-              className="absolute left-0 right-0 h-0.5 bg-white" 
+              className="absolute start-0 end-0 h-0.5 bg-white" 
               style={{ bottom: `${image.weight * 100}%` }}
             />
-            <div className="absolute bottom-1 right-1 text-xs font-medium text-white">
+            <div className="absolute bottom-1 end-1 text-xs font-medium text-white">
               {image.weight.toFixed(1)}
             </div>
           </div>
diff --git a/frontend/libs/components/pagedraw/src/lib/components/ui/InpaintToolBar.tsx b/frontend/libs/components/pagedraw/src/lib/components/ui/InpaintToolBar.tsx
--- a/frontend/libs/components/pagedraw/src/lib/components/ui/InpaintToolBar.tsx
+++ b/frontend/libs/components/pagedraw/src/lib/components/ui/InpaintToolBar.tsx
@@ -18,7 +18,7 @@ const InpaintToolBar = ({
       <div className="glass flex w-[400px] items-center gap-2 rounded-xl p-2">
         <div className="flex rounded-lg border border-white/10 bg-white/5">
           <button
-            className={`rounded-l-lg px-4 py-2 text-sm font-medium transition-colors ${
+            className={`rounded-s-lg px-4 py-2 text-sm font-medium transition-colors ${
               operation === "add"
                 ? "bg-white/20 text-white"
                 : "text-white/60 hover:bg-white/10 hover:text-white"
@@ -28,7 +28,7 @@ const InpaintToolBar = ({
             Add
           </button>
           <button
-            className={`rounded-r-lg px-4 py-2 text-sm font-medium transition-colors ${
+            className={`rounded-e-lg px-4 py-2 text-sm font-medium transition-colors ${
               operation === "minus"
                 ? "bg-white/20 text-white"
                 : "text-white/60 hover:bg-white/10 hover:text-white"
diff --git a/frontend/libs/components/pagedraw/src/lib/components/ui/SideToolbar.tsx b/frontend/libs/components/pagedraw/src/lib/components/ui/SideToolbar.tsx
--- a/frontend/libs/components/pagedraw/src/lib/components/ui/SideToolbar.tsx
+++ b/frontend/libs/components/pagedraw/src/lib/components/ui/SideToolbar.tsx
@@ -173,7 +173,7 @@ const SideToolbar: React.FC<SideToolbarProps> = ({
     extra?: React.ReactNode,
   ) => (
     <div className={`glass relative w-fit rounded-2xl p-4 shadow-lg`}>
-      <button className="bg-ui-controls/60 text-base-fg hover:bg-ui-controls/80 absolute left-4 top-4 flex h-8 w-8 items-center justify-center rounded-full">
+      <button className="bg-ui-controls/60 text-base-fg hover:bg-ui-controls/80 absolute start-4 top-4 flex h-8 w-8 items-center justify-center rounded-full">
         <PipetteIcon size="0.875em" />
       </button>
 
@@ -234,7 +234,7 @@ const SideToolbar: React.FC<SideToolbarProps> = ({
       id: "select",
       label: "Select & Move",
       icon: (
-        <MousePointerIcon  className="pl-0.5 text-lg" />
+        <MousePointerIcon  className="ps-0.5 text-lg" />
       ),
       onClick: () => {
         onSelect();
@@ -381,7 +381,7 @@ const SideToolbar: React.FC<SideToolbarProps> = ({
 
   return (
     <aside
-      className={`glass ml-4 flex flex-col items-center gap-3 rounded-xl p-1.5 shadow-lg ${className}`}
+      className={`glass ms-4 flex flex-col items-center gap-3 rounded-xl p-1.5 shadow-lg ${className}`}
     >
       {/* Shape Color Picker - appears above toolbar when shape is selected */}
       {selectedColorableNodes.length > 0 && (
@@ -400,7 +400,7 @@ const SideToolbar: React.FC<SideToolbarProps> = ({
             {open === "shape-color" && (
               <div
                 onMouseLeave={() => setOpen(null)}
-                className="absolute left-14 top-1/2 -translate-y-1/2 rounded-xl border border-white/10 bg-ui-panel transition-all duration-200 ease-in-out"
+                className="absolute start-14 top-1/2 -translate-y-1/2 rounded-xl border border-white/10 bg-ui-panel transition-all duration-200 ease-in-out"
               >
                 {ShapePopout}
               </div>
@@ -426,7 +426,7 @@ const SideToolbar: React.FC<SideToolbarProps> = ({
         const toolActionId = TOOL_ACTION_IDS[id];
         const toolBinding = toolActionId ? forAction(toolActionId)[0] : undefined;
         const keyBadge = toolBinding ? (
-          <span className="pointer-events-none absolute bottom-0 right-0 min-w-[13px] rounded-[4px] bg-black/60 px-0.5 text-center text-[9px] font-medium leading-[13px] text-white/80">
+          <span className="pointer-events-none absolute bottom-0 end-0 min-w-[13px] rounded-[4px] bg-black/60 px-0.5 text-center text-[9px] font-medium leading-[13px] text-white/80">
             {formatBinding(toolBinding)}
           </span>
         ) : null;
@@ -589,7 +589,7 @@ const SideToolbar: React.FC<SideToolbarProps> = ({
                 onMouseLeave={() => {
                   setOpen(null);
                 }}
-                className="absolute left-14 top-1/2 -translate-y-1/2 rounded-xl border border-white/10 bg-ui-panel transition-all duration-200 ease-in-out"
+                className="absolute start-14 top-1/2 -translate-y-1/2 rounded-xl border border-white/10 bg-ui-panel transition-all duration-200 ease-in-out"
               >
                 {popout}
               </div>
diff --git a/frontend/libs/components/pagescene/src/lib/Stage3DBody.tsx b/frontend/libs/components/pagescene/src/lib/Stage3DBody.tsx
--- a/frontend/libs/components/pagescene/src/lib/Stage3DBody.tsx
+++ b/frontend/libs/components/pagescene/src/lib/Stage3DBody.tsx
@@ -485,14 +485,14 @@ export const Stage3DBody = ({
             <CompletionModal />
 
             <div
-              className="absolute left-0 top-0 w-full"
+              className="absolute start-0 top-0 w-full"
               onClick={handleOverlayClick}
             >
               <div className="grid grid-cols-2 gap-x-4 gap-y-1 2xl:grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)]">
                 <div className="min-w-0 flex flex-col items-start gap-2">
                   {!isRecord && <ControlsTopButtons />}
                   {!isRecord && topBarStartSlot && (
-                    <div className="pl-3">{topBarStartSlot}</div>
+                    <div className="ps-3">{topBarStartSlot}</div>
                   )}
                 </div>
                 <div className="col-span-2 row-start-2 flex flex-col items-center 2xl:col-span-1 2xl:col-start-2 2xl:row-start-1">
@@ -501,7 +501,7 @@ export const Stage3DBody = ({
                     <Controls3D showImageTo3DButton={showImageTo3DButton} />
                   )}
                 </div>
-                <div className="col-start-2 row-start-1 flex min-w-0 flex-wrap items-start justify-end gap-2 pr-3 pt-3 2xl:col-start-3">
+                <div className="col-start-2 row-start-1 flex min-w-0 flex-wrap items-start justify-end gap-2 pe-3 pt-3 2xl:col-start-3">
                   {/* Camera cluster: which camera drives the viewport +
                       the render-frame aspect ratio. */}
                   {!isRecord && <CameraStatusPill />}
@@ -515,7 +515,7 @@ export const Stage3DBody = ({
             {/* Outliner: vertically centered against the viewport. Sized in
                 vh — deliberately independent of the camera aspect ratio. */}
             <div
-              className={`absolute left-4 top-1/2 -translate-y-1/2${
+              className={`absolute start-4 top-1/2 -translate-y-1/2${
                 isRecord ? " hidden" : ""
               }`}
             >
@@ -523,7 +523,7 @@ export const Stage3DBody = ({
             </div>
 
             <div
-              className="absolute bottom-0 left-0 right-0"
+              className="absolute bottom-0 start-0 end-0"
               onClick={handleOverlayClick}
             >
               {/* Always mounted: PreviewEngineCamera owns the cam-view canvas
@@ -531,7 +531,7 @@ export const Stage3DBody = ({
                   tears down + recreates the whole engine (wiping the scene).
                   Hide with CSS in record instead. */}
               <div
-                className={`absolute bottom-20 mb-4 ml-4 flex origin-bottom-left flex-col gap-2${
+                className={`absolute bottom-20 mb-4 ms-4 flex origin-bottom-left rtl:origin-bottom-right flex-col gap-2${
                   isRecord ? " hidden" : ""
                 }`}
                 style={{ transform: `scale(${getScale()})` }}
@@ -550,27 +550,27 @@ export const Stage3DBody = ({
             {!isRecord && !timelineExpanded && (
               <div className="absolute bottom-4 left-1/2 w-[90vw] max-w-3xl -translate-x-1/2">
                 <TimelineBar />
-                <ExitCameraView className="absolute -right-2 top-1/2 -translate-y-1/2 translate-x-full" />
+                <ExitCameraView className="absolute -end-2 top-1/2 -translate-y-1/2 translate-x-full rtl:-translate-x-full" />
               </div>
             )}
             {!isRecord && timelineExpanded && (
               <>
                 <TimelineEditor />
-                <ExitCameraView className="absolute bottom-4 right-4" />
+                <ExitCameraView className="absolute bottom-4 end-4" />
               </>
             )}
 
             {isRecord && <RecordControls />}
 
             <LoadingDots
-              className="absolute left-0 top-0 z-50"
+              className="absolute start-0 top-0 z-50"
               isShowing={editorLoader.isShowing}
               type="bricks"
               message={editorLoader.message}
             />
 
             {modelSelectorPlacement === "bottom-left" && (
-              <div className="absolute bottom-6 left-6 z-20 flex items-center gap-3">
+              <div className="absolute bottom-6 start-6 z-20 flex items-center gap-3">
                 <ClassyModelSelector
                   items={stage3dModelList}
                   page={PAGE_ID}
@@ -583,7 +583,7 @@ export const Stage3DBody = ({
               </div>
             )}
             {(showCostCalculator || showHelpMenu) && (
-              <div className="absolute bottom-4 right-4 z-20 flex items-center gap-2">
+              <div className="absolute bottom-4 end-4 z-20 flex items-center gap-2">
                 {showCostCalculator && (
                   <CostCalculatorButton modelPage={PAGE_ID} />
                 )}
diff --git a/frontend/libs/components/pagescene/src/lib/comps/AssetMenu/AnimationsModal.tsx b/frontend/libs/components/pagescene/src/lib/comps/AssetMenu/AnimationsModal.tsx
--- a/frontend/libs/components/pagescene/src/lib/comps/AssetMenu/AnimationsModal.tsx
+++ b/frontend/libs/components/pagescene/src/lib/comps/AssetMenu/AnimationsModal.tsx
@@ -224,10 +224,10 @@ export const AnimationsModal = () => {
         contentHidden={assetDraggingUnder && !reopenAfterDrag}
       >
         <Modal.DragHandle>
-          <div className="absolute left-0 top-0 z-[50] h-[46px] w-full cursor-move" />
+          <div className="absolute start-0 top-0 z-[50] h-[46px] w-full cursor-move" />
         </Modal.DragHandle>
         <div className="grid h-full grid-cols-12 gap-3">
-          <div className="relative col-span-3 flex h-full flex-col p-3 pt-2 after:absolute after:right-0 after:top-0 after:h-full after:w-px after:bg-gray-200 after:bg-white/10">
+          <div className="relative col-span-3 flex h-full flex-col p-3 pt-2 after:absolute after:end-0 after:top-0 after:h-full after:w-px after:bg-gray-200 after:bg-white/10">
             <div className="flex h-10 items-center justify-between gap-2.5">
               <h2 className="font-display text-lg tracking-tight text-white">
                 Animations
@@ -240,7 +240,7 @@ export const AnimationsModal = () => {
                   key={tab.id}
                   variant={activeTab === tab.id ? "primary" : "secondary"}
                   className={twMerge(
-                    "w-full justify-start rounded-[3px] border border-transparent bg-transparent px-3.5 py-2.5 text-left hover:bg-white/15",
+                    "w-full justify-start rounded-[3px] border border-transparent bg-transparent px-3.5 py-2.5 text-start hover:bg-white/15",
                     activeTab === tab.id &&
                       "border-white/15 bg-white/10 text-white hover:bg-white/10",
                   )}
@@ -248,7 +248,7 @@ export const AnimationsModal = () => {
                 >
                   <DynamicIcon
                     icon={tab.icon}
-                    className="mr-2 opacity-70"
+                    className="me-2 opacity-70"
                   />
                   {tab.label}
                 </Button>
@@ -281,18 +281,18 @@ export const AnimationsModal = () => {
                       ref={searchInputRef}
                       placeholder="Search animations"
                       className="relative z-[51] grow"
-                      inputClassName="bg-white/5 pr-10 placeholder-white/40"
+                      inputClassName="bg-white/5 pe-10 placeholder-white/40"
                       icon={SearchIcon}
                       value={searchTerm}
                       onChange={(e: ChangeEvent<HTMLInputElement>) =>
                         setSearchTerm(e.target.value)
                       }
-                      iconClassName="pointer-events-none left-3 top-1/2 h-4 w-4 -translate-y-1/2 p-0 text-white/50"
+                      iconClassName="pointer-events-none start-3 top-1/2 h-4 w-4 -translate-y-1/2 p-0 text-white/50"
                     />
                     {searchTerm && (
                       <CloseButton
                         onClick={clearSearch}
-                        className="absolute right-2.5 top-1/2 z-[51] h-4 w-4 -translate-y-1/2 bg-white/10 text-[10px] hover:bg-white/20"
+                        className="absolute end-2.5 top-1/2 z-[51] h-4 w-4 -translate-y-1/2 bg-white/10 text-[10px] hover:bg-white/20"
                       />
                     )}
                   </div>
diff --git a/frontend/libs/components/pagescene/src/lib/comps/AssetMenu/AssetMenu.tsx b/frontend/libs/components/pagescene/src/lib/comps/AssetMenu/AssetMenu.tsx
--- a/frontend/libs/components/pagescene/src/lib/comps/AssetMenu/AssetMenu.tsx
+++ b/frontend/libs/components/pagescene/src/lib/comps/AssetMenu/AssetMenu.tsx
@@ -20,7 +20,7 @@ export const AssetMenu = () => {
 
   return (
     <>
-      <div className="glass absolute left-2 top-1/2 flex -translate-y-1/2 flex-col gap-1 rounded-lg p-1">
+      <div className="glass absolute start-2 top-1/2 flex -translate-y-1/2 flex-col gap-1 rounded-lg p-1">
         <Tooltip content="Add 3D object to scene" position="right" delay={100}>
           <Button
             icon={BoxIcon}
diff --git a/frontend/libs/components/pagescene/src/lib/comps/AssetMenu/AssetModal.tsx b/frontend/libs/components/pagescene/src/lib/comps/AssetMenu/AssetModal.tsx
--- a/frontend/libs/components/pagescene/src/lib/comps/AssetMenu/AssetModal.tsx
+++ b/frontend/libs/components/pagescene/src/lib/comps/AssetMenu/AssetModal.tsx
@@ -54,10 +54,10 @@ const AllTabSection = ({
 }) => (
   <div className="mb-0">
     <div className="mb-2 flex items-center justify-between">
-      <h3 className="text-md ml-2 font-semibold opacity-90">{label}</h3>
+      <h3 className="text-md ms-2 font-semibold opacity-90">{label}</h3>
       <Button
         variant="ghost"
-        className="mr-3 h-6 gap-1 px-1.5 py-0 text-[10px] tracking-[0.08em] leading-none"
+        className="me-3 h-6 gap-1 px-1.5 py-0 text-[10px] tracking-[0.08em] leading-none"
         onClick={onViewAll}
       >
         View all
@@ -503,10 +503,10 @@ export const AssetModal = () => {
         contentHidden={assetDraggingUnder && !reopenAfterDrag}
       >
         <Modal.DragHandle>
-          <div className="absolute left-0 top-0 z-[50] h-[46px] w-full cursor-move" />
+          <div className="absolute start-0 top-0 z-[50] h-[46px] w-full cursor-move" />
         </Modal.DragHandle>
         <div className="grid h-full grid-cols-12 gap-3">
-          <div className="relative col-span-3 flex h-full flex-col p-3 pt-2 after:absolute after:right-0 after:top-0 after:h-full after:w-px after:bg-gray-200 after:bg-white/10">
+          <div className="relative col-span-3 flex h-full flex-col p-3 pt-2 after:absolute after:end-0 after:top-0 after:h-full after:w-px after:bg-gray-200 after:bg-white/10">
             <div className="flex h-10 items-center justify-between gap-2.5">
               <h2 className="font-display text-lg tracking-tight text-white">
                 ArtCraft Presets
@@ -519,7 +519,7 @@ export const AssetModal = () => {
                   key={tab.id}
                   variant={activeAssetTab === tab.id ? "primary" : "secondary"}
                   className={twMerge(
-                    "w-full justify-start rounded-[3px] border border-transparent bg-transparent px-3.5 py-2.5 text-left hover:bg-white/15",
+                    "w-full justify-start rounded-[3px] border border-transparent bg-transparent px-3.5 py-2.5 text-start hover:bg-white/15",
                     activeAssetTab === tab.id &&
                       "border-white/15 bg-white/10 text-white hover:bg-white/10",
                   )}
@@ -527,7 +527,7 @@ export const AssetModal = () => {
                 >
                   <DynamicIcon
                     icon={tab.icon}
-                    className="mr-2 opacity-70"
+                    className="me-2 opacity-70"
                   />
                   {tab.label}
                 </Button>
@@ -551,18 +551,18 @@ export const AssetModal = () => {
                       ref={searchInputRef}
                       placeholder="Search"
                       className="relative z-[51] grow"
-                      inputClassName="bg-white/5 pr-10 placeholder-white/40"
+                      inputClassName="bg-white/5 pe-10 placeholder-white/40"
                       icon={SearchIcon}
                       value={searchTerm}
                       onChange={(e: ChangeEvent<HTMLInputElement>) =>
                         setSearchTerm(e.target.value)
                       }
-                      iconClassName="pointer-events-none left-3 top-1/2 h-4 w-4 -translate-y-1/2 p-0 text-white/50"
+                      iconClassName="pointer-events-none start-3 top-1/2 h-4 w-4 -translate-y-1/2 p-0 text-white/50"
                     />
                     {searchTerm && (
                       <CloseButton
                         onClick={clearSearch}
-                        className="absolute right-2.5 top-1/2 z-[51] h-4 w-4 -translate-y-1/2 bg-white/10 text-[10px] hover:bg-white/20"
+                        className="absolute end-2.5 top-1/2 z-[51] h-4 w-4 -translate-y-1/2 bg-white/10 text-[10px] hover:bg-white/20"
                       />
                     )}
                   </div>
diff --git a/frontend/libs/components/pagescene/src/lib/comps/AssetMenu/shared/ItemElement.tsx b/frontend/libs/components/pagescene/src/lib/comps/AssetMenu/shared/ItemElement.tsx
--- a/frontend/libs/components/pagescene/src/lib/comps/AssetMenu/shared/ItemElement.tsx
+++ b/frontend/libs/components/pagescene/src/lib/comps/AssetMenu/shared/ItemElement.tsx
@@ -105,7 +105,7 @@ export const ItemElement = ({ item }: Props) => {
                 ? patchExpressionObjectType(item.media_type)
                 : item.media_type.toUpperCase()
           }
-          className="absolute right-0 mr-[3px] mt-[3px]"
+          className="absolute end-0 me-[3px] mt-[3px]"
         />
       )}
 
@@ -140,18 +140,18 @@ export const ItemElement = ({ item }: Props) => {
         <div className="text-shadow-md absolute inset-0 flex items-center justify-center bg-brand-primary-950/50 text-[13px] font-medium text-white opacity-0 transition-opacity duration-200 group-hover:opacity-100">
           {item.type === AssetType.ANIMATION ? (
             <>
-              <FootprintsIcon  className="mr-1.5" />
+              <FootprintsIcon  className="me-1.5" />
               Add to Character
             </>
           ) : (
             <>
-              <MoveIcon  className="mr-1.5" />
+              <MoveIcon  className="me-1.5" />
               Drag to Scene
             </>
           )}
         </div>
       </div>
-      <div className="pointer-events-none w-full select-none truncate py-1.5 text-start text-[13px] text-white/80 transition-all duration-200">
+      <div dir="auto" className="pointer-events-none w-full select-none truncate py-1.5 text-start text-[13px] text-white/80 transition-all duration-200">
         {item.name || item.media_id}
       </div>
     </div>
diff --git a/frontend/libs/components/pagescene/src/lib/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx b/frontend/libs/components/pagescene/src/lib/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx
--- a/frontend/libs/components/pagescene/src/lib/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx
+++ b/frontend/libs/components/pagescene/src/lib/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx
@@ -305,7 +305,7 @@ export const ControlPanelSceneObject = () => {
       as="div"
       show={isShowing}
       className={twMerge(
-        "border border-ui-panel-border bg-ui-controls absolute bottom-16 right-0 mb-4 mr-4 flex h-fit w-56 origin-bottom-right flex-col gap-2 rounded-none p-3.5 text-white",
+        "border border-ui-panel-border bg-ui-controls absolute bottom-16 end-0 mb-4 me-4 flex h-fit w-56 origin-bottom-right rtl:origin-bottom-left flex-col gap-2 rounded-none p-3.5 text-white",
       )}
       enter="transition-opacity duration-150"
       enterFrom="opacity-0"
diff --git a/frontend/libs/components/pagescene/src/lib/comps/ControlsTopButtons/ControlsTopButtons.tsx b/frontend/libs/components/pagescene/src/lib/comps/ControlsTopButtons/ControlsTopButtons.tsx
--- a/frontend/libs/components/pagescene/src/lib/comps/ControlsTopButtons/ControlsTopButtons.tsx
+++ b/frontend/libs/components/pagescene/src/lib/comps/ControlsTopButtons/ControlsTopButtons.tsx
@@ -240,7 +240,7 @@ export const ControlsTopButtons = () => {
   };
 
   return (
-    <div className="flex flex-col gap-2 pl-3 pt-3">
+    <div className="flex flex-col gap-2 ps-3 pt-3">
       <div className="flex items-center gap-1.5">
         <ButtonDropdown
           label="File"
diff --git a/frontend/libs/components/pagescene/src/lib/comps/ControlsTopButtons/LoadUserScenes/LoadUserScenes.tsx b/frontend/libs/components/pagescene/src/lib/comps/ControlsTopButtons/LoadUserScenes/LoadUserScenes.tsx
--- a/frontend/libs/components/pagescene/src/lib/comps/ControlsTopButtons/LoadUserScenes/LoadUserScenes.tsx
+++ b/frontend/libs/components/pagescene/src/lib/comps/ControlsTopButtons/LoadUserScenes/LoadUserScenes.tsx
@@ -136,7 +136,7 @@ export const LoadUserScenes = ({ onSceneSelect }: LoadSceneProps) => {
         )}
 
         <div
-          className="pointer-events-none absolute bottom-0 left-0 right-0 z-10 h-10 bg-gradient-to-t from-ui-panel to-transparent transition-opacity duration-200"
+          className="pointer-events-none absolute bottom-0 start-0 end-0 z-10 h-10 bg-gradient-to-t from-ui-panel to-transparent transition-opacity duration-200"
           style={{ opacity: bottomGradientOpacity }}
         />
       </div>
diff --git a/frontend/libs/components/pagescene/src/lib/comps/ControlsTopButtons/NewSceneFromTemplate/NewSceneFromTemplate.tsx b/frontend/libs/components/pagescene/src/lib/comps/ControlsTopButtons/NewSceneFromTemplate/NewSceneFromTemplate.tsx
--- a/frontend/libs/components/pagescene/src/lib/comps/ControlsTopButtons/NewSceneFromTemplate/NewSceneFromTemplate.tsx
+++ b/frontend/libs/components/pagescene/src/lib/comps/ControlsTopButtons/NewSceneFromTemplate/NewSceneFromTemplate.tsx
@@ -114,7 +114,7 @@ export const NewSceneFromTemplate = ({
           </div>
         )}
         <div
-          className="pointer-events-none absolute bottom-0 left-0 right-0 z-10 h-10 bg-gradient-to-t from-ui-panel to-transparent transition-opacity duration-200"
+          className="pointer-events-none absolute bottom-0 start-0 end-0 z-10 h-10 bg-gradient-to-t from-ui-panel to-transparent transition-opacity duration-200"
           style={{ opacity: bottomGradientOpacity }}
         />
       </div>
diff --git a/frontend/libs/components/pagescene/src/lib/comps/ControlsTopButtons/ScenePicker/SceneCard.tsx b/frontend/libs/components/pagescene/src/lib/comps/ControlsTopButtons/ScenePicker/SceneCard.tsx
--- a/frontend/libs/components/pagescene/src/lib/comps/ControlsTopButtons/ScenePicker/SceneCard.tsx
+++ b/frontend/libs/components/pagescene/src/lib/comps/ControlsTopButtons/ScenePicker/SceneCard.tsx
@@ -61,10 +61,10 @@ export const SceneCard: React.FC<SceneCardProps> = ({
         onError={() => setLoadError(true)}
         loading="lazy"
       />
-      <div className="absolute left-0 top-0 h-full w-full bg-gradient-to-t from-black/80 to-transparent" />
-      <div className="absolute bottom-[8px] left-[10px] text-start text-sm drop-shadow-md">
+      <div className="absolute start-0 top-0 h-full w-full bg-gradient-to-t from-black/80 to-transparent" />
+      <div className="absolute bottom-[8px] start-[10px] text-start text-sm drop-shadow-md">
         <div className="flex flex-col">
-          <span className="w-60 truncate text-sm font-medium">
+          <span dir="auto" className="w-60 truncate text-sm font-medium">
             {scene.name}
           </span>
           {showDate && (
@@ -75,7 +75,7 @@ export const SceneCard: React.FC<SceneCardProps> = ({
       <svg
         xmlns="http://www.w3.org/2000/svg"
         viewBox="0 0 512 512"
-        className={`absolute right-1.5 top-1.5 h-[22px] w-[22px] transition-opacity duration-200 ease-in-out ${
+        className={`absolute end-1.5 top-1.5 h-[22px] w-[22px] transition-opacity duration-200 ease-in-out ${
           selectedSceneId === scene.token ? "opacity-100" : "opacity-0"
         }`}
       >
diff --git a/frontend/libs/components/pagescene/src/lib/comps/EditorLoadingBar.tsx b/frontend/libs/components/pagescene/src/lib/comps/EditorLoadingBar.tsx
--- a/frontend/libs/components/pagescene/src/lib/comps/EditorLoadingBar.tsx
+++ b/frontend/libs/components/pagescene/src/lib/comps/EditorLoadingBar.tsx
@@ -11,7 +11,7 @@ export const EditorLoadingBar = () => {
     <LoadingBar
       id="editor-loading-bar"
       show={editorLoader.isShowing}
-      wrapperClassName="absolute top-0 left-0 z-[80]"
+      wrapperClassName="absolute top-0 start-0 z-[80]"
       innerWrapperClassName="max-w-screen-sm"
       hasSpinner
       progressData={{
diff --git a/frontend/libs/components/pagescene/src/lib/comps/EntranceDebugPanel.tsx b/frontend/libs/components/pagescene/src/lib/comps/EntranceDebugPanel.tsx
--- a/frontend/libs/components/pagescene/src/lib/comps/EntranceDebugPanel.tsx
+++ b/frontend/libs/components/pagescene/src/lib/comps/EntranceDebugPanel.tsx
@@ -34,7 +34,7 @@ export function EntranceDebugPanel() {
   const splatSec = (ENTRANCE_BASE_DURATIONS.splat * scale).toFixed(2);
 
   return (
-    <div className="pointer-events-auto absolute right-2 top-16 z-50 w-56 rounded-lg border border-white/10 bg-black/70 p-3 text-xs text-white/80 backdrop-blur-md">
+    <div className="pointer-events-auto absolute end-2 top-16 z-50 w-56 rounded-lg border border-white/10 bg-black/70 p-3 text-xs text-white/80 backdrop-blur-md">
       <div className="mb-2 flex items-center justify-between font-medium text-white/90">
         <span>Entrance speed</span>
         <span className="tabular-nums">{scale.toFixed(2)}×</span>
diff --git a/frontend/libs/components/pagescene/src/lib/comps/Outliner/Outliner.tsx b/frontend/libs/components/pagescene/src/lib/comps/Outliner/Outliner.tsx
--- a/frontend/libs/components/pagescene/src/lib/comps/Outliner/Outliner.tsx
+++ b/frontend/libs/components/pagescene/src/lib/comps/Outliner/Outliner.tsx
@@ -71,7 +71,7 @@ const OutlinerRow = ({ item }: { item: OutlinerItem }) => {
           <DynamicIcon icon={item.icon} />
         </div>
         <span className="min-w-0">
-          <span className="block truncate text-[13px] font-semibold text-white">
+          <span dir="auto" className="block truncate text-[13px] font-semibold text-white">
             {item.name}
           </span>
           <span className="block truncate text-xs text-white/50">
diff --git a/frontend/libs/components/pagescene/src/lib/comps/PerfStatsOverlay.tsx b/frontend/libs/components/pagescene/src/lib/comps/PerfStatsOverlay.tsx
--- a/frontend/libs/components/pagescene/src/lib/comps/PerfStatsOverlay.tsx
+++ b/frontend/libs/components/pagescene/src/lib/comps/PerfStatsOverlay.tsx
@@ -47,7 +47,7 @@ export function PerfStatsOverlay() {
   return (
     <div
       ref={containerRef}
-      className="pointer-events-none absolute bottom-2 left-2 z-50"
+      className="pointer-events-none absolute bottom-2 start-2 z-50"
     />
   );
 }
diff --git a/frontend/libs/components/pagescene/src/lib/comps/PreviewBox/PreviewBox.tsx b/frontend/libs/components/pagescene/src/lib/comps/PreviewBox/PreviewBox.tsx
--- a/frontend/libs/components/pagescene/src/lib/comps/PreviewBox/PreviewBox.tsx
+++ b/frontend/libs/components/pagescene/src/lib/comps/PreviewBox/PreviewBox.tsx
@@ -19,11 +19,11 @@ export const PreviewBox = ({ imageUrl }: PreviewBoxProps) => {
   if (!imageUrl || dismissed) return null;
 
   return (
-    <div className="glass absolute bottom-6 right-6 z-20 flex w-48 flex-col gap-2 rounded-lg border border-white/10 p-2 shadow-lg">
+    <div className="glass absolute bottom-6 end-6 z-20 flex w-48 flex-col gap-2 rounded-lg border border-white/10 p-2 shadow-lg">
       <button
         type="button"
         onClick={() => setDismissed(true)}
-        className="absolute right-1 top-1 flex h-5 w-5 items-center justify-center rounded-full text-white/60 hover:bg-white/10 hover:text-white"
+        className="absolute end-1 top-1 flex h-5 w-5 items-center justify-center rounded-full text-white/60 hover:bg-white/10 hover:text-white"
         aria-label="Hide preview"
       >
         <XIcon  className="text-xs" />
diff --git a/frontend/libs/components/pagescene/src/lib/comps/PreviewEngineCamera/PreviewEngineCamera.tsx b/frontend/libs/components/pagescene/src/lib/comps/PreviewEngineCamera/PreviewEngineCamera.tsx
--- a/frontend/libs/components/pagescene/src/lib/comps/PreviewEngineCamera/PreviewEngineCamera.tsx
+++ b/frontend/libs/components/pagescene/src/lib/comps/PreviewEngineCamera/PreviewEngineCamera.tsx
@@ -137,7 +137,7 @@ export const PreviewEngineCamera = () => {
           <div className="flex h-full w-full items-center justify-center bg-ui-controls">
             <LoaderCircleIcon size="3em" className="animate-spin" />
           </div>
-          <div className="absolute left-0 top-0 h-full w-full overflow-hidden">
+          <div className="absolute start-0 top-0 h-full w-full overflow-hidden">
             <CameraViewCanvas className="!h-full !w-full" />
           </div>
         </div>
diff --git a/frontend/libs/components/pagescene/src/lib/comps/RenderOverlay/RenderOverlay.tsx b/frontend/libs/components/pagescene/src/lib/comps/RenderOverlay/RenderOverlay.tsx
--- a/frontend/libs/components/pagescene/src/lib/comps/RenderOverlay/RenderOverlay.tsx
+++ b/frontend/libs/components/pagescene/src/lib/comps/RenderOverlay/RenderOverlay.tsx
@@ -21,7 +21,7 @@ export const RenderOverlay = () => {
   return (
     <>
       <LoadingDots
-        className="absolute left-0 top-0 z-[60]"
+        className="absolute start-0 top-0 z-[60]"
         isShowing={progress !== null}
         type="bricks"
         message={message}
diff --git a/frontend/libs/components/pagescene/src/lib/comps/Timeline/TimelineClipRow.tsx b/frontend/libs/components/pagescene/src/lib/comps/Timeline/TimelineClipRow.tsx
--- a/frontend/libs/components/pagescene/src/lib/comps/Timeline/TimelineClipRow.tsx
+++ b/frontend/libs/components/pagescene/src/lib/comps/Timeline/TimelineClipRow.tsx
@@ -211,9 +211,10 @@ export const TimelineClipRow = ({
                   >
                     {bakedClips.map((clipName, clipIndex) => (
                       <button
+                        dir="auto"
                         key={clipIndex}
                         type="button"
-                        className="block w-full truncate px-2.5 py-1 text-left text-[11px] text-base-fg/90 hover:bg-white/10"
+                        className="block w-full truncate px-2.5 py-1 text-start text-[11px] text-base-fg/90 hover:bg-white/10"
                         onClick={() => addBaked(clipIndex)}
                       >
                         {clipName}
diff --git a/frontend/libs/components/pagescene/src/lib/comps/Timeline/TimelineEditor.tsx b/frontend/libs/components/pagescene/src/lib/comps/Timeline/TimelineEditor.tsx
--- a/frontend/libs/components/pagescene/src/lib/comps/Timeline/TimelineEditor.tsx
+++ b/frontend/libs/components/pagescene/src/lib/comps/Timeline/TimelineEditor.tsx
@@ -350,12 +350,12 @@ export const TimelineEditor = () => {
         >
           <SkipForwardIcon  className="h-3.5 w-3.5" />
         </button>
-        <span className="ml-2 font-mono tabular-nums text-xs text-base-fg/60">
+        <span className="ms-2 font-mono tabular-nums text-xs text-base-fg/60">
           {formatTimecodeFrames(playhead, fps)} / {formatTimecode(duration)}
         </span>
-        {/* ml-auto lives on a wrapper: Tooltip's className styles the popup
+        {/* ms-auto lives on a wrapper: Tooltip's className styles the popup
             panel, and its trigger div is hard-coded to "relative". */}
-        <div className="ml-auto">
+        <div className="ms-auto">
           <Tooltip
             content={
               focusSelected
diff --git a/frontend/libs/components/popover/src/lib/popover.tsx b/frontend/libs/components/popover/src/lib/popover.tsx
--- a/frontend/libs/components/popover/src/lib/popover.tsx
+++ b/frontend/libs/components/popover/src/lib/popover.tsx
@@ -219,7 +219,7 @@ function InfoHint({ content }: { content: ReactNode }) {
         ref={anchorRef}
         type="button"
         aria-label="More info"
-        className="ml-0.5 inline-flex h-4 w-4 shrink-0 items-center justify-center text-base-fg/40 transition-colors hover:text-base-fg/80"
+        className="ms-0.5 inline-flex h-4 w-4 shrink-0 items-center justify-center text-base-fg/40 transition-colors hover:text-base-fg/80"
         onClick={(e) => {
           // Don't let the click select the row.
           e.stopPropagation();
@@ -406,12 +406,12 @@ function RichListRow({
           )}
       </div>
       {item.trailing && (
-        <div className="ml-1 flex shrink-0 items-center">{item.trailing}</div>
+        <div className="ms-1 flex shrink-0 items-center">{item.trailing}</div>
       )}
       {rightNode ??
         (item.selected &&
           (item.selectedRight ?? (
-            <span className="ml-1 flex h-5 w-5 shrink-0 items-center justify-center">
+            <span className="ms-1 flex h-5 w-5 shrink-0 items-center justify-center">
               <CheckIcon aria-hidden="true" className="h-4 w-4 text-white" strokeWidth={2.5} />
             </span>
           )))}
@@ -879,9 +879,9 @@ export const PopoverMenu = ({
   };
 
   const alignClasses = {
-    start: "left-0",
+    start: "start-0",
     center: "left-1/2 -translate-x-1/2",
-    end: "right-0",
+    end: "end-0",
   };
 
   // Hover timers and refs
@@ -1176,7 +1176,7 @@ export const PopoverMenu = ({
                                       item={item}
                                       active={openTooltipIdx === index}
                                       rightNode={
-                                        <ChevronRightIcon className="ml-1 shrink-0 text-xs text-base-fg/50" />
+                                        <ChevronRightIcon className="ms-1 shrink-0 text-xs text-base-fg/50" />
                                       }
                                     />
                                   </SubmenuFlyout>
@@ -1247,7 +1247,7 @@ export const PopoverMenu = ({
                     ) : mode === "hoverSelect" ? (
                       <div className="relative flex flex-col text-sm text-base-fg overflow-visible">
                         {maxListHeight && canScrollUp && (
-                          <div className="absolute top-0 left-0 right-0 z-20 flex justify-center bg-gradient-to-b from-ui-controls via-ui-controls/80 to-transparent py-1.5 pointer-events-none">
+                          <div className="absolute top-0 start-0 end-0 z-20 flex justify-center bg-gradient-to-b from-ui-controls via-ui-controls/80 to-transparent py-1.5 pointer-events-none">
                             <ChevronUpIcon className="text-base-fg/60 text-xs animate-bounce" />
                           </div>
                         )}
@@ -1288,7 +1288,7 @@ export const PopoverMenu = ({
                                 className={twMerge(
                                   "group flex cursor-pointer items-start gap-2 rounded-[3px] px-2 py-2 transition-all",
                                   item.selected
-                                    ? "bg-white/10 border-l-2 border-white"
+                                    ? "bg-white/10 border-s-2 border-white"
                                     : "hover:bg-white/5",
                                   !item.selected && openTooltipIdx === index
                                     ? "bg-white/5"
@@ -1340,14 +1340,14 @@ export const PopoverMenu = ({
                                   </div>
 
                                   {item.trailing && (
-                                    <div className="ml-2 mr-1 flex items-center">
+                                    <div className="ms-2 me-1 flex items-center">
                                       {item.trailing}
                                     </div>
                                   )}
 
                                   {item.selected &&
                                     (item.selectedRight ?? (
-                                      <span className="text-white text-xl flex items-center justify-center mr-1">
+                                      <span className="text-white text-xl flex items-center justify-center me-1">
                                         <CheckIcon aria-hidden="true" className="h-4 w-4 shrink-0" strokeWidth={2.5} />
                                       </span>
                                     ))}
@@ -1406,7 +1406,7 @@ export const PopoverMenu = ({
                           })}
                         </div>
                         {maxListHeight && canScrollDown && (
-                          <div className="absolute bottom-0 left-0 right-0 z-20 flex justify-center bg-gradient-to-t from-ui-controls via-ui-controls/80 to-transparent py-1.5 pointer-events-none">
+                          <div className="absolute bottom-0 start-0 end-0 z-20 flex justify-center bg-gradient-to-t from-ui-controls via-ui-controls/80 to-transparent py-1.5 pointer-events-none">
                             <ChevronDownIcon className="text-base-fg/60 text-xs animate-bounce" />
                           </div>
                         )}
@@ -1495,20 +1495,20 @@ export const PopoverMenu = ({
                                   </div>
                                   {/* Optional trailing content on the right */}
                                   {item.trailing && (
-                                    <div className="ml-2 flex items-center">
+                                    <div className="ms-2 flex items-center">
                                       {item.trailing}
                                     </div>
                                   )}
                                   {/* Optional trailing content on the right */}
                                   {item.trailing && (
-                                    <div className="ml-2 flex items-center">
+                                    <div className="ms-2 flex items-center">
                                       {item.trailing}
                                     </div>
                                   )}
 
                                   {mode === "toggle" && (
                                     <span
-                                      className="ml-2 flex h-5 w-5 shrink-0 items-center justify-center"
+                                      className="ms-2 flex h-5 w-5 shrink-0 items-center justify-center"
                                     >
                                       {item.selected && (
                                         <CheckIcon aria-hidden="true" className="h-4 w-4 text-white" strokeWidth={2.5} />
@@ -1558,14 +1558,14 @@ export const PopoverMenu = ({
                                 </div>
                                 {/* Optional trailing content on the right */}
                                 {item.trailing && (
-                                  <div className="ml-2 flex items-center">
+                                  <div className="ms-2 flex items-center">
                                     {item.trailing}
                                   </div>
                                 )}
 
                                 {mode === "toggle" && (
                                   <span
-                                    className="ml-2 flex h-5 w-5 shrink-0 items-center justify-center"
+                                    className="ms-2 flex h-5 w-5 shrink-0 items-center justify-center"
                                   >
                                     {item.selected && (
                                       <CheckIcon aria-hidden="true" className="h-4 w-4 text-white" strokeWidth={2.5} />
diff --git a/frontend/libs/components/pricing-modal/src/lib/cost-breakdown-modal.tsx b/frontend/libs/components/pricing-modal/src/lib/cost-breakdown-modal.tsx
--- a/frontend/libs/components/pricing-modal/src/lib/cost-breakdown-modal.tsx
+++ b/frontend/libs/components/pricing-modal/src/lib/cost-breakdown-modal.tsx
@@ -346,6 +346,7 @@ export function CostBreakdownModal({ activeTabId }: CostBreakdownModalProps) {
           <div className="flex justify-between items-center">
             <span className="text-base-fg/60">Model</span>
             <span
+              dir="auto"
               className="text-base-fg font-medium truncate max-w-[140px]"
               title={selectedModel.selectorName}
             >
diff --git a/frontend/libs/components/pricing-modal/src/lib/credits-modal.tsx b/frontend/libs/components/pricing-modal/src/lib/credits-modal.tsx
--- a/frontend/libs/components/pricing-modal/src/lib/credits-modal.tsx
+++ b/frontend/libs/components/pricing-modal/src/lib/credits-modal.tsx
@@ -91,14 +91,14 @@ export function CreditsModal({
                   onClick={() => handlePurchase(pack)}
                   disabled={purchasingId !== null}
                   className={twMerge(
-                    "group relative flex flex-col gap-6 border p-6 text-left transition-all disabled:cursor-not-allowed disabled:opacity-60",
+                    "group relative flex flex-col gap-6 border p-6 text-start transition-all disabled:cursor-not-allowed disabled:opacity-60",
                     isPopular
                       ? "border-white/60 bg-white/[0.06] hover:border-white"
                       : "border-white/15 bg-white/[0.02] hover:border-white/30 hover:bg-white/[0.04]",
                   )}
                 >
                   {pack.badge && (
-                    <span className="absolute -top-2.5 right-3 bg-white px-2.5 py-0.5 font-mono text-[10px] font-semibold uppercase tracking-[0.12em] text-black">
+                    <span className="absolute -top-2.5 end-3 bg-white px-2.5 py-0.5 font-mono text-[10px] font-semibold uppercase tracking-[0.12em] text-black">
                       {pack.badge}
                     </span>
                   )}
diff --git a/frontend/libs/components/pricing-modal/src/lib/pricing-modal.tsx b/frontend/libs/components/pricing-modal/src/lib/pricing-modal.tsx
--- a/frontend/libs/components/pricing-modal/src/lib/pricing-modal.tsx
+++ b/frontend/libs/components/pricing-modal/src/lib/pricing-modal.tsx
@@ -73,7 +73,7 @@ export function PricingContent({ title, subtitle }: PricingContentProps) {
   return (
     <div className="min-h-0 flex-1 bg-[#101014] text-white">
       <div className="bg-primary text-white">
-        <div className="flex flex-wrap items-center justify-between gap-x-6 gap-y-1 py-2.5 pl-6 pr-14 md:pl-10">
+        <div className="flex flex-wrap items-center justify-between gap-x-6 gap-y-1 py-2.5 ps-6 pe-14 md:ps-10">
           <p className="pricing-hud flex items-center gap-2 font-bold">
             <TagIcon aria-hidden className="h-3.5 w-3.5" />
             Limited-time offer
@@ -156,7 +156,7 @@ export function PricingContent({ title, subtitle }: PricingContentProps) {
               Buy credits
             </Button>
           </div>
-          <div className="border-t border-white/15 p-6 md:border-l md:border-t-0 md:p-10">
+          <div className="border-t border-white/15 p-6 md:border-s md:border-t-0 md:p-10">
             <p className="pricing-hud text-white/40">† Footnote</p>
             <p className="mt-4 max-w-md leading-relaxed text-white/55">
               ArtCraft can be used without paying for a subscription. You can
diff --git a/frontend/libs/components/pricing-table/src/lib/pricing-promo-banner.tsx b/frontend/libs/components/pricing-table/src/lib/pricing-promo-banner.tsx
--- a/frontend/libs/components/pricing-table/src/lib/pricing-promo-banner.tsx
+++ b/frontend/libs/components/pricing-table/src/lib/pricing-promo-banner.tsx
@@ -16,7 +16,7 @@ export const PricingPromoBanner = ({ className }: PricingPromoBannerProps) => (
     {/* Decorative glow, top-right */}
     <div
       aria-hidden
-      className="pointer-events-none absolute -right-16 -top-20 h-64 w-64 bg-primary/25 blur-3xl"
+      className="pointer-events-none absolute -end-16 -top-20 h-64 w-64 bg-primary/25 blur-3xl"
     />
 
     <div className="relative flex flex-col gap-3">
diff --git a/frontend/libs/components/promptbox/src/lib/CharactersModal.tsx b/frontend/libs/components/promptbox/src/lib/CharactersModal.tsx
--- a/frontend/libs/components/promptbox/src/lib/CharactersModal.tsx
+++ b/frontend/libs/components/promptbox/src/lib/CharactersModal.tsx
@@ -276,7 +276,7 @@ const CharacterListView = ({
       title: "Delete character?",
       message: (
         <p className="text-sm text-white/70">
-          This will permanently delete <strong>{character.name}</strong>. This
+          This will permanently delete <strong><bdi>{character.name}</bdi></strong>. This
           action cannot be undone.
         </p>
       ),
@@ -390,7 +390,7 @@ const CharacterListView = ({
                 </div>
               </div>
               <div className="px-2 py-1.5 text-center">
-                <p className="truncate text-xs font-medium text-base-fg/50">
+                <p dir="auto" className="truncate text-xs font-medium text-base-fg/50">
                   {pending.name}
                 </p>
               </div>
@@ -426,7 +426,7 @@ const CharacterListView = ({
                     )}
                   </div>
                   <div className="px-2 py-1.5">
-                    <p className="truncate text-xs font-medium text-base-fg/80">
+                    <p dir="auto" className="truncate text-xs font-medium text-base-fg/80">
                       {character.name}
                     </p>
                   </div>
@@ -435,7 +435,7 @@ const CharacterListView = ({
                 {/* View / Edit / Delete overlay buttons. Hover-revealed on
                     desktop; always visible on touch devices (no hover). */}
                 {(character.maybe_avatar?.cdn_url || isUserCreated) && (
-                  <div className="absolute right-1.5 top-1.5 flex gap-1 opacity-0 transition-opacity group-hover:opacity-100 [@media(hover:none)]:opacity-100">
+                  <div className="absolute end-1.5 top-1.5 flex gap-1 opacity-0 transition-opacity group-hover:opacity-100 [@media(hover:none)]:opacity-100">
                     {character.maybe_avatar?.cdn_url && (
                       <button
                         onClick={(e) => {
@@ -606,6 +606,7 @@ const EditCharacterView = ({
             <span className="font-normal text-base-fg/40">(optional)</span>
           </Label>
           <textarea
+            dir="auto"
             id="edit-character-description"
             value={description}
             onChange={(e) => setDescription(e.target.value)}
@@ -867,7 +868,7 @@ const NewCharacterView = ({
                   e.stopPropagation();
                   removeImage(0);
                 }}
-                className="absolute right-2 top-2 flex h-7 w-7 items-center justify-center bg-black/60 text-white/80 opacity-0 transition-all group-hover:opacity-100 hover:bg-red-500"
+                className="absolute end-2 top-2 flex h-7 w-7 items-center justify-center bg-black/60 text-white/80 opacity-0 transition-all group-hover:opacity-100 hover:bg-red-500"
               >
                 <XIcon  className="text-sm" />
               </button>
@@ -937,6 +938,7 @@ const NewCharacterView = ({
             <span className="font-normal text-base-fg/40">(optional)</span>
           </Label>
           <textarea
+            dir="auto"
             id="character-description"
             value={description}
             onChange={(e) => setDescription(e.target.value)}
diff --git a/frontend/libs/components/promptbox/src/lib/ImagePromptRow.tsx b/frontend/libs/components/promptbox/src/lib/ImagePromptRow.tsx
--- a/frontend/libs/components/promptbox/src/lib/ImagePromptRow.tsx
+++ b/frontend/libs/components/promptbox/src/lib/ImagePromptRow.tsx
@@ -134,7 +134,7 @@ const AudioRefTile = ({
           )}
         />
       </button>
-      <div className="absolute bottom-0 left-0 right-0 flex items-center justify-center bg-black/70 py-0.5 text-[10px] font-bold text-white pointer-events-none">
+      <div className="absolute bottom-0 start-0 end-0 flex items-center justify-center bg-black/70 py-0.5 text-[10px] font-bold text-white pointer-events-none">
         #{index + 1} · {audio.duration}s
       </div>
       <button
@@ -146,7 +146,7 @@ const AudioRefTile = ({
           }
           onRemove(audio.id);
         }}
-        className="opacity-0 group-hover:opacity-100 absolute right-[2px] top-[2px] flex h-5 w-5 items-center justify-center rounded-full bg-black/50 hover:bg-red/70 text-white backdrop-blur-md transition-colors hover:bg-black cursor-pointer"
+        className="opacity-0 group-hover:opacity-100 absolute end-[2px] top-[2px] flex h-5 w-5 items-center justify-center rounded-full bg-black/50 hover:bg-red/70 text-white backdrop-blur-md transition-colors hover:bg-black cursor-pointer"
       >
         <XIcon  className="h-2.5 w-2.5" />
       </button>
@@ -287,7 +287,7 @@ export const ImagePromptRow = ({
           className="h-full w-full object-cover"
         />
         {isReferenceMode && (
-          <div className="absolute bottom-0 left-0 right-0 flex items-center justify-center bg-black/60 py-0.5 text-[11px] font-bold text-white">
+          <div className="absolute bottom-0 start-0 end-0 flex items-center justify-center bg-black/60 py-0.5 text-[11px] font-bold text-white">
             {index + 1}
           </div>
         )}
@@ -302,7 +302,7 @@ export const ImagePromptRow = ({
           onPointerDown={(e) => {
             e.stopPropagation();
           }}
-          className="opacity-0 group-hover:opacity-100 absolute right-[2px] top-[2px] flex h-5 w-5 items-center justify-center rounded-full bg-black/50 hover:bg-red/70 text-white backdrop-blur-md transition-colors hover:bg-black cursor-pointer"
+          className="opacity-0 group-hover:opacity-100 absolute end-[2px] top-[2px] flex h-5 w-5 items-center justify-center rounded-full bg-black/50 hover:bg-red/70 text-white backdrop-blur-md transition-colors hover:bg-black cursor-pointer"
         >
           <XIcon  className="h-2.5 w-2.5" />
         </button>
@@ -865,7 +865,7 @@ export const ImagePromptRow = ({
       )}
       <div
         className={twMerge(
-          "absolute left-0 glass w-full rounded-t-[3px] flex",
+          "absolute start-0 glass w-full rounded-t-[3px] flex",
           showVideoReferenceSection ? "-top-[144px]" : "-top-[72px]",
           className,
         )}
@@ -945,7 +945,7 @@ export const ImagePromptRow = ({
                           className="h-full w-full object-cover"
                         />
                         {isReferenceMode && (
-                          <div className="absolute bottom-0 left-0 right-0 flex items-center justify-center bg-black/60 py-0.5 text-[11px] font-bold text-white">
+                          <div className="absolute bottom-0 start-0 end-0 flex items-center justify-center bg-black/60 py-0.5 text-[11px] font-bold text-white">
                             {index + 1}
                           </div>
                         )}
@@ -954,7 +954,7 @@ export const ImagePromptRow = ({
                             e.stopPropagation();
                             handleRemoveReference(image.id);
                           }}
-                          className="opacity-0 group-hover:opacity-100 absolute right-[2px] top-[2px] flex h-5 w-5 items-center justify-center rounded-full bg-black/50 hover:bg-red/70 text-white backdrop-blur-md transition-colors hover:bg-black cursor-pointer"
+                          className="opacity-0 group-hover:opacity-100 absolute end-[2px] top-[2px] flex h-5 w-5 items-center justify-center rounded-full bg-black/50 hover:bg-red/70 text-white backdrop-blur-md transition-colors hover:bg-black cursor-pointer"
                         >
                           <XIcon
                             
@@ -1077,7 +1077,7 @@ export const ImagePromptRow = ({
                           e.stopPropagation();
                           setEndFrameImage?.(undefined);
                         }}
-                        className="opacity-0 group-hover:opacity-100 absolute right-[2px] top-[2px] flex h-5 w-5 items-center justify-center rounded-full bg-black/50 hover:bg-red/70 text-white backdrop-blur-md transition-colors hover:bg-black cursor-pointer"
+                        className="opacity-0 group-hover:opacity-100 absolute end-[2px] top-[2px] flex h-5 w-5 items-center justify-center rounded-full bg-black/50 hover:bg-red/70 text-white backdrop-blur-md transition-colors hover:bg-black cursor-pointer"
                       >
                         <XIcon
                           
@@ -1185,7 +1185,7 @@ export const ImagePromptRow = ({
                         preload="metadata"
                         className="h-full w-full object-cover"
                       />
-                      <div className="absolute bottom-0 left-0 right-0 flex items-center justify-center bg-black/70 py-0.5 text-[10px] font-bold text-white">
+                      <div className="absolute bottom-0 start-0 end-0 flex items-center justify-center bg-black/70 py-0.5 text-[10px] font-bold text-white">
                         {video.duration}s
                       </div>
                       <button
@@ -1193,7 +1193,7 @@ export const ImagePromptRow = ({
                           e.stopPropagation();
                           handleRemoveVideo(video.id);
                         }}
-                        className="opacity-0 group-hover:opacity-100 absolute right-[2px] top-[2px] flex h-5 w-5 items-center justify-center rounded-full bg-black/50 hover:bg-red/70 text-white backdrop-blur-md transition-colors hover:bg-black cursor-pointer"
+                        className="opacity-0 group-hover:opacity-100 absolute end-[2px] top-[2px] flex h-5 w-5 items-center justify-center rounded-full bg-black/50 hover:bg-red/70 text-white backdrop-blur-md transition-colors hover:bg-black cursor-pointer"
                       >
                         <XIcon
                           
diff --git a/frontend/libs/components/promptbox/src/lib/MentionChipMenu.tsx b/frontend/libs/components/promptbox/src/lib/MentionChipMenu.tsx
--- a/frontend/libs/components/promptbox/src/lib/MentionChipMenu.tsx
+++ b/frontend/libs/components/promptbox/src/lib/MentionChipMenu.tsx
@@ -222,7 +222,7 @@ export function MentionChipMenu({
               </span>
             </button>
             <div className="min-w-0">
-              <div className="truncate text-sm font-medium text-base-fg">
+              <div dir="auto" className="truncate text-sm font-medium text-base-fg">
                 {currentName}
               </div>
               <div className="text-[11px] text-base-fg/50">
@@ -233,12 +233,12 @@ export function MentionChipMenu({
           <div className="my-1 border-t border-ui-panel-border" />
           <button type="button" className={MENU_ROW} onClick={() => setView("replace")}>
             <RefreshCwIcon  className="h-3.5 w-3.5 opacity-60" />
-            <span className="flex-1 text-left">Replace</span>
+            <span className="flex-1 text-start">Replace</span>
             <span className="text-xs text-base-fg/40">{replaceItems.length}</span>
           </button>
           <button type="button" className={MENU_ROW} onClick={onPreview}>
             <EyeIcon  className="h-3.5 w-3.5 opacity-60" />
-            <span className="flex-1 text-left">Preview</span>
+            <span className="flex-1 text-start">Preview</span>
           </button>
           <div className="my-1 border-t border-ui-panel-border" />
           <button
@@ -247,7 +247,7 @@ export function MentionChipMenu({
             onClick={onRemove}
           >
             <Trash2Icon  className="h-3.5 w-3.5 opacity-60" />
-            <span className="flex-1 text-left">Remove</span>
+            <span className="flex-1 text-start">Remove</span>
           </button>
         </>
       ) : (
@@ -264,7 +264,7 @@ export function MentionChipMenu({
             <div className="min-w-0">
               <div className="text-sm font-medium text-base-fg">Replace</div>
               <div className="truncate text-[11px] text-base-fg/50">
-                Currently {currentName}
+                Currently <bdi>{currentName}</bdi>
               </div>
             </div>
           </div>
@@ -288,7 +288,7 @@ export function MentionChipMenu({
                   name={item.label}
                   type={item.type}
                 />
-                <span className="min-w-0 flex-1 truncate text-left">
+                <span dir="auto" className="min-w-0 flex-1 truncate text-start">
                   {item.label.replace(/^@/, "")}
                 </span>
               </button>
diff --git a/frontend/libs/components/promptbox/src/lib/MentionTextarea.tsx b/frontend/libs/components/promptbox/src/lib/MentionTextarea.tsx
--- a/frontend/libs/components/promptbox/src/lib/MentionTextarea.tsx
+++ b/frontend/libs/components/promptbox/src/lib/MentionTextarea.tsx
@@ -1585,6 +1585,7 @@ export const MentionTextarea = forwardRef<HTMLDivElement, MentionTextareaProps>(
 
         <div
           ref={editorRef}
+          dir="auto"
           contentEditable={!disabled}
           onInput={handleInput}
           onCompositionStart={handleCompositionStart}
diff --git a/frontend/libs/components/promptbox/src/lib/PromptBox2D.tsx b/frontend/libs/components/promptbox/src/lib/PromptBox2D.tsx
--- a/frontend/libs/components/promptbox/src/lib/PromptBox2D.tsx
+++ b/frontend/libs/components/promptbox/src/lib/PromptBox2D.tsx
@@ -430,10 +430,11 @@ export const PromptBox2D = ({
 
             <div className="promptbox-resize-wrap relative flex-1">
               <textarea
+                dir="auto"
                 ref={textareaRef}
                 rows={1}
                 placeholder="Describe your image..."
-                className={`promptbox-scrollbar text-md min-h-[2.5em] w-full resize-y overflow-y-auto bg-transparent pr-8 text-base-fg placeholder-base-fg/60 focus:outline-none ${isExpanded ? "max-h-[500px]" : "max-h-[5.5em]"}`}
+                className={`promptbox-scrollbar text-md min-h-[2.5em] w-full resize-y overflow-y-auto bg-transparent pr-8 rtl:pr-0 rtl:pl-8 text-base-fg placeholder-base-fg/60 focus:outline-none ${isExpanded ? "max-h-[500px]" : "max-h-[5.5em]"}`}
                 value={prompt}
                 onChange={handleChange}
                 onPaste={handlePaste}
@@ -443,7 +444,7 @@ export const PromptBox2D = ({
               />
               <PromptFullscreenButton onClick={openFullscreen} />
               <span
-                className={`pointer-events-none absolute -bottom-1 right-4 text-[10px] tabular-nums ${isFinite(maxLen) && prompt.length > maxLen ? "text-red-500" : "text-base-fg/40"}`}
+                className={`pointer-events-none absolute -bottom-1 end-4 text-[10px] tabular-nums ${isFinite(maxLen) && prompt.length > maxLen ? "text-red-500" : "text-base-fg/40"}`}
               >
                 {prompt.length} / {isFinite(maxLen) ? maxLen : "∞"}
               </span>
@@ -586,6 +587,7 @@ export const PromptBox2D = ({
         }
       >
         <textarea
+          dir="auto"
           placeholder="Describe your image..."
           className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded-[3px] bg-transparent text-base-fg placeholder-base-fg/60 focus:outline-none"
           value={prompt}
diff --git a/frontend/libs/components/promptbox/src/lib/PromptBox3D.tsx b/frontend/libs/components/promptbox/src/lib/PromptBox3D.tsx
--- a/frontend/libs/components/promptbox/src/lib/PromptBox3D.tsx
+++ b/frontend/libs/components/promptbox/src/lib/PromptBox3D.tsx
@@ -728,10 +728,11 @@ export const PromptBox3D = ({
 
             <div className="promptbox-resize-wrap relative flex-1">
               <textarea
+                dir="auto"
                 ref={textareaRef}
                 rows={1}
                 placeholder="Describe your image..."
-                className={`promptbox-scrollbar text-md min-h-[2.5em] w-full resize-y overflow-y-auto bg-transparent pr-8 text-base-fg placeholder-base-fg/60 focus:outline-none ${isExpanded ? "max-h-[300px]" : "max-h-[5.5em]"}`}
+                className={`promptbox-scrollbar text-md min-h-[2.5em] w-full resize-y overflow-y-auto bg-transparent pr-8 rtl:pr-0 rtl:pl-8 text-base-fg placeholder-base-fg/60 focus:outline-none ${isExpanded ? "max-h-[300px]" : "max-h-[5.5em]"}`}
                 value={prompt}
                 onChange={handleChange}
                 onPaste={handlePaste}
@@ -747,7 +748,7 @@ export const PromptBox3D = ({
               />
               <PromptFullscreenButton onClick={openFullscreen} />
               <span
-                className={`pointer-events-none absolute -bottom-1 right-4 text-[10px] tabular-nums ${isFinite(maxLen) && prompt.length > maxLen ? "text-red-500" : "text-base-fg/40"}`}
+                className={`pointer-events-none absolute -bottom-1 end-4 text-[10px] tabular-nums ${isFinite(maxLen) && prompt.length > maxLen ? "text-red-500" : "text-base-fg/40"}`}
               >
                 {prompt.length} / {isFinite(maxLen) ? maxLen : "∞"}
               </span>
@@ -1034,6 +1035,7 @@ export const PromptBox3D = ({
         }
       >
         <textarea
+          dir="auto"
           placeholder="Describe your image..."
           className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded-[3px] bg-transparent text-base-fg placeholder-base-fg/60 focus:outline-none"
           value={prompt}
diff --git a/frontend/libs/components/promptbox/src/lib/PromptBoxAudio.tsx b/frontend/libs/components/promptbox/src/lib/PromptBoxAudio.tsx
--- a/frontend/libs/components/promptbox/src/lib/PromptBoxAudio.tsx
+++ b/frontend/libs/components/promptbox/src/lib/PromptBoxAudio.tsx
@@ -567,10 +567,11 @@ export const PromptBoxAudio = ({
           <div className="flex gap-3">
             <div className="promptbox-resize-wrap relative flex-1">
               <textarea
+                dir="auto"
                 ref={textareaRef}
                 rows={1}
                 placeholder="Describe the music or sound you want..."
-                className="promptbox-scrollbar text-md min-h-[2.5em] w-full resize-y overflow-y-auto bg-transparent pr-8 text-base-fg placeholder-base-fg/60 transition-[height] duration-200 ease-out focus:outline-none"
+                className="promptbox-scrollbar text-md min-h-[2.5em] w-full resize-y overflow-y-auto bg-transparent pr-8 rtl:pr-0 rtl:pl-8 text-base-fg placeholder-base-fg/60 transition-[height] duration-200 ease-out focus:outline-none"
                 value={prompt}
                 onChange={handleChange}
                 onPaste={handlePaste}
@@ -668,6 +669,7 @@ export const PromptBoxAudio = ({
         }
       >
         <textarea
+          dir="auto"
           placeholder="Describe the music or sound you want..."
           className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded-[3px] bg-transparent text-base-fg placeholder-base-fg/60 focus:outline-none"
           value={prompt}
diff --git a/frontend/libs/components/promptbox/src/lib/PromptBoxEdit.tsx b/frontend/libs/components/promptbox/src/lib/PromptBoxEdit.tsx
--- a/frontend/libs/components/promptbox/src/lib/PromptBoxEdit.tsx
+++ b/frontend/libs/components/promptbox/src/lib/PromptBoxEdit.tsx
@@ -425,10 +425,11 @@ export const PromptBoxEdit = ({
               )}
               <div className="promptbox-resize-wrap relative flex-1">
                 <textarea
+                  dir="auto"
                   ref={textareaRef}
                   rows={1}
                   placeholder="Write what you want to change in your image and click generate..."
-                  className={`promptbox-scrollbar text-md min-h-[2.5em] w-full resize-y overflow-y-auto bg-transparent pr-8 text-white placeholder-white placeholder:text-white/60 focus:outline-none ${isExpanded ? "max-h-[500px]" : "max-h-[5.5em]"}`}
+                  className={`promptbox-scrollbar text-md min-h-[2.5em] w-full resize-y overflow-y-auto bg-transparent pr-8 rtl:pr-0 rtl:pl-8 text-white placeholder-white placeholder:text-white/60 focus:outline-none ${isExpanded ? "max-h-[500px]" : "max-h-[5.5em]"}`}
                   value={prompt}
                   onChange={handleChange}
                   onPaste={handlePaste}
@@ -438,7 +439,7 @@ export const PromptBoxEdit = ({
                 />
                 <PromptFullscreenButton onClick={openFullscreen} />
                 <span
-                  className={`pointer-events-none absolute -bottom-1 right-4 text-[10px] tabular-nums ${isFinite(maxLen) && prompt.length > maxLen ? "text-red-500" : "text-white/40"}`}
+                  className={`pointer-events-none absolute -bottom-1 end-4 text-[10px] tabular-nums ${isFinite(maxLen) && prompt.length > maxLen ? "text-red-500" : "text-white/40"}`}
                 >
                   {prompt.length} / {isFinite(maxLen) ? maxLen : "∞"}
                 </span>
@@ -589,6 +590,7 @@ export const PromptBoxEdit = ({
         }
       >
         <textarea
+          dir="auto"
           placeholder="Write what you want to change in your image and click generate..."
           className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded-[3px] bg-transparent text-base-fg placeholder-base-fg/60 focus:outline-none"
           value={prompt}
diff --git a/frontend/libs/components/promptbox/src/lib/PromptBoxImage.tsx b/frontend/libs/components/promptbox/src/lib/PromptBoxImage.tsx
--- a/frontend/libs/components/promptbox/src/lib/PromptBoxImage.tsx
+++ b/frontend/libs/components/promptbox/src/lib/PromptBoxImage.tsx
@@ -484,10 +484,11 @@ export const PromptBoxImage = ({
 
             <div className="promptbox-resize-wrap relative flex-1">
               <textarea
+                dir="auto"
                 ref={textareaRef}
                 rows={1}
                 placeholder="Describe what you want in the image..."
-                className="promptbox-scrollbar text-md min-h-[2.5em] w-full resize-y overflow-y-auto bg-transparent pr-8 text-base-fg placeholder-base-fg/60 transition-[height] duration-200 ease-out focus:outline-none"
+                className="promptbox-scrollbar text-md min-h-[2.5em] w-full resize-y overflow-y-auto bg-transparent pr-8 rtl:pr-0 rtl:pl-8 text-base-fg placeholder-base-fg/60 transition-[height] duration-200 ease-out focus:outline-none"
                 value={prompt}
                 onChange={handleChange}
                 onPaste={handlePaste}
@@ -497,7 +498,7 @@ export const PromptBoxImage = ({
               />
               <PromptFullscreenButton onClick={openFullscreen} />
               <span
-                className={`pointer-events-none absolute -bottom-1 right-4 text-[10px] tabular-nums ${isFinite(maxLen) && prompt.length > maxLen ? "text-red-500" : "text-base-fg/40"}`}
+                className={`pointer-events-none absolute -bottom-1 end-4 text-[10px] tabular-nums ${isFinite(maxLen) && prompt.length > maxLen ? "text-red-500" : "text-base-fg/40"}`}
               >
                 {prompt.length} / {isFinite(maxLen) ? maxLen : "∞"}
               </span>
@@ -625,6 +626,7 @@ export const PromptBoxImage = ({
         }
       >
         <textarea
+          dir="auto"
           placeholder="Describe what you want in the image..."
           className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded-[3px] bg-transparent text-base-fg placeholder-base-fg/60 focus:outline-none"
           value={prompt}
diff --git a/frontend/libs/components/promptbox/src/lib/PromptBoxVideo.tsx b/frontend/libs/components/promptbox/src/lib/PromptBoxVideo.tsx
--- a/frontend/libs/components/promptbox/src/lib/PromptBoxVideo.tsx
+++ b/frontend/libs/components/promptbox/src/lib/PromptBoxVideo.tsx
@@ -1380,7 +1380,7 @@ export const PromptBoxVideo = ({
                       ? "Use @Image1, @Video1, @Audio1... to reference uploads in prompt..."
                       : "Describe what you want to happen in the video..."
                   }
-                  className="promptbox-scrollbar text-md relative min-h-[2.5em] w-full resize-y overflow-y-auto bg-transparent pr-8 text-base-fg"
+                  className="promptbox-scrollbar text-md relative min-h-[2.5em] w-full resize-y overflow-y-auto bg-transparent pr-8 rtl:pr-0 rtl:pl-8 text-base-fg"
                   onKeyDown={(e) => {
                     if (e.key !== "Enter") return;
                     const isSubmitCombo = enterToGenerate && !e.shiftKey;
@@ -1400,10 +1400,11 @@ export const PromptBoxVideo = ({
                 />
               ) : (
                 <textarea
+                  dir="auto"
                   ref={textareaRef}
                   rows={1}
                   placeholder="Describe what you want to happen in the video..."
-                  className="promptbox-scrollbar text-md relative min-h-[2.5em] w-full resize-y overflow-y-auto bg-transparent pr-8 text-base-fg placeholder-base-fg/60 focus:outline-none"
+                  className="promptbox-scrollbar text-md relative min-h-[2.5em] w-full resize-y overflow-y-auto bg-transparent pr-8 rtl:pr-0 rtl:pl-8 text-base-fg placeholder-base-fg/60 focus:outline-none"
                   value={prompt}
                   onChange={handleChange}
                   onPaste={handlePaste}
@@ -1414,7 +1415,7 @@ export const PromptBoxVideo = ({
               )}
               <PromptFullscreenButton onClick={openFullscreen} />
               <span
-                className={`pointer-events-none absolute -bottom-1 right-4 text-[10px] tabular-nums ${isFinite(maxLen) && prompt.length > maxLen ? "text-red-500" : "text-base-fg/40"}`}
+                className={`pointer-events-none absolute -bottom-1 end-4 text-[10px] tabular-nums ${isFinite(maxLen) && prompt.length > maxLen ? "text-red-500" : "text-base-fg/40"}`}
               >
                 {prompt.length} / {isFinite(maxLen) ? maxLen : "∞"}
               </span>
@@ -1679,6 +1680,7 @@ export const PromptBoxVideo = ({
           />
         ) : (
           <textarea
+            dir="auto"
             placeholder="Describe what you want to happen in the video..."
             className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded-[3px] bg-transparent text-base-fg placeholder-base-fg/60 focus:outline-none"
             value={prompt}
diff --git a/frontend/libs/components/promptbox/src/lib/PromptFullscreenButton.tsx b/frontend/libs/components/promptbox/src/lib/PromptFullscreenButton.tsx
--- a/frontend/libs/components/promptbox/src/lib/PromptFullscreenButton.tsx
+++ b/frontend/libs/components/promptbox/src/lib/PromptFullscreenButton.tsx
@@ -19,7 +19,7 @@ export const PromptFullscreenButton = ({
   className,
 }: PromptFullscreenButtonProps) => {
   return (
-    <div className={`absolute right-0 top-0 z-10 ${className ?? ""}`}>
+    <div className={`absolute end-0 top-0 z-10 ${className ?? ""}`}>
       <Tooltip content="Focus mode" position="top" delay={200}>
         <button
           type="button"
diff --git a/frontend/libs/components/promptbox/src/lib/common/AudioReferenceRow.tsx b/frontend/libs/components/promptbox/src/lib/common/AudioReferenceRow.tsx
--- a/frontend/libs/components/promptbox/src/lib/common/AudioReferenceRow.tsx
+++ b/frontend/libs/components/promptbox/src/lib/common/AudioReferenceRow.tsx
@@ -220,7 +220,7 @@ export const AudioReferenceRow = forwardRef<
               ({referenceAudios.length}/{maxAudioCount})
             </span>
             {audioRequired && referenceAudios.length === 0 && (
-              <span className="ml-1.5 text-xs font-medium text-red-500">
+              <span className="ms-1.5 text-xs font-medium text-red-500">
                 required
               </span>
             )}
@@ -346,7 +346,7 @@ function AudioRefTile({
       >
         <DynamicIcon
           icon={isPlaying ? SquareIcon : PlayIcon}
-          className={`h-2 w-2 ${isPlaying ? "" : "ml-px"}`}
+          className={`h-2 w-2 ${isPlaying ? "" : "ms-px"}`}
         />
       </button>
       <span className="text-xs font-medium text-base-fg/80">
diff --git a/frontend/libs/components/promptbox/src/lib/deck/DeckCard.tsx b/frontend/libs/components/promptbox/src/lib/deck/DeckCard.tsx
--- a/frontend/libs/components/promptbox/src/lib/deck/DeckCard.tsx
+++ b/frontend/libs/components/promptbox/src/lib/deck/DeckCard.tsx
@@ -143,18 +143,18 @@ export const DeckCard = ({
       )}
 
       {item.kind === "video" && (
-        <div className="pointer-events-none absolute left-[3px] top-[3px] flex h-4 w-4 items-center justify-center bg-black/60 text-white">
+        <div className="pointer-events-none absolute start-[3px] top-[3px] flex h-4 w-4 items-center justify-center bg-black/60 text-white">
           <VideoIcon  className="h-2.5 w-2.5" />
         </div>
       )}
       {item.kind === "audio" && (
-        <div className="pointer-events-none absolute left-[3px] top-[3px] flex h-4 w-4 items-center justify-center bg-black/60 text-white">
+        <div className="pointer-events-none absolute start-[3px] top-[3px] flex h-4 w-4 items-center justify-center bg-black/60 text-white">
           <MusicIcon  className="h-2.5 w-2.5" />
         </div>
       )}
 
       {item.duration != null && (
-        <div className="pointer-events-none absolute bottom-0 left-0 right-0 flex items-center justify-center bg-black/70 py-0.5 text-[10px] font-bold text-white">
+        <div className="pointer-events-none absolute bottom-0 start-0 end-0 flex items-center justify-center bg-black/70 py-0.5 text-[10px] font-bold text-white">
           {item.duration}s
         </div>
       )}
@@ -176,7 +176,7 @@ export const DeckCard = ({
           }}
           onMouseDown={(e) => e.stopPropagation()}
           onPointerDown={(e) => e.stopPropagation()}
-          className="absolute right-[2px] top-[2px] flex h-5 w-5 cursor-pointer items-center justify-center rounded-full bg-black/50 text-white opacity-0 backdrop-blur-md transition-colors hover:bg-red/70 group-hover:opacity-100"
+          className="absolute end-[2px] top-[2px] flex h-5 w-5 cursor-pointer items-center justify-center rounded-full bg-black/50 text-white opacity-0 backdrop-blur-md transition-colors hover:bg-red/70 group-hover:opacity-100"
         >
           <XIcon  className="h-2.5 w-2.5" />
         </button>
@@ -235,7 +235,7 @@ export const DeckAddMenu = ({
               key={action.key}
               type="button"
               onClick={action.onSelect}
-              className="flex w-full items-center gap-2.5 rounded-[3px] px-2.5 py-1.5 text-left text-[13px] font-medium text-base-fg transition-colors hover:bg-white/10"
+              className="flex w-full items-center gap-2.5 rounded-[3px] px-2.5 py-1.5 text-start text-[13px] font-medium text-base-fg transition-colors hover:bg-white/10"
             >
               <DynamicIcon
                 icon={
diff --git a/frontend/libs/components/promptbox/src/lib/deck/ReferenceDeck.tsx b/frontend/libs/components/promptbox/src/lib/deck/ReferenceDeck.tsx
--- a/frontend/libs/components/promptbox/src/lib/deck/ReferenceDeck.tsx
+++ b/frontend/libs/components/promptbox/src/lib/deck/ReferenceDeck.tsx
@@ -276,7 +276,7 @@ export const ReferenceDeck = ({
         alwaysExpanded
           ? "relative max-w-full"
           : twMerge(
-              "absolute bottom-0 left-0 z-40 origin-bottom-left transition-all duration-200 ease-out",
+              "absolute bottom-0 start-0 z-40 origin-bottom-left transition-all rtl:origin-bottom-right duration-200 ease-out",
               expanded
                 ? "visible translate-y-0 scale-100 opacity-100"
                 : "pointer-events-none invisible translate-y-1 scale-95 opacity-0 [transition:opacity_.2s,transform_.2s,visibility_0s_.2s]",
@@ -439,7 +439,7 @@ export const ReferenceDeck = ({
             />
           ))}
           {overflowCount > 0 && (
-            <div className="pointer-events-none absolute -right-1 -top-1 z-10 rounded-full bg-primary px-1.5 py-0.5 text-[10px] font-bold text-white shadow">
+            <div className="pointer-events-none absolute -end-1 -top-1 z-10 rounded-full bg-primary px-1.5 py-0.5 text-[10px] font-bold text-white shadow">
               {items.length}
             </div>
           )}
diff --git a/frontend/libs/components/select/src/lib/select.tsx b/frontend/libs/components/select/src/lib/select.tsx
--- a/frontend/libs/components/select/src/lib/select.tsx
+++ b/frontend/libs/components/select/src/lib/select.tsx
@@ -38,14 +38,14 @@ export const Select = ({
           <>
             <ListboxButton
               id={id}
-              className="relative h-10 w-full cursor-pointer rounded-[3px] bg-ui-controls text-base-fg border border-ui-controls-border py-2 pl-3 pr-10 text-left outline-none outline-offset-0 transition-colors duration-150 ease-in-out sm:text-sm focus:!outline-none hover:border-white/40"
+              className="relative h-10 w-full cursor-pointer rounded-[3px] bg-ui-controls text-base-fg border border-ui-controls-border py-2 ps-3 pe-10 text-start outline-none outline-offset-0 transition-colors duration-150 ease-in-out sm:text-sm focus:!outline-none hover:border-white/40"
             >
               <span
                 className={twMerge("block truncate", !value && "opacity-50")}
               >
                 {selectedOption.label}
               </span>
-              <span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2.5">
+              <span className="pointer-events-none absolute inset-y-0 end-0 flex items-center pe-2.5">
                 <ChevronDownIcon aria-hidden="true" />
               </span>
             </ListboxButton>
@@ -63,7 +63,7 @@ export const Select = ({
                     key={itemIdx}
                     className={({ focus, selected }) =>
                       twMerge(
-                        "relative cursor-pointer select-none py-2 pl-7 pr-2 transition-colors duration-150 ease-in-out",
+                        "relative cursor-pointer select-none py-2 ps-7 pe-2 transition-colors duration-150 ease-in-out",
                         !selected ? "text-base-fg/90" : "text-base-fg",
                         selected && "bg-white/10",
                         focus && "bg-white text-black",
@@ -81,7 +81,7 @@ export const Select = ({
                           {option.label}
                         </span>
                         {selected ? (
-                          <span className="absolute inset-y-0 left-0 flex items-center pl-2.5">
+                          <span className="absolute inset-y-0 start-0 flex items-center ps-2.5">
                             <CheckIcon aria-hidden="true" className="text-xs" />
                           </span>
                         ) : null}
diff --git a/frontend/libs/components/settings-modal/src/lib/panes/AccountSettings/ArtcraftAccountBlock.tsx b/frontend/libs/components/settings-modal/src/lib/panes/AccountSettings/ArtcraftAccountBlock.tsx
--- a/frontend/libs/components/settings-modal/src/lib/panes/AccountSettings/ArtcraftAccountBlock.tsx
+++ b/frontend/libs/components/settings-modal/src/lib/panes/AccountSettings/ArtcraftAccountBlock.tsx
@@ -69,7 +69,7 @@ export const ArtcraftAccountBlock = ({
         <p className="font-mono text-[11px] font-semibold uppercase tracking-[0.12em]">
           ArtCraft account
         </p>
-        <p className="truncate text-sm font-medium text-base-fg/80">
+        <p dir="auto" className="truncate text-sm font-medium text-base-fg/80">
           {artcraftSession?.display_name || "Not logged in"}
         </p>
       </div>
diff --git a/frontend/libs/components/settings-modal/src/lib/panes/AppearanceSettingsPane.tsx b/frontend/libs/components/settings-modal/src/lib/panes/AppearanceSettingsPane.tsx
--- a/frontend/libs/components/settings-modal/src/lib/panes/AppearanceSettingsPane.tsx
+++ b/frontend/libs/components/settings-modal/src/lib/panes/AppearanceSettingsPane.tsx
@@ -90,7 +90,7 @@ export const AppearanceSettingsPane = () => {
             <button
               key={t.id}
               className={twMerge(
-                "group flex items-center gap-3 border p-2 text-left transition-colors duration-100",
+                "group flex items-center gap-3 border p-2 text-start transition-colors duration-100",
                 isSelected
                   ? "border-base-fg/70 bg-base-fg/10"
                   : "border-ui-panel-border bg-base-fg/[0.03] hover:border-base-fg/30 hover:bg-base-fg/[0.06]",
@@ -103,7 +103,7 @@ export const AppearanceSettingsPane = () => {
                 {t.label}
               </span>
               {isSelected && (
-                <CheckIcon aria-hidden className="mr-1 h-3.5 w-3.5" />
+                <CheckIcon aria-hidden className="me-1 h-3.5 w-3.5" />
               )}
             </button>
           );
diff --git a/frontend/libs/components/settings-modal/src/lib/settings-modal.tsx b/frontend/libs/components/settings-modal/src/lib/settings-modal.tsx
--- a/frontend/libs/components/settings-modal/src/lib/settings-modal.tsx
+++ b/frontend/libs/components/settings-modal/src/lib/settings-modal.tsx
@@ -138,7 +138,7 @@ export const SettingsModal = ({
       >
         <div className="h-[600px]">
           <div className="grid h-full grid-cols-12 gap-3">
-            <div className="relative col-span-4 p-3 pt-2 after:absolute after:right-0 after:top-0 after:h-full after:w-px after:bg-ui-panel-border">
+            <div className="relative col-span-4 p-3 pt-2 after:absolute after:end-0 after:top-0 after:h-full after:w-px after:bg-ui-panel-border">
               <div className="flex items-center justify-between gap-2.5 py-0.5">
                 <h2 className="font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-base-fg/60">
                   Settings
@@ -150,7 +150,7 @@ export const SettingsModal = ({
                   <button
                     key={section.id}
                     className={twMerge(
-                      "h-9 w-full px-2 text-left transition-colors duration-100",
+                      "h-9 w-full px-2 text-start transition-colors duration-100",
                       section.id === selectedSection
                         ? "bg-base-fg/10 text-base-fg"
                         : "text-base-fg/70 hover:bg-base-fg/[0.06] hover:text-base-fg",
diff --git a/frontend/libs/components/switch/src/lib/switch.tsx b/frontend/libs/components/switch/src/lib/switch.tsx
--- a/frontend/libs/components/switch/src/lib/switch.tsx
+++ b/frontend/libs/components/switch/src/lib/switch.tsx
@@ -31,8 +31,8 @@ export function Switch({ enabled, setEnabled, id, label = "Enable notifications"
             className={clsx(
               "size-4 rounded-[2px] transition",
               checked
-                ? "translate-x-[22px] bg-black"
-                : "translate-x-1 bg-white/70",
+                ? "translate-x-[22px] bg-black rtl:-translate-x-[22px]"
+                : "translate-x-1 bg-white/70 rtl:-translate-x-1",
             )}
           />
         </button>
diff --git a/frontend/libs/components/upload-modal/src/lib/UploadModal3D/UploadFiles3D.tsx b/frontend/libs/components/upload-modal/src/lib/UploadModal3D/UploadFiles3D.tsx
--- a/frontend/libs/components/upload-modal/src/lib/UploadModal3D/UploadFiles3D.tsx
+++ b/frontend/libs/components/upload-modal/src/lib/UploadModal3D/UploadFiles3D.tsx
@@ -610,7 +610,7 @@ export const UploadFiles3D = ({
   // chrome. Shared by the single and multi layouts.
   const animationPicker = (previewAnimations.length > 0 ||
     previewHasRig) && (
-    <div className="pointer-events-auto absolute right-2 top-2 z-10 flex items-start gap-2">
+    <div className="pointer-events-auto absolute end-2 top-2 z-10 flex items-start gap-2">
       {previewHasRig && (
         <button
           type="button"
@@ -665,12 +665,12 @@ export const UploadFiles3D = ({
   // or when its conversion failed (the sidebar only exists in multi mode).
   const currentEntry = fileEntries[previewIndex];
   const convertingOverlay = isConvertingCurrent ? (
-    <h6 className="pointer-events-none absolute left-0 top-1/2 -mt-5 flex w-full items-center justify-center gap-2.5 text-center opacity-60">
+    <h6 className="pointer-events-none absolute start-0 top-1/2 -mt-5 flex w-full items-center justify-center gap-2.5 text-center opacity-60">
       <LoaderIcon  className="animate-spin" />
       Converting FBX to GLB...
     </h6>
   ) : currentEntry?.status === "error" && isFbx(currentEntry.file) ? (
-    <h6 className="pointer-events-none absolute left-0 top-1/2 -mt-5 w-full px-4 text-center text-red-400">
+    <h6 className="pointer-events-none absolute start-0 top-1/2 -mt-5 w-full px-4 text-center text-red-400">
       {currentEntry.errorMessage ?? "FBX conversion failed."}
     </h6>
   ) : null;
@@ -706,7 +706,7 @@ export const UploadFiles3D = ({
                 }`}
                 onClick={() => setPreviewIndex(i)}
               >
-                <span className="flex-1 truncate" title={entry.file.name}>
+                <span dir="auto" className="flex-1 truncate" title={entry.file.name}>
                   {entry.file.name.slice(0, entry.file.name.lastIndexOf("."))}
                 </span>
                 <span className="shrink-0">
@@ -773,13 +773,13 @@ export const UploadFiles3D = ({
               {animationPicker}
               {convertingOverlay}
               {!currentFile && (
-                <h6 className="pointer-events-auto absolute left-0 top-1/2 -mt-5 flex w-full items-center justify-center gap-2.5 text-center opacity-50">
+                <h6 className="pointer-events-auto absolute start-0 top-1/2 -mt-5 flex w-full items-center justify-center gap-2.5 text-center opacity-50">
                   <BoxIcon />
                   Your model preview will appear here
                 </h6>
               )}
               {previewStatus.type.includes("Error") && (
-                <h6 className="pointer-events-auto absolute left-0 top-1/2 -mt-5 w-full text-center">
+                <h6 className="pointer-events-auto absolute start-0 top-1/2 -mt-5 w-full text-center">
                   {previewStatus.type}
                   {previewStatus.message && <br />}
                   {previewStatus.message}
@@ -821,13 +821,13 @@ export const UploadFiles3D = ({
           {animationPicker}
           {convertingOverlay}
           {!currentFile && (
-            <h6 className="pointer-events-auto absolute left-0 top-1/2 -mt-5 flex w-full items-center justify-center gap-2.5 text-center opacity-50">
+            <h6 className="pointer-events-auto absolute start-0 top-1/2 -mt-5 flex w-full items-center justify-center gap-2.5 text-center opacity-50">
               <BoxIcon />
               Your model preview will appear here
             </h6>
           )}
           {previewStatus.type.includes("Error") && (
-            <h6 className="pointer-events-auto absolute left-0 top-1/2 -mt-5 w-full text-center">
+            <h6 className="pointer-events-auto absolute start-0 top-1/2 -mt-5 w-full text-center">
               {previewStatus.type}
               {previewStatus.message && <br />}
               {previewStatus.message}
diff --git a/frontend/libs/components/upload-modal/src/lib/UploadModalImage/UploadFilesImage.tsx b/frontend/libs/components/upload-modal/src/lib/UploadModalImage/UploadFilesImage.tsx
--- a/frontend/libs/components/upload-modal/src/lib/UploadModalImage/UploadFilesImage.tsx
+++ b/frontend/libs/components/upload-modal/src/lib/UploadModalImage/UploadFilesImage.tsx
@@ -213,7 +213,7 @@ export const UploadFilesImage = ({
                   }`}
                   onClick={() => setPreviewIndex(i)}
                 >
-                  <span className="truncate flex-1" title={entry.file.name}>
+                  <span dir="auto" className="truncate flex-1" title={entry.file.name}>
                     {entry.file.name.slice(0, entry.file.name.lastIndexOf("."))}
                   </span>
                   <span className="shrink-0">
diff --git a/frontend/libs/components/upload-modal/src/lib/UploadModalSplat/UploadFilesSplat.tsx b/frontend/libs/components/upload-modal/src/lib/UploadModalSplat/UploadFilesSplat.tsx
--- a/frontend/libs/components/upload-modal/src/lib/UploadModalSplat/UploadFilesSplat.tsx
+++ b/frontend/libs/components/upload-modal/src/lib/UploadModalSplat/UploadFilesSplat.tsx
@@ -342,7 +342,7 @@ export const UploadFilesSplat = ({
                 }`}
                 onClick={() => setPreviewIndex(i)}
               >
-                <span className="truncate flex-1" title={entry.file.name}>
+                <span dir="auto" className="truncate flex-1" title={entry.file.name}>
                   {entry.file.name.slice(0, entry.file.name.lastIndexOf("."))}
                 </span>
                 <span className="shrink-0">
@@ -404,13 +404,13 @@ export const UploadFilesSplat = ({
                 ref={canvasCallbackRef}
               />
               {!currentFile && (
-                <h6 className="pointer-events-auto absolute left-0 top-1/2 -mt-5 flex w-full items-center justify-center gap-2.5 text-center opacity-50">
+                <h6 className="pointer-events-auto absolute start-0 top-1/2 -mt-5 flex w-full items-center justify-center gap-2.5 text-center opacity-50">
                   <BoxIcon />
                   Your model preview will appear here
                 </h6>
               )}
               {previewStatus.type.includes("Error") && (
-                <h6 className="pointer-events-auto absolute left-0 top-1/2 -mt-5 w-full text-center">
+                <h6 className="pointer-events-auto absolute start-0 top-1/2 -mt-5 w-full text-center">
                   {previewStatus.type}
                   {previewStatus.message && <br />}
                   {previewStatus.message}
@@ -450,13 +450,13 @@ export const UploadFilesSplat = ({
             ref={canvasCallbackRef}
           />
           {!currentFile && (
-            <h6 className="pointer-events-auto absolute left-0 top-1/2 -mt-5 flex w-full items-center justify-center gap-2.5 text-center opacity-50">
+            <h6 className="pointer-events-auto absolute start-0 top-1/2 -mt-5 flex w-full items-center justify-center gap-2.5 text-center opacity-50">
               <BoxIcon />
               Your model preview will appear here
             </h6>
           )}
           {previewStatus.type.includes("Error") && (
-            <h6 className="pointer-events-auto absolute left-0 top-1/2 -mt-5 w-full text-center">
+            <h6 className="pointer-events-auto absolute start-0 top-1/2 -mt-5 w-full text-center">
               {previewStatus.type}
               {previewStatus.message && <br />}
               {previewStatus.message}
diff --git a/frontend/libs/components/vfx/src/lib/PromptBoxVFX.tsx b/frontend/libs/components/vfx/src/lib/PromptBoxVFX.tsx
--- a/frontend/libs/components/vfx/src/lib/PromptBoxVFX.tsx
+++ b/frontend/libs/components/vfx/src/lib/PromptBoxVFX.tsx
@@ -270,6 +270,7 @@ export const PromptBoxVFX = ({
               </button>
             </div>
             <textarea
+              dir="auto"
               ref={promptTextareaRef}
               value={prompt}
               onChange={(e) => setPrompt(e.target.value)}
@@ -400,7 +401,7 @@ export const PromptBoxVFX = ({
             </button>
           </Tooltip>
 
-          <div className="ml-auto flex items-center gap-2">
+          <div className="ms-auto flex items-center gap-2">
             <PromptClearAllButton
               onClick={handleClearAll}
               disabled={!hasClearableContent}
@@ -551,7 +552,7 @@ const UploadTile = ({
               <button
                 type="button"
                 onClick={onClear}
-                className="absolute right-1 top-1 flex h-5 w-5 items-center justify-center rounded-full bg-black/60 text-white opacity-0 transition-opacity hover:bg-red-500/70 group-hover:opacity-100"
+                className="absolute end-1 top-1 flex h-5 w-5 items-center justify-center rounded-full bg-black/60 text-white opacity-0 transition-opacity hover:bg-red-500/70 group-hover:opacity-100"
               >
                 <XIcon  className="h-2.5 w-2.5" />
               </button>
diff --git a/frontend/libs/components/vfx/src/lib/VFXResultCard.tsx b/frontend/libs/components/vfx/src/lib/VFXResultCard.tsx
--- a/frontend/libs/components/vfx/src/lib/VFXResultCard.tsx
+++ b/frontend/libs/components/vfx/src/lib/VFXResultCard.tsx
@@ -77,7 +77,7 @@ export const VFXResultCard = ({
           </div>
         ) : null}
         {data.title && data.status === "complete" && (
-          <div className="absolute left-3 top-3 rounded-md bg-black/60 px-2 py-1 text-xs font-medium uppercase tracking-wider text-white/80 backdrop-blur-sm">
+          <div className="absolute start-3 top-3 rounded-md bg-black/60 px-2 py-1 text-xs font-medium uppercase tracking-wider text-white/80 backdrop-blur-sm">
             Output
           </div>
         )}
@@ -180,7 +180,7 @@ const SidePanelMedia = ({ label, url, isVideo }: SidePanelMediaProps) => (
     ) : (
       <img src={url} alt={label} className="h-full w-full object-cover" />
     )}
-    <span className="absolute left-2 top-2 rounded bg-black/60 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wider text-white/80 backdrop-blur-sm">
+    <span className="absolute start-2 top-2 rounded bg-black/60 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wider text-white/80 backdrop-blur-sm">
       {label}
     </span>
   </div>
diff --git a/frontend/libs/components/video-editor/src/lib/components/editor/export-button.tsx b/frontend/libs/components/video-editor/src/lib/components/editor/export-button.tsx
--- a/frontend/libs/components/video-editor/src/lib/components/editor/export-button.tsx
+++ b/frontend/libs/components/video-editor/src/lib/components/editor/export-button.tsx
@@ -243,7 +243,7 @@ function ExportPopover({
   };
 
   return (
-    <PopoverContent className="bg-ui-controls mr-4 flex w-80 flex-col p-0">
+    <PopoverContent className="bg-ui-controls me-4 flex w-80 flex-col p-0">
       {exportResult && !exportResult.success ? (
         <ExportError
           error={exportResult.error || "Unknown error occurred"}
@@ -278,13 +278,13 @@ function ExportPopover({
                           }
                         }}
                       >
-                        <div className="flex items-center space-x-2">
+                        <div className="flex items-center space-x-2 rtl:space-x-reverse">
                           <RadioGroupItem value="mp4" id="mp4" />
                           <Label htmlFor="mp4">
                             MP4 (H.264) - Better compatibility
                           </Label>
                         </div>
-                        <div className="flex items-center space-x-2">
+                        <div className="flex items-center space-x-2 rtl:space-x-reverse">
                           <RadioGroupItem value="webm" id="webm" />
                           <Label htmlFor="webm">
                             WebM (VP9) - Smaller file size
@@ -307,19 +307,19 @@ function ExportPopover({
                           }
                         }}
                       >
-                        <div className="flex items-center space-x-2">
+                        <div className="flex items-center space-x-2 rtl:space-x-reverse">
                           <RadioGroupItem value="low" id="low" />
                           <Label htmlFor="low">Low - Smallest file size</Label>
                         </div>
-                        <div className="flex items-center space-x-2">
+                        <div className="flex items-center space-x-2 rtl:space-x-reverse">
                           <RadioGroupItem value="medium" id="medium" />
                           <Label htmlFor="medium">Medium - Balanced</Label>
                         </div>
-                        <div className="flex items-center space-x-2">
+                        <div className="flex items-center space-x-2 rtl:space-x-reverse">
                           <RadioGroupItem value="high" id="high" />
                           <Label htmlFor="high">High - Recommended</Label>
                         </div>
-                        <div className="flex items-center space-x-2">
+                        <div className="flex items-center space-x-2 rtl:space-x-reverse">
                           <RadioGroupItem value="very_high" id="very_high" />
                           <Label htmlFor="very_high">
                             Very high - Largest file size
@@ -334,7 +334,7 @@ function ExportPopover({
                       <SectionTitle>Audio</SectionTitle>
                     </SectionHeader>
                     <SectionContent>
-                      <div className="flex items-center space-x-2">
+                      <div className="flex items-center space-x-2 rtl:space-x-reverse">
                         <Checkbox
                           id="include-audio"
                           checked={shouldIncludeAudio}
@@ -355,7 +355,7 @@ function ExportPopover({
                     </SectionHeader>
                     <SectionContent>
                       <div className="flex flex-col gap-2">
-                        <div className="flex items-center space-x-2">
+                        <div className="flex items-center space-x-2 rtl:space-x-reverse">
                           <Checkbox
                             id="save-to-disk"
                             checked={saveToDisk}
@@ -367,7 +367,7 @@ function ExportPopover({
                             Save to disk
                           </Label>
                         </div>
-                        <div className="flex items-center space-x-2">
+                        <div className="flex items-center space-x-2 rtl:space-x-reverse">
                           <Checkbox
                             id="save-to-library"
                             checked={saveToLibrary}
@@ -515,7 +515,7 @@ function DestinationRow({
         </span>
       </div>
       {status === "error" && error && (
-        <p className="text-destructive ml-6 text-[11px]">{error}</p>
+        <p className="text-destructive ms-6 text-[11px]">{error}</p>
       )}
     </div>
   );
diff --git a/frontend/libs/components/video-editor/src/lib/components/editor/mobile-gate.tsx b/frontend/libs/components/video-editor/src/lib/components/editor/mobile-gate.tsx
--- a/frontend/libs/components/video-editor/src/lib/components/editor/mobile-gate.tsx
+++ b/frontend/libs/components/video-editor/src/lib/components/editor/mobile-gate.tsx
@@ -38,7 +38,7 @@ export function MobileGate({ children }: MobileGateProps) {
     <div className="bg-background relative flex h-full w-full flex-col overflow-hidden">
       <Button
         variant="text"
-        className="absolute top-6 left-6 flex items-center gap-1 text-muted-foreground"
+        className="absolute top-6 start-6 flex items-center gap-1 text-muted-foreground"
         onClick={handleGoBack}
       >
         <HugeiconsIcon icon={ArrowLeft01Icon} className="size-4" />
diff --git a/frontend/libs/components/video-editor/src/lib/components/editor/scenes-view.tsx b/frontend/libs/components/video-editor/src/lib/components/editor/scenes-view.tsx
--- a/frontend/libs/components/video-editor/src/lib/components/editor/scenes-view.tsx
+++ b/frontend/libs/components/video-editor/src/lib/components/editor/scenes-view.tsx
@@ -156,7 +156,7 @@ export function ScenesView({ children }: { children: React.ReactNode }) {
                   )}
                   onClick={() => handleSceneSwitch(scene.id)}
                 >
-                  <span>{scene.name}</span>
+                  <span dir="auto">{scene.name}</span>
                   <div className="flex items-center gap-2">
                     {((isSelectMode && selectedScenes.has(scene.id)) ||
                       (!isSelectMode && currentScene?.id === scene.id)) && (
diff --git a/frontend/libs/components/video-editor/src/lib/components/section.tsx b/frontend/libs/components/video-editor/src/lib/components/section.tsx
--- a/frontend/libs/components/video-editor/src/lib/components/section.tsx
+++ b/frontend/libs/components/video-editor/src/lib/components/section.tsx
@@ -135,7 +135,7 @@ export function SectionHeader({
   const innerContent = isInteractive ? (
     <button
       type="button"
-      className="min-w-0 flex-1 flex items-center gap-2 h-full cursor-pointer text-left"
+      className="min-w-0 flex-1 flex items-center gap-2 h-full cursor-pointer text-start"
       onClick={handleClick}
     >
       {leading}
diff --git a/frontend/libs/components/video-editor/src/lib/components/ui/alert.tsx b/frontend/libs/components/video-editor/src/lib/components/ui/alert.tsx
--- a/frontend/libs/components/video-editor/src/lib/components/ui/alert.tsx
+++ b/frontend/libs/components/video-editor/src/lib/components/ui/alert.tsx
@@ -5,7 +5,7 @@ import { HugeiconsIcon } from "@hugeicons/react";
 import { Alert02Icon } from "@hugeicons/core-free-icons";
 
 const alertVariants = cva(
-  "relative w-full rounded-lg border p-5 py-4.5 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-destructive [&>svg~*]:pl-7",
+  "relative w-full rounded-lg border p-5 py-4.5 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:start-4 [&>svg]:top-4 [&>svg]:text-destructive [&>svg~*]:ps-7",
   {
     variants: {
       variant: {
diff --git a/frontend/libs/components/video-editor/src/lib/components/ui/context-menu.tsx b/frontend/libs/components/video-editor/src/lib/components/ui/context-menu.tsx
--- a/frontend/libs/components/video-editor/src/lib/components/ui/context-menu.tsx
+++ b/frontend/libs/components/video-editor/src/lib/components/ui/context-menu.tsx
@@ -68,7 +68,7 @@ const ContextMenuSubTrigger = React.forwardRef<
       className={cn(
         contextMenuItemVariants({ variant }),
         "data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
-        inset && "pl-8",
+        inset && "ps-8",
         className,
       )}
       {...props}
@@ -79,7 +79,7 @@ const ContextMenuSubTrigger = React.forwardRef<
       {children}
       <HugeiconsIcon
         icon={ArrowRightIcon}
-        className="ml-auto text-muted-foreground/80"
+        className="ms-auto text-muted-foreground/80"
       />
     </ContextMenuPrimitive.SubTrigger>
   ),
@@ -148,19 +148,19 @@ const ContextMenuItem = React.forwardRef<
         ref={ref}
         className={cn(
           contextMenuItemVariants({ variant }),
-          shouldInsetContent && "pl-8",
+          shouldInsetContent && "ps-8",
           className,
         )}
         {...props}
       >
         {icon && (
-          <span className="absolute left-3 flex size-3.5 items-center justify-center text-muted-foreground [&_svg]:size-3.5 [&_svg]:shrink-0">
+          <span className="absolute start-3 flex size-3.5 items-center justify-center text-muted-foreground [&_svg]:size-3.5 [&_svg]:shrink-0">
             {icon}
           </span>
         )}
         {children}
         {textRight && (
-          <span className="ml-auto text-[0.60rem] tracking-widest text-muted-foreground/80 mb-0.5">
+          <span className="ms-auto text-[0.60rem] tracking-widest text-muted-foreground/80 mb-0.5">
             {textRight}
           </span>
         )}
@@ -185,13 +185,13 @@ const ContextMenuCheckboxItem = React.forwardRef<
       ref={ref}
       className={cn(
         contextMenuItemVariants({ variant }),
-        "pr-2 pl-8",
+        "pe-2 ps-8",
         className,
       )}
       checked={checked}
       {...props}
     >
-      <span className="absolute left-3 flex size-3.5 items-center justify-center">
+      <span className="absolute start-3 flex size-3.5 items-center justify-center">
         <ContextMenuPrimitive.ItemIndicator>
           <HugeiconsIcon icon={Tick02Icon} className="size-4" />
         </ContextMenuPrimitive.ItemIndicator>
@@ -215,10 +215,10 @@ const ContextMenuRadioItem = React.forwardRef<
 >(({ className, children, variant = "default", icon, ...props }, ref) => (
   <ContextMenuPrimitive.RadioItem
     ref={ref}
-    className={cn(contextMenuItemVariants({ variant }), "pr-2 pl-8", className)}
+    className={cn(contextMenuItemVariants({ variant }), "pe-2 ps-8", className)}
     {...props}
   >
-    <span className="absolute left-2 flex size-3.5 items-center justify-center">
+    <span className="absolute start-2 flex size-3.5 items-center justify-center">
       <ContextMenuPrimitive.ItemIndicator>
         <HugeiconsIcon icon={CircleIcon} className="size-2 fill-current" />
       </ContextMenuPrimitive.ItemIndicator>
@@ -242,7 +242,7 @@ const ContextMenuLabel = React.forwardRef<
     ref={ref}
     className={cn(
       "flex items-center gap-2 px-3 py-1.5 text-sm font-semibold text-foreground",
-      inset && "pl-8",
+      inset && "ps-8",
       className,
     )}
     {...props}
@@ -274,7 +274,7 @@ const ContextMenuShortcut = ({
   return (
     <span
       className={cn(
-        "ml-auto text-xs tracking-widest text-muted-foreground opacity-60",
+        "ms-auto text-xs tracking-widest text-muted-foreground opacity-60",
         className,
       )}
       {...props}
diff --git a/frontend/libs/components/video-editor/src/lib/components/ui/dialog.tsx b/frontend/libs/components/video-editor/src/lib/components/ui/dialog.tsx
--- a/frontend/libs/components/video-editor/src/lib/components/ui/dialog.tsx
+++ b/frontend/libs/components/video-editor/src/lib/components/ui/dialog.tsx
@@ -64,7 +64,7 @@ const DialogContent = React.forwardRef<
       {...props}
     >
       {children}
-      <DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-6 right-6 cursor-pointer opacity-70 hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
+      <DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-6 end-6 cursor-pointer opacity-70 hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
         <X className="size-5 text-muted-foreground" />
         <span className="sr-only">Close</span>
       </DialogPrimitive.Close>
@@ -79,7 +79,7 @@ const DialogHeader = ({
 }: React.HTMLAttributes<HTMLDivElement>) => (
   <div
     className={cn(
-      "flex flex-col space-y-2 text-left border-b border-ui-panel-border p-6",
+      "flex flex-col space-y-2 text-start border-b border-ui-panel-border p-6",
       className,
     )}
     {...props}
diff --git a/frontend/libs/components/video-editor/src/lib/components/ui/dropdown-menu.tsx b/frontend/libs/components/video-editor/src/lib/components/ui/dropdown-menu.tsx
--- a/frontend/libs/components/video-editor/src/lib/components/ui/dropdown-menu.tsx
+++ b/frontend/libs/components/video-editor/src/lib/components/ui/dropdown-menu.tsx
@@ -63,13 +63,13 @@ const DropdownMenuSubTrigger = React.forwardRef<
     className={cn(
       dropdownMenuItemVariants({ variant }),
       "data-[state=open]:bg-muted data-[state=open]:text-foreground",
-      inset && "pl-8",
+      inset && "ps-8",
       className,
     )}
     {...props}
   >
     {children}
-    <ChevronRight className="ml-auto" />
+    <ChevronRight className="ms-auto" />
   </DropdownMenuPrimitive.SubTrigger>
 ));
 DropdownMenuSubTrigger.displayName =
@@ -162,7 +162,7 @@ const DropdownMenuItem = React.forwardRef<
         data-has-icon={icon ? "" : undefined}
         className={cn(
           dropdownMenuItemVariants({ variant }),
-          inset && "pl-8",
+          inset && "ps-8",
           className,
         )}
         {...props}
@@ -184,7 +184,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
     ref={ref}
     className={cn(
       dropdownMenuItemVariants({ variant }),
-      "pr-8 pl-2",
+      "pe-8 ps-2",
       className,
     )}
     checked={checked}
@@ -194,7 +194,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
     {...props}
   >
     {children}
-    <span className="absolute right-2 flex size-3.5 items-center justify-center">
+    <span className="absolute end-2 flex size-3.5 items-center justify-center">
       <DropdownMenuPrimitive.ItemIndicator>
         <Check className="size-4" />
       </DropdownMenuPrimitive.ItemIndicator>
@@ -215,12 +215,12 @@ const DropdownMenuRadioItem = React.forwardRef<
     ref={ref}
     className={cn(
       dropdownMenuItemVariants({ variant }),
-      "pr-2 pl-8",
+      "pe-2 ps-8",
       className,
     )}
     {...props}
   >
-    <span className="absolute left-2 flex size-3.5 items-center justify-center">
+    <span className="absolute start-2 flex size-3.5 items-center justify-center">
       <DropdownMenuPrimitive.ItemIndicator>
         <Circle className="size-2 fill-current" />
       </DropdownMenuPrimitive.ItemIndicator>
@@ -240,7 +240,7 @@ const DropdownMenuLabel = React.forwardRef<
     ref={ref}
     className={cn(
       "px-2 pb-1 pt-0.5 text-[11px] font-bold uppercase tracking-wider text-muted-foreground",
-      inset && "pl-8",
+      inset && "ps-8",
       className,
     )}
     {...props}
@@ -266,7 +266,7 @@ const DropdownMenuShortcut = ({
 }: React.HTMLAttributes<HTMLSpanElement>) => {
   return (
     <span
-      className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
+      className={cn("ms-auto text-xs tracking-widest opacity-60", className)}
       {...props}
     />
   );
diff --git a/frontend/libs/components/video-editor/src/lib/components/ui/font-picker.tsx b/frontend/libs/components/video-editor/src/lib/components/ui/font-picker.tsx
--- a/frontend/libs/components/video-editor/src/lib/components/ui/font-picker.tsx
+++ b/frontend/libs/components/video-editor/src/lib/components/ui/font-picker.tsx
@@ -117,14 +117,14 @@ export function FontPicker({
         }}
       >
         <div className="relative px-3 py-1.5">
-          <Search className="absolute left-3 top-1/2 -translate-y-1/2 size-3.5 shrink-0 opacity-50" />
+          <Search className="absolute start-3 top-1/2 -translate-y-1/2 size-3.5 shrink-0 opacity-50" />
           <Input
             ref={searchInputRef}
             placeholder={`Search ${activeTabLabel}...`}
             value={search}
             onChange={(event) => setSearch(event.target.value)}
             size="xs"
-            className="w-full pl-5 bg-transparent border-none! shadow-none!"
+            className="w-full ps-5 bg-transparent border-none! shadow-none!"
           />
         </div>
         <div className="flex border-b px-3">
diff --git a/frontend/libs/components/video-editor/src/lib/components/ui/input.tsx b/frontend/libs/components/video-editor/src/lib/components/ui/input.tsx
--- a/frontend/libs/components/video-editor/src/lib/components/ui/input.tsx
+++ b/frontend/libs/components/video-editor/src/lib/components/ui/input.tsx
@@ -75,7 +75,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
     const hasIcons = showPasswordToggle || showClear;
     const iconCount = Number(showPasswordToggle) + Number(showClear);
     const paddingRight =
-      iconCount === 2 ? "pr-20" : iconCount === 1 ? "pr-10" : "";
+      iconCount === 2 ? "pe-20" : iconCount === 1 ? "pe-10" : "";
 
     return (
       <div
@@ -110,7 +110,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
               e.preventDefault();
               onClear?.();
             }}
-            className="text-muted-foreground absolute top-0 right-0 h-full px-3 !opacity-100"
+            className="text-muted-foreground absolute top-0 end-0 h-full px-3 !opacity-100"
             aria-label="Clear input"
           >
             <X className="!size-[0.85]" />
@@ -123,7 +123,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
             onClick={() => onShowPasswordChange?.(!showPassword)}
             className={cn(
               "text-muted-foreground hover:text-foreground absolute top-0 h-full px-3",
-              showClear ? "right-10" : "right-0",
+              showClear ? "end-10" : "end-0",
             )}
             aria-label={showPassword ? "Hide password" : "Show password"}
           >
diff --git a/frontend/libs/components/video-editor/src/lib/components/ui/resizable.tsx b/frontend/libs/components/video-editor/src/lib/components/ui/resizable.tsx
--- a/frontend/libs/components/video-editor/src/lib/components/ui/resizable.tsx
+++ b/frontend/libs/components/video-editor/src/lib/components/ui/resizable.tsx
@@ -28,7 +28,7 @@ const ResizableHandle = ({
 }) => (
   <ResizablePrimitive.PanelResizeHandle
     className={cn(
-      "focus-visible:ring-ring relative flex w-px items-center justify-center bg-transparent after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:translate-x-0 data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90",
+      "focus-visible:ring-ring relative flex w-px items-center justify-center bg-transparent after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:start-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:translate-x-0 data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90",
       className,
     )}
     {...props}
diff --git a/frontend/libs/components/video-editor/src/lib/components/ui/select.tsx b/frontend/libs/components/video-editor/src/lib/components/ui/select.tsx
--- a/frontend/libs/components/video-editor/src/lib/components/ui/select.tsx
+++ b/frontend/libs/components/video-editor/src/lib/components/ui/select.tsx
@@ -185,10 +185,10 @@ const SelectItem = React.forwardRef<
 >(({ className, children, variant = "default", ...props }, ref) => (
   <SelectPrimitive.Item
     ref={ref}
-    className={cn(selectItemVariants({ variant }), "pl-6 pr-2", className)}
+    className={cn(selectItemVariants({ variant }), "ps-6 pe-2", className)}
     {...props}
   >
-    <span className="absolute left-1.5 flex size-3.5 items-center justify-center">
+    <span className="absolute start-1.5 flex size-3.5 items-center justify-center">
       <SelectPrimitive.ItemIndicator>
         <Check className="size-3.5" />
       </SelectPrimitive.ItemIndicator>
diff --git a/frontend/libs/components/video-editor/src/lib/components/ui/sheet.tsx b/frontend/libs/components/video-editor/src/lib/components/ui/sheet.tsx
--- a/frontend/libs/components/video-editor/src/lib/components/ui/sheet.tsx
+++ b/frontend/libs/components/video-editor/src/lib/components/ui/sheet.tsx
@@ -85,7 +85,7 @@ const SheetContent = React.forwardRef<
       }}
       {...props}
     >
-      <SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 cursor-pointer rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
+      <SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 end-4 cursor-pointer rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
         <X className="size-5" />
         <span className="sr-only">Close</span>
       </SheetPrimitive.Close>
@@ -101,7 +101,7 @@ const SheetHeader = ({
 }: React.HTMLAttributes<HTMLDivElement>) => (
   <div
     className={cn(
-      "flex flex-col space-y-2 text-center sm:text-left",
+      "flex flex-col space-y-2 text-center sm:text-start",
       className,
     )}
     {...props}
@@ -115,7 +115,7 @@ const SheetFooter = ({
 }: React.HTMLAttributes<HTMLDivElement>) => (
   <div
     className={cn(
-      "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-4",
+      "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-4 sm:rtl:space-x-reverse",
       className,
     )}
     {...props}
diff --git a/frontend/libs/components/video-editor/src/lib/components/ui/split-button.tsx b/frontend/libs/components/video-editor/src/lib/components/ui/split-button.tsx
--- a/frontend/libs/components/video-editor/src/lib/components/ui/split-button.tsx
+++ b/frontend/libs/components/video-editor/src/lib/components/ui/split-button.tsx
@@ -61,14 +61,14 @@ SplitButtonSide.displayName = "SplitButtonSide";
 
 const SplitButtonLeft = forwardRef<HTMLButtonElement, SplitButtonSideProps>(
   ({ ...props }, ref) => {
-    return <SplitButtonSide ref={ref} paddingClass="pl-3 pr-2" {...props} />;
+    return <SplitButtonSide ref={ref} paddingClass="ps-3 pe-2" {...props} />;
   },
 );
 SplitButtonLeft.displayName = "SplitButtonLeft";
 
 const SplitButtonRight = forwardRef<HTMLButtonElement, SplitButtonSideProps>(
   ({ ...props }, ref) => {
-    return <SplitButtonSide ref={ref} paddingClass="pl-2 pr-3" {...props} />;
+    return <SplitButtonSide ref={ref} paddingClass="ps-2 pe-3" {...props} />;
   },
 );
 SplitButtonRight.displayName = "SplitButtonRight";
diff --git a/frontend/libs/components/video-editor/src/lib/components/ui/switch.tsx b/frontend/libs/components/video-editor/src/lib/components/ui/switch.tsx
--- a/frontend/libs/components/video-editor/src/lib/components/ui/switch.tsx
+++ b/frontend/libs/components/video-editor/src/lib/components/ui/switch.tsx
@@ -19,7 +19,7 @@ const Switch = React.forwardRef<
   >
     <SwitchPrimitives.Thumb
       className={cn(
-        "bg-background pointer-events-none block size-4 rounded-full shadow-lg ring-0 data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0",
+        "bg-background pointer-events-none block size-4 rounded-full shadow-lg ring-0 data-[state=checked]:translate-x-4 rtl:data-[state=checked]:-translate-x-4 data-[state=unchecked]:translate-x-0",
       )}
     />
   </SwitchPrimitives.Root>
diff --git a/frontend/libs/components/video-editor/src/lib/components/ui/textarea.tsx b/frontend/libs/components/video-editor/src/lib/components/ui/textarea.tsx
--- a/frontend/libs/components/video-editor/src/lib/components/ui/textarea.tsx
+++ b/frontend/libs/components/video-editor/src/lib/components/ui/textarea.tsx
@@ -8,6 +8,7 @@ const Textarea = React.forwardRef<
 >(({ className, ...props }, ref) => {
   return (
     <textarea
+      dir="auto"
       className={cn(
         "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input bg-accent flex min-h-[60px] w-full rounded-md border px-3 py-2 resize-none text-base shadow-xs outline-none disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
         "focus-visible:border-primary focus-visible:ring-0 focus-visible:ring-primary/10",
diff --git a/frontend/libs/components/video-editor/src/lib/panels/assets/draggable-item.tsx b/frontend/libs/components/video-editor/src/lib/panels/assets/draggable-item.tsx
--- a/frontend/libs/components/video-editor/src/lib/panels/assets/draggable-item.tsx
+++ b/frontend/libs/components/video-editor/src/lib/panels/assets/draggable-item.tsx
@@ -126,7 +126,8 @@ export function DraggableItem({
             </AspectRatio>
             {shouldShowLabel && (
               <span
-                className="text-muted-foreground w-full truncate text-left text-[0.7rem]"
+                dir="auto"
+                className="text-muted-foreground w-full truncate text-start text-[0.7rem]"
                 title={name}
               >
                 <span className="sr-only">{name}</span>
@@ -158,7 +159,10 @@ export function DraggableItem({
             <div className="size-6 shrink-0 overflow-hidden rounded-sm">
               {preview}
             </div>
-            <span className="w-full flex-1 truncate text-sm text-left">
+            <span
+              dir="auto"
+              className="w-full flex-1 truncate text-sm text-start"
+            >
               {name}
             </span>
           </button>
@@ -212,7 +216,7 @@ function PlusButton({
     <Button
       size="icon"
       className={cn(
-        "bg-background hover:bg-background text-foreground absolute right-2 bottom-2 size-5",
+        "bg-background hover:bg-background text-foreground absolute end-2 bottom-2 size-5",
         className,
       )}
       onClick={(e) => {
diff --git a/frontend/libs/components/video-editor/src/lib/panels/assets/tabbar.tsx b/frontend/libs/components/video-editor/src/lib/panels/assets/tabbar.tsx
--- a/frontend/libs/components/video-editor/src/lib/panels/assets/tabbar.tsx
+++ b/frontend/libs/components/video-editor/src/lib/panels/assets/tabbar.tsx
@@ -97,7 +97,7 @@ function FadeOverlay({
   return (
     <div
       className={cn(
-        "pointer-events-none absolute right-0 left-0 h-6",
+        "pointer-events-none absolute end-0 start-0 h-6",
         direction === "top" && show
           ? "from-background top-0 bg-linear-to-b to-transparent"
           : "from-background bottom-0 bg-linear-to-t to-transparent",
diff --git a/frontend/libs/components/video-editor/src/lib/panels/assets/views/assets.tsx b/frontend/libs/components/video-editor/src/lib/panels/assets/views/assets.tsx
--- a/frontend/libs/components/video-editor/src/lib/panels/assets/views/assets.tsx
+++ b/frontend/libs/components/video-editor/src/lib/panels/assets/views/assets.tsx
@@ -498,7 +498,7 @@ function MediaDurationBadge({ duration }: { duration?: number }) {
   if (!duration) return null;
 
   return (
-    <div className="absolute right-1 bottom-1 rounded bg-black/70 px-1 text-xs text-white">
+    <div className="absolute end-1 bottom-1 rounded bg-black/70 px-1 text-xs text-white">
       {formatDuration({ duration })}
     </div>
   );
diff --git a/frontend/libs/components/video-editor/src/lib/panels/assets/views/base-panel.tsx b/frontend/libs/components/video-editor/src/lib/panels/assets/views/base-panel.tsx
--- a/frontend/libs/components/video-editor/src/lib/panels/assets/views/base-panel.tsx
+++ b/frontend/libs/components/video-editor/src/lib/panels/assets/views/base-panel.tsx
@@ -32,7 +32,7 @@ export function PanelView({
       {...rest}
     >
       {!hideHeader && (
-        <div className="bg-background h-11 shrink-0 pl-3 pr-2 flex items-center justify-between border-b border-ui-panel-border">
+        <div className="bg-background h-11 shrink-0 ps-3 pe-2 flex items-center justify-between border-b border-ui-panel-border">
           {title && (
             <span className="text-muted-foreground text-sm">{title}</span>
           )}
diff --git a/frontend/libs/components/video-editor/src/lib/panels/assets/views/settings/background.tsx b/frontend/libs/components/video-editor/src/lib/panels/assets/views/settings/background.tsx
--- a/frontend/libs/components/video-editor/src/lib/panels/assets/views/settings/background.tsx
+++ b/frontend/libs/components/video-editor/src/lib/panels/assets/views/settings/background.tsx
@@ -73,7 +73,7 @@ const BlurPreview = memo(
           ref={canvasRef}
           className="absolute inset-0 h-full w-full object-cover"
         />
-        <div className="absolute right-1 bottom-1 left-1 text-center">
+        <div className="absolute end-1 bottom-1 start-1 text-center">
           <span className="rounded bg-black/50 px-1 text-xs text-white">
             {blur.label}
           </span>
@@ -165,7 +165,7 @@ function CustomColorPreview({
             style={{ background: CUSTOM_COLOR_SWATCH_BACKGROUND }}
           />
           <span
-            className="absolute right-1 bottom-1 size-5 rounded-sm border border-white/70 shadow-sm"
+            className="absolute end-1 bottom-1 size-5 rounded-sm border border-white/70 shadow-sm"
             style={{ backgroundColor: currentBackgroundColor }}
           />
         </button>
diff --git a/frontend/libs/components/video-editor/src/lib/panels/assets/views/settings/index.tsx b/frontend/libs/components/video-editor/src/lib/panels/assets/views/settings/index.tsx
--- a/frontend/libs/components/video-editor/src/lib/panels/assets/views/settings/index.tsx
+++ b/frontend/libs/components/video-editor/src/lib/panels/assets/views/settings/index.tsx
@@ -234,7 +234,7 @@ export function SettingsView() {
           <Section showTopBorder={false}>
             <SectionHeader>
               <SectionTitle className="flex-1">Name</SectionTitle>
-              <span className="text-sm truncate">
+              <span dir="auto" className="text-sm truncate">
                 {activeProject.metadata.name}
               </span>
             </SectionHeader>
diff --git a/frontend/libs/components/video-editor/src/lib/panels/properties/index.tsx b/frontend/libs/components/video-editor/src/lib/panels/properties/index.tsx
--- a/frontend/libs/components/video-editor/src/lib/panels/properties/index.tsx
+++ b/frontend/libs/components/video-editor/src/lib/panels/properties/index.tsx
@@ -64,7 +64,7 @@ export function PropertiesPanel() {
   return (
     <div className="panel bg-background flex h-full overflow-hidden rounded-lg border border-ui-panel-border">
       <TooltipProvider delayDuration={0}>
-        <div className="flex shrink-0 flex-col gap-0.5 border-r border-ui-panel-border p-1 scrollbar-hidden overflow-y-auto">
+        <div className="flex shrink-0 flex-col gap-0.5 border-e border-ui-panel-border p-1 scrollbar-hidden overflow-y-auto">
           {visibleTabs.map((tab) => (
             <Tooltip key={tab.id}>
               <TooltipTrigger asChild>
diff --git a/frontend/libs/components/video-editor/src/lib/project/components/delete-project-dialog.tsx b/frontend/libs/components/video-editor/src/lib/project/components/delete-project-dialog.tsx
--- a/frontend/libs/components/video-editor/src/lib/project/components/delete-project-dialog.tsx
+++ b/frontend/libs/components/video-editor/src/lib/project/components/delete-project-dialog.tsx
@@ -39,7 +39,10 @@ export function DeleteProjectDialog({
             {singleName ? (
               <>
                 {"Delete '"}
-                <span className="inline-block max-w-[300px] truncate align-bottom">
+                <span
+                  dir="auto"
+                  className="inline-block max-w-[300px] truncate align-bottom"
+                >
                   {singleName}
                 </span>
                 {"'?"}
@@ -54,8 +57,14 @@ export function DeleteProjectDialog({
             <AlertTitle>Warning</AlertTitle>
             <AlertDescription>
               This will permanently delete{" "}
-              {singleName ? `"${singleName}"` : `${count} projects`} and all
-              associated files.
+              {singleName ? (
+                <>
+                  "<bdi>{singleName}</bdi>"
+                </>
+              ) : (
+                `${count} projects`
+              )}{" "}
+              and all associated files.
             </AlertDescription>
           </Alert>
           <div className="flex flex-col gap-3">
diff --git a/frontend/libs/components/video-editor/src/lib/sounds/components/assets-view.tsx b/frontend/libs/components/video-editor/src/lib/sounds/components/assets-view.tsx
--- a/frontend/libs/components/video-editor/src/lib/sounds/components/assets-view.tsx
+++ b/frontend/libs/components/video-editor/src/lib/sounds/components/assets-view.tsx
@@ -510,7 +510,7 @@ function AudioItem({ sound, isPlaying, onPlay }: AudioItemProps) {
     <div className="group flex items-center gap-3 opacity-100 hover:opacity-75">
       <button
         type="button"
-        className="flex min-w-0 flex-1 items-center gap-3 text-left"
+        className="flex min-w-0 flex-1 items-center gap-3 text-start"
         onClick={handleClick}
       >
         <div className="bg-accent relative flex size-12 shrink-0 items-center justify-center overflow-hidden rounded-md">
@@ -523,14 +523,19 @@ function AudioItem({ sound, isPlaying, onPlay }: AudioItemProps) {
         </div>
 
         <div className="min-w-0 flex-1 overflow-hidden">
-          <p className="truncate text-sm font-medium">{sound.name}</p>
-          <span className="text-muted-foreground block truncate text-xs">
+          <p dir="auto" className="truncate text-sm font-medium">
+            {sound.name}
+          </p>
+          <span
+            dir="auto"
+            className="text-muted-foreground block truncate text-xs"
+          >
             {sound.username}
           </span>
         </div>
       </button>
 
-      <div className="flex items-center gap-3 pr-2">
+      <div className="flex items-center gap-3 pe-2">
         <Button
           variant="text"
           size="icon"
diff --git a/frontend/libs/components/video-editor/src/lib/stickers/components/assets-view.tsx b/frontend/libs/components/video-editor/src/lib/stickers/components/assets-view.tsx
--- a/frontend/libs/components/video-editor/src/lib/stickers/components/assets-view.tsx
+++ b/frontend/libs/components/video-editor/src/lib/stickers/components/assets-view.tsx
@@ -276,7 +276,7 @@ function StickerSection({
             <div />
           )}
 
-          <div className="ml-auto flex items-center gap-2">
+          <div className="ms-auto flex items-center gap-2">
             {section.id === "recent" && (
               <Button
                 onClick={onClearRecent}
diff --git a/frontend/libs/components/video-editor/src/lib/timeline/components/timeline-element.tsx b/frontend/libs/components/video-editor/src/lib/timeline/components/timeline-element.tsx
--- a/frontend/libs/components/video-editor/src/lib/timeline/components/timeline-element.tsx
+++ b/frontend/libs/components/video-editor/src/lib/timeline/components/timeline-element.tsx
@@ -1042,7 +1042,7 @@ function AudioElementContent({
   return (
     <div className="group/audio relative size-full">
       <div className="flex size-full items-center pl-2">
-        <span className="text-foreground/80 truncate text-xs">
+        <span dir="auto" className="text-foreground/80 truncate text-xs">
           {element.name}
         </span>
       </div>
@@ -1098,7 +1098,7 @@ function TiledMediaContent({
 
   if (!imageUrl) {
     return (
-      <span className="text-foreground/80 truncate text-xs">
+      <span dir="auto" className="text-foreground/80 truncate text-xs">
         {element.name}
       </span>
     );
@@ -1155,7 +1155,7 @@ function MediaElementHeader({
     >
       {leading && <div className="pl-1">{leading}</div>}
       {name && (
-        <span className="truncate px-1.5 text-[0.6rem] leading-tight text-white/75">
+        <span dir="auto" className="truncate px-1.5 text-[0.6rem] leading-tight text-white/75">
           {name}
         </span>
       )}
diff --git a/frontend/libs/components/viewer-3d/src/lib/viewer-3d.tsx b/frontend/libs/components/viewer-3d/src/lib/viewer-3d.tsx
--- a/frontend/libs/components/viewer-3d/src/lib/viewer-3d.tsx
+++ b/frontend/libs/components/viewer-3d/src/lib/viewer-3d.tsx
@@ -560,7 +560,7 @@ export function Viewer3D({
 
   return (
     <div
-      className={`relative h-full w-full overflow-hidden rounded-l-xl bg-[#282828] ${className}`}
+      className={`relative h-full w-full overflow-hidden rounded-s-xl bg-[#282828] ${className}`}
       onMouseDown={stopPropagation}
       onMouseMove={stopPropagation}
       onMouseUp={stopPropagation}
@@ -580,7 +580,7 @@ export function Viewer3D({
           the animation picker (models that ship clips). Models with
           neither get no extra chrome. */}
       {showViewer && (hasRig || animationNames.length > 0) && (
-        <div className="absolute right-2.5 top-2.5 z-20 flex items-start gap-2">
+        <div className="absolute end-2.5 top-2.5 z-20 flex items-start gap-2">
           {hasRig && (
             <button
               type="button"
diff --git a/frontend/libs/markdown-content/src/lib/pages/NewsPost.tsx b/frontend/libs/markdown-content/src/lib/pages/NewsPost.tsx
--- a/frontend/libs/markdown-content/src/lib/pages/NewsPost.tsx
+++ b/frontend/libs/markdown-content/src/lib/pages/NewsPost.tsx
@@ -58,7 +58,7 @@ export const NewsPost = ({ slug: propSlug, basePath }: NewsPostProps) => {
         </h1>
 
         {post.description && (
-          <p className="text-xl text-white/70 mb-8 leading-relaxed font-light border-l-4 border-primary pl-4">
+          <p className="text-xl text-white/70 mb-8 leading-relaxed font-light border-s-4 border-primary ps-4">
             {post.description}
           </p>
         )}
