@brftech/filex-core 0.42.1 → 0.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/README.md +14 -2
  2. package/dist/ArchiveViewer-BqU761Wk.js +149 -0
  3. package/dist/ArchiveViewer-BqU761Wk.js.map +1 -0
  4. package/dist/CsvViewer-ByHVK5ga.js +172 -0
  5. package/dist/CsvViewer-ByHVK5ga.js.map +1 -0
  6. package/dist/{DrawioViewer-BuhZOAx6.js → DrawioViewer-Bgrj8-zG.js} +45 -44
  7. package/dist/DrawioViewer-Bgrj8-zG.js.map +1 -0
  8. package/dist/{EpubViewer-DS3mb3k8.js → EpubViewer-DJwUKlHW.js} +50 -50
  9. package/dist/EpubViewer-DJwUKlHW.js.map +1 -0
  10. package/dist/IpynbViewer-Bz0eJ4NS.js +184 -0
  11. package/dist/IpynbViewer-Bz0eJ4NS.js.map +1 -0
  12. package/dist/MermaidViewer-CEDTD_pp.js +136 -0
  13. package/dist/MermaidViewer-CEDTD_pp.js.map +1 -0
  14. package/dist/PsdViewer-CZcxUpZO.js +121 -0
  15. package/dist/PsdViewer-CZcxUpZO.js.map +1 -0
  16. package/dist/TiffViewer-BwI62CbQ.js +141 -0
  17. package/dist/TiffViewer-BwI62CbQ.js.map +1 -0
  18. package/dist/{Viewer3D-CIrOn63I.js → Viewer3D-DoeZcuMm.js} +16 -16
  19. package/dist/Viewer3D-DoeZcuMm.js.map +1 -0
  20. package/dist/filex-core.js +475 -219
  21. package/dist/filex-core.umd.cjs +165 -108
  22. package/dist/filex-core.umd.cjs.map +1 -1
  23. package/dist/fonts/caveat-latin-ext.woff2 +0 -0
  24. package/dist/fonts/caveat-latin.woff2 +0 -0
  25. package/dist/fonts/dancing-script-latin-ext.woff2 +0 -0
  26. package/dist/fonts/dancing-script-latin.woff2 +0 -0
  27. package/dist/fonts/homemade-apple-latin.woff2 +0 -0
  28. package/dist/fonts/source-serif-4-latin-ext.woff2 +0 -0
  29. package/dist/fonts/source-serif-4-latin.woff2 +0 -0
  30. package/dist/index-DuWnGuv5.js +35901 -0
  31. package/dist/index-DuWnGuv5.js.map +1 -0
  32. package/dist/index.d.ts +5241 -553
  33. package/dist/pdf-CaPYsOUo.js +21302 -0
  34. package/dist/pdf-CaPYsOUo.js.map +1 -0
  35. package/dist/style.css +1 -1
  36. package/package.json +1 -1
  37. package/src/FileExplorer.vue +1207 -343
  38. package/src/assets/fonts/LICENSE-Apache-2.0.txt +202 -0
  39. package/src/assets/fonts/LICENSE-OFL-1.1.txt +93 -0
  40. package/src/assets/fonts/README.md +58 -0
  41. package/src/assets/fonts/caveat-latin-ext.woff2 +0 -0
  42. package/src/assets/fonts/caveat-latin.woff2 +0 -0
  43. package/src/assets/fonts/dancing-script-latin-ext.woff2 +0 -0
  44. package/src/assets/fonts/dancing-script-latin.woff2 +0 -0
  45. package/src/assets/fonts/homemade-apple-latin.woff2 +0 -0
  46. package/src/assets/fonts/source-serif-4-latin-ext.woff2 +0 -0
  47. package/src/assets/fonts/source-serif-4-latin.woff2 +0 -0
  48. package/src/components/AdvancedSearch.vue +49 -0
  49. package/src/components/Breadcrumb.vue +5 -5
  50. package/src/components/ChoiceButtons.vue +152 -0
  51. package/src/components/CommandPalette.vue +5 -5
  52. package/src/components/ConnectionGuideView.vue +1 -1
  53. package/src/components/ConnectionNotice.vue +59 -0
  54. package/src/components/ConnectionsPanel.vue +30 -568
  55. package/src/components/ContextMenu.vue +31 -5
  56. package/src/components/DataTable.vue +1331 -0
  57. package/src/components/FilePane.vue +3 -0
  58. package/src/components/FilterBar.vue +12 -5
  59. package/src/components/GalleryView.vue +50 -2
  60. package/src/components/GridView.vue +51 -3
  61. package/src/components/HomeView.vue +18 -6
  62. package/src/components/InspectorPanel.vue +134 -17
  63. package/src/components/ListView.vue +366 -900
  64. package/src/components/LogoMark.vue +57 -0
  65. package/src/components/NFSExportsPanel.vue +76 -28
  66. package/src/components/OnboardingTour.vue +19 -7
  67. package/src/components/OperationsCenter.vue +77 -1
  68. package/src/components/PendingOpsTray.vue +59 -5
  69. package/src/components/PresenceBar.vue +7 -7
  70. package/src/components/ProductVersion.vue +31 -0
  71. package/src/components/QuickLook.vue +10 -14
  72. package/src/components/RecentlyOpened.vue +47 -17
  73. package/src/components/RecoveryKeyModal.vue +1 -1
  74. package/src/components/RowActions.vue +134 -0
  75. package/src/components/S3KeysPanel.vue +98 -56
  76. package/src/components/SSHKeysPanel.vue +79 -26
  77. package/src/components/SideNav.vue +260 -96
  78. package/src/components/StarButton.vue +1 -0
  79. package/src/components/StorageFields.vue +74 -6
  80. package/src/components/StorageTags.vue +62 -0
  81. package/src/components/TabBar.vue +1 -1
  82. package/src/components/TagKindIcon.vue +56 -0
  83. package/src/components/TagPicker.vue +226 -40
  84. package/src/components/ThemePalette.vue +4 -4
  85. package/src/components/TimeZonePicker.vue +29 -10
  86. package/src/components/TokensPanel.vue +82 -27
  87. package/src/components/Toolbar.vue +28 -6
  88. package/src/components/plugin/PluginConfirmModal.vue +54 -0
  89. package/src/components/plugin/PluginInspectorSection.vue +149 -0
  90. package/src/components/plugin/PluginPageView.vue +339 -0
  91. package/src/components/plugin/PluginViewModal.vue +159 -0
  92. package/src/components/plugin/SurfaceConversation.vue +76 -0
  93. package/src/components/plugin/SurfaceFooterButtons.vue +73 -0
  94. package/src/components/plugin/SurfaceRenderer.vue +299 -0
  95. package/src/components/plugin/SurfaceSections.vue +91 -0
  96. package/src/components/plugin/nodes/PdfFieldEditor.vue +509 -0
  97. package/src/components/plugin/nodes/SurfaceFileChooser.vue +94 -0
  98. package/src/components/plugin/nodes/SurfaceForm.vue +82 -0
  99. package/src/components/plugin/nodes/SurfaceList.vue +127 -0
  100. package/src/components/plugin/nodes/SurfacePdfFields.vue +1285 -0
  101. package/src/components/plugin/nodes/SurfacePeoplePicker.vue +213 -0
  102. package/src/components/plugin/nodes/SurfacePinInput.vue +57 -0
  103. package/src/components/plugin/nodes/SurfacePreview.vue +119 -0
  104. package/src/components/plugin/nodes/SurfaceProgress.vue +48 -0
  105. package/src/components/plugin/nodes/SurfaceSignaturePad.vue +472 -0
  106. package/src/components/plugin/nodes/SurfaceSteps.vue +53 -0
  107. package/src/components/public/PublicLanguagePicker.vue +58 -0
  108. package/src/components/public/PublicLinkPage.vue +286 -0
  109. package/src/components/public/PublicLinkPreview.vue +85 -0
  110. package/src/components/public/PublicPinGate.vue +108 -0
  111. package/src/components/public/PublicRequestBody.vue +204 -0
  112. package/src/components/public/PublicShareBody.vue +188 -0
  113. package/src/components/public/PublicShell.vue +332 -0
  114. package/src/composables/useConnections.ts +8 -2
  115. package/src/composables/useFileApi.ts +183 -44
  116. package/src/composables/useLocale.ts +277 -41
  117. package/src/composables/useNFSExports.ts +8 -10
  118. package/src/composables/useOperations.ts +44 -3
  119. package/src/composables/usePendingOps.ts +59 -8
  120. package/src/composables/usePluginActions.ts +121 -0
  121. package/src/composables/usePluginSurface.ts +223 -0
  122. package/src/composables/usePublicBranding.ts +172 -0
  123. package/src/composables/usePublicLink.ts +573 -0
  124. package/src/composables/usePublicPage.ts +61 -0
  125. package/src/composables/useS3Keys.ts +17 -12
  126. package/src/composables/useSSHKeys.ts +8 -10
  127. package/src/composables/useTokens.ts +8 -12
  128. package/src/composables/useUploadChunked.ts +18 -8
  129. package/src/composables/useViewerFetch.ts +6 -3
  130. package/src/index.ts +404 -8
  131. package/src/lib/actionIcons.ts +46 -1
  132. package/src/lib/advSearch.ts +14 -7
  133. package/src/lib/appLock.ts +130 -0
  134. package/src/lib/connection.ts +110 -0
  135. package/src/lib/destinationTree.ts +19 -3
  136. package/src/lib/direction.ts +353 -0
  137. package/src/lib/dragGhost.ts +6 -0
  138. package/src/lib/dropLimits.ts +69 -0
  139. package/src/lib/errorWords.ts +394 -0
  140. package/src/lib/externalReach.ts +5 -1
  141. package/src/lib/fileFilters.ts +7 -2
  142. package/src/lib/fileIcons.ts +4 -0
  143. package/src/lib/internalPaths.ts +77 -0
  144. package/src/lib/listInput.ts +64 -0
  145. package/src/lib/listing.ts +58 -23
  146. package/src/lib/modalStack.ts +26 -0
  147. package/src/lib/nodeRow.ts +121 -0
  148. package/src/lib/pdfFieldRules.ts +126 -0
  149. package/src/lib/pdfFields.ts +589 -0
  150. package/src/lib/pdfFieldsGeom.ts +203 -0
  151. package/src/lib/pdfjsLoader.ts +51 -0
  152. package/src/lib/personName.ts +47 -0
  153. package/src/lib/pluginApplies.ts +123 -0
  154. package/src/lib/pluginLabel.ts +72 -0
  155. package/src/lib/pluginMenu.ts +253 -0
  156. package/src/lib/pluginPage.ts +58 -0
  157. package/src/lib/plural.ts +181 -0
  158. package/src/lib/popupLayer.ts +103 -0
  159. package/src/lib/prefs.ts +563 -0
  160. package/src/lib/productVersion.ts +35 -0
  161. package/src/lib/publicLayout.ts +39 -0
  162. package/src/lib/serviceGate.ts +104 -0
  163. package/src/lib/shareTtl.ts +25 -11
  164. package/src/lib/signFonts.ts +85 -0
  165. package/src/lib/signaturePad.ts +111 -0
  166. package/src/lib/sortOrder.ts +100 -38
  167. package/src/lib/star.ts +7 -1
  168. package/src/lib/surfaceCell.ts +41 -0
  169. package/src/lib/surfaceConditions.ts +156 -0
  170. package/src/lib/surfaceOpen.ts +106 -0
  171. package/src/lib/surfaceSteps.ts +68 -0
  172. package/src/lib/surfaceValues.ts +191 -0
  173. package/src/lib/symlink.ts +150 -0
  174. package/src/lib/tableColumns.ts +493 -0
  175. package/src/lib/tableEnv.ts +34 -0
  176. package/src/lib/tablePrefs.ts +84 -0
  177. package/src/lib/tags.ts +121 -12
  178. package/src/lib/themes.ts +286 -18
  179. package/src/lib/tour.ts +152 -0
  180. package/src/lib/trashEmpty.ts +111 -0
  181. package/src/lib/trashTimeLeft.ts +23 -0
  182. package/src/lib/uiLocales.ts +460 -0
  183. package/src/lib/viewPrefs.ts +696 -660
  184. package/src/lib/viewPrefsHttp.ts +78 -0
  185. package/src/locales/en.ts +522 -119
  186. package/src/locales/resolve.ts +17 -8
  187. package/src/locales/tr.ts +539 -155
  188. package/src/modals/ConvertModal.vue +62 -11
  189. package/src/modals/DestinationPickerModal.vue +59 -16
  190. package/src/modals/Modal.vue +113 -23
  191. package/src/modals/NewDocumentModal.vue +16 -6
  192. package/src/modals/NewFolderModal.vue +8 -0
  193. package/src/modals/PermissionsModal.vue +272 -163
  194. package/src/modals/PreviewModal.vue +119 -20
  195. package/src/modals/RenameModal.vue +37 -1
  196. package/src/styles/base.css +2895 -241
  197. package/src/styles/sign-fonts.css +118 -0
  198. package/src/styles/variables.css +60 -0
  199. package/src/types/Connections.ts +17 -0
  200. package/src/types/ExplorerConfig.ts +117 -1
  201. package/src/types/FileNode.ts +58 -0
  202. package/src/types/Plugins.ts +494 -0
  203. package/src/types/Public.ts +212 -0
  204. package/src/viewers/ArchiveViewer.vue +93 -48
  205. package/src/viewers/CsvViewer.vue +99 -53
  206. package/src/viewers/DrawioViewer.vue +35 -9
  207. package/src/viewers/EpubViewer.vue +7 -8
  208. package/src/viewers/IpynbViewer.vue +3 -1
  209. package/src/viewers/MermaidViewer.vue +4 -5
  210. package/src/viewers/PdfViewer.vue +8 -17
  211. package/src/viewers/PsdViewer.vue +2 -4
  212. package/src/viewers/TiffViewer.vue +4 -5
  213. package/src/viewers/Viewer3D.vue +1 -1
  214. package/LICENSE +0 -21
  215. package/dist/ArchiveViewer-CLNevfPE.js +0 -124
  216. package/dist/ArchiveViewer-CLNevfPE.js.map +0 -1
  217. package/dist/CsvViewer-K8XA3z7N.js +0 -141
  218. package/dist/CsvViewer-K8XA3z7N.js.map +0 -1
  219. package/dist/DrawioViewer-BuhZOAx6.js.map +0 -1
  220. package/dist/EpubViewer-DS3mb3k8.js.map +0 -1
  221. package/dist/IpynbViewer-BTk7p4Dn.js +0 -184
  222. package/dist/IpynbViewer-BTk7p4Dn.js.map +0 -1
  223. package/dist/MermaidViewer-DqucH_io.js +0 -137
  224. package/dist/MermaidViewer-DqucH_io.js.map +0 -1
  225. package/dist/PsdViewer-CwiVENmq.js +0 -122
  226. package/dist/PsdViewer-CwiVENmq.js.map +0 -1
  227. package/dist/TiffViewer-CpewSVmQ.js +0 -142
  228. package/dist/TiffViewer-CpewSVmQ.js.map +0 -1
  229. package/dist/Viewer3D-CIrOn63I.js.map +0 -1
  230. package/dist/index-Dx0IbBcI.js +0 -26831
  231. package/dist/index-Dx0IbBcI.js.map +0 -1
  232. package/dist/useViewerFetch-czqbd2Lj.js +0 -25
  233. package/dist/useViewerFetch-czqbd2Lj.js.map +0 -1
@@ -7,6 +7,80 @@
7
7
  reported). The general default comes first, the component writes its own
8
8
  exception afterwards. */
9
9
 
10
+ /* === RTL — the one sign every direction-bearing MOTION reads ==========
11
+ Layout here is written in logical properties (margin-inline-start,
12
+ inset-inline-end, text-align: start …), and those turn by themselves under
13
+ `dir="rtl"`. What does not turn by itself is anything written as an
14
+ x-OFFSET: a translateX, a box-shadow's first length. Those multiply by this
15
+ sign instead — `translateX(calc(8px * var(--filex-dir-x, 1)))` — so the
16
+ inspector still slides in from ITS edge, a switch's knob still travels
17
+ toward "on", and a pinned column's edge still falls on the side the rows
18
+ scroll under. In LTR it is `* 1`: the same pixels as before.
19
+
20
+ ⚠ Set on the element that CARRIES `dir` and inherited from there, so the
21
+ NEAREST `dir` wins — which is the nesting rule (an English explorer inside
22
+ an Arabic page keeps +1; see lib/direction).
23
+
24
+ ⚠⚠ Never in document space. A PDF page, an image, a drawing, a signature
25
+ box has its own coordinates; a box at the left of a page is at the left of
26
+ the page in every language (lib/pdfFieldsGeom). */
27
+ [dir='ltr'] { --filex-dir-x: 1; }
28
+ [dir='rtl'] { --filex-dir-x: -1; }
29
+
30
+ /* ⚠ RTL — MACHINE text reads left to right in every language: a command, a
31
+ path, a URL, an address, a key. The connection guides print
32
+ `sftp -P 2022 {user}@host` with the placeholder translated; laid out right
33
+ to left the command's words swap places and the line cannot be typed back.
34
+ Isolated, so the Arabic around it stays right to left and an Arabic word
35
+ inside it (a translated placeholder) stays a readable word.
36
+ Keyed on `[dir='rtl']` so an LTR page is untouched — this rule does not
37
+ exist for English or Turkish. */
38
+ [dir='rtl'] :is(pre, code, kbd, samp, .font-mono, .tbl-mono, .fe-share__url, .fe-inspector__share-url) {
39
+ direction: ltr;
40
+ unicode-bidi: isolate;
41
+ }
42
+ /* A plain-text file and a notebook's raw markdown are the person's PROSE in a
43
+ <pre>: each line decides its own direction (declared after the rule above,
44
+ which it overrides for exactly these two). */
45
+ [dir='rtl'] :is(.fe-preview__pre, .filex-viewer-ipynb__md-raw) {
46
+ unicode-bidi: plaintext;
47
+ }
48
+ /* An app's own sentences are in the APP's language, which is not always the
49
+ interface's: an English hint inside an Arabic wizard keeps its full stop at
50
+ its end. `plaintext` lets each paragraph take the direction of its own first
51
+ letter — for English or Turkish in an LTR page that is exactly what it was. */
52
+ :is(.fe-surface__text, .fe-surface__error) {
53
+ unicode-bidi: plaintext;
54
+ }
55
+ /* …and what a person TYPES decides its own direction: an e-mail, a URL, a
56
+ file name or `tag:invoice` in a right-to-left form reads left to right,
57
+ Arabic reads right to left, and a placeholder follows its own words.
58
+ `plaintext` is `dir="auto"` for every field at once, without touching the
59
+ markup of each. RTL only, so no English or Turkish field changes. */
60
+ [dir='rtl'] :is(input:not([type]), input[type='text'], input[type='search'], input[type='email'], input[type='url'], input[type='tel'], textarea) {
61
+ unicode-bidi: plaintext;
62
+ }
63
+
64
+ /* ⚠ RTL — icons that MEAN a direction, mirrored in one place. The set is
65
+ decided where the glyph is drawn: lib/actionIcons marks its directional
66
+ keys `fe-aicon--dir`; the few glyphs drawn inline are listed here — the
67
+ viewer's previous / next chevrons (which now sit at the start / end edges)
68
+ and the destination picker's "go into" chevron.
69
+ `:dir(rtl)` on the ICON, never `[dir='rtl'] .icon`: an English explorer
70
+ inside an Arabic page keeps its arrows. Text arrows need nothing — the `›`
71
+ of a breadcrumb and the `‹ ›` of a pager are bidi-MIRRORED characters and
72
+ the browser turns them itself. */
73
+ :is(.fe-aicon--dir, .fe-viewer__chev > svg, .fe-destpick__rowinto):dir(rtl) {
74
+ transform: scaleX(-1);
75
+ }
76
+ /* A sideways disclosure caret points the way the line reads while closed
77
+ (left, in RTL) and DOWN when open in both directions. Turned rather than
78
+ mirrored so the open state's `rotate(90deg)` (declared later, same weight)
79
+ animates the short way round. */
80
+ :is(.fe-share__caret, .fe-pinsp__chev):dir(rtl) {
81
+ transform: rotate(180deg);
82
+ }
83
+
10
84
  /* === ui-fix — scrollbars ==============================================
11
85
  The platform scrollbar ignores the theme completely: on Terminal Green or
12
86
  any dark variant it paints a wide light-grey slab down the edge of the
@@ -129,6 +203,16 @@ filex-explorer {
129
203
  border-bottom: 1px solid var(--fe-border, rgba(0, 0, 0, 0.06));
130
204
  }
131
205
 
206
+ /* "More results than shown" — a search answer the server cut (FileExplorer's
207
+ * banners slot). Quiet on purpose: it qualifies the list, it is not an error. */
208
+ .fe-search-cut {
209
+ flex: 0 0 auto;
210
+ padding: 0.375rem 0.75rem;
211
+ border-bottom: 1px solid var(--fe-border, rgba(0, 0, 0, 0.06));
212
+ color: var(--fe-text-muted);
213
+ font-size: 0.8125rem;
214
+ }
215
+
132
216
  /* Initial-load spinner (shown until the first listing arrives). */
