collective

Browse / github.com/storytold/artcraft / patch

frontend: lang/dir on the document and logical start/end styling for RTL

frontend: lang/dir on the document and logical start/end styling for RTL Groundwork for right-to-left interface languages in the web frontend, from the language-support plan in storytold/craft-libs#198 (track 8, N1). Nothing changes for English: the document stays `lang="en" dir="ltr"`. **Document language** - `setDocumentLanguage(tag, dir?)` in `@storyteller/common` sets `lang` and `dir` on `<html>`, deriving the direction from the tag (`Intl.Locale` text info, with a language/script list as fallback). The app calls it once at startup with the default `en`. **Logical styling** - 422 physical Tailwind classes become logical: `ml/mr` to `ms/me`, `pl/pr` to `ps/pe`

Status
open
Score
0.00
Adopters
0 other operators
Author
aiongg (imported from pull request #2519, open)
Base
800217ed04d10fc12706c28e6e91ae2a6b286092
Size
+635 −435 in 148 files
Review
pending
Shared
2026-10-28
Id
546c05efad11bbee4d664893

How it went

No other operator has reported using it yet.

Diff

raw · frontend/apps/artcraft/app/index.html frontend/apps/artcraft/app/src/components/experiences/ImageTo3DExperience.tsx frontend/apps/artcraft/app/src/components/generation-feed/CreateEmptyState.tsx frontend/apps/artcraft/app/src/components/generation-feed/DesktopCreatePageShell.tsx frontend/apps/artcraft/app/src/components/reusable/ConfirmationModal.tsx frontend/apps/artcraft/app/src/components/reusable/CostModal.tsx frontend/apps/artcraft/app/src/components/reusable/UploadModalMedia/UploadFilesMedia.tsx frontend/apps/artcraft/app/src/components/reusable/UploadModalMedia/UploadImageMedia.tsx frontend/apps/artcraft/app/src/components/signaled/TopBar/AppsQuickMenu.tsx frontend/apps/artcraft/app/src/components/signaled/TopBar/ProfileDropdown.tsx frontend/apps/artcraft/app/src/components/signaled/TopBar/SceneTitleInput.tsx frontend/apps/artcraft/app/src/components/signaled/TopBar/TaskQueue.tsx frontend/apps/artcraft/app/src/components/signaled/TopBar/TopBar.tsx frontend/apps/artcraft/app/src/index.tsx frontend/apps/artcraft/app/src/pages/PageAngles/Angles.tsx frontend/apps/artcraft/app/src/pages/PageApps/AppsIndexPage.tsx frontend/apps/artcraft/app/src/pages/PageBackgroundChange/PageBackgroundChange.tsx frontend/apps/artcraft/app/src/pages/PageDraw/BaseImageSelector.tsx frontend/apps/artcraft/app/src/pages/PageImageWatermarkRemover/ImageWatermarkRemover.tsx frontend/apps/artcraft/app/src/pages/PageStoryboard/Storyboard.tsx frontend/apps/artcraft/app/src/pages/PageVideoFrameExtractor/VideoFrameExtractor.tsx frontend/apps/artcraft/app/src/pages/PageVideoWatermarkRemover/VideoWatermarkRemover.tsx frontend/apps/artcraft/app/src/styles/base.css frontend/libs/common/pnpm-lock.yaml frontend/libs/common/src/lib/utils/document-language.spec.ts frontend/libs/common/src/lib/utils/document-language.ts frontend/libs/common/src/lib/utils/index.ts frontend/libs/components/audio-player/src/lib/WaveformAudioPlayer.tsx frontend/libs/components/button-dropdown/src/lib/button-dropdown.tsx frontend/libs/components/button-icon-select/src/lib/button-icon-select.tsx frontend/libs/components/camera-settings-modal/src/lib/camera-settings-modal.tsx frontend/libs/components/file-uploader/src/lib/drag-and-drop-zone.tsx frontend/libs/components/gallery-modal/src/lib/DragGhost.tsx frontend/libs/components/gallery-modal/src/lib/GalleryDraggableItem.tsx frontend/libs/components/gallery-modal/src/lib/GalleryFolderChip.tsx frontend/libs/components/gallery-modal/src/lib/GalleryItemMenuItems.tsx frontend/libs/components/gallery-modal/src/lib/gallery-modal.tsx frontend/libs/components/generation-list/src/lib/FailedRow.tsx frontend/libs/components/generation-list/src/lib/GalleryCard.tsx frontend/libs/components/generation-list/src/lib/GalleryRow.tsx frontend/libs/components/generation-list/src/lib/GenerationGridView.tsx frontend/libs/components/generation-list/src/lib/PendingCard.tsx frontend/libs/components/generation-list/src/lib/PendingRow.tsx frontend/libs/components/help-menu/src/lib/help-menu.tsx frontend/libs/components/input/src/lib/input.tsx frontend/libs/components/keybinds/src/lib/settings/KeybindsSettings.tsx frontend/libs/components/label/src/lib/label.tsx frontend/libs/components/lightbox-modal/src/lib/lightbox-modal.tsx frontend/libs/components/lightbox-modal/src/lib/tags/TagChipInput.tsx frontend/libs/components/list-dropdown/src/lib/ListDropdown.tsx frontend/libs/components/login-modal/src/lib/DesktopLoginBridge.tsx frontend/libs/components/login-modal/src/lib/artcraft-signup.tsx frontend/libs/components/login-modal/src/lib/login-modal.tsx frontend/libs/components/modal/src/lib/modal.tsx frontend/libs/components/model-selector/src/lib/classy-model-selector.tsx frontend/libs/components/moodboard/src/lib/BoardPicker.tsx frontend/libs/components/moodboard/src/lib/MoodboardWorkspace.tsx frontend/libs/components/moodboard/src/lib/canvas/Moodboard.tsx frontend/libs/components/moodboard/src/lib/canvas/overlays/RecenterIndicator.tsx frontend/libs/components/moodboard/src/lib/grid/BoardCard.tsx frontend/libs/components/moodboard/src/lib/grid/BoardGridToolbar.tsx frontend/libs/components/moodboard/src/lib/grid/ItemInspector.tsx frontend/libs/components/moodboard/src/lib/grid/PresentationView.tsx frontend/libs/components/moodboard/src/lib/grid/SectionHeader.tsx frontend/libs/components/moodboard/src/lib/grid/SelectionBar.tsx frontend/libs/components/moodboard/src/lib/grid/SmartSearchBar.tsx frontend/libs/components/pagedraw/src/lib/HistoryStack.tsx frontend/libs/components/pagedraw/src/lib/PageDraw.tsx frontend/libs/components/pagedraw/src/lib/PromptEditor/ImagePreview.tsx frontend/libs/components/pagedraw/src/lib/components/ui/InpaintToolBar.tsx frontend/libs/components/pagedraw/src/lib/components/ui/SideToolbar.tsx frontend/libs/components/pagescene/src/lib/Stage3DBody.tsx frontend/libs/components/pagescene/src/lib/comps/AssetMenu/AnimationsModal.tsx frontend/libs/components/pagescene/src/lib/comps/AssetMenu/AssetMenu.tsx frontend/libs/components/pagescene/src/lib/comps/AssetMenu/AssetModal.tsx frontend/libs/components/pagescene/src/lib/comps/AssetMenu/shared/ItemElement.tsx frontend/libs/components/pagescene/src/lib/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx frontend/libs/components/pagescene/src/lib/comps/ControlsTopButtons/ControlsTopButtons.tsx frontend/libs/components/pagescene/src/lib/comps/ControlsTopButtons/LoadUserScenes/LoadUserScenes.tsx frontend/libs/components/pagescene/src/lib/comps/ControlsTopButtons/NewSceneFromTemplate/NewSceneFromTemplate.tsx frontend/libs/components/pagescene/src/lib/comps/ControlsTopButtons/ScenePicker/SceneCard.tsx frontend/libs/components/pagescene/src/lib/comps/EditorLoadingBar.tsx frontend/libs/components/pagescene/src/lib/comps/EntranceDebugPanel.tsx frontend/libs/components/pagescene/src/lib/comps/Outliner/Outliner.tsx frontend/libs/components/pagescene/src/lib/comps/PerfStatsOverlay.tsx frontend/libs/components/pagescene/src/lib/comps/PreviewBox/PreviewBox.tsx frontend/libs/components/pagescene/src/lib/comps/PreviewEngineCamera/PreviewEngineCamera.tsx frontend/libs/components/pagescene/src/lib/comps/RenderOverlay/RenderOverlay.tsx frontend/libs/components/pagescene/src/lib/comps/Timeline/TimelineClipRow.tsx frontend/libs/components/pagescene/src/lib/comps/Timeline/TimelineEditor.tsx frontend/libs/components/popover/src/lib/popover.tsx frontend/libs/components/pricing-modal/src/lib/cost-breakdown-modal.tsx frontend/libs/components/pricing-modal/src/lib/credits-modal.tsx frontend/libs/components/pricing-modal/src/lib/pricing-modal.tsx frontend/libs/components/pricing-table/src/lib/pricing-promo-banner.tsx frontend/libs/components/promptbox/src/lib/CharactersModal.tsx frontend/libs/components/promptbox/src/lib/ImagePromptRow.tsx frontend/libs/components/promptbox/src/lib/MentionChipMenu.tsx frontend/libs/components/promptbox/src/lib/MentionTextarea.tsx frontend/libs/components/promptbox/src/lib/PromptBox2D.tsx frontend/libs/components/promptbox/src/lib/PromptBox3D.tsx frontend/libs/components/promptbox/src/lib/PromptBoxAudio.tsx frontend/libs/components/promptbox/src/lib/PromptBoxEdit.tsx frontend/libs/components/promptbox/src/lib/PromptBoxImage.tsx frontend/libs/components/promptbox/src/lib/PromptBoxVideo.tsx frontend/libs/components/promptbox/src/lib/PromptFullscreenButton.tsx frontend/libs/components/promptbox/src/lib/common/AudioReferenceRow.tsx frontend/libs/components/promptbox/src/lib/deck/DeckCard.tsx frontend/libs/components/promptbox/src/lib/deck/ReferenceDeck.tsx frontend/libs/components/select/src/lib/select.tsx frontend/libs/components/settings-modal/src/lib/panes/AccountSettings/ArtcraftAccountBlock.tsx frontend/libs/components/settings-modal/src/lib/panes/AppearanceSettingsPane.tsx frontend/libs/components/settings-modal/src/lib/settings-modal.tsx frontend/libs/components/switch/src/lib/switch.tsx frontend/libs/components/upload-modal/src/lib/UploadModal3D/UploadFiles3D.tsx frontend/libs/components/upload-modal/src/lib/UploadModalImage/UploadFilesImage.tsx frontend/libs/components/upload-modal/src/lib/UploadModalSplat/UploadFilesSplat.tsx frontend/libs/components/vfx/src/lib/PromptBoxVFX.tsx frontend/libs/components/vfx/src/lib/VFXResultCard.tsx frontend/libs/components/video-editor/src/lib/components/editor/export-button.tsx frontend/libs/components/video-editor/src/lib/components/editor/mobile-gate.tsx frontend/libs/components/video-editor/src/lib/components/editor/scenes-view.tsx frontend/libs/components/video-editor/src/lib/components/section.tsx frontend/libs/components/video-editor/src/lib/components/ui/alert.tsx frontend/libs/components/video-editor/src/lib/components/ui/context-menu.tsx frontend/libs/components/video-editor/src/lib/components/ui/dialog.tsx frontend/libs/components/video-editor/src/lib/components/ui/dropdown-menu.tsx frontend/libs/components/video-editor/src/lib/components/ui/font-picker.tsx frontend/libs/components/video-editor/src/lib/components/ui/input.tsx frontend/libs/components/video-editor/src/lib/components/ui/resizable.tsx frontend/libs/components/video-editor/src/lib/components/ui/select.tsx frontend/libs/components/video-editor/src/lib/components/ui/sheet.tsx frontend/libs/components/video-editor/src/lib/components/ui/split-button.tsx frontend/libs/components/video-editor/src/lib/components/ui/switch.tsx frontend/libs/components/video-editor/src/lib/components/ui/textarea.tsx frontend/libs/components/video-editor/src/lib/panels/assets/draggable-item.tsx frontend/libs/components/video-editor/src/lib/panels/assets/tabbar.tsx frontend/libs/components/video-editor/src/lib/panels/assets/views/assets.tsx frontend/libs/components/video-editor/src/lib/panels/assets/views/base-panel.tsx frontend/libs/components/video-editor/src/lib/panels/assets/views/settings/background.tsx frontend/libs/components/video-editor/src/lib/panels/assets/views/settings/index.tsx frontend/libs/components/video-editor/src/lib/panels/properties/index.tsx frontend/libs/components/video-editor/src/lib/project/components/delete-project-dialog.tsx frontend/libs/components/video-editor/src/lib/sounds/components/assets-view.tsx frontend/libs/components/video-editor/src/lib/stickers/components/assets-view.tsx frontend/libs/components/video-editor/src/lib/timeline/components/timeline-element.tsx frontend/libs/components/viewer-3d/src/lib/viewer-3d.tsx frontend/libs/markdown-content/src/lib/pages/NewsPost.tsx

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"       />        <divdiff --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 ? (           <SketchCanvasdiff --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.yamlnew 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.tsnew 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.tsnew 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 ? (           <Tooltipdiff --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">                             Foldersdiff --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">                       <imgdiff --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={               focusSelecteddiff --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 candiff --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>         )}