133
217
  .fe__loading {
134
218
  display: flex;
@@ -194,7 +278,7 @@ filex-explorer {
194
278
 
195
279
  .fe__file-input {
196
280
  position: absolute;
197
- left: -9999px;
281
+ inset-inline-start: -9999px;
198
282
  width: 1px;
199
283
  height: 1px;
200
284
  opacity: 0;
@@ -379,7 +463,7 @@ filex-explorer {
379
463
  }
380
464
  /* Edit pencil button — V1.5, verbatim (translated): "let me type a path and go" */
381
465
  .fe-breadcrumb__edit {
382
- margin-left: auto;
466
+ margin-inline-start: auto;
383
467
  background: none;
384
468
  border: 1px solid transparent;
385
469
  color: var(--fe-text-muted);
@@ -612,7 +696,7 @@ filex-explorer {
612
696
  -webkit-box-orient: vertical;
613
697
  overflow: hidden;
614
698
  overflow-wrap: anywhere;
615
- text-align: left;
699
+ text-align: start;
616
700
  }
617
701
  .fe-grid__empty {
618
702
  grid-column: 1 / -1;
@@ -651,7 +735,7 @@ filex-explorer {
651
735
 
652
736
  .fe-upload {
653
737
  position: absolute;
654
- right: 16px;
738
+ inset-inline-end: 16px;
655
739
  bottom: 16px;
656
740
  width: min(360px, calc(100% - 32px));
657
741
  background: var(--fe-bg);
@@ -751,7 +835,7 @@ filex-explorer {
751
835
  * two stacks don't overlap when both are active. */
752
836
  .fe-ops {
753
837
  position: absolute;
754
- left: 16px;
838
+ inset-inline-start: 16px;
755
839
  bottom: 16px;
756
840
  width: min(320px, calc(100% - 32px));
757
841
  display: flex;
@@ -863,7 +947,7 @@ filex-explorer {
863
947
  color: var(--fe-text);
864
948
  font: inherit;
865
949
  width: 100%;
866
- text-align: left;
950
+ text-align: start;
867
951
  cursor: pointer;
868
952
  }
869
953
  .fe-ctx__item:hover:not(.is-disabled) {
@@ -889,13 +973,23 @@ filex-explorer {
889
973
  }
890
974
  .fe-ctx__key {
891
975
  flex: 0 0 auto;
892
- margin-left: auto;
893
- padding-left: 16px;
976
+ margin-inline-start: auto;
977
+ padding-inline-start: 16px;
894
978
  color: var(--fe-text-muted);
895
979
  font-size: 11px;
896
980
  font-family: var(--fe-font-mono);
897
981
  white-space: nowrap;
898
982
  }
983
+ /* A key on a touch screen is an instruction nobody holding it can follow:
984
+ * the phone's row sheet listed "Enter", "Ctrl+X", "F2" beside every verb (QA,
985
+ * 2026-09-21). The same test the explorer uses to open the menu as a sheet
986
+ * (`(pointer: coarse)`), so every menu — the sheet, the New menu, the
987
+ * toolbar's — answers alike. */
988
+ @media (pointer: coarse) {
989
+ .fe-ctx__key {
990
+ display: none;
991
+ }
992
+ }
899
993
  .fe-ctx__sep {
900
994
  height: 1px;
901
995
  background: var(--fe-border);
@@ -1022,7 +1116,7 @@ filex-explorer {
1022
1116
  line-height: 1.6;
1023
1117
  background: var(--fe-bg, #fff);
1024
1118
  color: var(--fe-text, #111);
1025
- border-right: 1px solid var(--fe-border, #e2e6ed);
1119
+ border-inline-end: 1px solid var(--fe-border, #e2e6ed);
1026
1120
  overflow: auto;
1027
1121
  tab-size: 2;
1028
1122
  }
@@ -1050,7 +1144,7 @@ filex-explorer {
1050
1144
  grid-template-rows: 1fr 1fr;
1051
1145
  }
1052
1146
  .fe-preview__md-split-input {
1053
- border-right: 0;
1147
+ border-inline-end: 0;
1054
1148
  border-bottom: 1px solid var(--fe-border, #e2e6ed);
1055
1149
  }
1056
1150
  }
@@ -1246,7 +1340,7 @@ filex-explorer {
1246
1340
  letter-spacing: 0.04em;
1247
1341
  }
1248
1342
  .fe-preview__code-toolbar > .fe-btn {
1249
- margin-left: auto;
1343
+ margin-inline-start: auto;
1250
1344
  }
1251
1345
  .fe-preview__code-status {
1252
1346
  font-size: 12px;
@@ -1266,6 +1360,15 @@ filex-explorer {
1266
1360
  .fe-preview__code-editor .cm-scroller {
1267
1361
  font-family: var(--fe-font-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
1268
1362
  }
1363
+ /* An administrator's second line under a viewer failure — the raw words
1364
+ behind the sentence (lib/errorWords). Nobody else is given it. */
1365
+ .fe-preview__error-detail {
1366
+ font-family: var(--fe-font-mono, ui-monospace, monospace);
1367
+ font-size: 11px;
1368
+ color: var(--fe-text-muted);
1369
+ overflow-wrap: anywhere;
1370
+ max-width: 60ch;
1371
+ }
1269
1372
  .fe-preview__fallback {
1270
1373
  text-align: center;
1271
1374
  color: var(--fe-text-muted);
@@ -1350,10 +1453,10 @@ filex-explorer {
1350
1453
  }
1351
1454
  .fe-preview__md pre code { background: transparent; padding: 0; }
1352
1455
  .fe-preview__md ul,
1353
- .fe-preview__md ol { padding-left: 2em; margin: 0.8em 0; }
1456
+ .fe-preview__md ol { padding-inline-start: 2em; margin: 0.8em 0; }
1354
1457
  .fe-preview__md li { margin: 0.3em 0; }
1355
1458
  .fe-preview__md blockquote {
1356
- border-left: 3px solid var(--fe-border-strong);
1459
+ border-inline-start: 3px solid var(--fe-border-strong);
1357
1460
  padding: 0 1em;
1358
1461
  color: var(--fe-text-muted);
1359
1462
  margin: 0.8em 0;
@@ -1367,7 +1470,7 @@ filex-explorer {
1367
1470
  .fe-preview__md td {
1368
1471
  border: 1px solid var(--fe-border);
1369
1472
  padding: 6px 10px;
1370
- text-align: left;
1473
+ text-align: start;
1371
1474
  }
1372
1475
  .fe-preview__md th { background: var(--fe-bg-elev); }
1373
1476
  .fe-preview__md img { max-width: 100%; }
@@ -1377,7 +1480,7 @@ filex-explorer {
1377
1480
  .fe-toast {
1378
1481
  position: absolute;
1379
1482
  bottom: 24px;
1380
- left: 50%;
1483
+ left: 50%; /* rtl-physical: centred by translate(-50%) — the same place in both directions */
1381
1484
  transform: translateX(-50%);
1382
1485
  background: var(--fe-text);
1383
1486
  color: var(--fe-bg);
@@ -1394,7 +1497,7 @@ filex-explorer {
1394
1497
  .fe-toast-enter-from,
1395
1498
  .fe-toast-leave-to {
1396
1499
  opacity: 0;
1397
- transform: translate(-50%, 10px);
1500
+ transform: translate(-50%, 10px); /* rtl-physical: centred (see .fe-toast) */
1398
1501
  }
1399
1502
 
1400
1503
  /* ---------- Responsive ---------- */
@@ -1623,7 +1726,7 @@ filex-explorer {
1623
1726
  align-items: center;
1624
1727
  gap: 10px;
1625
1728
  width: 100%;
1626
- text-align: left;
1729
+ text-align: start;
1627
1730
  background: transparent;
1628
1731
  border: 0;
1629
1732
  border-radius: var(--fe-radius-sm);
@@ -1747,7 +1850,7 @@ filex-explorer {
1747
1850
  .fe-breadcrumb__menu {
1748
1851
  position: absolute;
1749
1852
  top: calc(100% + 4px);
1750
- left: 0;
1853
+ inset-inline-start: 0;
1751
1854
  z-index: 80;
1752
1855
  min-width: 160px;
1753
1856
  max-width: 280px;
@@ -1762,7 +1865,7 @@ filex-explorer {
1762
1865
  box-shadow: var(--fe-shadow);
1763
1866
  }
1764
1867
  .fe-breadcrumb__menu-item {
1765
- text-align: left;
1868
+ text-align: start;
1766
1869
  background: transparent;
1767
1870
  border: 0;
1768
1871
  padding: 6px 10px;
@@ -1831,7 +1934,8 @@ filex-explorer {
1831
1934
  content: '';
1832
1935
  position: absolute;
1833
1936
  inset: 0;
1834
- transform: translateX(-100%);
1937
+ /* ⚠ RTL: the sweep runs the way the line reads (--filex-dir-x, top of file). */
1938
+ transform: translateX(calc(-100% * var(--filex-dir-x, 1)));
1835
1939
  background: linear-gradient(
1836
1940
  90deg,
1837
1941
  transparent,
@@ -1841,7 +1945,7 @@ filex-explorer {
1841
1945
  animation: fe-skel-shimmer 1.4s ease-in-out infinite;
1842
1946
  }
1843
1947
  @keyframes fe-skel-shimmer {
1844
- to { transform: translateX(100%); }
1948
+ to { transform: translateX(calc(100% * var(--filex-dir-x, 1))); }
1845
1949
  }
1846
1950
  @media (prefers-reduced-motion: reduce) {
1847
1951
  /* Static faint blocks — no sweep. */
@@ -1954,7 +2058,7 @@ filex-explorer {
1954
2058
  display: flex;
1955
2059
  align-items: center;
1956
2060
  gap: 14px;
1957
- padding: 8px 10px 8px 14px;
2061
+ padding-block: 8px; padding-inline: 14px 10px;
1958
2062
  cursor: pointer;
1959
2063
  max-width: min(480px, calc(100% - 32px));
1960
2064
  }
@@ -2007,6 +2111,67 @@ filex-explorer {
2007
2111
  background: var(--fe-warning, #f59e0b);
2008
2112
  }
2009
2113
 
2114
+ /* The one line a page shows while the SERVER cannot be reached at all —
2115
+ components/ConnectionNotice.vue, drawn from the shared folding rule in
2116
+ lib/connection instead of a toast per failed request.
2117
+ ⚠⚠ Here and not in the component's own `<style>`: a single-file component's
2118
+ styles are emitted only into a bundle that IMPORTS it, and the web-component
2119
+ distribution mounts the explorer without this one — so the sheet it ships
2120
+ lost these rules while core's kept them, and
2121
+ web/tests/deploy/packageLook.test.ts ("the two shipped stylesheets have
2122
+ drifted") caught it. base.css is in both.
2123
+ ⚠ Logical properties only; web/tests/quality/rtlLogical.test.ts refuses the
2124
+ physical spellings, and this strip is drawn in Arabic too.
2125
+ ⚠ Only tokens declared in styles/variables.css — a `--fe-*` invented at a
2126
+ call site is a phantom to every other reader of the theme
2127
+ (web/tests/api/themeTokens.test.ts). The amber is carried by the border and
2128
+ the dot, the way the connection badge above says it. */
2129
+ .fe-connlost {
2130
+ position: fixed;
2131
+ inset-block-start: 0;
2132
+ inset-inline: 0;
2133
+ z-index: 60;
2134
+ display: flex;
2135
+ align-items: center;
2136
+ justify-content: center;
2137
+ gap: 8px;
2138
+ padding: 6px 12px;
2139
+ font-size: 13px;
2140
+ line-height: 1.4;
2141
+ text-align: center;
2142
+ color: var(--fe-text);
2143
+ background: var(--fe-bg-elev);
2144
+ border-block-end: 1px solid var(--fe-warning);
2145
+ /* A statement, not a control: it must never take a click from what is
2146
+ underneath it. */
2147
+ pointer-events: none;
2148
+ }
2149
+ .fe-connlost__dot {
2150
+ flex: 0 0 auto;
2151
+ width: 7px;
2152
+ height: 7px;
2153
+ border-radius: 50%;
2154
+ background: var(--fe-warning);
2155
+ }
2156
+ .fe-connlost__text {
2157
+ min-width: 0;
2158
+ }
2159
+
2160
+ /* `filex 0.43.0` — which filex this is, at the foot of an account menu and in
2161
+ the user settings dialog's head (components/ProductVersion.vue). A quiet
2162
+ line: the muted colour and the smallest size, selectable so it can be
2163
+ copied into a bug report. ⚠ Here, not in the component: see `.fe-connlost`
2164
+ above for why (lesson #416). */
2165
+ .fe-version {
2166
+ margin: 0;
2167
+ font-size: var(--fe-text-xs);
2168
+ line-height: 1.4;
2169
+ color: var(--fe-text-muted);
2170
+ font-variant-numeric: tabular-nums;
2171
+ user-select: text;
2172
+ white-space: nowrap;
2173
+ }
2174
+
2010
2175
  /* === bul:s3 === */
2011
2176
 
2012
2177
  /* Palette — "Everywhere" hit rows (name + crumb + snippet stack). */
@@ -2135,7 +2300,7 @@ filex-explorer {
2135
2300
  /* Upload FAB — bottom-right corner of the explorer (narrow mode only). */
2136
2301
  .fe-fab {
2137
2302
  position: absolute;
2138
- right: 16px;
2303
+ inset-inline-end: 16px;
2139
2304
  bottom: 16px;
2140
2305
  width: 56px;
2141
2306
  height: 56px;
@@ -2174,8 +2339,8 @@ filex-explorer {
2174
2339
  }
2175
2340
  .fe-sheet {
2176
2341
  position: fixed;
2177
- left: 0;
2178
- right: 0;
2342
+ inset-inline-start: 0;
2343
+ inset-inline-end: 0;
2179
2344
  bottom: 0;
2180
2345
  max-height: 70vh;
2181
2346
  display: flex;
@@ -2184,8 +2349,8 @@ filex-explorer {
2184
2349
  color: var(--fe-text);
2185
2350
  border: 1px solid var(--fe-border);
2186
2351
  border-bottom: 0;
2187
- border-top-left-radius: var(--fe-radius-lg);
2188
- border-top-right-radius: var(--fe-radius-lg);
2352
+ border-start-start-radius: var(--fe-radius-lg);
2353
+ border-start-end-radius: var(--fe-radius-lg);
2189
2354
  box-shadow: 0 -10px 32px rgba(15, 23, 42, 0.28);
2190
2355
  padding: 0 8px calc(8px + env(safe-area-inset-bottom, 0px));
2191
2356
  outline: none;
@@ -2268,7 +2433,7 @@ filex-explorer {
2268
2433
  min-height: 0;
2269
2434
  display: flex;
2270
2435
  flex-direction: column;
2271
- border-left: 1px solid var(--fe-border);
2436
+ border-inline-start: 1px solid var(--fe-border);
2272
2437
  background: var(--fe-bg);
2273
2438
  color: var(--fe-text);
2274
2439
  }
@@ -2279,7 +2444,7 @@ filex-explorer {
2279
2444
  inset: 0;
2280
2445
  z-index: 60;
2281
2446
  width: auto;
2282
- border-left: none;
2447
+ border-inline-start: none;
2283
2448
  }
2284
2449
 
2285
2450
  .fe-inspector__head {
@@ -2402,7 +2567,7 @@ filex-explorer {
2402
2567
  text-overflow: ellipsis;
2403
2568
  white-space: nowrap;
2404
2569
  direction: rtl; /* keep the tail (basename) visible when truncated */
2405
- text-align: left;
2570
+ text-align: start;
2406
2571
  }
2407
2572
  .fe-inspector__mime {
2408
2573
  font-family: var(--fe-font-mono);
@@ -2558,7 +2723,8 @@ filex-explorer {
2558
2723
  @keyframes fe-inspector-in {
2559
2724
  from {
2560
2725
  opacity: 0;
2561
- transform: translateX(8px);
2726
+ /* From the inspector's own edge — the inline END, left in RTL. */
2727
+ transform: translateX(calc(8px * var(--filex-dir-x, 1)));
2562
2728
  }
2563
2729
  to {
2564
2730
  opacity: 1;
@@ -2575,7 +2741,7 @@ filex-explorer {
2575
2741
  color: var(--fe-text-muted);
2576
2742
  }
2577
2743
  .fe-shortcuts__custom {
2578
- margin-left: 4px;
2744
+ margin-inline-start: 4px;
2579
2745
  font-size: 9px;
2580
2746
  vertical-align: 2px;
2581
2747
  color: var(--fe-primary);
@@ -2663,7 +2829,7 @@ filex-explorer {
2663
2829
  }
2664
2830
  /* tus:t1 — "Desktop app only" on a combo the browser keeps for itself. */
2665
2831
  .fe-shortset__reserved {
2666
- margin-left: 6px;
2832
+ margin-inline-start: 6px;
2667
2833
  font-size: 10.5px;
2668
2834
  color: var(--fe-text-muted);
2669
2835
  border: 1px dashed var(--fe-border);
@@ -2715,7 +2881,7 @@ filex-explorer {
2715
2881
  * the pill grew to the full viewport height inside the admin UI. */
2716
2882
  .fe.fe-ql-hint {
2717
2883
  position: fixed;
2718
- left: 50%;
2884
+ left: 50%; /* rtl-physical: centred by translate(-50%) — the same place in both directions */
2719
2885
  bottom: 14px;
2720
2886
  transform: translateX(-50%);
2721
2887
  z-index: 72;
@@ -2761,7 +2927,7 @@ filex-explorer {
2761
2927
 
2762
2928
  .fe-opc {
2763
2929
  position: absolute;
2764
- right: 16px;
2930
+ inset-inline-end: 16px;
2765
2931
  bottom: 16px;
2766
2932
  z-index: 45;
2767
2933
  display: flex;
@@ -2775,7 +2941,7 @@ filex-explorer {
2775
2941
  display: flex;
2776
2942
  align-items: center;
2777
2943
  gap: 8px;
2778
- padding: 8px 14px 8px 10px;
2944
+ padding-block: 8px; padding-inline: 10px 14px;
2779
2945
  border-radius: 999px;
2780
2946
  border: 1px solid var(--fe-border);
2781
2947
  background: var(--fe-bg);
@@ -2922,6 +3088,26 @@ filex-explorer {
2922
3088
  .fe-opc__item.is-error .fe-opc__state {
2923
3089
  color: var(--fe-danger);
2924
3090
  }
3091
+ /* An administrator's second line under a failure: the raw words behind the
3092
+ sentence (lib/errorWords). Muted and monospace — it is evidence, not the
3093
+ message. */
3094
+ .fe-opc__sub--detail {
3095
+ font-family: var(--fe-font-mono, ui-monospace, monospace);
3096
+ font-size: 10.5px;
3097
+ overflow-wrap: anywhere;
3098
+ }
3099
+ /* An app job's output mode ("new file" / "new version") — the one fact a
3100
+ finished row cannot show any other way, because in `version` mode "Open"
3101
+ lands on the file the job started from. */
3102
+ .fe-opc__outmode {
3103
+ flex: 0 0 auto;
3104
+ padding: 0 5px;
3105
+ border: 1px solid var(--fe-border);
3106
+ border-radius: 8px;
3107
+ line-height: 15px;
3108
+ white-space: nowrap;
3109
+ cursor: help;
3110
+ }
2925
3111
  .fe-opc__state {
2926
3112
  overflow: hidden;
2927
3113
  text-overflow: ellipsis;
@@ -3029,8 +3215,8 @@ filex-explorer {
3029
3215
  /* Narrow / embedded: clear the FAB and behave like a bottom sheet —
3030
3216
  * badge stays compact at the right, panel stretches edge to edge. */
3031
3217
  .fe--narrow .fe-opc {
3032
- left: 12px;
3033
- right: 12px;
3218
+ inset-inline-start: 12px;
3219
+ inset-inline-end: 12px;
3034
3220
  bottom: 84px;
3035
3221
  max-width: none;
3036
3222
  }
@@ -3117,7 +3303,7 @@ filex-explorer {
3117
3303
  margin-top: 10px;
3118
3304
  max-width: 480px;
3119
3305
  font-size: 12px;
3120
- text-align: left;
3306
+ text-align: start;
3121
3307
  }
3122
3308
  .fe-state__details-summary {
3123
3309
  cursor: pointer;
@@ -3183,7 +3369,7 @@ filex-explorer {
3183
3369
  }
3184
3370
  .fe-tour__card--center {
3185
3371
  top: 50%;
3186
- left: 50%;
3372
+ left: 50%; /* rtl-physical: centred by translate(-50%) — the same place in both directions */
3187
3373
  transform: translate(-50%, -50%);
3188
3374
  width: min(360px, calc(100vw - 24px));
3189
3375
  }
@@ -3322,7 +3508,7 @@ filex-explorer {
3322
3508
  gap: 6px;
3323
3509
  max-width: 180px;
3324
3510
  min-width: 72px;
3325
- padding: 4px 6px 4px 10px;
3511
+ padding-block: 4px; padding-inline: 10px 6px;
3326
3512
  font-size: 12px;
3327
3513
  color: var(--fe-text-muted);
3328
3514
  background: transparent;
@@ -3346,8 +3532,8 @@ filex-explorer {
3346
3532
  .fe-tabs__tab.is-active::after {
3347
3533
  content: '';
3348
3534
  position: absolute;
3349
- left: 0;
3350
- right: 0;
3535
+ inset-inline-start: 0;
3536
+ inset-inline-end: 0;
3351
3537
  bottom: -1px;
3352
3538
  height: 1px;
3353
3539
  background: var(--fe-bg);
@@ -3454,7 +3640,7 @@ filex-explorer {
3454
3640
  min-height: 0;
3455
3641
  display: flex;
3456
3642
  flex-direction: column;
3457
- border-left: 1px solid var(--fe-border);
3643
+ border-inline-start: 1px solid var(--fe-border);
3458
3644
  background: var(--fe-bg);
3459
3645
  color: var(--fe-text);
3460
3646
  }
@@ -3677,8 +3863,8 @@ filex-explorer {
3677
3863
  title is enough). */
3678
3864
  .fe-gal__meta {
3679
3865
  position: absolute;
3680
- left: 0;
3681
- right: 0;
3866
+ inset-inline-start: 0;
3867
+ inset-inline-end: 0;
3682
3868
  bottom: 0;
3683
3869
  display: flex;
3684
3870
  flex-direction: column;
@@ -3766,7 +3952,7 @@ filex-explorer {
3766
3952
  min-width: 16px;
3767
3953
  height: 16px;
3768
3954
  padding: 0 4px;
3769
- margin-left: 6px;
3955
+ margin-inline-start: 6px;
3770
3956
  border-radius: 8px;
3771
3957
  font-size: 10px;
3772
3958
  font-weight: 600;
@@ -3950,7 +4136,7 @@ filex-explorer {
3950
4136
  font-size: 12.5px;
3951
4137
  color: var(--fe-primary);
3952
4138
  cursor: pointer;
3953
- text-align: left;
4139
+ text-align: start;
3954
4140
  }
3955
4141
  .fe-e2e-optlink:hover {
3956
4142
  text-decoration: underline;
@@ -3994,7 +4180,7 @@ filex-explorer {
3994
4180
  }
3995
4181
  .fe-e2e-rk__escrow {
3996
4182
  border: 1px solid var(--fe-border);
3997
- border-left: 3px solid var(--fe-warning, var(--fe-primary));
4183
+ border-inline-start: 3px solid var(--fe-warning, var(--fe-primary));
3998
4184
  border-radius: var(--fe-radius-sm);
3999
4185
  padding: 8px 10px;
4000
4186
  font-size: 12.5px;
@@ -4038,7 +4224,7 @@ filex-explorer {
4038
4224
  line-height: 1.45;
4039
4225
  color: var(--fe-text);
4040
4226
  border: 1px solid var(--fe-border);
4041
- border-left: 3px solid var(--fe-warning, #f59e0b);
4227
+ border-inline-start: 3px solid var(--fe-warning, #f59e0b);
4042
4228
  border-radius: var(--fe-radius);
4043
4229
  background: var(--fe-bg-elev);
4044
4230
  }
@@ -4090,7 +4276,7 @@ filex-explorer {
4090
4276
  margin: 0 0 10px;
4091
4277
  padding: 10px 12px;
4092
4278
  border: 1px solid var(--fe-border);
4093
- border-left: 3px solid var(--fe-primary);
4279
+ border-inline-start: 3px solid var(--fe-primary);
4094
4280
  border-radius: var(--fe-radius-sm);
4095
4281
  background: color-mix(in srgb, var(--fe-primary) 6%, transparent);
4096
4282
  }
@@ -4120,7 +4306,7 @@ filex-explorer {
4120
4306
  accepting hands the operator a permanent second key. Amber rather than
4121
4307
  the recovery offer's primary tint, so it does not read as routine. */
4122
4308
  .fe-e2e-upgrade--escrow {
4123
- border-left-color: var(--fe-warning, #f59e0b);
4309
+ border-inline-start-color: var(--fe-warning, #f59e0b);
4124
4310
  background: color-mix(in srgb, var(--fe-warning, #f59e0b) 7%, transparent);
4125
4311
  }
4126
4312
  /* The link to the honest long version has to LOOK like a link: it inherits
@@ -4248,7 +4434,7 @@ filex-explorer {
4248
4434
  .fe-keepbadge {
4249
4435
  flex: 0 0 auto;
4250
4436
  display: inline-block;
4251
- margin-left: 4px;
4437
+ margin-inline-start: 4px;
4252
4438
  font-size: 11px;
4253
4439
  line-height: 1;
4254
4440
  vertical-align: middle;
@@ -4278,8 +4464,8 @@ filex-explorer {
4278
4464
  pointer-events none — it reports, it never blocks a click. */
4279
4465
  .fe-keep-strip {
4280
4466
  position: absolute;
4281
- left: 12px;
4282
- right: 12px;
4467
+ inset-inline-start: 12px;
4468
+ inset-inline-end: 12px;
4283
4469
  bottom: 10px;
4284
4470
  z-index: 30;
4285
4471
  display: flex;
@@ -4332,7 +4518,7 @@ filex-explorer {
4332
4518
  min-height: 0;
4333
4519
  display: flex;
4334
4520
  flex-direction: column;
4335
- border-right: 1px solid var(--fe-border);
4521
+ border-inline-end: 1px solid var(--fe-border);
4336
4522
  /* No `background: var(--fe-bg)` here on purpose — see .fe-conn. This one IS
4337
4523
  inside the full-screen explorer, whose root already paints --fe-bg, so a
4338
4524
  second identical fill would only be a second chance to disagree with it. */
@@ -4410,7 +4596,7 @@ filex-explorer {
4410
4596
  font: inherit;
4411
4597
  font-size: 13.5px;
4412
4598
  cursor: pointer;
4413
- text-align: left;
4599
+ text-align: start;
4414
4600
  white-space: nowrap;
4415
4601
  overflow: hidden;
4416
4602
  }
@@ -4480,7 +4666,7 @@ filex-explorer {
4480
4666
  font: inherit;
4481
4667
  font-size: 13.5px;
4482
4668
  cursor: pointer;
4483
- text-align: left;
4669
+ text-align: start;
4484
4670
  white-space: nowrap;
4485
4671
  overflow: hidden;
4486
4672
  }
@@ -4519,7 +4705,7 @@ filex-explorer {
4519
4705
  }
4520
4706
  /* "Shared" marker on a storage the caller only reaches through a grant. */
4521
4707
  .fe-sidenav__tag {
4522
- margin-left: auto;
4708
+ margin-inline-start: auto;
4523
4709
  flex: 0 0 auto;
4524
4710
  padding: 1px 6px;
4525
4711
  border-radius: 999px;
@@ -4529,6 +4715,23 @@ filex-explorer {
4529
4715
  font-weight: 600;
4530
4716
  letter-spacing: 0.02em;
4531
4717
  }
4718
+ /* The read-only mark (StorageTags, the one tag for that fact on every
4719
+ surface): at the row's end in the side panel; on a Home card, under the
4720
+ size line, from the start edge, and never wider than the card. */
4721
+ .fe-sidenav__stags {
4722
+ margin-inline-start: auto;
4723
+ flex: 0 0 auto;
4724
+ }
4725
+ .fe-home__storage-ro {
4726
+ align-self: flex-start;
4727
+ margin-top: 3px;
4728
+ max-width: 100%;
4729
+ }
4730
+ .fe-home__storage-ro .fe-stag {
4731
+ max-width: 100%;
4732
+ overflow: hidden;
4733
+ text-overflow: ellipsis;
4734
+ }
4532
4735
 
4533
4736
  /* === etiket:t1 — the Tags section ==================================== */
4534
4737
  /* A tag row is an ordinary panel item; only its label wraps differently,
@@ -4536,6 +4739,23 @@ filex-explorer {
4536
4739
  .fe-sidenav__item--tag .fe-sidenav__text {
4537
4740
  text-transform: none;
4538
4741
  }
4742
+ /* etiket:k2 — "Personal" / "Team" under the Tags caption: quieter than the
4743
+ caption itself (no capitals, no tracking) and carrying the kind's glyph, the
4744
+ same one on every row below it and on every chip on a file. Logical
4745
+ properties, so a right-to-left language mirrors it for free. */
4746
+ .fe-sidenav__subheading {
4747
+ display: flex;
4748
+ align-items: center;
4749
+ gap: 6px;
4750
+ margin: 6px 0 0;
4751
+ padding-inline: 18px;
4752
+ font-size: 11px;
4753
+ font-weight: 600;
4754
+ color: var(--fe-text-muted);
4755
+ }
4756
+ .fe-sidenav__subheading .fe-tagkind {
4757
+ flex: none;
4758
+ }
4539
4759
  /* "Show all (12)" / "Show fewer" — a link, not a button: it changes what this
4540
4760
  section shows, it is not a destination. */
4541
4761
  .fe-sidenav__more {
@@ -4549,7 +4769,7 @@ filex-explorer {
4549
4769
  color: var(--fe-primary);
4550
4770
  font: inherit;
4551
4771
  font-size: 12.5px;
4552
- text-align: left;
4772
+ text-align: start;
4553
4773
  cursor: pointer;
4554
4774
  }
4555
4775
  .fe-sidenav__more:hover {
@@ -4576,7 +4796,7 @@ filex-explorer {
4576
4796
  .fe-gal__star {
4577
4797
  position: absolute;
4578
4798
  top: 6px;
4579
- right: 6px;
4799
+ inset-inline-end: 6px;
4580
4800
  z-index: 2;
4581
4801
  /* Hidden until the tile is hovered or something inside it is focused —
4582
4802
  EXCEPT when the file is starred, which is the whole point: the mark has
@@ -4671,7 +4891,7 @@ filex-explorer {
4671
4891
  .fe-gal__check {
4672
4892
  position: absolute;
4673
4893
  top: 6px;
4674
- left: 6px;
4894
+ inset-inline-start: 6px;
4675
4895
  z-index: 2;
4676
4896
  }
4677
4897
 
@@ -4711,7 +4931,7 @@ filex-explorer {
4711
4931
  position: absolute;
4712
4932
  top: 0;
4713
4933
  bottom: 0;
4714
- left: 0;
4934
+ inset-inline-start: 0;
4715
4935
  width: min(280px, 84%);
4716
4936
  flex: none;
4717
4937
  /* Same layer as .fe-inspector--overlay: above the listing and the FAB (45),
@@ -4719,7 +4939,7 @@ filex-explorer {
4719
4939
  appended to <body> and would paint over anything lower anyway. */
4720
4940
  z-index: 60;
4721
4941
  background: var(--fe-bg);
4722
- border-right: 1px solid var(--fe-border);
4942
+ border-inline-end: 1px solid var(--fe-border);
4723
4943
  box-shadow: var(--fe-shadow);
4724
4944
  }
4725
4945
  .fe-sidenav__scrim {
@@ -4851,7 +5071,7 @@ filex-explorer {
4851
5071
  flex-wrap: wrap;
4852
5072
  }
4853
5073
  .fe-tokform__row .fe-s3keys__btn {
4854
- margin-left: auto;
5074
+ margin-inline-start: auto;
4855
5075
  }
4856
5076
  /* --- /gezinti:g1 --- */
4857
5077
 
@@ -4889,7 +5109,7 @@ filex-explorer {
4889
5109
  flex: 1 1 auto;
4890
5110
  min-width: 0;
4891
5111
  max-width: 720px;
4892
- padding: 0 10px 0 12px;
5112
+ padding-block: 0; padding-inline: 12px 10px;
4893
5113
  height: 40px;
4894
5114
  border-radius: 999px;
4895
5115
  border: 1px solid transparent;
@@ -4959,7 +5179,7 @@ filex-explorer {
4959
5179
  font: inherit;
4960
5180
  font-size: 14px;
4961
5181
  font-weight: 600;
4962
- text-align: left;
5182
+ text-align: start;
4963
5183
  white-space: nowrap;
4964
5184
  overflow: hidden;
4965
5185
  cursor: pointer;
@@ -5044,7 +5264,7 @@ filex-explorer {
5044
5264
  display: flex;
5045
5265
  align-items: center;
5046
5266
  gap: 6px;
5047
- padding-right: 8px;
5267
+ padding-inline-end: 8px;
5048
5268
  }
5049
5269
 
5050
5270
  /* ── the filter row ───────────────────────────────────────────────────── */
@@ -5070,7 +5290,7 @@ filex-explorer {
5070
5290
  align-items: center;
5071
5291
  gap: 4px;
5072
5292
  height: 32px;
5073
- padding: 0 10px 0 12px;
5293
+ padding-block: 0; padding-inline: 12px 10px;
5074
5294
  border: 1px solid var(--fe-border-strong);
5075
5295
  border-radius: 999px;
5076
5296
  background: transparent;
@@ -5116,8 +5336,8 @@ filex-explorer {
5116
5336
  }
5117
5337
  .fe-filterbar__count {
5118
5338
  flex: 0 0 auto;
5119
- margin-left: auto;
5120
- padding-left: 8px;
5339
+ margin-inline-start: auto;
5340
+ padding-inline-start: 8px;
5121
5341
  font-size: 12px;
5122
5342
  color: var(--fe-text-muted);
5123
5343
  white-space: nowrap;
@@ -5151,7 +5371,7 @@ filex-explorer {
5151
5371
  color: inherit;
5152
5372
  font: inherit;
5153
5373
  font-size: 13px;
5154
- text-align: left;
5374
+ text-align: start;
5155
5375
  cursor: pointer;
5156
5376
  }
5157
5377
  .fe-filterpop__item:hover {
@@ -5290,7 +5510,7 @@ filex-explorer {
5290
5510
  max-width: none;
5291
5511
  }
5292
5512
  .fe--narrow .fe-subhead__actions {
5293
- padding-right: 4px;
5513
+ padding-inline-end: 4px;
5294
5514
  }
5295
5515
  .fe--narrow .fe-filterbar {
5296
5516
  padding: 6px 8px;
@@ -5435,12 +5655,12 @@ filex-explorer {
5435
5655
  }
5436
5656
  .fe-grid__card--folder .fe-grid__foot {
5437
5657
  min-height: 54px;
5438
- padding: 0 var(--fe-gap-xs) 0 var(--fe-gap);
5658
+ padding-block: 0; padding-inline: var(--fe-gap) var(--fe-gap-xs);
5439
5659
  gap: var(--fe-gap);
5440
5660
  }
5441
5661
  .fe-grid__card--file .fe-grid__foot {
5442
5662
  min-height: 56px;
5443
- padding: 0 var(--fe-gap-xs) 0 var(--fe-gap-sm);
5663
+ padding-block: 0; padding-inline: var(--fe-gap-sm) var(--fe-gap-xs);
5444
5664
  gap: var(--fe-gap-sm);
5445
5665
  }
5446
5666
 
@@ -5648,7 +5868,7 @@ filex-explorer {
5648
5868
  .fe-drivesearch {
5649
5869
  height: var(--fe-h-lg);
5650
5870
  max-width: 560px;
5651
- padding: 0 var(--fe-gap-sm) 0 var(--fe-gap);
5871
+ padding-block: 0; padding-inline: var(--fe-gap) var(--fe-gap-sm);
5652
5872
  gap: var(--fe-gap-sm);
5653
5873
  border-radius: var(--fe-radius);
5654
5874
  border: 1px solid var(--fe-border);
@@ -5726,7 +5946,7 @@ filex-explorer {
5726
5946
  }
5727
5947
  .fe-subhead__actions {
5728
5948
  gap: var(--fe-gap-sm);
5729
- padding-right: var(--fe-gap);
5949
+ padding-inline-end: var(--fe-gap);
5730
5950
  }
5731
5951
  .fe-breadcrumb {
5732
5952
  gap: var(--fe-gap-xs);
@@ -5781,7 +6001,7 @@ filex-explorer {
5781
6001
  caret that says it opens something. */
5782
6002
  .fe-filterbar__chip {
5783
6003
  height: var(--fe-h-sm);
5784
- padding: 0 var(--fe-gap-sm) 0 var(--fe-gap);
6004
+ padding-block: 0; padding-inline: var(--fe-gap) var(--fe-gap-sm);
5785
6005
  gap: var(--fe-gap-xs);
5786
6006
  border-radius: var(--fe-radius);
5787
6007
  border-color: var(--fe-border);
@@ -5969,7 +6189,7 @@ filex-explorer {
5969
6189
  .fe-selbar__measure {
5970
6190
  position: absolute;
5971
6191
  top: 0;
5972
- left: 0;
6192
+ inset-inline-start: 0;
5973
6193
  display: flex;
5974
6194
  align-items: center;
5975
6195
  gap: var(--fe-gap-xs);
@@ -6062,12 +6282,12 @@ filex-explorer {
6062
6282
  .fe-list__check.is-on::after {
6063
6283
  content: '';
6064
6284
  position: absolute;
6065
- left: 6px;
6285
+ left: 6px; /* rtl-physical: a tick reads the same in every script — it is not an arrow */
6066
6286
  top: 2.5px;
6067
6287
  width: 4px;
6068
6288
  height: 9px;
6069
6289
  border: solid var(--fe-text-on-primary);
6070
- border-width: 0 2px 2px 0;
6290
+ border-width: 0 2px 2px 0; /* rtl-physical: the tick's two strokes (see `left` above) */
6071
6291
  transform: rotate(45deg);
6072
6292
  }
6073
6293
  .fe-list__check:focus-visible {
@@ -6187,10 +6407,12 @@ filex-explorer {
6187
6407
  .fe--narrow .fe-list__col--type,
6188
6408
  .fe--narrow .fe-list__col--owner,
6189
6409
  .fe--narrow .fe-list__col--mod,
6410
+ .fe--narrow .fe-list__col--remaining,
6190
6411
  .fe--narrow .fe-list__col--star,
6191
6412
  .fe--narrow .fe-list__head .fe-list__col--type,
6192
6413
  .fe--narrow .fe-list__head .fe-list__col--owner,
6193
6414
  .fe--narrow .fe-list__head .fe-list__col--mod,
6415
+ .fe--narrow .fe-list__head .fe-list__col--remaining,
6194
6416
  .fe--narrow .fe-list__head .fe-list__col--star {
6195
6417
  display: none;
6196
6418
  }
@@ -6204,6 +6426,7 @@ filex-explorer {
6204
6426
  .fe-list__col--type,
6205
6427
  .fe-list__col--owner,
6206
6428
  .fe-list__col--mod,
6429
+ .fe-list__col--remaining,
6207
6430
  .fe-list__col--star,
6208
6431
  /* ⚠ The header's cells need the same two-class weight as the rule that
6209
6432
  lays them out (`.fe-list__head .fe-list__col`), or they stay visible and
@@ -6212,6 +6435,7 @@ filex-explorer {
6212
6435
  .fe-list__head .fe-list__col--type,
6213
6436
  .fe-list__head .fe-list__col--owner,
6214
6437
  .fe-list__head .fe-list__col--mod,
6438
+ .fe-list__head .fe-list__col--remaining,
6215
6439
  .fe-list__head .fe-list__col--star {
6216
6440
  display: none;
6217
6441
  }
@@ -6240,7 +6464,7 @@ filex-explorer {
6240
6464
  font-size: 8.5px;
6241
6465
  line-height: 11px;
6242
6466
  color: var(--fe-text-muted);
6243
- text-align: left;
6467
+ text-align: start;
6244
6468
  white-space: pre;
6245
6469
  overflow: hidden;
6246
6470
  /* The box always ends mid-file, so END it. A hard crop through a line of
@@ -6532,13 +6756,13 @@ filex-explorer {
6532
6756
  background: var(--fe-bg-hover);
6533
6757
  color: var(--fe-text);
6534
6758
  }
6535
- .fe-viewer__chev--prev { left: var(--fe-gap); }
6536
- .fe-viewer__chev--next { right: var(--fe-gap); }
6759
+ .fe-viewer__chev--prev { inset-inline-start: var(--fe-gap); }
6760
+ .fe-viewer__chev--next { inset-inline-end: var(--fe-gap); }
6537
6761
 
6538
6762
  /* ---- Zoom pill ---- */
6539
6763
  .fe-viewer__zoom {
6540
6764
  position: absolute;
6541
- left: 50%;
6765
+ left: 50%; /* rtl-physical: centred by translate(-50%) — the same place in both directions */
6542
6766
  bottom: 20px;
6543
6767
  transform: translateX(-50%);
6544
6768
  display: inline-flex;
@@ -6607,8 +6831,8 @@ filex-explorer {
6607
6831
  gap: var(--fe-gap-sm);
6608
6832
  padding: 0 var(--fe-gap-sm);
6609
6833
  }
6610
- .fe-viewer__chev--prev { left: var(--fe-gap-xs); }
6611
- .fe-viewer__chev--next { right: var(--fe-gap-xs); }
6834
+ .fe-viewer__chev--prev { inset-inline-start: var(--fe-gap-xs); }
6835
+ .fe-viewer__chev--next { inset-inline-end: var(--fe-gap-xs); }
6612
6836
  }
6613
6837
  /* === /gorunum:v1-viewer === */
6614
6838
 
@@ -6794,6 +7018,42 @@ filex-explorer {
6794
7018
  .fe-advsearch__hint--block {
6795
7019
  margin: 0;
6796
7020
  }
7021
+ /* etiket:k2 — the known tags under the Tags box: one-click picks, each with
7022
+ its kind's glyph, grouped Personal then Team like the navigation panel. */
7023
+ .fe-advsearch__tagpicks {
7024
+ display: flex;
7025
+ flex-wrap: wrap;
7026
+ align-items: center;
7027
+ gap: 4px 6px;
7028
+ margin-top: 6px;
7029
+ }
7030
+ .fe-advsearch__tagkind {
7031
+ display: inline-flex;
7032
+ align-items: center;
7033
+ gap: 4px;
7034
+ font-size: 11px;
7035
+ font-weight: 600;
7036
+ color: var(--fe-text-muted);
7037
+ margin-inline-end: 2px;
7038
+ }
7039
+ .fe-advsearch__tagpick {
7040
+ display: inline-flex;
7041
+ align-items: center;
7042
+ gap: 4px;
7043
+ padding: 2px 8px;
7044
+ border: 1px solid var(--fe-border);
7045
+ border-radius: 999px;
7046
+ background: transparent;
7047
+ color: var(--fe-text);
7048
+ font: inherit;
7049
+ font-size: 12px;
7050
+ cursor: pointer;
7051
+ }
7052
+ .fe-advsearch__tagpick:hover,
7053
+ .fe-advsearch__tagpick:focus-visible {
7054
+ border-color: var(--fe-primary);
7055
+ color: var(--fe-primary);
7056
+ }
6797
7057
 
6798
7058
  /* The live count. Boxed, because it is a measurement and not a caption: the
6799
7059
  number and the sentence explaining what it cost belong to each other. */
@@ -6831,7 +7091,7 @@ filex-explorer {
6831
7091
 
6832
7092
  /* Reset sits apart from Cancel/Search: it changes the form, they leave it. */
6833
7093
  .fe-advsearch__reset {
6834
- margin-right: auto;
7094
+ margin-inline-end: auto;
6835
7095
  }
6836
7096
 
6837
7097
  /* One column on a narrow viewport — two 1fr fields at 390px leave a date
@@ -7029,7 +7289,7 @@ filex-explorer {
7029
7289
  the crumbs ended at x=381 and the pencil sat at x=1242. It belongs beside the
7030
7290
  chevron, which is where the reference puts it. */
7031
7291
  .fe-breadcrumb__edit {
7032
- margin-left: 0;
7292
+ margin-inline-start: 0;
7033
7293
  }
7034
7294
  .fe-breadcrumb__edit .fe-icon,
7035
7295
  .fe-breadcrumb__edit .fe-aicon {
@@ -7215,7 +7475,7 @@ filex-explorer {
7215
7475
  .fe-share__knob {
7216
7476
  position: absolute;
7217
7477
  top: 2px;
7218
- left: 2px;
7478
+ inset-inline-start: 2px;
7219
7479
  width: 18px;
7220
7480
  height: 18px;
7221
7481
  border-radius: 50%;
@@ -7224,7 +7484,8 @@ filex-explorer {
7224
7484
  }
7225
7485
  .fe-share__switch[aria-checked='true'] .fe-share__knob {
7226
7486
  background: var(--fe-text-on-primary);
7227
- transform: translateX(16px);
7487
+ /* Toward "on" = toward the inline end, in either direction. */
7488
+ transform: translateX(calc(16px * var(--filex-dir-x, 1)));
7228
7489
  }
7229
7490
 
7230
7491
  /* The sentence. Full-strength text on purpose: it is the answer to the
@@ -7270,27 +7531,38 @@ filex-explorer {
7270
7531
  background: var(--fe-bg-elev);
7271
7532
  }
7272
7533
  .fe-share__copy {
7534
+ /* ⚠⚠ THE copy button — every "Copy" in this dialog (the link, the PIN, the
7535
+ command line, an older link, the upload link) is this one control. There
7536
+ were three: a filled primary button, a small text button and the same
7537
+ small button beside a revoke cross (QA, 2026-09-21). Same glyph, same
7538
+ words, same "Copied" answer; only the row decides the height. */
7273
7539
  flex: 0 0 auto;
7274
7540
  display: inline-flex;
7275
7541
  align-items: center;
7276
7542
  gap: var(--fe-gap-xs);
7277
- height: var(--fe-h-lg);
7278
- padding: 0 var(--fe-gap);
7279
- border: 1px solid var(--fe-primary);
7543
+ height: var(--fe-h-md);
7544
+ padding: 0 var(--fe-gap-sm);
7545
+ border: 1px solid var(--fe-border);
7280
7546
  border-radius: var(--fe-radius);
7281
- background: var(--fe-primary);
7282
- color: var(--fe-text-on-primary);
7547
+ background: var(--fe-bg);
7548
+ color: var(--fe-text);
7283
7549
  font-family: inherit;
7284
- font-size: var(--fe-text-md);
7550
+ font-size: var(--fe-text-sm);
7285
7551
  font-weight: 500;
7552
+ white-space: nowrap;
7286
7553
  cursor: pointer;
7287
7554
  }
7288
7555
  .fe-share__copy .fe-aicon {
7289
7556
  color: inherit;
7557
+ width: 14px;
7558
+ height: 14px;
7290
7559
  }
7291
7560
  .fe-share__copy:hover {
7292
- background: var(--fe-primary-hover);
7293
- border-color: var(--fe-primary-hover);
7561
+ background: var(--fe-bg-hover);
7562
+ border-color: var(--fe-border-strong);
7563
+ }
7564
+ .fe-share__linkrow > .fe-share__copy {
7565
+ height: var(--fe-h-lg);
7294
7566
  }
7295
7567
 
7296
7568
  .fe-share__pinrow {
@@ -7334,7 +7606,7 @@ filex-explorer {
7334
7606
  font-family: inherit;
7335
7607
  font-size: var(--fe-text-md);
7336
7608
  font-weight: 500;
7337
- text-align: left;
7609
+ text-align: start;
7338
7610
  cursor: pointer;
7339
7611
  }
7340
7612
  .fe-share__sechead:hover .fe-share__seclabel {
@@ -7363,7 +7635,7 @@ filex-explorer {
7363
7635
  .fe-share__secmeta {
7364
7636
  flex: 1 1 auto;
7365
7637
  min-width: 0;
7366
- text-align: right;
7638
+ text-align: end;
7367
7639
  font-size: var(--fe-text-xs);
7368
7640
  font-weight: 400;
7369
7641
  color: var(--fe-text-muted);
@@ -7374,7 +7646,7 @@ filex-explorer {
7374
7646
  /* Indented to the label, not to the caret: the panel belongs to the heading
7375
7647
  it opened from. */
7376
7648
  .fe-share__panel {
7377
- padding: 0 0 var(--fe-gap) calc(var(--fe-gap-lg) + var(--fe-gap-sm));
7649
+ padding-block: 0 var(--fe-gap); padding-inline: calc(var(--fe-gap-lg) + var(--fe-gap-sm)) 0;
7378
7650
  }
7379
7651
 
7380
7652
  /* ---- fields ---- */
@@ -7384,7 +7656,7 @@ filex-explorer {
7384
7656
  gap: var(--fe-gap-sm) var(--fe-gap);
7385
7657
  align-items: end;
7386
7658
  }
7387
- .fe-share__opts > .fe-share__check {
7659
+ .fe-share__opts > .fe-share__togglerow {
7388
7660
  grid-column: 1 / -1;
7389
7661
  }
7390
7662
  .fe-share__field {
@@ -7416,6 +7688,13 @@ filex-explorer {
7416
7688
  outline: none;
7417
7689
  border-color: var(--fe-primary);
7418
7690
  }
7691
+ /* Greyed with its reason above it (RBAC off on the storage): it has to LOOK
7692
+ unusable, not merely refuse the click. */
7693
+ .fe-share__select:disabled,
7694
+ .fe-share__input:disabled {
7695
+ opacity: 0.55;
7696
+ cursor: not-allowed;
7697
+ }
7419
7698
  /* The per-row level select sits in a 34px row and takes that height rather
7420
7699
  than stretching the row it lives in. */
7421
7700
  .fe-share__select--sm {
@@ -7428,14 +7707,21 @@ filex-explorer {
7428
7707
  width: 140px;
7429
7708
  height: var(--fe-h-md);
7430
7709
  }
7431
- .fe-share__check {
7432
- display: inline-flex;
7710
+ /* A yes/no option: its words, then the dialog's one switch. The whole row is
7711
+ the label, so a click on the words flips it too. */
7712
+ .fe-share__togglerow {
7713
+ display: flex;
7433
7714
  align-items: center;
7434
- gap: var(--fe-gap-xs);
7715
+ justify-content: space-between;
7716
+ gap: var(--fe-gap);
7717
+ min-height: var(--fe-h-md);
7435
7718
  font-size: var(--fe-text-md);
7436
7719
  color: var(--fe-text);
7437
7720
  cursor: pointer;
7438
7721
  }
7722
+ .fe-share__togglelabel {
7723
+ min-width: 0;
7724
+ }
7439
7725
 
7440
7726
  /* ---- buttons ---- */
7441
7727
  .fe-share__btn {
@@ -7478,22 +7764,6 @@ filex-explorer {
7478
7764
  .fe-share__btn .fe-aicon {
7479
7765
  color: inherit;
7480
7766
  }
7481
- .fe-share__mini {
7482
- flex: 0 0 auto;
7483
- height: var(--fe-h-sm);
7484
- padding: 0 var(--fe-gap-sm);
7485
- border: 1px solid var(--fe-border);
7486
- border-radius: var(--fe-radius-sm);
7487
- background: var(--fe-bg);
7488
- color: var(--fe-text);
7489
- font-family: inherit;
7490
- font-size: var(--fe-text-xs);
7491
- white-space: nowrap;
7492
- cursor: pointer;
7493
- }
7494
- .fe-share__mini:hover {
7495
- background: var(--fe-bg-hover);
7496
- }
7497
7767
  /* A verb rendered as text, for the two doors that lead somewhere else
7498
7768
  ("just send a link", "upload limits") rather than acting here. */
7499
7769
  .fe-share__linkbtn {
@@ -7505,7 +7775,7 @@ filex-explorer {
7505
7775
  color: var(--fe-primary);
7506
7776
  font-family: inherit;
7507
7777
  font-size: var(--fe-text-sm);
7508
- text-align: left;
7778
+ text-align: start;
7509
7779
  cursor: pointer;
7510
7780
  }
7511
7781
  .fe-share__linkbtn:hover {
@@ -7553,8 +7823,8 @@ filex-explorer {
7553
7823
  .fe-share__suggest {
7554
7824
  position: absolute;
7555
7825
  top: calc(100% + var(--fe-gap-xs));
7556
- left: 0;
7557
- right: 0;
7826
+ inset-inline-start: 0;
7827
+ inset-inline-end: 0;
7558
7828
  z-index: 5;
7559
7829
  margin: 0;
7560
7830
  padding: var(--fe-gap-xs);
@@ -7829,13 +8099,13 @@ filex-explorer {
7829
8099
  control in the row for a control that did not move. */
7830
8100
  .fe-toolbar__brand--gutter {
7831
8101
  min-width: calc(var(--fe-sidenav-w) - 12px);
7832
- padding-right: var(--fe-gap-sm);
8102
+ padding-inline-end: var(--fe-gap-sm);
7833
8103
  }
7834
8104
  /* At 390px there is no room to reserve anything: the row is toggle · field ·
7835
8105
  "⋯" and the brand mark is not rendered at all (Toolbar.vue). */
7836
8106
  .fe--narrow .fe-toolbar__brand--gutter {
7837
8107
  min-width: 0;
7838
- padding-right: 0;
8108
+ padding-inline-end: 0;
7839
8109
  }
7840
8110
  /* The host's mark. It is a slot, so this styles whatever arrives: enough room
7841
8111
  for a 22px logo beside a word, ellipsised rather than wrapped — a two-line
@@ -7883,7 +8153,7 @@ filex-explorer {
7883
8153
  flex: 1 1 auto;
7884
8154
  min-width: 0;
7885
8155
  margin: 0;
7886
- padding-left: var(--fe-gap-xs);
8156
+ padding-inline-start: var(--fe-gap-xs);
7887
8157
  font-size: var(--fe-text-md);
7888
8158
  font-weight: 600;
7889
8159
  line-height: 1.3;
@@ -7909,7 +8179,9 @@ filex-explorer {
7909
8179
  grid-template-columns: repeat(auto-fill, 186px);
7910
8180
  gap: var(--fe-gap);
7911
8181
  align-content: start;
7912
- align-items: start;
8182
+ /* A read-only card carries one more line (its tag); its row-mates grow
8183
+ with it rather than standing short beside it. */
8184
+ align-items: stretch;
7913
8185
  }
7914
8186
  /* The storage card IS the grid's folder card — 186×56, the solid glyph inset
7915
8187
  12px — because in the explorer's own root listing a storage is a folder row.
@@ -7922,7 +8194,7 @@ filex-explorer {
7922
8194
  width: 186px;
7923
8195
  min-height: 56px;
7924
8196
  padding: 0 var(--fe-gap);
7925
- text-align: left;
8197
+ text-align: start;
7926
8198
  border: 1px solid var(--fe-border);
7927
8199
  border-radius: var(--fe-radius-md);
7928
8200
  background: var(--fe-bg);
@@ -7991,7 +8263,7 @@ filex-explorer {
7991
8263
  min-height: 0;
7992
8264
  height: auto;
7993
8265
  align-items: flex-start;
7994
- text-align: left;
8266
+ text-align: start;
7995
8267
  padding: var(--fe-gap) 2px var(--fe-gap-lg);
7996
8268
  }
7997
8269
  /* The grids inside Home are GridView's, and GridView fills its pane. Inside a
@@ -8083,7 +8355,7 @@ filex-explorer {
8083
8355
  justify-content: center;
8084
8356
  width: var(--fe-h-sm);
8085
8357
  height: var(--fe-h-sm);
8086
- margin: -2px -4px 0 0;
8358
+ margin-block: -2px 0; margin-inline: 0 -4px;
8087
8359
  padding: 0;
8088
8360
  border: 0;
8089
8361
  border-radius: var(--fe-radius-sm);
@@ -8128,12 +8400,12 @@ filex-explorer {
8128
8400
  align-items: center;
8129
8401
  justify-content: center;
8130
8402
  border-radius: 0;
8131
- border-right: 1px solid var(--fe-border);
8403
+ border-inline-end: 1px solid var(--fe-border);
8132
8404
  padding: 0 var(--fe-gap-sm);
8133
8405
  min-width: 0;
8134
8406
  }
8135
8407
  .fe-advsearch__seg--tabs .fe-advsearch__seg-btn:last-child {
8136
- border-right: 0;
8408
+ border-inline-end: 0;
8137
8409
  }
8138
8410
  .fe-advsearch__seg--tabs .fe-advsearch__seg-btn.is-active {
8139
8411
  background: var(--fe-primary-soft);
@@ -8261,13 +8533,17 @@ filex-explorer {
8261
8533
  .fe-advsearch__viewall::after {
8262
8534
  content: ' \2192';
8263
8535
  }
8536
+ /* ⚠ RTL: "view all" points the way the line reads. */
8537
+ .fe-advsearch__viewall:dir(rtl)::after {
8538
+ content: ' \2190';
8539
+ }
8264
8540
  .fe-advsearch__viewall:hover {
8265
8541
  text-decoration: underline;
8266
8542
  }
8267
8543
 
8268
8544
  .fe-advsearch__btn-icon {
8269
8545
  display: inline-flex;
8270
- margin-right: 6px;
8546
+ margin-inline-end: 6px;
8271
8547
  vertical-align: -3px;
8272
8548
  }
8273
8549
  .fe-advsearch__btn-icon .fe-aicon {
@@ -8410,7 +8686,7 @@ filex-explorer {
8410
8686
  .fe-inspector__close {
8411
8687
  font-size: 0;
8412
8688
  padding: 0;
8413
- margin-right: -4px;
8689
+ margin-inline-end: -4px;
8414
8690
  }
8415
8691
 
8416
8692
  /* Tabs: two equal halves under the head, the active one underlined. */
@@ -8602,10 +8878,10 @@ filex-explorer {
8602
8878
  .fe-thumb__play {
8603
8879
  position: absolute;
8604
8880
  top: 50%;
8605
- left: 50%;
8881
+ left: 50%; /* rtl-physical: centred by the negative margins below — symmetric */
8606
8882
  width: 34px;
8607
8883
  height: 34px;
8608
- margin: -17px 0 0 -17px;
8884
+ margin: -17px 0 0 -17px; /* rtl-physical: half the disc, to centre it */
8609
8885
  border-radius: 50%;
8610
8886
  background: color-mix(in srgb, var(--fe-bg-elev) 88%, transparent);
8611
8887
  box-shadow: 0 1px 4px color-mix(in srgb, var(--fe-text) 22%, transparent);
@@ -8617,22 +8893,22 @@ filex-explorer {
8617
8893
  content: '';
8618
8894
  position: absolute;
8619
8895
  top: 50%;
8620
- left: 50%;
8621
- margin: -6px 0 0 -4px;
8896
+ left: 50%; /* rtl-physical: ⚠ PLAY points the way media plays, in every language — never mirrored */
8897
+ margin: -6px 0 0 -4px; /* rtl-physical: optical centre of the play triangle */
8622
8898
  border-style: solid;
8623
- border-width: 6px 0 6px 10px;
8624
- border-color: transparent transparent transparent var(--fe-text);
8899
+ border-width: 6px 0 6px 10px; /* rtl-physical: the play triangle, pointing right */
8900
+ border-color: transparent transparent transparent var(--fe-text); /* rtl-physical: the play triangle */
8625
8901
  }
8626
8902
  /* The gallery tile is several times the area of a grid card's preview box, so
8627
8903
  the badge scales with it. Scale only: one badge, one rule, two sizes. */
8628
8904
  .fe-gal__thumb .fe-thumb__play {
8629
8905
  width: 46px;
8630
8906
  height: 46px;
8631
- margin: -23px 0 0 -23px;
8907
+ margin: -23px 0 0 -23px; /* rtl-physical: half the disc, to centre it */
8632
8908
  }
8633
8909
  .fe-gal__thumb .fe-thumb__play::after {
8634
- margin: -8px 0 0 -5px;
8635
- border-width: 8px 0 8px 13px;
8910
+ margin: -8px 0 0 -5px; /* rtl-physical: optical centre of the play triangle */
8911
+ border-width: 8px 0 8px 13px; /* rtl-physical: the play triangle, pointing right */
8636
8912
  }
8637
8913
  /* === /gorunum:v1-preview === */
8638
8914
 
@@ -8667,17 +8943,17 @@ filex-explorer {
8667
8943
  /* The trailing edge. ⚠ `.fe-filterbar__count` carried this same `auto`
8668
8944
  while it was the last child; leaving both would split the free space
8669
8945
  between them and park the count in mid-row the moment a filter is set. */
8670
- margin-left: auto;
8946
+ margin-inline-start: auto;
8671
8947
  display: flex;
8672
8948
  align-items: center;
8673
8949
  /* Flush, deliberately: the chevron belongs TO the button on its left — it
8674
8950
  opens the menu that changes what that button says. A gap would read as
8675
8951
  two unrelated controls. */
8676
8952
  gap: 0;
8677
- padding-left: var(--fe-gap-sm);
8953
+ padding-inline-start: var(--fe-gap-sm);
8678
8954
  }
8679
8955
  .fe-filterbar__count {
8680
- margin-left: 0;
8956
+ margin-inline-start: 0;
8681
8957
  }
8682
8958
 
8683
8959
  .fe-filterbar__sortdir {
@@ -8808,9 +9084,11 @@ filex-explorer {
8808
9084
  under a transparent button. */
8809
9085
  .fe-filterbar__actions {
8810
9086
  position: sticky;
8811
- right: 0;
9087
+ inset-inline-end: 0;
8812
9088
  background: var(--fe-bg);
8813
- box-shadow: -8px 0 8px -6px var(--fe-bg);
9089
+ /* The fade falls on the START side of this end-pinned cell, where the chips
9090
+ slide under it. */
9091
+ box-shadow: calc(-8px * var(--filex-dir-x, 1)) 0 8px -6px var(--fe-bg);
8814
9092
  }
8815
9093
  .fe-filterbar__actions:hover,
8816
9094
  .fe-filterbar__actions[aria-expanded='true'] {
@@ -8942,7 +9220,7 @@ filex-explorer {
8942
9220
  .fe-toolbar__soon-tip {
8943
9221
  position: absolute;
8944
9222
  top: calc(100% + var(--fe-gap-xs));
8945
- right: 0;
9223
+ inset-inline-end: 0;
8946
9224
  z-index: 6;
8947
9225
  padding: 4px 8px;
8948
9226
  border: 1px solid var(--fe-border);
@@ -9262,7 +9540,7 @@ filex-explorer {
9262
9540
  color: var(--fe-text);
9263
9541
  font: inherit;
9264
9542
  font-size: var(--fe-text-md);
9265
- text-align: left;
9543
+ text-align: start;
9266
9544
  cursor: pointer;
9267
9545
  }
9268
9546
  .fe-newdoc__row:hover {
@@ -9372,9 +9650,14 @@ filex-explorer {
9372
9650
  the label beside it. */
9373
9651
  .fe-list__resize {
9374
9652
  position: absolute;
9653
+ /* Above the pinned ⋮ cell (z-index 1): the LAST column's handle straddles
9654
+ into it, and under it the edge could not be grabbed at all — measured in
9655
+ LTR and RTL alike (elementFromPoint at the handle = the column-menu
9656
+ button). Below the frozen lead (3). */
9657
+ z-index: 2;
9375
9658
  top: 0;
9376
9659
  bottom: 0;
9377
- right: calc(var(--fe-gap-sm) / -2 - 1px);
9660
+ inset-inline-end: calc(var(--fe-gap-sm) / -2 - 1px);
9378
9661
  width: 9px;
9379
9662
  cursor: col-resize;
9380
9663
  /* ⚠ `touch-action: none` or a drag on a touchscreen scrolls the listing
@@ -9387,7 +9670,7 @@ filex-explorer {
9387
9670
  position: absolute;
9388
9671
  top: 20%;
9389
9672
  bottom: 20%;
9390
- left: 4px;
9673
+ inset-inline-start: 4px;
9391
9674
  width: 1px;
9392
9675
  background: var(--fe-border);
9393
9676
  opacity: 0;
@@ -9470,7 +9753,7 @@ filex-explorer {
9470
9753
  background: transparent;
9471
9754
  color: inherit;
9472
9755
  font: inherit;
9473
- text-align: left;
9756
+ text-align: start;
9474
9757
  cursor: pointer;
9475
9758
  }
9476
9759
  .fe-colmenu__row:hover {
@@ -9529,6 +9812,13 @@ filex-explorer {
9529
9812
  .fe-trashbar__action {
9530
9813
  flex: 0 0 auto;
9531
9814
  }
9815
+ /* A purge that goes on in the background, counted as it goes. */
9816
+ .fe-trashbar__progress {
9817
+ flex: 0 0 auto;
9818
+ margin: 0;
9819
+ color: var(--fe-text);
9820
+ font-variant-numeric: tabular-nums;
9821
+ }
9532
9822
 
9533
9823
  /* tablo:t1 — the row's real thumbnail. Exactly the type tile's box (24px), so
9534
9824
  turning a glyph into a picture does not move a single row. */
@@ -9684,10 +9974,10 @@ filex-explorer {
9684
9974
  background: var(--fe-primary);
9685
9975
  }
9686
9976
  .fe-list__col.is-drop-before::before {
9687
- left: calc(var(--fe-gap-sm) / -2 - 1px);
9977
+ inset-inline-start: calc(var(--fe-gap-sm) / -2 - 1px);
9688
9978
  }
9689
9979
  .fe-list__col.is-drop-after::after {
9690
- right: calc(var(--fe-gap-sm) / -2 - 1px);
9980
+ inset-inline-end: calc(var(--fe-gap-sm) / -2 - 1px);
9691
9981
  }
9692
9982
 
9693
9983
  /* ══ tasi:m1 — the destination picker ("Move to" / "Copy to") ══════════
@@ -9809,7 +10099,7 @@ filex-explorer {
9809
10099
  background: transparent;
9810
10100
  color: var(--fe-text);
9811
10101
  font-size: var(--fe-text-md);
9812
- text-align: left;
10102
+ text-align: start;
9813
10103
  cursor: pointer;
9814
10104
  }
9815
10105
  .fe-destpick__row:hover:not(:disabled) {
@@ -10016,7 +10306,7 @@ filex-explorer {
10016
10306
  display: flex;
10017
10307
  align-items: center;
10018
10308
  gap: 2px;
10019
- padding-right: var(--fe-gap-xs);
10309
+ padding-inline-end: var(--fe-gap-xs);
10020
10310
  }
10021
10311
  .fe-colmenu__line .fe-colmenu__row {
10022
10312
  flex: 1 1 auto;
@@ -10084,7 +10374,7 @@ filex-explorer {
10084
10374
  * and that only the second one can be closed.
10085
10375
  */
10086
10376
  .fe-pane--split {
10087
- border-left: 1px solid var(--fe-border);
10377
+ border-inline-start: 1px solid var(--fe-border);
10088
10378
  }
10089
10379
  /* The pane's × — the one control the main pane does not have. Sized like the
10090
10380
  view switcher beside it so the address row keeps its 37px height, which is
@@ -10323,6 +10613,22 @@ filex-explorer {
10323
10613
  display: block;
10324
10614
  height: 100%;
10325
10615
  overflow: auto;
10616
+ /* ⚠⚠ Reserve the vertical scrollbar's room even when it is not showing
10617
+ (PR #39, Berk Başarır). Every table in the product (DataTable) lays itself
10618
+ out against THIS box's width, and the layout decides whether the box
10619
+ needs scrollbars — so without the reserve there is a loop waiting to
10620
+ close: the table overflows by a pixel, the horizontal bar takes a row's
10621
+ height, the vertical bar comes, the box is a scrollbar narrower, the table
10622
+ fits, both bars go, the box is wide again — every frame. Reported from a
10623
+ Windows PC (a 12-file folder froze the tab); not reproduced on v0.42.2 or
10624
+ v0.43.0, whose widths round to the pixel the box snaps to. But a table
10625
+ that is told it has 2px more than it does on alternate width bands loops
10626
+ exactly like that in a pane its rows just fit — and stops looping with
10627
+ this one line (e2e 139 measures both). The reserve is what makes the
10628
+ table's width independent of its own vertical scrollbar, so no future
10629
+ width rule can close the loop. Overlay scrollbars (macOS by default,
10630
+ phones) take no room, so there it reserves nothing. */
10631
+ scrollbar-gutter: stable;
10326
10632
  }
10327
10633
 
10328
10634
  /* ⚠⚠ FLEX ROWS, not grid rows. A sticky GRID item is confined to its own grid
@@ -10391,8 +10697,9 @@ filex-explorer {
10391
10697
  display: contents;
10392
10698
  }
10393
10699
  /* Location was never in the muted-value rule above it — it is a value like
10394
- Type and Owner and reads like one. */
10395
- .fe-list__col--location {
10700
+ Type and Owner and reads like one. So is the Trash's "time left". */
10701
+ .fe-list__col--location,
10702
+ .fe-list__col--remaining {
10396
10703
  overflow: hidden;
10397
10704
  text-overflow: ellipsis;
10398
10705
  white-space: nowrap;
@@ -10420,15 +10727,12 @@ filex-explorer {
10420
10727
  .fe-list__row,
10421
10728
  .fe--density-compact .fe-list__head,
10422
10729
  .fe--density-compact .fe-list__row {
10423
- padding-right: 0;
10730
+ padding-inline-end: 0;
10424
10731
  }
10425
10732
  .fe-list__head .fe-list__col--menu,
10426
10733
  .fe-list__row .fe-list__col--menu {
10427
- position: sticky;
10428
- right: 0;
10429
- z-index: 1;
10430
10734
  flex: 0 0 calc(var(--fe-h-sm) + var(--fe-gap));
10431
- padding-right: var(--fe-gap);
10735
+ padding-inline-end: var(--fe-gap);
10432
10736
  justify-content: center;
10433
10737
  background: var(--fe-bg);
10434
10738
  }
@@ -10441,97 +10745,498 @@ filex-explorer {
10441
10745
  .fe-list__row.is-droptarget .fe-list__col--menu {
10442
10746
  background: var(--fe-bg-selected);
10443
10747
  }
10748
+ /* Frozen to the end edge — but only while it leaves the columns sliding
10749
+ under it somewhere to be (DataTable: `pinMenu` / PIN_MENU_ROOM).
10750
+
10751
+ ⚠⚠ The cell carries an opaque ground, as a sticky cell must, so what it
10752
+ covers is GONE, not dimmed. At 104px of a 265px inspector panel it covered
10753
+ both columns an app's list was drawing and its signer table read as names
10754
+ and buttons with a blank gap between them (v0.43.0). Below the threshold
10755
+ nothing is pinned and the row scrolls as one piece — the same answer the
10756
+ frozen lead gives, for the same reason. */
10757
+ .fe-list.is-pin-menu .fe-list__head .fe-list__col--menu,
10758
+ .fe-list.is-pin-menu .fe-list__row .fe-list__col--menu {
10759
+ position: sticky;
10760
+ inset-inline-end: 0;
10761
+ z-index: 1;
10762
+ }
10763
+
10444
10764
  /* The edge, only while there is something scrolled underneath. A permanent
10445
10765
  divider would claim the column is floating when it is simply the last cell
10446
10766
  of a table that fits. `box-shadow` rather than a border so turning it on
10447
10767
  moves nothing. */
10448
- .fe-list.is-scrolled-x .fe-list__col--menu {
10449
- box-shadow: -1px 0 0 0 var(--fe-border);
10768
+ .fe-list.is-pin-menu.is-scrolled-x .fe-list__col--menu {
10769
+ /* On the cell's START side — the side the rows scroll under (left in LTR,
10770
+ right in RTL, where the ⋮ is pinned to the left edge). */
10771
+ box-shadow: calc(-1px * var(--filex-dir-x, 1)) 0 0 0 var(--fe-border);
10772
+ }
10773
+
10774
+ /* ══ THE FROZEN LEAD: the tick and Name ═══════════════════════════════
10775
+ (owner, 2026-09-20: "yatay kaydırmada ad solda, işlemler sağda kalsın;
10776
+ ikisinin de kendi gölgesi olsun")
10777
+
10778
+ The ⋮ has ridden the right edge since this became a table; the left edge was
10779
+ never frozen, so a listing scrolled sideways to reach Size or Modified lost
10780
+ the one column that says WHICH FILE each row is, and read as a wall of
10781
+ `PDF document · Sep 20, 2026`. Name is the row's handle — the tick, the type
10782
+ tile, the click target — so the tick freezes with it rather than sliding out
10783
+ from under its own checkboxes.
10784
+
10785
+ The same three parts as the ⋮, mirrored:
10786
+
10787
+ 1. The padding moves INTO the cell. `sticky; left: 0` pulls the cell to the
10788
+ scrollport's edge, so a 12px inset living on the ROW would be scrolled
10789
+ away and the ticks would sit against the glass. The tick's track is
10790
+ `28 + 12` with the 12 as its own padding — the same total width, so
10791
+ `tableLayout`'s arithmetic is untouched (exactly the trade the ⋮ makes).
10792
+
10793
+ 2. Opaque grounds, hover and selection included, or the columns sliding
10794
+ underneath show through the frozen cells.
10795
+
10796
+ 3. An edge only while something is actually scrolled under it.
10797
+
10798
+ ⚠⚠ GATED ON `.is-pin-lead`, which ListView computes. Name is the person's to
10799
+ resize, and a sticky cell WIDER than the pane covers the pane: 900px of Name
10800
+ frozen in a 706px list paints over every column it was frozen to keep
10801
+ company. The class is set only while the frozen lead leaves at least 160px
10802
+ of pane for the columns that slide under it; below that nothing is pinned
10803
+ and the table scrolls as one piece, which is the honest answer.
10804
+
10805
+ ⚠ `--fe-list-lead` (Name's offset) is inline on `.fe-list`, because it is
10806
+ the tick's track plus the flex gap and only the component knows the first. */
10807
+ /* ⚠ The loading ghosts are deliberately NOT in this list: they have no cells
10808
+ of their own to carry the padding, so taking it off the row would start them
10809
+ 12px left of the rows they stand in for and the swap to real rows would
10810
+ jump. They keep the ordinary inset and line up either way. */
10811
+ .fe-list.is-pin-lead .fe-list__head,
10812
+ .fe-list.is-pin-lead .fe-list__row,
10813
+ .fe--density-compact .fe-list.is-pin-lead .fe-list__head,
10814
+ .fe--density-compact .fe-list.is-pin-lead .fe-list__row {
10815
+ padding-inline-start: 0;
10816
+ }
10817
+ .fe-list.is-pin-lead .fe-list__col--check {
10818
+ position: sticky;
10819
+ inset-inline-start: 0;
10820
+ z-index: 2;
10821
+ flex: 0 0 calc(var(--fe-h-sm) + var(--fe-gap));
10822
+ padding-inline-start: var(--fe-gap);
10823
+ background: var(--fe-bg);
10450
10824
  }
10451
-
10452
- /* The date headings travel with the horizontal scroll rather than sliding off
10453
- the left edge — they are `max-content` wide so sticky has somewhere to move
10454
- them to (a full-width block has none). */
10455
- .fe-list__group {
10825
+ .fe-list.is-pin-lead .fe-list__col--lead {
10456
10826
  position: sticky;
10457
- left: 0;
10458
- width: max-content;
10459
- max-width: 100%;
10827
+ inset-inline-start: var(--fe-list-lead, calc(var(--fe-h-sm) + var(--fe-gap) + var(--fe-gap-sm)));
10828
+ z-index: 2;
10829
+ background: var(--fe-bg);
10460
10830
  }
10461
-
10462
- /* Name's handle sits on its trailing edge like every other column's. The cell
10463
- is `display: flex` with the sort button as its first child, so without this
10464
- the absolutely positioned handle would have no positioned ancestor inside
10465
- the header row. */
10466
- .fe-list__head .fe-list__col--name {
10467
- position: relative;
10468
- overflow: visible;
10831
+ .fe-list.is-pin-lead .fe-list__row:hover .fe-list__col--check,
10832
+ .fe-list.is-pin-lead .fe-list__row:hover .fe-list__col--lead {
10833
+ background: var(--fe-bg-hover);
10834
+ }
10835
+ .fe-list.is-pin-lead .fe-list__row.is-selected .fe-list__col--check,
10836
+ .fe-list.is-pin-lead .fe-list__row.is-selected .fe-list__col--lead,
10837
+ .fe-list.is-pin-lead .fe-list__row.is-droptarget .fe-list__col--check,
10838
+ .fe-list.is-pin-lead .fe-list__row.is-droptarget .fe-list__col--lead {
10839
+ background: var(--fe-bg-selected);
10840
+ }
10841
+ /* Its own edge, mirroring the ⋮'s — drawn on Name's trailing side, and only
10842
+ while there is something scrolled underneath it. */
10843
+ .fe-list.is-pin-lead.is-scrolled-x .fe-list__col--lead {
10844
+ /* Name's END side, whichever physical side that is. */
10845
+ box-shadow: calc(1px * var(--filex-dir-x, 1)) 0 0 0 var(--fe-border);
10846
+ }
10847
+ /* ⚠ The header's frozen cells sit ABOVE the rows' (the head is drawn first and
10848
+ a row's sticky cell would otherwise paint over it while the head is the
10849
+ thing being scrolled past). Same reason the header's ⋮ outranks a row's. */
10850
+ .fe-list.is-pin-lead .fe-list__head .fe-list__col--check,
10851
+ .fe-list.is-pin-lead .fe-list__head .fe-list__col--lead {
10852
+ z-index: 3;
10469
10853
  }
10470
- /* === /tablo:t2 === */
10471
10854
 
10855
+ /* ══ THE ONE TABLE — what DataTable adds over the explorer's list ═════════
10856
+ (packages/core/src/components/DataTable.vue)
10472
10857
 
10473
- /* === ikon:emoji =======================================================
10474
- * The boxes that used to hold an emoji and now hold a drawing.
10475
- *
10476
- * Sizing only — every mark here comes from `lib/actionIcons.ts` or
10477
- * `lib/fileIcons.ts` and paints on `currentColor`, so the surface it sits in
10478
- * keeps deciding its colour. Two shapes need a box: `.fe-aicon` is a fixed
10479
- * 16px (right for a menu row, far too small for a 48px fallback screen), and
10480
- * `.fe-ftile` is `1em` (so its box is the `font-size` the slot already had —
10481
- * which is why most of the rules below only have to switch a text span into
10482
- * a flex one).
10483
- * ==================================================================== */
10858
+ The rules above were written for the explorer's listing, and since
10859
+ 2026-09-21 they are the stylesheet of EVERY table in the product: the
10860
+ explorer's list renders through DataTable, and so does every admin page,
10861
+ the connection panels and an app's `list` node. The owner: "Artık explore
10862
+ tablomuz bizim her yerde kullanacağımız tablo yapısıdır." What follows is
10863
+ only what a table that is not a file listing needs on top — the frozen lead
10864
+ is `.fe-list__col--lead` (the explorer's Name carries both classes), a table
10865
+ with no tick column freezes its lead at the glass, cells that are plain
10866
+ text, a card for the admin pages, and the pager.
10484
10867
 
10485
- /* --- the command palette's 20px column ------------------------------- */
10486
- .fe-cmdp__icon {
10487
- display: inline-flex;
10488
- align-items: center;
10489
- justify-content: center;
10490
- /* The em the file tiles size themselves from. Slightly over the 16px an
10491
- action icon draws at, because a tile is the full box and a stroked glyph
10492
- is not — at a shared 20 the tiles shouted over the commands. */
10493
- font-size: 18px;
10868
+ ⚠ Every value is a `--fe-*` token. A Tailwind colour cannot follow the
10869
+ palette the person picked, and the panel following it was half of the
10870
+ complaint that started the admin-table work ("admin panel seçili renk
10871
+ paletinden etkilenmiyor, etkilenmeli"). */
10872
+
10873
+ /* The explorer's listing is not framed: the wrapper generates no box, so its
10874
+ pane lays `.fe-list` out exactly as it did before there was a wrapper. */
10875
+ .fe-table--bare {
10876
+ display: contents;
10494
10877
  }
10495
- .fe-cmdp__icon .fe-aicon {
10496
- width: 18px;
10497
- height: 18px;
10878
+ /* An admin page's table is a card: the toolbar, the table and the pager in one
10879
+ box. `--tbl-bg` is the ground the frozen cells paint with, so a table on a
10880
+ raised panel (the connection panels) can say so once. */
10881
+ .fe-table--framed {
10882
+ display: block;
10883
+ min-width: 0;
10884
+ background: var(--tbl-bg, var(--fe-bg));
10885
+ border: 1px solid var(--fe-border-soft);
10886
+ border-radius: var(--fe-radius-lg);
10887
+ overflow: hidden;
10498
10888
  }
10499
- .fe-cmdp__del .fe-aicon {
10500
- width: 15px;
10501
- height: 15px;
10889
+ /* ⚠ Not the explorer's `height: 100%`: a card on a page is as tall as its rows,
10890
+ and a percentage of an auto-height parent is nothing. It still owns the
10891
+ sideways scroll. */
10892
+ .fe-table--framed .fe-list {
10893
+ height: auto;
10894
+ overflow-x: auto;
10895
+ overflow-y: hidden;
10502
10896
  }
10503
-
10504
- /* --- the viewer / preview fallback screen ---------------------------- */
10505
- /* Both slots were `font-size: 48px` text boxes. A tile obeys that already; an
10506
- action icon has to be told, and both need centring once they are elements
10507
- rather than glyphs on a text baseline. */
10508
- .fe-preview__fallback-icon,
10509
- .filex-viewer-fallback__icon {
10510
- display: flex;
10511
- align-items: center;
10512
- justify-content: center;
10897
+ .fe-table--framed .fe-list__head,
10898
+ .fe-table--framed .fe-list__head .fe-list__col--menu,
10899
+ .fe-table--framed .fe-list__row .fe-list__col--menu,
10900
+ .fe-table--framed .fe-list.is-pin-lead .fe-list__col--lead,
10901
+ .fe-table--framed .fe-list.is-pin-lead .fe-list__col--check {
10902
+ background: var(--tbl-bg, var(--fe-bg));
10513
10903
  }
10514
- .fe-preview__fallback-icon .fe-aicon,
10515
- .filex-viewer-fallback__icon .fe-aicon {
10516
- width: 44px;
10517
- height: 44px;
10904
+ .fe-table--framed .fe-list__row:hover .fe-list__col--menu,
10905
+ .fe-table--framed .fe-list.is-pin-lead .fe-list__row:hover .fe-list__col--lead,
10906
+ .fe-table--framed .fe-list.is-pin-lead .fe-list__row:hover .fe-list__col--check {
10907
+ background: var(--fe-bg-hover);
10518
10908
  }
10519
- /* "Still loading" cannot be said by a still picture: the open ring turns.
10520
- `fe-spin` is the keyframe the initial-load spinner at the top of this file
10521
- already uses, so there is one rotation in the package. */
10522
- .fe-preview__fallback-icon--spin .fe-aicon,
10523
- .filex-viewer-fallback__icon--spin .fe-aicon {
10524
- animation: fe-spin 0.9s linear infinite;
10909
+ .fe-table--framed .fe-list.is-pin-lead .fe-list__row.is-selected .fe-list__col--lead,
10910
+ .fe-table--framed .fe-list__row.is-selected .fe-list__col--menu {
10911
+ background: var(--fe-bg-selected);
10525
10912
  }
10526
- @media (prefers-reduced-motion: reduce) {
10527
- .fe-preview__fallback-icon--spin .fe-aicon,
10528
- .filex-viewer-fallback__icon--spin .fe-aicon {
10529
- animation-duration: 2.4s;
10530
- }
10913
+ /* The last row's hairline would double the card's own border. */
10914
+ .fe-table--framed .fe-list__row:last-child {
10915
+ border-bottom: 0;
10531
10916
  }
10532
- /* The one state that is an ERROR rather than a kind of file keeps saying so
10533
- in colour, which the emoji it replaces could not do (a colour emoji paints
10534
- its own palette and ignores the row it is in). */
10917
+ .fe-table--framed .tbl-bar {
10918
+ border-bottom: 1px solid var(--fe-border-soft);
10919
+ }
10920
+
10921
+ /* With no tick in front of it, a frozen lead sits at the glass and takes the
10922
+ row's leading padding INTO itself (DataTable adds the 12px to its track) —
10923
+ the same trade the ⋮ makes on the right, so the table's arithmetic is
10924
+ untouched. */
10925
+ .fe-list.is-pin-lead.fe-list--nocheck .fe-list__col--lead {
10926
+ inset-inline-start: 0;
10927
+ padding-inline-start: var(--fe-gap);
10928
+ }
10929
+
10930
+ /* A plain cell: one line, cut with an ellipsis, the whole value in its title.
10931
+ ⚠ `min-width: 0` on the text too, or one long unbroken value would push its
10932
+ cell past the track the header allocated. */
10933
+ /* ⚠ `:where()`, zero specificity: the explorer's own cells carry this class AND
10934
+ `fe-list__col--type` / `--owner` / `--mod` / `--size`, whose muted colour is
10935
+ written earlier in this file at one class of weight. A plain `.fe-list__cell`
10936
+ here would out-rank them by source order and turn every file's Type and
10937
+ Size into body-coloured text. */
10938
+ :where(.fe-list__cell) {
10939
+ font-size: var(--fe-text-md);
10940
+ color: var(--fe-text);
10941
+ overflow: hidden;
10942
+ }
10943
+ .fe-list__cell.is-right {
10944
+ justify-content: flex-end;
10945
+ text-align: end;
10946
+ }
10947
+ .fe-list__cell.is-center {
10948
+ justify-content: center;
10949
+ text-align: center;
10950
+ }
10951
+ .fe-list__cell-text {
10952
+ min-width: 0;
10953
+ overflow: hidden;
10954
+ text-overflow: ellipsis;
10955
+ white-space: nowrap;
10956
+ }
10957
+ .fe-list__col--lead .fe-list__cell-text {
10958
+ font-weight: 500;
10959
+ }
10960
+ /* A labelled Actions control is wider than the explorer's ⋮ and reads best
10961
+ flush with the row's right edge; the header's column ⋮ lines up with it. */
10962
+ .fe-list__head .fe-list__col--menu.is-wide,
10963
+ .fe-list__row .fe-list__col--menu.is-wide {
10964
+ justify-content: flex-end;
10965
+ }
10966
+ .fe-list__row.is-clickable {
10967
+ cursor: pointer;
10968
+ }
10969
+ /* A row the page wants to quieten (a notification already read, a key that
10970
+ is disabled). Opacity rather than a colour, so it reads the same under
10971
+ every palette. */
10972
+ .fe-list__row.is-muted {
10973
+ opacity: 0.65;
10974
+ }
10975
+ /* Whatever a page puts in a cell may shrink below its content, or one long
10976
+ value would push its cell past the track the header allocated. Zero
10977
+ specificity, so a component's own rule still wins. */
10978
+ :where(.fe-list__cell) > * {
10979
+ min-width: 0;
10980
+ }
10981
+ /* The admin pages' "do not let free text stretch the table" helper: inside a
10982
+ cell of fixed width the cell IS the clamp, so it only has to fill it. */
10983
+ .fe-list__cell .tbl-clamp {
10984
+ max-width: 100%;
10985
+ }
10986
+ /* A cell whose slot puts secondary text (`.tbl-sub`) straight under its main
10987
+ value: stack them. A cell lays its children out in a ROW, so without this
10988
+ the value and the line meant to sit under it were drawn side by side — the
10989
+ Panel's Recent activity read "Profil: güncellendiProfil" and Duplicates put
10990
+ a file's path beside its name (release-candidate sweep, 2026-09-22). */
10991
+ .fe-list__cell:has(> .tbl-sub) {
10992
+ flex-direction: column;
10993
+ align-items: flex-start;
10994
+ justify-content: center;
10995
+ }
10996
+ .fe-list__empty--loading {
10997
+ color: var(--fe-text-muted);
10998
+ }
10999
+ .tbl-pager {
11000
+ display: flex;
11001
+ align-items: center;
11002
+ gap: var(--fe-gap-xs);
11003
+ }
11004
+ .tbl-pager__at {
11005
+ padding: 0 var(--fe-gap-xs);
11006
+ }
11007
+
11008
+ /* ==================================================================
11009
+ THE ONE TABLE'S CELL VOCABULARY — and what is left of "the admin table".
11010
+
11011
+ Until 2026-09-21 this block was a second table: `web/src/components/ui/
11012
+ Table.vue` drew a real <table class="tbl"> with these rules, frozen edges
11013
+ of its own (`.tbl-lead`, `.tbl-actions`, `.tbl-scroll`) and a sort button
11014
+ of its own. It IMITATED the explorer's list and never got what lives in
11015
+ the explorer's code — resizing, the column menu, persistence. The owner:
11016
+ "Admin tabloları hâlâ explore tablolarıyla AYNI KODDA DEĞİL … tablo
11017
+ sütunları düzenlenebilir değil, büyütme küçültme yok, sıralama yok." Every
11018
+ table is DataTable now (components/DataTable.vue, styled by the
11019
+ `fe-list` rules above), and those table rules are deleted rather than kept
11020
+ "just in case": a stylesheet for a table nobody draws is an invitation to
11021
+ draw one.
11022
+
11023
+ What remains is what a CELL or the card around a table uses, whichever
11024
+ page draws it:
11025
+ · `.tbl-sub` / `.tbl-clamp` / `.tbl-mono` — secondary text, a
11026
+ free-text value that must not stretch its column, a technical value;
11027
+ · `.tbl-rowactions*` — the row's ONE labelled Actions control
11028
+ (components/RowActions.vue), the admin counterpart of the ⋮;
11029
+ · `.tbl-bar` / `.tbl-foot` / `.tbl-page` — the toolbar above a framed
11030
+ table and the pager below it (DataTable draws both).
11031
+
11032
+ ⚠ EVERY COLOUR IS A `--fe-*` TOKEN: "admin panel seçili renk paletinden
11033
+ etkilenmiyor, etkilenmeli" was half of the complaint that started this.
11034
+ ================================================================== */
11035
+
11036
+ /* The raised box a connection panel draws is not the page's ground, and the
11037
+ table's frozen cells paint with `--tbl-bg` — so the token moves, not the
11038
+ rules. */
11039
+ .fe-s3keys .fe-table--framed {
11040
+ --tbl-bg: var(--fe-bg-elev);
11041
+ }
11042
+
11043
+ /* Secondary text inside a cell — a path under a name, a timestamp under a
11044
+ badge. One class rather than a `text-xs text-zinc-500` in twelve files. */
11045
+ .tbl-sub {
11046
+ display: block;
11047
+ margin-top: 2px;
11048
+ font-size: var(--fe-text-xs);
11049
+ color: var(--fe-text-muted);
11050
+ }
11051
+ /* A cell that must not be allowed to stretch the table: `min-width:
11052
+ max-content` means a free-text column otherwise sets the table's width and
11053
+ pushes every other column off the right-hand edge. */
11054
+ .tbl-clamp {
11055
+ display: block;
11056
+ max-width: 28rem;
11057
+ overflow: hidden;
11058
+ text-overflow: ellipsis;
11059
+ white-space: nowrap;
11060
+ }
11061
+ .tbl-mono {
11062
+ font-family: var(--fe-font-mono);
11063
+ font-size: var(--fe-text-xs);
11064
+ }
11065
+
11066
+ /* The row's ONE control, and the label the owner asked for. The explorer's
11067
+ handle is the ⋮ glyph; the admin panel's says what it is, because an
11068
+ admin row's verbs (revoke a token, reset a password, purge a version) are
11069
+ not guessable from a glyph. Sized to the row so a table of them does not
11070
+ grow taller than one without. */
11071
+ .tbl-rowactions {
11072
+ display: inline-flex;
11073
+ justify-content: flex-end;
11074
+ width: 100%;
11075
+ }
11076
+ .tbl-rowactions__btn {
11077
+ display: inline-flex;
11078
+ align-items: center;
11079
+ gap: var(--fe-gap-xs);
11080
+ height: var(--fe-h-sm);
11081
+ padding: 0 var(--fe-gap-sm);
11082
+ border: 1px solid var(--fe-border);
11083
+ border-radius: var(--fe-radius-sm);
11084
+ background: transparent;
11085
+ color: var(--fe-text);
11086
+ font: inherit;
11087
+ font-size: var(--fe-text-md);
11088
+ line-height: 1;
11089
+ white-space: nowrap;
11090
+ cursor: pointer;
11091
+ }
11092
+ .tbl-rowactions__btn:hover:not(:disabled) {
11093
+ background: var(--fe-bg-hover);
11094
+ border-color: var(--fe-border-strong);
11095
+ }
11096
+ .tbl-rowactions__btn:focus-visible {
11097
+ outline: 2px solid var(--fe-primary);
11098
+ outline-offset: 1px;
11099
+ }
11100
+ .tbl-rowactions__btn:disabled {
11101
+ opacity: 0.45;
11102
+ cursor: not-allowed;
11103
+ }
11104
+ .tbl-rowactions__caret {
11105
+ font-size: 10px;
11106
+ color: var(--fe-text-muted);
11107
+ }
11108
+
11109
+
11110
+ /* The bar above the table (search, filters) and the one below it (the pager).
11111
+ Part of the same card, so they carry the card's ground and its hairlines. */
11112
+ .tbl-bar {
11113
+ display: flex;
11114
+ align-items: center;
11115
+ flex-wrap: wrap;
11116
+ gap: var(--fe-gap-sm);
11117
+ padding: var(--fe-gap-sm) var(--fe-gap);
11118
+ border-bottom: 1px solid var(--fe-border-soft);
11119
+ }
11120
+ .tbl-foot {
11121
+ display: flex;
11122
+ align-items: center;
11123
+ justify-content: space-between;
11124
+ gap: var(--fe-gap-sm);
11125
+ padding: var(--fe-gap-sm) var(--fe-gap);
11126
+ border-top: 1px solid var(--fe-border-soft);
11127
+ font-size: var(--fe-text-md);
11128
+ color: var(--fe-text-muted);
11129
+ }
11130
+ .tbl-page {
11131
+ display: inline-flex;
11132
+ align-items: center;
11133
+ justify-content: center;
11134
+ min-width: var(--fe-h-sm);
11135
+ height: var(--fe-h-sm);
11136
+ padding: 0 var(--fe-gap-sm);
11137
+ border: 0;
11138
+ border-radius: var(--fe-radius-sm);
11139
+ background: transparent;
11140
+ color: inherit;
11141
+ font: inherit;
11142
+ cursor: pointer;
11143
+ }
11144
+ .tbl-page:hover:not(:disabled) {
11145
+ background: var(--fe-bg-hover);
11146
+ color: var(--fe-text);
11147
+ }
11148
+ .tbl-page:focus-visible {
11149
+ outline: 2px solid var(--fe-primary);
11150
+ outline-offset: -2px;
11151
+ }
11152
+ .tbl-page:disabled {
11153
+ opacity: 0.4;
11154
+ cursor: not-allowed;
11155
+ }
11156
+
11157
+ /* The date headings travel with the horizontal scroll rather than sliding off
11158
+ the left edge — they are `max-content` wide so sticky has somewhere to move
11159
+ them to (a full-width block has none). */
11160
+ .fe-list__group {
11161
+ position: sticky;
11162
+ inset-inline-start: 0;
11163
+ width: max-content;
11164
+ max-width: 100%;
11165
+ }
11166
+
11167
+ /* Name's handle sits on its trailing edge like every other column's. The cell
11168
+ is `display: flex` with the sort button as its first child, so without this
11169
+ the absolutely positioned handle would have no positioned ancestor inside
11170
+ the header row. */
11171
+ .fe-list__head .fe-list__col--name {
11172
+ position: relative;
11173
+ overflow: visible;
11174
+ }
11175
+ /* === /tablo:t2 === */
11176
+
11177
+
11178
+ /* === ikon:emoji =======================================================
11179
+ * The boxes that used to hold an emoji and now hold a drawing.
11180
+ *
11181
+ * Sizing only — every mark here comes from `lib/actionIcons.ts` or
11182
+ * `lib/fileIcons.ts` and paints on `currentColor`, so the surface it sits in
11183
+ * keeps deciding its colour. Two shapes need a box: `.fe-aicon` is a fixed
11184
+ * 16px (right for a menu row, far too small for a 48px fallback screen), and
11185
+ * `.fe-ftile` is `1em` (so its box is the `font-size` the slot already had —
11186
+ * which is why most of the rules below only have to switch a text span into
11187
+ * a flex one).
11188
+ * ==================================================================== */
11189
+
11190
+ /* --- the command palette's 20px column ------------------------------- */
11191
+ .fe-cmdp__icon {
11192
+ display: inline-flex;
11193
+ align-items: center;
11194
+ justify-content: center;
11195
+ /* The em the file tiles size themselves from. Slightly over the 16px an
11196
+ action icon draws at, because a tile is the full box and a stroked glyph
11197
+ is not — at a shared 20 the tiles shouted over the commands. */
11198
+ font-size: 18px;
11199
+ }
11200
+ .fe-cmdp__icon .fe-aicon {
11201
+ width: 18px;
11202
+ height: 18px;
11203
+ }
11204
+ .fe-cmdp__del .fe-aicon {
11205
+ width: 15px;
11206
+ height: 15px;
11207
+ }
11208
+
11209
+ /* --- the viewer / preview fallback screen ---------------------------- */
11210
+ /* Both slots were `font-size: 48px` text boxes. A tile obeys that already; an
11211
+ action icon has to be told, and both need centring once they are elements
11212
+ rather than glyphs on a text baseline. */
11213
+ .fe-preview__fallback-icon,
11214
+ .filex-viewer-fallback__icon {
11215
+ display: flex;
11216
+ align-items: center;
11217
+ justify-content: center;
11218
+ }
11219
+ .fe-preview__fallback-icon .fe-aicon,
11220
+ .filex-viewer-fallback__icon .fe-aicon {
11221
+ width: 44px;
11222
+ height: 44px;
11223
+ }
11224
+ /* "Still loading" cannot be said by a still picture: the open ring turns.
11225
+ `fe-spin` is the keyframe the initial-load spinner at the top of this file
11226
+ already uses, so there is one rotation in the package. */
11227
+ .fe-preview__fallback-icon--spin .fe-aicon,
11228
+ .filex-viewer-fallback__icon--spin .fe-aicon {
11229
+ animation: fe-spin 0.9s linear infinite;
11230
+ }
11231
+ @media (prefers-reduced-motion: reduce) {
11232
+ .fe-preview__fallback-icon--spin .fe-aicon,
11233
+ .filex-viewer-fallback__icon--spin .fe-aicon {
11234
+ animation-duration: 2.4s;
11235
+ }
11236
+ }
11237
+ /* The one state that is an ERROR rather than a kind of file keeps saying so
11238
+ in colour, which the emoji it replaces could not do (a colour emoji paints
11239
+ its own palette and ignores the row it is in). */
10535
11240
  .fe-preview__fallback-icon--alert .fe-aicon {
10536
11241
  color: var(--fe-danger);
10537
11242
  }
@@ -10645,7 +11350,7 @@ filex-explorer {
10645
11350
  }
10646
11351
  .filex-viewer-ipynb__md ul,
10647
11352
  .filex-viewer-ipynb__md ol {
10648
- padding-left: 2em;
11353
+ padding-inline-start: 2em;
10649
11354
  }
10650
11355
 
10651
11356
  /* 2. A form field with a visible name (EncryptedFolderModal, NewFolderModal).
@@ -10662,3 +11367,1952 @@ filex-explorer {
10662
11367
  color: var(--fe-text-muted);
10663
11368
  }
10664
11369
  /* === /agent-fix2 === */
11370
+
11371
+ /* === App plugins — a plugin's declarative screen (components/plugin/) ===
11372
+ * Drawn from data with our own elements; the classes here are the whole
11373
+ * vocabulary a surface can reach, which is the point. */
11374
+ .fe-surface {
11375
+ display: flex;
11376
+ flex-direction: column;
11377
+ gap: 10px;
11378
+ }
11379
+ .fe-surface__text {
11380
+ margin: 0;
11381
+ font-size: var(--fe-text-sm);
11382
+ line-height: 1.5;
11383
+ white-space: pre-wrap;
11384
+ }
11385
+ .fe-surface__text--heading {
11386
+ font-size: var(--fe-text-md, 15px);
11387
+ font-weight: 600;
11388
+ }
11389
+ .fe-surface__text--muted {
11390
+ color: var(--fe-text-muted);
11391
+ }
11392
+ .fe-surface__text--danger {
11393
+ color: var(--fe-danger);
11394
+ }
11395
+ .fe-surface__text--info {
11396
+ color: var(--fe-primary);
11397
+ }
11398
+ .fe-surface__divider {
11399
+ border: 0;
11400
+ border-top: 1px solid var(--fe-border);
11401
+ margin: 4px 0;
11402
+ }
11403
+ .fe-surface__row {
11404
+ display: flex;
11405
+ flex-wrap: wrap;
11406
+ gap: 10px;
11407
+ align-items: flex-start;
11408
+ }
11409
+ .fe-surface__row > .fe-surface {
11410
+ flex-direction: row;
11411
+ flex-wrap: wrap;
11412
+ }
11413
+ .fe-surface__unknown {
11414
+ margin: 0;
11415
+ padding: 8px 10px;
11416
+ border: 1px dashed var(--fe-border-strong);
11417
+ border-radius: var(--fe-radius-sm);
11418
+ color: var(--fe-text-muted);
11419
+ font-size: 12px;
11420
+ }
11421
+ .fe-surface__error {
11422
+ color: var(--fe-danger);
11423
+ font-size: 12px;
11424
+ margin: 0;
11425
+ }
11426
+ .fe-plugin-view {
11427
+ min-width: 0;
11428
+ }
11429
+ /* === /App plugins === */
11430
+
11431
+ /* === App plugins — M2 surface components (components/plugin/nodes/*) === */
11432
+ .fe-surface__form {
11433
+ min-width: 0;
11434
+ }
11435
+ .fe-steps {
11436
+ list-style: none;
11437
+ margin: 0;
11438
+ padding: 0;
11439
+ display: flex;
11440
+ flex-wrap: wrap;
11441
+ gap: 6px 18px;
11442
+ }
11443
+ .fe-steps__item {
11444
+ display: inline-flex;
11445
+ align-items: center;
11446
+ gap: 8px;
11447
+ font-size: var(--fe-text-sm);
11448
+ color: var(--fe-text-muted);
11449
+ }
11450
+ .fe-steps__item.is-active {
11451
+ color: var(--fe-text);
11452
+ font-weight: 600;
11453
+ }
11454
+ .fe-steps__item.is-done {
11455
+ color: var(--fe-text);
11456
+ }
11457
+ .fe-steps__marker {
11458
+ display: inline-flex;
11459
+ align-items: center;
11460
+ justify-content: center;
11461
+ width: 22px;
11462
+ height: 22px;
11463
+ border-radius: 50%;
11464
+ border: 1px solid var(--fe-border-strong);
11465
+ font-size: 12px;
11466
+ font-weight: 600;
11467
+ flex: 0 0 auto;
11468
+ }
11469
+ .fe-steps__marker .fe-ficon {
11470
+ width: 14px;
11471
+ height: 14px;
11472
+ }
11473
+ .fe-steps__item.is-active .fe-steps__marker {
11474
+ border-color: var(--fe-primary);
11475
+ background: var(--fe-primary);
11476
+ color: #fff;
11477
+ }
11478
+ .fe-steps__item.is-done .fe-steps__marker {
11479
+ border-color: var(--fe-primary);
11480
+ color: var(--fe-primary);
11481
+ }
11482
+ /* The plugin surfaces' `list` node draws `.tbl` — the one admin table — so
11483
+ everything `.fe-slist__table` used to say (12px/600 headers, 6px 8px cells,
11484
+ `--fe-border`, a `text-align: right` actions cell that was never actually
11485
+ pinned) is deleted rather than tuned. What is left is the node's own box:
11486
+ it must be allowed to be narrower than its content so the table inside can
11487
+ scroll instead of squeezing a surface into a column of single words. */
11488
+ .fe-slist {
11489
+ min-width: 0;
11490
+ }
11491
+
11492
+ .fe-sprogress {
11493
+ display: flex;
11494
+ flex-direction: column;
11495
+ gap: 6px;
11496
+ }
11497
+ .fe-sprogress__head {
11498
+ display: flex;
11499
+ justify-content: space-between;
11500
+ gap: 10px;
11501
+ font-size: var(--fe-text-sm);
11502
+ }
11503
+ .fe-sprogress__pct {
11504
+ color: var(--fe-text-muted);
11505
+ font-variant-numeric: tabular-nums;
11506
+ }
11507
+ .fe-sprogress__track {
11508
+ position: relative;
11509
+ height: 6px;
11510
+ border-radius: 3px;
11511
+ background: var(--fe-border);
11512
+ overflow: hidden;
11513
+ }
11514
+ .fe-sprogress__bar {
11515
+ height: 100%;
11516
+ background: var(--fe-primary);
11517
+ border-radius: 3px;
11518
+ transition: width 0.2s ease;
11519
+ }
11520
+ .fe-sprogress__track.is-indeterminate .fe-sprogress__bar {
11521
+ position: absolute;
11522
+ width: 35%;
11523
+ animation: fe-sprogress-slide 1.2s ease-in-out infinite;
11524
+ }
11525
+ @keyframes fe-sprogress-slide {
11526
+ 0% {
11527
+ inset-inline-start: -35%;
11528
+ }
11529
+ 100% {
11530
+ inset-inline-start: 100%;
11531
+ }
11532
+ }
11533
+ .fe-speople {
11534
+ display: flex;
11535
+ flex-direction: column;
11536
+ gap: 8px;
11537
+ min-width: 0;
11538
+ }
11539
+ .fe-speople__chips {
11540
+ list-style: none;
11541
+ margin: 0;
11542
+ padding: 0;
11543
+ display: flex;
11544
+ flex-wrap: wrap;
11545
+ gap: 6px;
11546
+ }
11547
+ .fe-speople__chip {
11548
+ display: inline-flex;
11549
+ align-items: center;
11550
+ gap: 6px;
11551
+ padding-block: 3px; padding-inline: 3px 6px;
11552
+ border-radius: 999px;
11553
+ border: 1px solid var(--fe-border-strong);
11554
+ background: var(--fe-bg-elev);
11555
+ font-size: 12.5px;
11556
+ max-width: 100%;
11557
+ }
11558
+ .fe-speople__av {
11559
+ display: inline-flex;
11560
+ align-items: center;
11561
+ justify-content: center;
11562
+ width: 20px;
11563
+ height: 20px;
11564
+ border-radius: 50%;
11565
+ background: var(--fe-primary);
11566
+ color: #fff;
11567
+ font-size: 11px;
11568
+ font-weight: 600;
11569
+ flex: 0 0 auto;
11570
+ }
11571
+ .fe-speople__name {
11572
+ overflow: hidden;
11573
+ text-overflow: ellipsis;
11574
+ white-space: nowrap;
11575
+ }
11576
+ .fe-speople__remove {
11577
+ border: 0;
11578
+ background: none;
11579
+ color: var(--fe-text-muted);
11580
+ cursor: pointer;
11581
+ padding: 0;
11582
+ display: inline-flex;
11583
+ align-items: center;
11584
+ }
11585
+ .fe-speople__remove .fe-ficon {
11586
+ width: 14px;
11587
+ height: 14px;
11588
+ }
11589
+ .fe-speople__remove:hover {
11590
+ color: var(--fe-danger);
11591
+ }
11592
+ .fe-speople__addwrap {
11593
+ position: relative;
11594
+ display: flex;
11595
+ gap: 6px;
11596
+ align-items: stretch;
11597
+ }
11598
+ .fe-speople__input {
11599
+ flex: 1 1 auto;
11600
+ }
11601
+ .fe-speople__suggest {
11602
+ top: 100%;
11603
+ inset-inline-start: 0;
11604
+ inset-inline-end: 0;
11605
+ }
11606
+ .fe-speople.is-invalid .fe-speople__input {
11607
+ border-color: var(--fe-danger);
11608
+ }
11609
+ .fe-spin {
11610
+ display: flex;
11611
+ flex-direction: column;
11612
+ gap: 5px;
11613
+ }
11614
+ .fe-spin__input {
11615
+ width: auto;
11616
+ max-width: calc(var(--spin-len, 6) * 1.6ch + 24px);
11617
+ letter-spacing: 0.5ch;
11618
+ font-size: 18px;
11619
+ text-align: center;
11620
+ }
11621
+ .fe-spin__input.is-invalid {
11622
+ border-color: var(--fe-danger);
11623
+ }
11624
+ .fe-sfile {
11625
+ display: flex;
11626
+ align-items: center;
11627
+ gap: 8px;
11628
+ flex-wrap: wrap;
11629
+ min-width: 0;
11630
+ }
11631
+ .fe-sfile__value {
11632
+ flex: 1 1 160px;
11633
+ min-width: 0;
11634
+ overflow: hidden;
11635
+ text-overflow: ellipsis;
11636
+ white-space: nowrap;
11637
+ font-size: var(--fe-text-sm);
11638
+ padding: 6px 10px;
11639
+ border: 1px solid var(--fe-border-strong);
11640
+ border-radius: var(--fe-radius);
11641
+ background: var(--fe-bg);
11642
+ }
11643
+ .fe-sfile__value--empty {
11644
+ color: var(--fe-text-muted);
11645
+ }
11646
+ .fe-sfile.is-invalid .fe-sfile__value {
11647
+ border-color: var(--fe-danger);
11648
+ }
11649
+ .fe-destpick__row.is-file .fe-destpick__rowname {
11650
+ color: var(--fe-text);
11651
+ }
11652
+ .fe-destpick__row.is-picked {
11653
+ background: var(--fe-primary-soft, rgba(37, 99, 235, 0.12));
11654
+ outline: 1px solid var(--fe-primary);
11655
+ }
11656
+ .fe-spreview {
11657
+ margin: 0;
11658
+ display: flex;
11659
+ flex-direction: column;
11660
+ align-items: center;
11661
+ gap: 6px;
11662
+ min-width: 0;
11663
+ }
11664
+ .fe-spreview__media {
11665
+ max-width: 100%;
11666
+ max-height: 320px;
11667
+ border-radius: var(--fe-radius);
11668
+ object-fit: contain;
11669
+ background: var(--fe-bg-elev);
11670
+ }
11671
+ .fe-spreview__audio {
11672
+ width: 100%;
11673
+ }
11674
+ .fe-spreview__tile {
11675
+ display: inline-flex;
11676
+ width: 48px;
11677
+ height: 48px;
11678
+ }
11679
+ .fe-spreview__tile > * {
11680
+ width: 100%;
11681
+ height: 100%;
11682
+ }
11683
+ .fe-spreview__name {
11684
+ font-size: 12px;
11685
+ color: var(--fe-text-muted);
11686
+ max-width: 100%;
11687
+ overflow: hidden;
11688
+ text-overflow: ellipsis;
11689
+ white-space: nowrap;
11690
+ }
11691
+ /* inspector placement */
11692
+ .fe-pinsp__heading {
11693
+ margin: 0;
11694
+ }
11695
+ .fe-pinsp__toggle {
11696
+ display: inline-flex;
11697
+ align-items: center;
11698
+ gap: 6px;
11699
+ border: 0;
11700
+ background: none;
11701
+ padding: 0;
11702
+ font: inherit;
11703
+ color: inherit;
11704
+ cursor: pointer;
11705
+ text-align: start;
11706
+ }
11707
+ .fe-pinsp__chev {
11708
+ width: 14px;
11709
+ height: 14px;
11710
+ transition: transform 0.15s ease;
11711
+ }
11712
+ .fe-pinsp__chev.is-open {
11713
+ transform: rotate(90deg);
11714
+ }
11715
+ .fe-pinsp__body {
11716
+ margin-top: 10px;
11717
+ display: flex;
11718
+ flex-direction: column;
11719
+ gap: 10px;
11720
+ }
11721
+ .fe-pinsp__actions {
11722
+ display: flex;
11723
+ flex-wrap: wrap;
11724
+ gap: 6px;
11725
+ }
11726
+ /* home placement — the "Apps" rows */
11727
+ .fe-sidenav__appicon {
11728
+ display: inline-flex;
11729
+ flex: 0 0 auto;
11730
+ }
11731
+ .fe-sidenav__appicon .fe-aicon,
11732
+ .fe-sidenav__appicon svg {
11733
+ width: 18px;
11734
+ height: 18px;
11735
+ }
11736
+ /* === /App plugins M2 === */
11737
+
11738
+ /* === App plugins — M3 sign track (signature-pad, pdf-fields) + the public page === */
11739
+ .fe-ssig {
11740
+ display: flex;
11741
+ flex-direction: column;
11742
+ gap: 8px;
11743
+ min-width: 0;
11744
+ }
11745
+ .fe-ssig__modes {
11746
+ display: flex;
11747
+ flex-wrap: wrap;
11748
+ gap: 6px;
11749
+ }
11750
+ .fe-ssig__padwrap,
11751
+ .fe-ssig__typewrap,
11752
+ .fe-ssig__uploadwrap {
11753
+ display: flex;
11754
+ flex-direction: column;
11755
+ gap: 6px;
11756
+ min-width: 0;
11757
+ }
11758
+ .fe-ssig__canvas,
11759
+ .fe-ssig__preview {
11760
+ display: block;
11761
+ width: 100%;
11762
+ border: 1px dashed var(--fe-border-strong);
11763
+ border-radius: var(--fe-radius);
11764
+ /* Paper, on both palettes: the ink is black and lands on a document. */
11765
+ background: #fff;
11766
+ touch-action: none;
11767
+ cursor: crosshair;
11768
+ }
11769
+ .fe-ssig__preview {
11770
+ cursor: default;
11771
+ display: flex;
11772
+ align-items: center;
11773
+ justify-content: center;
11774
+ overflow: hidden;
11775
+ min-height: 48px;
11776
+ }
11777
+ .fe-ssig__img {
11778
+ max-width: 100%;
11779
+ max-height: 200px;
11780
+ display: block;
11781
+ }
11782
+ .fe-ssig.is-invalid .fe-ssig__canvas,
11783
+ .fe-ssig.is-invalid .fe-ssig__preview {
11784
+ border-color: var(--fe-danger);
11785
+ }
11786
+ .fe-ssig.is-disabled .fe-ssig__canvas {
11787
+ cursor: not-allowed;
11788
+ opacity: 0.6;
11789
+ }
11790
+ .fe-ssig__hint {
11791
+ margin: 0;
11792
+ font-size: 12px;
11793
+ color: var(--fe-text-muted);
11794
+ }
11795
+ .fe-ssig__pick {
11796
+ position: relative;
11797
+ overflow: hidden;
11798
+ align-self: flex-start;
11799
+ }
11800
+ .fe-ssig__file {
11801
+ position: absolute;
11802
+ inset: 0;
11803
+ opacity: 0;
11804
+ cursor: pointer;
11805
+ }
11806
+ .fe-ssig__foot {
11807
+ display: flex;
11808
+ align-items: center;
11809
+ gap: 10px;
11810
+ }
11811
+ .fe-ssig__signed {
11812
+ font-size: 12px;
11813
+ color: var(--fe-ok);
11814
+ }
11815
+
11816
+ .fe-spdf {
11817
+ display: flex;
11818
+ flex-direction: column;
11819
+ gap: 8px;
11820
+ min-width: 0;
11821
+ }
11822
+ .fe-spdf__bar {
11823
+ display: flex;
11824
+ flex-wrap: wrap;
11825
+ align-items: center;
11826
+ justify-content: space-between;
11827
+ gap: 6px;
11828
+ }
11829
+ .fe-spdf__palette,
11830
+ .fe-spdf__zoom,
11831
+ .fe-spdf__props {
11832
+ display: flex;
11833
+ flex-wrap: wrap;
11834
+ align-items: center;
11835
+ gap: 6px;
11836
+ }
11837
+ .fe-spdf__zoomval {
11838
+ min-width: 52px;
11839
+ font-variant-numeric: tabular-nums;
11840
+ }
11841
+ .fe-spdf__hint {
11842
+ margin: 0;
11843
+ }
11844
+ /* ⚠⚠ The `place` step's one strip (component: `fe-spdf__strip`). Its height
11845
+ * is CONSTANT whatever it says — a hint, the selected box's controls, or an
11846
+ * idle line — because in a `page` layout the document is fitted to the pane
11847
+ * below it, and a strip that came and went re-fitted and redrew the whole
11848
+ * page on every tap (measured 2026-09-21: 383px → 349px wide mid-gesture).
11849
+ * One line of controls, scrolled sideways on a phone rather than wrapped to
11850
+ * a second line, for the same reason. */
11851
+ .fe-spdf__strip {
11852
+ min-height: 44px;
11853
+ display: flex;
11854
+ align-items: center;
11855
+ min-width: 0;
11856
+ }
11857
+ .fe-spdf__strip > .fe-spdf__props {
11858
+ flex: 1 1 auto;
11859
+ flex-wrap: nowrap;
11860
+ overflow-x: auto;
11861
+ white-space: nowrap;
11862
+ min-width: 0;
11863
+ }
11864
+ .fe-spdf__strip > .fe-spdf__hint {
11865
+ display: -webkit-box;
11866
+ -webkit-line-clamp: 2;
11867
+ -webkit-box-orient: vertical;
11868
+ overflow: hidden;
11869
+ line-height: 18px;
11870
+ }
11871
+ .fe-spdf__props {
11872
+ padding: 6px 8px;
11873
+ border: 1px solid var(--fe-border);
11874
+ border-radius: var(--fe-radius);
11875
+ background: var(--fe-bg-elev);
11876
+ font-size: var(--fe-text-sm);
11877
+ }
11878
+ .fe-spdf__propname {
11879
+ font-weight: 600;
11880
+ }
11881
+ .fe-spdf__prop {
11882
+ display: inline-flex;
11883
+ align-items: center;
11884
+ gap: 4px;
11885
+ }
11886
+ .fe-spdf__select {
11887
+ width: auto;
11888
+ padding: 2px 6px;
11889
+ height: 26px;
11890
+ }
11891
+ .fe-spdf__num {
11892
+ width: 72px;
11893
+ padding: 2px 6px;
11894
+ height: 26px;
11895
+ }
11896
+ /* The field's own name (v3 §3.1). Wide enough for "Yetkili imzası" and no
11897
+ wider: the property row is a strip over the document, not a form. */
11898
+ .fe-spdf__name {
11899
+ width: 160px;
11900
+ padding: 2px 6px;
11901
+ height: 26px;
11902
+ }
11903
+ /* The choice rows in the property strip are small — they sit beside the
11904
+ document rather than on a form of their own. */
11905
+ .fe-spdf__prop .fe-choice {
11906
+ gap: 4px;
11907
+ }
11908
+ .fe-spdf__prop .fe-choice__btn {
11909
+ padding: 3px 8px;
11910
+ font-size: var(--fe-text-xs);
11911
+ }
11912
+ .fe-spdf__prop .fe-choice__label {
11913
+ font-weight: 500;
11914
+ }
11915
+ /* What a text box will accept, or what is wrong with what is in it. ⚠ Drawn
11916
+ OUTSIDE the box: a field is a few millimetres of a page, and a hint inside
11917
+ it would cover the value it is about. */
11918
+ .fe-spdf__rule {
11919
+ position: absolute;
11920
+ top: 100%;
11921
+ inset-inline-start: 0;
11922
+ margin-top: 2px;
11923
+ padding: 1px 4px;
11924
+ max-width: 240px;
11925
+ border-radius: var(--fe-radius-sm);
11926
+ background: var(--fe-bg-elev);
11927
+ border: 1px solid var(--fe-border);
11928
+ color: var(--fe-text-muted);
11929
+ font-size: 10px;
11930
+ line-height: 1.3;
11931
+ white-space: nowrap;
11932
+ overflow: hidden;
11933
+ text-overflow: ellipsis;
11934
+ pointer-events: none;
11935
+ z-index: 2;
11936
+ }
11937
+ .fe-spdf__rule.is-bad {
11938
+ color: var(--fe-danger);
11939
+ border-color: var(--fe-danger);
11940
+ }
11941
+ .fe-spdf__scroll {
11942
+ overflow: auto;
11943
+ max-height: 70vh;
11944
+ border: 1px solid var(--fe-border);
11945
+ border-radius: var(--fe-radius);
11946
+ background: var(--fe-bg-hover);
11947
+ padding: 12px;
11948
+ min-width: 0;
11949
+ }
11950
+
11951
+ /* ⚠⚠ v3 §3.2 — `page` placement: the node owns the screen.
11952
+ *
11953
+ * The complaint that started this round was a signature page you had to
11954
+ * scroll in two directions to read: the document was fitted to the column's
11955
+ * WIDTH, so a portrait page in a landscape window ran off the bottom, and
11956
+ * the boxes were found by hunting. Here the node is a column of its own
11957
+ * height (`--spdf-h`, handed down by the frame), the toolbar and the
11958
+ * property strip keep their natural size, and the document takes the rest —
11959
+ * with the drawn width fitted to that remainder (component: `fitWidth`).
11960
+ *
11961
+ * `min-height: 0` is not decoration: without it a flex child refuses to
11962
+ * shrink below its content and the scroll area grows past the box instead
11963
+ * of scrolling inside it. */
11964
+ /* ── define: the boxes as cards, no document ──────────────────────────
11965
+ *
11966
+ * The step that asks WHAT is being asked of whom. Each card carries the
11967
+ * signer's colour down its edge, so the same box is recognisable on the
11968
+ * next step when it is dropped on the page. */
11969
+ .fe-spdf.is-define {
11970
+ gap: 12px;
11971
+ }
11972
+ .fe-spdf__define {
11973
+ display: flex;
11974
+ flex-direction: column;
11975
+ gap: 10px;
11976
+ }
11977
+ .fe-spdf__card {
11978
+ position: relative;
11979
+ display: flex;
11980
+ align-items: flex-start;
11981
+ gap: 10px;
11982
+ padding-block: 10px; padding-inline: 14px 12px;
11983
+ border: 1px solid var(--fe-border);
11984
+ border-inline-start: 3px solid var(--spdf-color, var(--fe-primary));
11985
+ border-radius: var(--fe-radius-md, 8px);
11986
+ background: var(--fe-bg-elev);
11987
+ }
11988
+ .fe-spdf__card .fe-spdf__props {
11989
+ flex: 1 1 auto;
11990
+ min-width: 0;
11991
+ margin: 0;
11992
+ padding: 0;
11993
+ border: 0;
11994
+ background: none;
11995
+ /* Row by row rather than one long line: a card is read, not scanned. */
11996
+ row-gap: 8px;
11997
+ column-gap: 10px;
11998
+ }
11999
+ .fe-spdf__card .fe-spdf__propname {
12000
+ flex: 0 0 auto;
12001
+ min-width: 84px;
12002
+ font-weight: 600;
12003
+ }
12004
+ .fe-spdf__card .fe-spdf__name {
12005
+ min-width: 180px;
12006
+ }
12007
+ /* The face row starts a line of its own — five buttons in the middle of a
12008
+ sentence of controls read as noise. */
12009
+ .fe-spdf__card .fe-signfonts {
12010
+ flex-basis: 100%;
12011
+ }
12012
+ /* What is printed under a signature: the choice, and the plugin's own words
12013
+ for it underneath — a card previews the paper, it does not describe it. */
12014
+ /* The date a box will carry, under the two answers that make it. Plain
12015
+ text, never a control: it is the result, not a third question. */
12016
+ .fe-spdf__dateex {
12017
+ margin: 0;
12018
+ display: flex;
12019
+ align-items: baseline;
12020
+ gap: 6px;
12021
+ flex-wrap: wrap;
12022
+ font-size: var(--fe-text-sm);
12023
+ }
12024
+ .fe-spdf__dateex-title {
12025
+ color: var(--fe-text-muted);
12026
+ }
12027
+ .fe-spdf__dateex-value {
12028
+ font-family: var(--fe-font-mono);
12029
+ font-weight: 600;
12030
+ }
12031
+ .fe-spdf__lines {
12032
+ flex-basis: 100%;
12033
+ flex-wrap: wrap;
12034
+ align-items: flex-start;
12035
+ row-gap: 6px;
12036
+ }
12037
+ .fe-spdf__linepreview {
12038
+ flex-basis: 100%;
12039
+ display: flex;
12040
+ flex-direction: column;
12041
+ gap: 1px;
12042
+ padding: 6px 10px;
12043
+ border: 1px dashed var(--fe-border-strong, var(--fe-border));
12044
+ border-radius: var(--fe-radius);
12045
+ background: var(--fe-bg);
12046
+ font-size: var(--fe-text-xs, 11px);
12047
+ line-height: 1.35;
12048
+ color: var(--fe-text);
12049
+ }
12050
+ .fe-spdf__linepreview-title {
12051
+ color: var(--fe-text-muted);
12052
+ font-weight: 600;
12053
+ margin-bottom: 2px;
12054
+ }
12055
+ .fe-spdf__linepreview-line.is-none {
12056
+ color: var(--fe-text-muted);
12057
+ font-style: italic;
12058
+ }
12059
+ .fe-spdf__propwho {
12060
+ font-size: var(--fe-text-sm);
12061
+ color: var(--fe-text-muted);
12062
+ }
12063
+ .fe-spdf__cardno {
12064
+ flex: 0 0 auto;
12065
+ width: 22px;
12066
+ height: 22px;
12067
+ border-radius: 50%;
12068
+ display: grid;
12069
+ place-items: center;
12070
+ font-size: var(--fe-text-xs, 11px);
12071
+ font-weight: 600;
12072
+ color: var(--fe-text-on-primary);
12073
+ background: var(--spdf-color, var(--fe-primary));
12074
+ }
12075
+ .fe-spdf__add {
12076
+ display: flex;
12077
+ align-items: center;
12078
+ flex-wrap: wrap;
12079
+ gap: 6px;
12080
+ padding-top: 2px;
12081
+ }
12082
+ .fe-spdf__addlbl {
12083
+ font-size: var(--fe-text-sm);
12084
+ color: var(--fe-text-muted);
12085
+ margin-inline-end: 4px;
12086
+ }
12087
+ .fe-spdf__empty {
12088
+ padding: 14px;
12089
+ border: 1px dashed var(--fe-border);
12090
+ border-radius: var(--fe-radius-md, 8px);
12091
+ color: var(--fe-text-muted);
12092
+ font-size: var(--fe-text-sm);
12093
+ line-height: 1.5;
12094
+ }
12095
+ .fe-spdf__done {
12096
+ font-size: var(--fe-text-sm);
12097
+ color: var(--fe-text-muted);
12098
+ align-self: center;
12099
+ }
12100
+ /* A waiting box wears its signer's colour, so the toolbar and the page
12101
+ agree about which box is which. */
12102
+ .fe-spdf__palette .fe-btn[style*='--spdf-color'] {
12103
+ border-inline-start: 3px solid var(--spdf-color, var(--fe-primary));
12104
+ }
12105
+
12106
+ .fe-spdf--page {
12107
+ display: flex;
12108
+ flex-direction: column;
12109
+ height: var(--spdf-h, 100%);
12110
+ min-height: 0;
12111
+ }
12112
+ .fe-spdf--page .fe-spdf__scroll {
12113
+ flex: 1 1 auto;
12114
+ max-height: none;
12115
+ min-height: 0;
12116
+ }
12117
+ /* Centred, because a fitted page is narrower than the box it is fitted in
12118
+ and a document pinned to the left edge of a wide screen reads as broken. */
12119
+ .fe-spdf--page .fe-spdf__pages {
12120
+ align-items: center;
12121
+ }
12122
+ .fe-spdf__pages {
12123
+ display: flex;
12124
+ flex-direction: column;
12125
+ align-items: flex-start;
12126
+ gap: 12px;
12127
+ }
12128
+ /* ⚠⚠ DOCUMENT SPACE from here in. A page is the PDF's own coordinate system:
12129
+ its boxes are placed with PHYSICAL inline `left`/`top` in % of the page
12130
+ (SurfacePdfFields, lib/pdfFieldsGeom → the signer stamps at those
12131
+ fractions), so a box at the left of the page is at the left of the page in
12132
+ Arabic too. Nothing inside a page may use logical properties or read
12133
+ `--filex-dir-x` — the RTL guard (web/tests/quality/rtlLogical.test.ts)
12134
+ allow-lists exactly these rules. */
12135
+ .fe-spdf__page {
12136
+ position: relative;
12137
+ background: #fff;
12138
+ box-shadow: var(--fe-shadow-sm);
12139
+ flex: 0 0 auto;
12140
+ touch-action: pan-y;
12141
+ user-select: none;
12142
+ }
12143
+ .fe-spdf.is-placing .fe-spdf__page {
12144
+ cursor: crosshair;
12145
+ }
12146
+ .fe-spdf__canvas {
12147
+ position: absolute;
12148
+ inset: 0;
12149
+ width: 100%;
12150
+ height: 100%;
12151
+ display: block;
12152
+ }
12153
+ .fe-spdf__field {
12154
+ position: absolute;
12155
+ box-sizing: border-box;
12156
+ border: 1.5px solid var(--spdf-color, var(--fe-primary));
12157
+ background: color-mix(in srgb, var(--spdf-color, var(--fe-primary)) 12%, transparent);
12158
+ border-radius: 2px;
12159
+ display: flex;
12160
+ align-items: center;
12161
+ justify-content: center;
12162
+ overflow: hidden;
12163
+ font-size: 11px;
12164
+ line-height: 1.15;
12165
+ color: var(--spdf-color, var(--fe-primary));
12166
+ cursor: pointer;
12167
+ }
12168
+ .fe-spdf.is-edit .fe-spdf__field {
12169
+ cursor: move;
12170
+ /* ⚠ A finger that drags a box must move the BOX. The page allows vertical
12171
+ panning (`pan-y`) so a signer can scroll a document with a finger; on a
12172
+ box that same pan was taken by the browser, which then cancelled the
12173
+ pointer — the box stopped half way and the pane scrolled instead. */
12174
+ touch-action: none;
12175
+ }
12176
+ .fe-spdf.is-edit.is-placing .fe-spdf__page {
12177
+ touch-action: none;
12178
+ }
12179
+ .fe-spdf__field.is-selected {
12180
+ outline: 2px solid var(--fe-primary);
12181
+ outline-offset: 1px;
12182
+ z-index: 2;
12183
+ }
12184
+ .fe-spdf__field.is-foreign {
12185
+ border-style: dashed;
12186
+ border-color: var(--fe-border-strong);
12187
+ background: rgba(128, 128, 128, 0.12);
12188
+ color: var(--fe-text-muted);
12189
+ cursor: default;
12190
+ pointer-events: none;
12191
+ }
12192
+ .fe-spdf__field.is-required::after {
12193
+ content: '*';
12194
+ position: absolute;
12195
+ top: 0;
12196
+ right: 3px; /* rtl-physical: ⚠⚠ DOCUMENT SPACE — drawn on the page, which never mirrors */
12197
+ font-weight: 700;
12198
+ }
12199
+ .fe-spdf__field--ghost {
12200
+ pointer-events: none;
12201
+ border-style: dotted;
12202
+ z-index: 3;
12203
+ }
12204
+ .fe-spdf__label {
12205
+ text-align: center;
12206
+ padding: 0 4px;
12207
+ display: flex;
12208
+ flex-direction: column;
12209
+ align-items: center;
12210
+ gap: 1px;
12211
+ white-space: nowrap;
12212
+ overflow: hidden;
12213
+ text-overflow: ellipsis;
12214
+ max-width: 100%;
12215
+ }
12216
+ .fe-spdf__who {
12217
+ font-size: 9px;
12218
+ opacity: 0.8;
12219
+ }
12220
+ .fe-spdf__sig {
12221
+ max-width: 100%;
12222
+ max-height: 100%;
12223
+ object-fit: contain;
12224
+ pointer-events: none;
12225
+ }
12226
+ .fe-spdf__text {
12227
+ width: 100%;
12228
+ height: 100%;
12229
+ border: 0;
12230
+ background: transparent;
12231
+ color: #111;
12232
+ font: inherit;
12233
+ font-size: 12px;
12234
+ padding: 0 4px;
12235
+ outline: none;
12236
+ min-width: 0;
12237
+ }
12238
+ .fe-spdf__check .fe-aicon {
12239
+ width: 90%;
12240
+ height: 90%;
12241
+ color: #111;
12242
+ }
12243
+ .fe-spdf__val {
12244
+ color: #111;
12245
+ font-variant-numeric: tabular-nums;
12246
+ }
12247
+ .fe-spdf__handle {
12248
+ position: absolute;
12249
+ /* ⚠⚠ DOCUMENT SPACE. The box sits at its PDF coordinates (inline `left`/`top`
12250
+ in % of the page, SurfacePdfFields) and grows right and down from there in
12251
+ every UI language — the resize maths reads the pointer against the page's
12252
+ own left edge. A handle mirrored to the box's left corner would drag the
12253
+ RIGHT edge while the pointer sits on the left one. */
12254
+ right: -5px; /* rtl-physical: document space (above) */
12255
+ bottom: -5px;
12256
+ width: 12px;
12257
+ height: 12px;
12258
+ border-radius: 50%;
12259
+ background: var(--spdf-color, var(--fe-primary));
12260
+ border: 2px solid #fff;
12261
+ cursor: nwse-resize;
12262
+ z-index: 3;
12263
+ }
12264
+ .fe-spdf__pad {
12265
+ display: flex;
12266
+ flex-direction: column;
12267
+ gap: 8px;
12268
+ padding: 10px;
12269
+ border: 1px solid var(--fe-border);
12270
+ border-radius: var(--fe-radius);
12271
+ background: var(--fe-bg-elev);
12272
+ }
12273
+ .fe-spdf__pad > .fe-btn {
12274
+ align-self: flex-end;
12275
+ }
12276
+ .fe-spreview__embed {
12277
+ width: 100%;
12278
+ height: 60vh;
12279
+ max-height: none;
12280
+ }
12281
+
12282
+ /* ── The outward-facing page ─────────────────────────────────────────────
12283
+ *
12284
+ * A share, a file request and an app plugin's public page are ONE page with
12285
+ * three bodies (v3 §1), and this is what that page LOOKS like. The shape is
12286
+ * the one the Go-rendered PIN gate had until v0.42.2 — a calm centred card
12287
+ * on a soft ground, the instance's mark above it, a tinted round badge, a
12288
+ * heading, a quiet line under it, a full-width field and a full-width accent
12289
+ * button — restored here so that unifying the pages levelled them UP.
12290
+ *
12291
+ * ⚠⚠ It was levelled DOWN in v0.43.0 (owner, 2026-09-23: "iki sayfa aynı
12292
+ * olsun dediğim için ikisini de kötü hale çevirmişsin"). The measurements
12293
+ * that had been lost, and are back: the card is 400px and CENTRED in the
12294
+ * viewport rather than 720px glued to the top; it is LIGHTER than the ground
12295
+ * (v0.43.0 drew `--fe-bg-elev`, the app's *sunken* tone, on `--fe-bg` — a
12296
+ * card you cannot see); 16px radius and 32/28 padding, not 12 and 20; a
12297
+ * 64px badge; a 20px/700 heading; and the primary button and the PIN box
12298
+ * span the card instead of hugging its left edge.
12299
+ *
12300
+ * Three widths, one family — exactly the old stylesheet's three cards:
12301
+ * · `--gate` 400px, centred text: the PIN gate, "this link is not
12302
+ * available", an error, an acceptance, and a single document.
12303
+ * · `--form` 520px, text from the start edge: a file request.
12304
+ * · `--wide` 880px: a folder's listing, and an app's screen.
12305
+ * The mode is the SHELL's (`layout`, forced to `gate` for every state that
12306
+ * is not `ready`), so no body carries a layout of its own.
12307
+ *
12308
+ * ⚠ The ground and the card are the public page's own tokens rather than
12309
+ * `--fe-bg`/`--fe-bg-elev`: in the app those two mean "surface" and "sunken
12310
+ * strip", and a card has to sit ABOVE its ground, not below it.
12311
+ * ⚠ Logical properties only (`padding-inline`, `margin-inline`): the page is
12312
+ * opened in Arabic too, and web/tests/quality/rtlLogical.test.ts says so. */
12313
+ .fe.fe-ppage {
12314
+ /* The ground, the card and the three widths are declared in
12315
+ styles/variables.css (light + dark) — see the note there. */
12316
+ min-height: 100vh;
12317
+ height: auto;
12318
+ border: 0;
12319
+ border-radius: 0;
12320
+ overflow: visible;
12321
+ display: flex;
12322
+ flex-direction: column;
12323
+ align-items: center;
12324
+ /* ⚠ `safe center` and not plain `center`: a tall body (an app's wizard with
12325
+ a document in it) is taller than the window, and a plainly centred flex
12326
+ column overflows past the TOP of the scrollport, where nothing can scroll
12327
+ it back. `safe` falls back to start alignment exactly then. The plain
12328
+ value stays as the fallback for anything that does not know the keyword. */
12329
+ justify-content: center;
12330
+ justify-content: safe center;
12331
+ gap: 18px;
12332
+ padding: 40px 16px;
12333
+ background: linear-gradient(160deg, var(--fe-ppage-ground-1), var(--fe-ppage-ground-2));
12334
+ color: var(--fe-text);
12335
+ }
12336
+ /* The instance's mark, ABOVE the card and centred on it — the first thing a
12337
+ * stranger reads, and (v3 §1.2) theirs, not ours. */
12338
+ .fe-ppage__brand {
12339
+ display: flex;
12340
+ align-items: center;
12341
+ justify-content: center;
12342
+ gap: 10px;
12343
+ max-width: 100%;
12344
+ min-width: 0;
12345
+ }
12346
+ .fe-ppage__logo {
12347
+ height: 34px;
12348
+ width: auto;
12349
+ max-width: 200px;
12350
+ object-fit: contain;
12351
+ display: block;
12352
+ flex: none;
12353
+ }
12354
+ .fe-ppage__brandname {
12355
+ font-size: 17px;
12356
+ font-weight: 700;
12357
+ letter-spacing: -0.01em;
12358
+ color: var(--fe-text);
12359
+ overflow-wrap: anywhere;
12360
+ }
12361
+
12362
+ .fe-ppage__card {
12363
+ width: 100%;
12364
+ max-width: var(--fe-ppage-max);
12365
+ background: var(--fe-ppage-card);
12366
+ border: 1px solid var(--fe-border);
12367
+ border-radius: 16px;
12368
+ box-shadow: var(--fe-ppage-shadow);
12369
+ padding: 28px 24px;
12370
+ display: flex;
12371
+ flex-direction: column;
12372
+ gap: 14px;
12373
+ min-width: 0;
12374
+ }
12375
+ .fe-ppage--gate .fe-ppage__card {
12376
+ padding: 32px 28px;
12377
+ text-align: center;
12378
+ gap: 12px;
12379
+ }
12380
+
12381
+ /* The round, accent-tinted badge the card opens with. One element with three
12382
+ * tones, so "protected", "gone" and "done" are the same shape in a different
12383
+ * colour rather than three drawings. */
12384
+ .fe-ppage__badge {
12385
+ width: 64px;
12386
+ height: 64px;
12387
+ margin-block-end: 2px;
12388
+ margin-inline: auto;
12389
+ border-radius: 50%;
12390
+ display: grid;
12391
+ place-items: center;
12392
+ flex: none;
12393
+ background: var(--fe-primary-soft);
12394
+ color: var(--fe-primary-ink);
12395
+ }
12396
+ .fe-ppage__badge svg {
12397
+ width: 30px;
12398
+ height: 30px;
12399
+ }
12400
+ .fe-ppage__badge--ok {
12401
+ background: color-mix(in srgb, var(--fe-ppage-ok) 14%, transparent);
12402
+ color: var(--fe-ppage-ok);
12403
+ }
12404
+ .fe-ppage__badge--err {
12405
+ background: color-mix(in srgb, var(--fe-danger) 12%, transparent);
12406
+ color: var(--fe-danger);
12407
+ }
12408
+ .fe-ppage__spinner {
12409
+ width: 26px;
12410
+ height: 26px;
12411
+ border: 2.5px solid currentColor;
12412
+ border-inline-end-color: transparent;
12413
+ border-radius: 50%;
12414
+ animation: fe-ppage-spin 0.8s linear infinite;
12415
+ opacity: 0.6;
12416
+ }
12417
+ @keyframes fe-ppage-spin {
12418
+ to {
12419
+ transform: rotate(360deg);
12420
+ }
12421
+ }
12422
+ @media (prefers-reduced-motion: reduce) {
12423
+ .fe-ppage__spinner {
12424
+ animation-duration: 2.4s;
12425
+ }
12426
+ }
12427
+
12428
+ .fe-ppage__head {
12429
+ display: flex;
12430
+ flex-direction: column;
12431
+ gap: 6px;
12432
+ }
12433
+ .fe-ppage__title {
12434
+ margin: 0;
12435
+ font-size: 20px;
12436
+ font-weight: 700;
12437
+ line-height: 1.3;
12438
+ letter-spacing: -0.01em;
12439
+ overflow-wrap: anywhere;
12440
+ }
12441
+ .fe-ppage__subject {
12442
+ margin: 0;
12443
+ color: var(--fe-text-muted);
12444
+ font-size: 14px;
12445
+ line-height: 1.5;
12446
+ overflow-wrap: anywhere;
12447
+ }
12448
+ .fe-ppage__state {
12449
+ display: flex;
12450
+ flex-direction: column;
12451
+ gap: 6px;
12452
+ }
12453
+ .fe-ppage__state-title {
12454
+ margin: 0;
12455
+ font-size: 20px;
12456
+ font-weight: 700;
12457
+ line-height: 1.3;
12458
+ letter-spacing: -0.01em;
12459
+ }
12460
+ .fe-ppage__state--ok .fe-ppage__state-title {
12461
+ color: var(--fe-ppage-ok);
12462
+ }
12463
+ /* The shell's OWN paragraphs at the reading size the old page used (0.9rem)
12464
+ * rather than the app's 13px table type.
12465
+ * ⚠ Scoped to the card's own children and to the shell's states — NOT to
12466
+ * every `.fe-surface__text` under `.fe-ppage`. An app plugin's screen is the
12467
+ * same `SurfaceConversation` a signed-in person sees, and resizing its text
12468
+ * only on a public link is exactly the surface-specific divergence the one
12469
+ * shell exists to prevent. */
12470
+ .fe-ppage__card > .fe-surface__text,
12471
+ .fe-ppage__state > .fe-surface__text,
12472
+ .fe-ppage__pin > .fe-surface__text,
12473
+ .fe-ppage__pin .fe-ppage__state > .fe-surface__text,
12474
+ .fe-pdrop > .fe-surface__text,
12475
+ .fe-pdrop__zone > .fe-surface__text,
12476
+ .fe-ppage__file-one > .fe-surface__text {
12477
+ font-size: 14px;
12478
+ line-height: 1.5;
12479
+ }
12480
+
12481
+ /* ⚠ On the gate the primary act spans the card: a 4-digit box and a
12482
+ * "Continue" hugging the left edge of a 400px card is what v0.43.0 shipped,
12483
+ * and it reads as a form fragment rather than the one thing to do. */
12484
+ .fe-ppage--gate .fe-btn {
12485
+ justify-content: center;
12486
+ }
12487
+ .fe-ppage--gate .fe-btn--primary {
12488
+ width: 100%;
12489
+ padding-block: 11px;
12490
+ font-size: 15px;
12491
+ font-weight: 600;
12492
+ border-radius: 10px;
12493
+ }
12494
+ .fe-ppage__pin {
12495
+ display: flex;
12496
+ flex-direction: column;
12497
+ gap: 12px;
12498
+ }
12499
+ /* The code box: the whole width, big, spaced and centred, as it was. A PIN is
12500
+ * read off a message and typed once — it is not a text field among others. */
12501
+ .fe-ppage__pinput.fe-ppage__pinput {
12502
+ box-sizing: border-box;
12503
+ width: 100%;
12504
+ max-width: none;
12505
+ height: auto;
12506
+ padding: 12px;
12507
+ font-size: 22px;
12508
+ font-weight: 600;
12509
+ letter-spacing: 0.32em;
12510
+ text-indent: 0.32em;
12511
+ text-align: center;
12512
+ border: 1.5px solid var(--fe-border-strong);
12513
+ border-radius: 10px;
12514
+ background: transparent;
12515
+ color: inherit;
12516
+ font-family: inherit;
12517
+ }
12518
+ .fe-ppage__pinput:focus {
12519
+ outline: none;
12520
+ border-color: var(--fe-primary);
12521
+ box-shadow: 0 0 0 3px var(--fe-primary-soft);
12522
+ }
12523
+ .fe-ppage__pinput.is-invalid {
12524
+ border-color: var(--fe-danger);
12525
+ }
12526
+ /* Named for a screen reader, not for the eye: the heading above it already
12527
+ * says what the box is, and a "PIN" label over a PIN box is furniture. */
12528
+ .fe-ppage__srlabel {
12529
+ position: absolute;
12530
+ width: 1px;
12531
+ height: 1px;
12532
+ padding: 0;
12533
+ margin: -1px;
12534
+ overflow: hidden;
12535
+ clip-path: inset(50%);
12536
+ white-space: nowrap;
12537
+ border: 0;
12538
+ }
12539
+ .fe-ppage__error {
12540
+ margin: 0;
12541
+ padding: 10px 12px;
12542
+ border-radius: 9px;
12543
+ background: color-mix(in srgb, var(--fe-danger) 10%, transparent);
12544
+ color: var(--fe-danger);
12545
+ font-size: 13.5px;
12546
+ }
12547
+
12548
+ /* A card's actions: a full-width row under a rule. ⚠ `width: 100%` is load
12549
+ bearing — inside a body that packs its children to the start
12550
+ (`__file-one`) the row shrink-wrapped and its rule stopped halfway across
12551
+ the card, which is what a folder share shipped as. */
12552
+ .fe-ppage__actions {
12553
+ display: flex;
12554
+ flex-wrap: wrap;
12555
+ justify-content: flex-end;
12556
+ gap: 8px;
12557
+ width: 100%;
12558
+ box-sizing: border-box;
12559
+ padding-top: 12px;
12560
+ border-top: 1px solid var(--fe-border-soft);
12561
+ }
12562
+ .fe-ppage--gate .fe-ppage__file-one {
12563
+ align-items: center;
12564
+ gap: 14px;
12565
+ }
12566
+ .fe-ppage--gate .fe-ppage__actions {
12567
+ justify-content: center;
12568
+ border-top: 0;
12569
+ padding-top: 0;
12570
+ }
12571
+ .fe-ppage__files {
12572
+ list-style: none;
12573
+ margin: 0;
12574
+ padding: 0;
12575
+ display: flex;
12576
+ flex-direction: column;
12577
+ border: 1px solid var(--fe-border);
12578
+ border-radius: 12px;
12579
+ overflow: hidden;
12580
+ }
12581
+ .fe-ppage__file {
12582
+ display: flex;
12583
+ align-items: center;
12584
+ gap: 10px;
12585
+ padding: 10px 14px;
12586
+ font-size: 14px;
12587
+ }
12588
+ .fe-ppage__file + .fe-ppage__file {
12589
+ border-top: 1px solid var(--fe-border);
12590
+ }
12591
+ .fe-ppage__file:hover {
12592
+ background: var(--fe-primary-soft);
12593
+ }
12594
+ .fe-ppage__file a {
12595
+ color: var(--fe-primary-ink);
12596
+ text-decoration: none;
12597
+ overflow: hidden;
12598
+ text-overflow: ellipsis;
12599
+ white-space: nowrap;
12600
+ }
12601
+ .fe-ppage__file a:hover {
12602
+ text-decoration: underline;
12603
+ }
12604
+ .fe-ppage__filesize {
12605
+ color: var(--fe-text-muted);
12606
+ flex: 0 0 auto;
12607
+ font-variant-numeric: tabular-nums;
12608
+ }
12609
+
12610
+ /* The phone. 390px is the measurement that matters: the card gives up its
12611
+ * generous side padding rather than its rhythm, and keeps a 12px gutter each
12612
+ * side. (A body too tall to centre starts at the top by itself — that is
12613
+ * `justify-content: safe center` above, not a rule here.) */
12614
+ @media (max-width: 560px) {
12615
+ .fe.fe-ppage {
12616
+ padding: 24px 12px 32px;
12617
+ }
12618
+ .fe-ppage__card {
12619
+ padding: 24px 18px;
12620
+ }
12621
+ .fe-ppage--gate .fe-ppage__card {
12622
+ padding: 28px 20px;
12623
+ }
12624
+ .fe-ppage__logo {
12625
+ height: 28px;
12626
+ }
12627
+ .fe-ppage__title,
12628
+ .fe-ppage__state-title {
12629
+ font-size: 18px;
12630
+ }
12631
+ .fe-ppage__actions .fe-btn {
12632
+ flex: 1 1 auto;
12633
+ justify-content: center;
12634
+ }
12635
+ }
12636
+ /* === /App plugins M3 === */
12637
+
12638
+ /* === App plugins v3 — the shared choice control and the ONE public
12639
+ * shell (docs/APP-PLUGINS-V3.md §1–§2).
12640
+ *
12641
+ * ⚠⚠ These rules live HERE and not in the components, for a reason the
12642
+ * build makes and not a taste: the web component ships only the styles of
12643
+ * the modules its entry imports, while `packages/core` ships all of them,
12644
+ * so a `<style>` block in a component one distribution does not mount makes
12645
+ * the two published stylesheets differ. `web/tests/deploy/packageLook` is
12646
+ * the gate that says so. === */
12647
+
12648
+ /* ChoiceButtons.vue */
12649
+ .fe-choice {
12650
+ display: flex;
12651
+ flex-wrap: wrap;
12652
+ gap: 8px;
12653
+ }
12654
+ .fe-choice__btn {
12655
+ flex: 0 1 auto;
12656
+ display: flex;
12657
+ flex-direction: column;
12658
+ align-items: flex-start;
12659
+ gap: 2px;
12660
+ min-width: 0;
12661
+ font: inherit;
12662
+ font-size: 13.5px;
12663
+ text-align: start;
12664
+ padding: 8px 12px;
12665
+ border-radius: var(--fe-radius);
12666
+ border: 1px solid var(--fe-border-strong);
12667
+ background: var(--fe-bg);
12668
+ color: var(--fe-text);
12669
+ cursor: pointer;
12670
+ }
12671
+ .fe-choice__btn:hover:not(:disabled) {
12672
+ background: var(--fe-bg-hover);
12673
+ }
12674
+ .fe-choice__btn:focus-visible {
12675
+ outline: 2px solid var(--fe-primary);
12676
+ outline-offset: 1px;
12677
+ }
12678
+ /* ⚠ The chosen one is marked by BORDER and BACKGROUND, not by colour alone:
12679
+ a palette where the primary is close to the surface still shows which is
12680
+ on, and so does a screen nobody has calibrated. */
12681
+ .fe-choice__btn.is-on {
12682
+ border-color: var(--fe-primary);
12683
+ background: var(--fe-bg-selected);
12684
+ color: var(--fe-text);
12685
+ box-shadow: inset 0 0 0 1px var(--fe-primary);
12686
+ }
12687
+ .fe-choice__btn:disabled {
12688
+ opacity: 0.55;
12689
+ cursor: default;
12690
+ }
12691
+ .fe-choice.is-invalid .fe-choice__btn {
12692
+ border-color: var(--fe-danger);
12693
+ }
12694
+ .fe-choice__label {
12695
+ font-weight: 600;
12696
+ }
12697
+ .fe-choice__help {
12698
+ font-size: 12px;
12699
+ color: var(--fe-text-muted);
12700
+ line-height: 1.4;
12701
+ }
12702
+
12703
+ /* SurfaceFooterButtons.vue */
12704
+ /* Back sits at the far end of the row, away from the way forward: a step's
12705
+ two directions must not be two neighbouring buttons. `margin-right: auto`
12706
+ works in every frame that lays the row out as a flex line, and is inert in
12707
+ one that does not. */
12708
+ .fe-surface__back {
12709
+ margin-inline-end: auto;
12710
+ }
12711
+
12712
+ /* PublicShell.vue — the meta line and the foot.
12713
+ The expiry and the visit counter are a quiet line under the heading, and
12714
+ the language picker + attribution sit OUTSIDE the card, centred under it,
12715
+ the way the old page's brand footer did. */
12716
+ .fe-ppage__meta {
12717
+ margin: 0;
12718
+ display: flex;
12719
+ flex-wrap: wrap;
12720
+ gap: 6px;
12721
+ font-size: 13px;
12722
+ color: var(--fe-text-muted);
12723
+ }
12724
+ .fe-ppage--gate .fe-ppage__meta {
12725
+ justify-content: center;
12726
+ }
12727
+ .fe-ppage__foot {
12728
+ display: flex;
12729
+ flex-direction: column;
12730
+ align-items: center;
12731
+ gap: 10px;
12732
+ width: 100%;
12733
+ }
12734
+ .fe-ppage__footmark {
12735
+ display: inline-flex;
12736
+ align-items: center;
12737
+ gap: 7px;
12738
+ font-size: 12.5px;
12739
+ color: var(--fe-text-muted);
12740
+ }
12741
+ .fe-ppage__footmark img,
12742
+ .fe-ppage__footmark svg {
12743
+ width: 16px;
12744
+ height: 16px;
12745
+ flex: none;
12746
+ border-radius: 3px;
12747
+ object-fit: contain;
12748
+ }
12749
+ .fe-ppage__footline {
12750
+ margin: 0;
12751
+ font-size: 12.5px;
12752
+ color: var(--fe-text-muted);
12753
+ text-align: center;
12754
+ max-width: 480px;
12755
+ overflow-wrap: anywhere;
12756
+ }
12757
+
12758
+ /* PublicShareBody.vue */
12759
+ .fe-ppage__crumbs {
12760
+ display: flex;
12761
+ align-items: center;
12762
+ gap: 8px;
12763
+ font-size: 13px;
12764
+ color: var(--fe-text-muted);
12765
+ }
12766
+ .fe-ppage__crumb {
12767
+ overflow: hidden;
12768
+ text-overflow: ellipsis;
12769
+ white-space: nowrap;
12770
+ }
12771
+ .fe-ppage__entry {
12772
+ font: inherit;
12773
+ font-size: 13.5px;
12774
+ color: var(--fe-primary);
12775
+ background: none;
12776
+ border: 0;
12777
+ padding: 0;
12778
+ text-align: start;
12779
+ cursor: pointer;
12780
+ text-decoration: none;
12781
+ }
12782
+ .fe-ppage__entry:hover {
12783
+ text-decoration: underline;
12784
+ }
12785
+ .fe-ppage__file-one {
12786
+ display: flex;
12787
+ flex-direction: column;
12788
+ align-items: flex-start;
12789
+ gap: 10px;
12790
+ }
12791
+ /* An app page's exposed files, drawn as a shared file is, under a rule that
12792
+ separates them from the app's own screen above. */
12793
+ .fe-ppage__appfiles {
12794
+ display: flex;
12795
+ flex-direction: column;
12796
+ gap: 12px;
12797
+ padding-top: 12px;
12798
+ border-top: 1px solid var(--fe-border);
12799
+ }
12800
+ .fe-ppage__shot {
12801
+ max-width: 100%;
12802
+ max-height: 46vh;
12803
+ border-radius: var(--fe-radius);
12804
+ border: 1px solid var(--fe-border);
12805
+ object-fit: contain;
12806
+ }
12807
+
12808
+ /* PublicRequestBody.vue */
12809
+ .fe-pdrop {
12810
+ display: flex;
12811
+ flex-direction: column;
12812
+ gap: 12px;
12813
+ }
12814
+ /* The uploader's name — above the drop area, because dropping sends. */
12815
+ .fe-pdrop__who {
12816
+ display: flex;
12817
+ flex-direction: column;
12818
+ gap: 4px;
12819
+ }
12820
+ .fe-pdrop__wholabel {
12821
+ font-size: var(--fe-text-sm);
12822
+ font-weight: 500;
12823
+ color: var(--fe-text);
12824
+ }
12825
+ .fe-pdrop__whoinput {
12826
+ box-sizing: border-box;
12827
+ width: 100%;
12828
+ height: var(--fe-h-lg);
12829
+ padding: 0 var(--fe-gap-sm);
12830
+ border: 1px solid var(--fe-border);
12831
+ border-radius: var(--fe-radius);
12832
+ background: var(--fe-bg);
12833
+ color: var(--fe-text);
12834
+ font-family: inherit;
12835
+ font-size: var(--fe-text-md);
12836
+ }
12837
+ .fe-pdrop__whoinput:focus {
12838
+ outline: none;
12839
+ border-color: var(--fe-primary);
12840
+ }
12841
+ .fe-pdrop__zone {
12842
+ display: flex;
12843
+ flex-direction: column;
12844
+ align-items: center;
12845
+ justify-content: center;
12846
+ gap: 4px;
12847
+ padding: 28px 16px;
12848
+ border: 2px dashed var(--fe-border-strong);
12849
+ border-radius: var(--fe-radius-lg);
12850
+ background: var(--fe-bg-elev);
12851
+ cursor: pointer;
12852
+ text-align: center;
12853
+ }
12854
+ .fe-pdrop__zone:focus-visible {
12855
+ outline: 2px solid var(--fe-primary);
12856
+ outline-offset: 2px;
12857
+ }
12858
+ .fe-pdrop__zone.is-over {
12859
+ border-color: var(--fe-primary);
12860
+ background: var(--fe-bg-selected);
12861
+ }
12862
+ .fe-pdrop__zone.is-disabled {
12863
+ cursor: default;
12864
+ opacity: 0.6;
12865
+ }
12866
+ .fe-pdrop__icon {
12867
+ width: 38px;
12868
+ height: 38px;
12869
+ margin-block-end: 6px;
12870
+ color: inherit;
12871
+ }
12872
+ .fe-pdrop__lead {
12873
+ margin: 0;
12874
+ font-size: 16px;
12875
+ font-weight: 600;
12876
+ color: var(--fe-text);
12877
+ }
12878
+ /* The zone answers the pointer the way the old one did: the accent's tint,
12879
+ its border and its ink, all three at once. */
12880
+ .fe-pdrop__zone:hover:not(.is-disabled),
12881
+ .fe-pdrop__zone.is-over {
12882
+ border-color: var(--fe-primary);
12883
+ background: var(--fe-primary-soft);
12884
+ color: var(--fe-primary-ink);
12885
+ }
12886
+ .fe-pdrop__zone:hover:not(.is-disabled) .fe-pdrop__lead,
12887
+ .fe-pdrop__zone.is-over .fe-pdrop__lead {
12888
+ color: inherit;
12889
+ }
12890
+ .fe-pdrop__input {
12891
+ display: none;
12892
+ }
12893
+ .fe-pdrop__list {
12894
+ list-style: none;
12895
+ margin: 0;
12896
+ padding: 0;
12897
+ display: flex;
12898
+ flex-direction: column;
12899
+ gap: 6px;
12900
+ }
12901
+ .fe-pdrop__item {
12902
+ display: flex;
12903
+ align-items: center;
12904
+ gap: 8px;
12905
+ font-size: 13px;
12906
+ color: var(--fe-text);
12907
+ }
12908
+ .fe-pdrop__name {
12909
+ flex: 1 1 auto;
12910
+ min-width: 0;
12911
+ overflow: hidden;
12912
+ text-overflow: ellipsis;
12913
+ white-space: nowrap;
12914
+ }
12915
+ .fe-pdrop__bar {
12916
+ flex: 0 0 120px;
12917
+ height: 6px;
12918
+ }
12919
+ .fe-pdrop__ok {
12920
+ color: var(--fe-ok);
12921
+ }
12922
+
12923
+ /* PublicLanguagePicker.vue */
12924
+ .fe-plang {
12925
+ display: flex;
12926
+ flex-wrap: wrap;
12927
+ gap: 6px;
12928
+ }
12929
+ .fe-plang__btn {
12930
+ display: inline-flex;
12931
+ align-items: baseline;
12932
+ gap: 4px;
12933
+ font: inherit;
12934
+ font-size: 12.5px;
12935
+ padding: 4px 10px;
12936
+ border-radius: 999px;
12937
+ border: 1px solid var(--fe-border);
12938
+ background: transparent;
12939
+ color: var(--fe-text-muted);
12940
+ cursor: pointer;
12941
+ }
12942
+ .fe-plang__btn:hover {
12943
+ color: var(--fe-text);
12944
+ background: var(--fe-bg-hover);
12945
+ }
12946
+ .fe-plang__btn.is-on {
12947
+ color: var(--fe-text);
12948
+ border-color: var(--fe-primary);
12949
+ background: var(--fe-bg-selected);
12950
+ }
12951
+ .fe-plang__btn:focus-visible {
12952
+ outline: 2px solid var(--fe-primary);
12953
+ outline-offset: 1px;
12954
+ }
12955
+ .fe-plang__src {
12956
+ font-size: 10.5px;
12957
+ opacity: 0.75;
12958
+ }
12959
+
12960
+
12961
+ /* === App plugins v2 ===================================================
12962
+ * A `page` view (`{base}apps/{plugin}/{view}`), the lock badge an app's
12963
+ * hold puts on a row, and the face picker a typed signature carries. */
12964
+
12965
+ /* The page view is a whole document, not a panel: the `.fe` root's explorer
12966
+ * chrome (border, radius, fixed height, hidden overflow) is undone here for
12967
+ * the same reason `.fe-ppage` undoes it. ⚠ Without this the wizard is a
12968
+ * 420px-tall bordered box floating in an empty tab. */
12969
+ .fe.fe-apppage {
12970
+ min-height: 100vh;
12971
+ height: auto;
12972
+ border: 0;
12973
+ border-radius: 0;
12974
+ overflow: visible;
12975
+ padding: 0 0 40px;
12976
+ gap: 0;
12977
+ }
12978
+ .fe-apppage__bar {
12979
+ display: flex;
12980
+ align-items: baseline;
12981
+ gap: 12px;
12982
+ flex-wrap: wrap;
12983
+ padding: 16px;
12984
+ border-bottom: 1px solid var(--fe-border);
12985
+ background: var(--fe-bg-elev);
12986
+ }
12987
+ .fe-apppage__title {
12988
+ margin: 0;
12989
+ font-size: 18px;
12990
+ font-weight: 600;
12991
+ line-height: 1.25;
12992
+ }
12993
+ .fe-apppage__path {
12994
+ margin: 0;
12995
+ flex: 1 1 200px;
12996
+ min-width: 0;
12997
+ color: var(--fe-text-muted);
12998
+ font-size: var(--fe-text-sm);
12999
+ font-family: var(--fe-font-mono);
13000
+ overflow: hidden;
13001
+ text-overflow: ellipsis;
13002
+ white-space: nowrap;
13003
+ }
13004
+ .fe-apppage__back {
13005
+ flex: 0 0 auto;
13006
+ text-decoration: none;
13007
+ }
13008
+ .fe-apppage__body {
13009
+ width: 100%;
13010
+ max-width: 960px;
13011
+ margin: 0 auto;
13012
+ padding: 20px 16px;
13013
+ display: flex;
13014
+ flex-direction: column;
13015
+ gap: 14px;
13016
+ min-width: 0;
13017
+ }
13018
+
13019
+ /* ⚠⚠ v3 §3.2 — the step that shows a DOCUMENT is shaped by the window,
13020
+ * not by the text column.
13021
+ *
13022
+ * A form step is a readable 960px column that the page scrolls; a step
13023
+ * carrying a `pdf-fields` node is not a form, it is a document. Measured
13024
+ * before this rule, on the request wizard's "place the fields" step at
13025
+ * 1440x900: the page box was 916x1296 inside a 916x618 viewport, i.e. the
13026
+ * document ran off the bottom AND sideways — which is the complaint that
13027
+ * started this round, reproduced exactly.
13028
+ *
13029
+ * `min-height: 0` on the body is not decoration: without it a flex child
13030
+ * refuses to shrink below its content, and the "remainder" the document is
13031
+ * fitted into grows to the document's own height, which is the bug again
13032
+ * wearing a fix's clothes. */
13033
+ .fe.fe-apppage.is-doc {
13034
+ height: 100vh;
13035
+ min-height: 0;
13036
+ overflow: hidden;
13037
+ padding-bottom: 0;
13038
+ }
13039
+ .fe-apppage.is-doc .fe-apppage__body {
13040
+ flex: 1 1 auto;
13041
+ min-height: 0;
13042
+ max-width: none;
13043
+ overflow: hidden;
13044
+ }
13045
+ /* ⚠⚠ ...and one step further down, or none of the above does anything.
13046
+ * `.fe-surface` sits between the body and the document node and grows to its
13047
+ * content by default, so `.fe-spdf--page { height: 100% }` resolved against
13048
+ * an auto-height parent and came back as "as tall as the document" —
13049
+ * measured at 1440x900: a 775px body holding a 1466px document, i.e. the
13050
+ * page running off the bottom exactly as before, with every rule above this
13051
+ * one in place and looking correct. */
13052
+ .fe-apppage.is-doc .fe-surface {
13053
+ flex: 1 1 auto;
13054
+ min-height: 0;
13055
+ }
13056
+ .fe-apppage__state {
13057
+ display: flex;
13058
+ flex-direction: column;
13059
+ gap: 8px;
13060
+ padding: 12px 0;
13061
+ }
13062
+ .fe-apppage__state-title {
13063
+ margin: 0;
13064
+ font-size: 16px;
13065
+ font-weight: 600;
13066
+ }
13067
+ .fe-apppage__state-actions {
13068
+ display: flex;
13069
+ gap: 8px;
13070
+ flex-wrap: wrap;
13071
+ padding-top: 4px;
13072
+ }
13073
+ .fe-apppage__state-actions .fe-btn {
13074
+ text-decoration: none;
13075
+ }
13076
+ .fe-apppage__foot {
13077
+ position: sticky;
13078
+ bottom: 0;
13079
+ width: 100%;
13080
+ max-width: 960px;
13081
+ margin: 0 auto;
13082
+ padding: 12px 16px;
13083
+ display: flex;
13084
+ justify-content: flex-end;
13085
+ gap: 8px;
13086
+ flex-wrap: wrap;
13087
+ border-top: 1px solid var(--fe-border);
13088
+ background: var(--fe-bg);
13089
+ }
13090
+
13091
+ /* The SAME screen inside a HOST's page (`PluginPageView frame="embedded"`) —
13092
+ * the admin panel's Apps section, an embed that already has a header.
13093
+ *
13094
+ * Exactly three things the tab frame provides have to go, and they are the
13095
+ * three the host has already decided: the viewport height (the panel's page
13096
+ * is as tall as its content), the 960px reading column (the host fixed the
13097
+ * width, and an app's home screen is a TABLE, which a text column cramps) and
13098
+ * the tail padding a scrolling tab needs.
13099
+ *
13100
+ * ⚠ The footer stops being sticky too. In a tab it hovers over the document
13101
+ * it belongs to; inside a card `bottom: 0` would pin it to the WINDOW's edge,
13102
+ * a screenful away from the form it submits. */
13103
+ .fe.fe-apppage--embedded {
13104
+ min-height: 0;
13105
+ padding: 0;
13106
+ }
13107
+ .fe-apppage--embedded .fe-apppage__body {
13108
+ max-width: none;
13109
+ padding: var(--fe-gap);
13110
+ }
13111
+ .fe-apppage--embedded .fe-apppage__foot {
13112
+ position: static;
13113
+ max-width: none;
13114
+ padding: var(--fe-gap);
13115
+ background: transparent;
13116
+ }
13117
+
13118
+ /* A home page's section menu (`SurfaceSections`, `surface.sections`). The
13119
+ * product's own underlined tab strip — the look of the Connections and
13120
+ * Plugins screens — and ⚠ one line on a phone, scrolled sideways: a strip
13121
+ * that wrapped moved the table under it every time a count changed. */
13122
+ .fe-sections {
13123
+ display: flex;
13124
+ gap: 2px;
13125
+ border-bottom: 1px solid var(--fe-border);
13126
+ overflow-x: auto;
13127
+ overflow-y: hidden;
13128
+ scrollbar-width: thin;
13129
+ flex: 0 0 auto;
13130
+ min-width: 0;
13131
+ }
13132
+ .fe-sections__tab {
13133
+ font: inherit;
13134
+ font-size: 13.5px;
13135
+ border: 0;
13136
+ background: none;
13137
+ color: var(--fe-text-muted);
13138
+ padding: 8px 12px;
13139
+ cursor: pointer;
13140
+ border-bottom: 2px solid transparent;
13141
+ margin-bottom: -1px;
13142
+ white-space: nowrap;
13143
+ display: inline-flex;
13144
+ align-items: center;
13145
+ gap: 6px;
13146
+ }
13147
+ .fe-sections__tab:hover:not(:disabled) {
13148
+ color: var(--fe-text);
13149
+ }
13150
+ .fe-sections__tab.is-active {
13151
+ color: var(--fe-primary);
13152
+ border-bottom-color: var(--fe-primary);
13153
+ font-weight: 600;
13154
+ }
13155
+ .fe-sections__tab:focus-visible {
13156
+ outline: 2px solid var(--fe-primary);
13157
+ outline-offset: -2px;
13158
+ }
13159
+ .fe-sections__count {
13160
+ min-width: 18px;
13161
+ padding: 0 6px;
13162
+ border-radius: 999px;
13163
+ background: var(--fe-bg-hover);
13164
+ color: var(--fe-text-muted);
13165
+ font-size: 11px;
13166
+ font-weight: 600;
13167
+ line-height: 18px;
13168
+ text-align: center;
13169
+ font-variant-numeric: tabular-nums;
13170
+ }
13171
+ .fe-sections__tab.is-active .fe-sections__count {
13172
+ background: color-mix(in srgb, var(--fe-primary) 14%, transparent);
13173
+ color: var(--fe-primary);
13174
+ }
13175
+
13176
+ /* The lock badge. ⚠ Drawn OUTSIDE `.fe-list__name`: the shot scripts read
13177
+ * that element's textContent as the filename, and a row whose name reads
13178
+ * "nda.pdf 🔒" breaks them silently (lesson #29). */
13179
+ .fe-applock {
13180
+ flex: 0 0 auto;
13181
+ display: inline-flex;
13182
+ align-items: center;
13183
+ gap: 4px;
13184
+ padding: 0 6px;
13185
+ height: 18px;
13186
+ border-radius: 9px;
13187
+ border: 1px solid var(--fe-border);
13188
+ background: var(--fe-bg-elev);
13189
+ color: var(--fe-text-muted);
13190
+ font-size: 11px;
13191
+ line-height: 1;
13192
+ white-space: nowrap;
13193
+ cursor: help;
13194
+ }
13195
+ .fe-applock__glyph {
13196
+ font-size: 11px;
13197
+ }
13198
+ .fe-applock--inline {
13199
+ vertical-align: middle;
13200
+ }
13201
+
13202
+ /* issue #34 — the badge on a symlink the server will not follow. Same chip as
13203
+ * the lock badge above, deliberately: a row that is not ordinary is marked one
13204
+ * way in this product, and a second vocabulary is how two marks end up meaning
13205
+ * the same thing on neighbouring rows.
13206
+ * ⚠ Drawn OUTSIDE `.fe-list__name` for the same reason (lesson #29). */
13207
+ .fe-symlink {
13208
+ flex: 0 0 auto;
13209
+ display: inline-flex;
13210
+ align-items: center;
13211
+ gap: 4px;
13212
+ padding: 0 6px;
13213
+ height: 18px;
13214
+ border-radius: 9px;
13215
+ border: 1px solid var(--fe-border);
13216
+ background: var(--fe-bg-elev);
13217
+ color: var(--fe-text-muted);
13218
+ font-size: 11px;
13219
+ line-height: 1;
13220
+ white-space: nowrap;
13221
+ cursor: help;
13222
+ }
13223
+ .fe-symlink__glyph {
13224
+ font-size: 11px;
13225
+ }
13226
+ /* A broken link is the one state nothing can be configured back into working,
13227
+ * so it is the one state that gets the amber. The other three are boundaries
13228
+ * the operator chose, not faults. */
13229
+ .fe-symlink--broken {
13230
+ border-color: var(--fe-warning);
13231
+ color: var(--fe-warning);
13232
+ }
13233
+
13234
+ /* One notice box, two names: an app's hold on a file and a link that cannot be
13235
+ * opened are the same shape of fact — the thing above the panel's facts that
13236
+ * explains why the facts alone mislead. */
13237
+ .fe-inspector__lock,
13238
+ .fe-inspector__linknote {
13239
+ display: flex;
13240
+ align-items: flex-start;
13241
+ gap: 8px;
13242
+ padding: 8px 10px;
13243
+ border: 1px solid var(--fe-border);
13244
+ border-radius: var(--fe-radius);
13245
+ background: var(--fe-bg-elev);
13246
+ color: var(--fe-text);
13247
+ font-size: var(--fe-text-sm);
13248
+ overflow-wrap: anywhere;
13249
+ }
13250
+
13251
+ /* The face picker a typed signature and a pdf text field share. */
13252
+ .fe-signfonts {
13253
+ display: flex;
13254
+ flex-wrap: wrap;
13255
+ gap: 6px;
13256
+ }
13257
+ .fe-signfonts__btn {
13258
+ padding: 4px 10px;
13259
+ border: 1px solid var(--fe-border);
13260
+ border-radius: var(--fe-radius);
13261
+ background: var(--fe-bg-elev);
13262
+ color: var(--fe-text);
13263
+ font-size: 15px;
13264
+ line-height: 1.4;
13265
+ cursor: pointer;
13266
+ }
13267
+ .fe-signfonts__btn:hover:not(:disabled) {
13268
+ border-color: var(--fe-primary);
13269
+ }
13270
+ .fe-signfonts__btn.is-on {
13271
+ border-color: var(--fe-primary);
13272
+ box-shadow: inset 0 0 0 1px var(--fe-primary);
13273
+ }
13274
+ .fe-signfonts__btn:disabled {
13275
+ opacity: 0.5;
13276
+ cursor: default;
13277
+ }
13278
+ /* === /App plugins v2 === */
13279
+
13280
+ /* ── StorageTags: a storage's driver, "Read-only", "Disabled" ───────────
13281
+ ONE vocabulary for every list of storages (components/StorageTags.vue,
13282
+ QA #34: "RO" on one admin list, "SALT OKUNUR" on the other). Global, like
13283
+ every core rule — scoped styles do not survive the web-component build. */
13284
+ .fe-stags {
13285
+ display: inline-flex;
13286
+ flex-wrap: wrap;
13287
+ align-items: center;
13288
+ gap: 4px;
13289
+ min-width: 0;
13290
+ }
13291
+ .fe-stag {
13292
+ font-size: 11px;
13293
+ line-height: 16px;
13294
+ border: 1px solid var(--fe-border-strong, currentColor);
13295
+ border-radius: 999px;
13296
+ padding: 0 8px;
13297
+ color: var(--fe-text-muted, inherit);
13298
+ white-space: nowrap;
13299
+ }
13300
+ .fe-stag--warn {
13301
+ color: var(--fe-warning, var(--fe-danger));
13302
+ border-color: var(--fe-warning, var(--fe-danger));
13303
+ }
13304
+
13305
+ /* ── PublicLinkPreview: the public page inside the admin's Corporate identity
13306
+ page. The page is a whole document (`min-height: 100vh`); in the admin card
13307
+ it is a panel. Only the height is undone — padding, width, colours and
13308
+ alignment stay the page's own, so what the operator sees is what a visitor
13309
+ gets. It is looked at, not used. */
13310
+ .fe-ppreview {
13311
+ pointer-events: none;
13312
+ user-select: none;
13313
+ border-radius: var(--fe-radius-lg, 12px);
13314
+ overflow: hidden;
13315
+ }
13316
+ .fe-ppreview > .fe.fe-ppage {
13317
+ min-height: 0;
13318
+ }