From 31b1872be1e8d010d2cbe08e92c589505e86d293 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 22 Jul 2024 15:26:27 -0400
Subject: adding type fixes to avoid 'any's

---
 src/client/views/collections/CollectionCarousel3DView.tsx | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

(limited to 'src/client/views/collections/CollectionCarousel3DView.tsx')

diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index 38f681e87..c799eb3c8 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -1,5 +1,3 @@
-/* eslint-disable jsx-a11y/no-static-element-interactions */
-/* eslint-disable jsx-a11y/click-events-have-key-events */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
@@ -15,7 +13,7 @@ import { StyleProp } from '../StyleProp';
 import { DocumentView } from '../nodes/DocumentView';
 import { FocusViewOptions } from '../nodes/FocusViewOptions';
 import './CollectionCarousel3DView.scss';
-import { CollectionSubView } from './CollectionSubView';
+import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
 
 // eslint-disable-next-line @typescript-eslint/no-var-requires
 const { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } = require('../global/globalCssVariables.module.scss');
@@ -25,7 +23,7 @@ export class CollectionCarousel3DView extends CollectionSubView() {
     @computed get scrollSpeed() {
         return this.layoutDoc._autoScrollSpeed ? NumCast(this.layoutDoc._autoScrollSpeed) : 1000; // default scroll speed
     }
-    constructor(props: any) {
+    constructor(props: SubCollectionViewProps) {
         super(props);
         makeObservable(this);
     }
@@ -181,8 +179,8 @@ export class CollectionCarousel3DView extends CollectionSubView() {
                 className="collectionCarousel3DView-outer"
                 ref={this.createDashEventsTarget}
                 style={{
-                    background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor),
-                    color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color),
+                    background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string,
+                    color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string,
                 }}>
                 <div className="carousel-wrapper" style={{ transform: `translateX(${this.translateX}px)` }}>
                     {this.content}
-- 
cgit v1.2.3-70-g09d2


From 4f2ee4a8642a93fb399b979750078374b317af32 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 17 Sep 2024 12:29:48 -0400
Subject: fixed carouselfView to fade.  cleaned up code a bit

---
 .../views/collections/CollectionCarousel3DView.tsx |   1 +
 .../views/collections/CollectionCarouselView.scss  |  15 ++
 .../views/collections/CollectionCarouselView.tsx   | 223 ++++++++++++---------
 src/client/views/collections/TabDocView.tsx        |   4 +-
 4 files changed, 151 insertions(+), 92 deletions(-)

(limited to 'src/client/views/collections/CollectionCarousel3DView.tsx')

diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index c799eb3c8..54cc02825 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -78,6 +78,7 @@ export class CollectionCarousel3DView extends CollectionSubView() {
                 NativeWidth={returnZero}
                 NativeHeight={returnZero}
                 fitWidth={undefined}
+                containerViewPath={this.childContainerViewPath}
                 onDoubleClickScript={this.onChildDoubleClick}
                 renderDepth={this._props.renderDepth + 1}
                 LayoutTemplate={this._props.childLayoutTemplate}
diff --git a/src/client/views/collections/CollectionCarouselView.scss b/src/client/views/collections/CollectionCarouselView.scss
index f115bb40a..01b20d6d3 100644
--- a/src/client/views/collections/CollectionCarouselView.scss
+++ b/src/client/views/collections/CollectionCarouselView.scss
@@ -12,6 +12,21 @@
         user-select: none;
     }
 }
+.collectionCarouselView-addFlashcards {
+    justify-content: center;
+    align-items: center;
+    height: 100%;
+    z-index: -1;
+    pointer-events: none;
+}
+.collectionCarouselView-recentlyMissed {
+    color: red;
+    z-index: 999;
+    position: relative;
+    left: 10px;
+    top: 10px;
+    pointer-events: none;
+}
 .carouselView-back,
 .carouselView-fwd,
 .carouselView-star,
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 4bec2d963..5d71177c3 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -1,12 +1,11 @@
 /* eslint-disable react/jsx-props-no-spreading */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { computed, makeObservable } from 'mobx';
+import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { StopEvent, returnFalse, returnOne, returnTrue, returnZero } from '../../../ClientUtils';
-import { emptyFunction } from '../../../Utils';
-import { Doc, Opt } from '../../../fields/Doc';
-import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { StopEvent, returnOne, returnZero } from '../../../ClientUtils';
+import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DragManager } from '../../util/DragManager';
 import { ContextMenu } from '../ContextMenu';
@@ -32,13 +31,34 @@ export class CollectionCarouselView extends CollectionSubView() {
     get practiceField() { return this.fieldKey + "_practice"; } // prettier-ignore
     get starField()     { return this.fieldKey + "_star"; } // prettier-ignore
 
+    _fadeTimer: NodeJS.Timeout | undefined;
+    _resetter: IReactionDisposer | undefined;
+
     constructor(props: SubCollectionViewProps) {
         super(props);
         makeObservable(this);
     }
 
+    @observable _last_index = this.carouselIndex;
+    @observable _last_opacity = 1;
+
+    componentDidMount() {
+        this._resetter = reaction(
+            // automatically reset practice fields when all cards have been marked as correct
+            () => this.carouselItems.length,
+            itemsCount => {
+                if (this.layoutDoc.filterOp === cardMode.PRACTICE && !itemsCount) {
+                    this.layoutDoc.filterOp = undefined; // if all of the cards are correct, show all cards and exit practice mode
+                    this.carouselItems.forEach(item => { // reset all the practice values
+                        item[this.practiceField] = undefined;
+                    });
+                }
+            } // prettier-ignore
+        );
+    }
     componentWillUnmount() {
         this._dropDisposer?.();
+        this._resetter?.();
     }
 
     protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
@@ -48,43 +68,24 @@ export class CollectionCarouselView extends CollectionSubView() {
         }
     };
 
+    @computed get marginX()       { return NumCast(this.layoutDoc.caption_xMargin, 50); } // prettier-ignore
+    @computed get carouselIndex() { return NumCast(this.layoutDoc._carousel_index) % this.carouselItems.length; } // prettier-ignore
     @computed get carouselItems() {
-        return this.childLayoutPairs.filter(pair => pair.layout.type !== DocumentType.LINK);
-    }
-    @computed get marginX() {
-        return NumCast(this.layoutDoc.caption_xMargin, 50);
+        return DocListCast(this.childDocList)
+            .filter(doc => doc.type !== DocumentType.LINK)
+            .filter(doc => {
+                switch (StrCast(this.layoutDoc.filterOp)) {
+                    case cardMode.STAR:      return !!doc[this.starField]; // show only cards that are starred
+                    case cardMode.PRACTICE:  return doc[this.practiceField] !== practiceVal.CORRECT;// show only cards that aren't marked as correct
+                    default:                 return true;
+                } // prettier-ignore
+            });
     }
 
-    move = (dir: number) => {
-        const moveToCardWithField = (match: (doc: Doc) => boolean): boolean => {
-            let startInd = (NumCast(this.layoutDoc._carousel_index) + dir) % this.carouselItems.length;
-            while (!match(this.carouselItems?.[startInd].layout) && (startInd + dir + this.carouselItems.length) % this.carouselItems.length !== this.layoutDoc._carousel_index) {
-                startInd = (startInd + dir + this.carouselItems.length) % this.carouselItems.length;
-            }
-            if (match(this.carouselItems?.[startInd].layout)) {
-                this.layoutDoc._carousel_index = startInd;
-                return true;
-            }
-            return match(this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)].layout);
-        };
-        switch (StrCast(this.layoutDoc.filterOp)) {
-            case cardMode.STAR:  // go to a flashcard that is starred, skip the ones that aren't
-                if (!moveToCardWithField((doc: Doc) => !!doc[this.starField])) {
-                    this.layoutDoc.filterOp = undefined;  // if there aren't any starred, show all cards
-                }
-                break;
-            case cardMode.PRACTICE: // go to a new index that is missed, skip the ones that are correct
-                if (!moveToCardWithField((doc: Doc) => doc[this.practiceField] !== practiceVal.CORRECT)) {
-                    this.layoutDoc.filterOp = undefined;  // if all of the cards are correct, show all cards and exit practice mode
-
-                    this.carouselItems.forEach(item => { // reset all the practice values
-                        item.layout[this.practiceField] = undefined;
-                    });
-                }
-                break;
-            default: moveToCardWithField(returnTrue);
-        } // prettier-ignore
-    };
+    move = action((dir: number) => {
+        this._last_index = this.carouselIndex;
+        this.layoutDoc._carousel_index = (this.carouselIndex + dir + this.carouselItems.length) % this.carouselItems.length;
+    });
 
     /**
      * Goes to the next Doc in the stack subject to the currently selected filter option.
@@ -107,8 +108,8 @@ export class CollectionCarouselView extends CollectionSubView() {
      */
     star = (e: React.MouseEvent) => {
         e.stopPropagation();
-        const curDoc = this.carouselItems[NumCast(this.layoutDoc._carousel_index)];
-        curDoc.layout[this.starField] = curDoc.layout[this.starField] ? undefined : true;
+        const curDoc = this.carouselItems[this.carouselIndex];
+        curDoc && (curDoc[this.starField] = curDoc[this.starField] ? undefined : true);
     };
 
     /*
@@ -116,8 +117,8 @@ export class CollectionCarouselView extends CollectionSubView() {
      */
     setPracticeVal = (e: React.MouseEvent, val: string) => {
         e.stopPropagation();
-        const curDoc = this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)];
-        curDoc.layout[this.practiceField] = val;
+        const curDoc = this.carouselItems[this.carouselIndex];
+        curDoc && (curDoc[this.practiceField] = val);
         this.advance(e);
     };
 
@@ -132,7 +133,6 @@ export class CollectionCarouselView extends CollectionSubView() {
     captionWidth = () => this._props.PanelWidth() - 2 * this.marginX;
     specificMenu = (): void => {
         const cm = ContextMenu.Instance;
-
         const revealOptions = cm.findByDescription('Filter Flashcards');
         const revealItems = revealOptions?.subitems ?? [];
         revealItems.push({description: 'All',           event: () => {this.layoutDoc.filterOp = undefined;},         icon: 'layer-group',}); // prettier-ignore
@@ -141,32 +141,78 @@ export class CollectionCarouselView extends CollectionSubView() {
         revealItems.push({description: 'Quiz Cards',    event: () => {this.layoutDoc.filterOp = cardMode.QUIZ;},     icon: 'pencil',}); // prettier-ignore
         !revealOptions && cm.addItem({ description: 'Filter Flashcards', addDivider: false, noexpand: true, subitems: revealItems, icon: 'layer-group' });
     };
+
+    isChildContentActive = () =>
+        this._props.isContentActive?.() === false
+            ? false
+            : this._props.isDocumentActive?.() && (this._props.childDocumentsActive?.() || BoolCast(this.Document.childDocumentsActive))
+              ? true
+              : this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false
+                ? false
+                : undefined;
+
+    renderDoc = (doc: Doc, showCaptions: boolean, overlayFunc?: (r: DocumentView | null) => void) => {
+        return (
+            <DocumentView
+                {...this._props}
+                ref={overlayFunc}
+                Document={doc}
+                NativeWidth={returnZero}
+                NativeHeight={returnZero}
+                fitWidth={undefined}
+                containerViewPath={this.childContainerViewPath}
+                setContentViewBox={undefined}
+                onDoubleClickScript={this.onContentDoubleClick}
+                onClickScript={this.onContentClick}
+                isDocumentActive={this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this._props.isContentActive}
+                isContentActive={this.isChildContentActive}
+                hideCaptions={showCaptions}
+                renderDepth={this._props.renderDepth + 1}
+                LayoutTemplate={this._props.childLayoutTemplate}
+                LayoutTemplateString={this._props.childLayoutString}
+                TemplateDataDocument={DocCast(Doc.Layout(doc).resolvedDataDoc)}
+                PanelHeight={this.panelHeight}
+            />
+        );
+    };
+    /**
+     * Display an overlay of the previous card that crossfades to the next card
+     */
+    @computed get overlay() {
+        const fadeTime = 500;
+        const lastDoc = this.carouselItems?.[this._last_index];
+        return !lastDoc || this.carouselIndex === this._last_index ? null : (
+            <div className="collectionCarouselView-image" style={{ opacity: this._last_opacity, position: 'absolute', top: 0, left: 0, transition: `opacity ${fadeTime}ms` }}>
+                {this.renderDoc(
+                    lastDoc,
+                    false, // hide captions if the carousel is configured to show the captions
+                    action((r: DocumentView | null) => {
+                        if (r) {
+                            this._fadeTimer && clearTimeout(this._fadeTimer);
+                            this._last_opacity = 0;
+                            this._fadeTimer = setTimeout(
+                                action(() => {
+                                    this._last_index = -1;
+                                    this._last_opacity = 1;
+                                }),
+                                fadeTime
+                            );
+                        }
+                    })
+                )}
+            </div>
+        );
+    }
     @computed get content() {
-        const index = NumCast(this.layoutDoc._carousel_index);
+        const index = this.carouselIndex;
         const curDoc = this.carouselItems?.[index];
         const captionProps = { ...this._props, NativeScaling: returnOne, PanelWidth: this.captionWidth, fieldKey: 'caption', setHeight: undefined, setContentView: undefined };
         const carouselShowsCaptions = StrCast(this.layoutDoc._layout_showCaption);
-        return !(curDoc?.layout instanceof Doc) ? null : (
+        return !curDoc ? null : (
             <>
                 <div className="collectionCarouselView-image" key="image">
-                    <DocumentView
-                        {...this._props}
-                        NativeWidth={returnZero}
-                        NativeHeight={returnZero}
-                        fitWidth={undefined}
-                        setContentViewBox={undefined}
-                        onDoubleClickScript={this.onContentDoubleClick}
-                        onClickScript={this.onContentClick}
-                        isDocumentActive={this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this._props.isContentActive}
-                        isContentActive={(this._props.childContentsActive ?? this._props.isContentActive() === false) ? returnFalse : emptyFunction}
-                        hideCaptions={!!carouselShowsCaptions} // hide captions if the carousel is configured to show the captions
-                        renderDepth={this._props.renderDepth + 1}
-                        LayoutTemplate={this._props.childLayoutTemplate}
-                        LayoutTemplateString={this._props.childLayoutString}
-                        Document={curDoc.layout}
-                        TemplateDataDocument={DocCast(curDoc.layout.resolvedDataDoc)}
-                        PanelHeight={this.panelHeight}
-                    />
+                    {this.renderDoc(curDoc, !!carouselShowsCaptions)}
+                    {this.overlay}
                 </div>
                 {!carouselShowsCaptions ? null : (
                     <div
@@ -179,14 +225,14 @@ export class CollectionCarouselView extends CollectionSubView() {
                             marginLeft: this.marginX,
                             width: `calc(100% - ${this.marginX * 2}px)`,
                         }}>
-                        <FormattedTextBox key={index} {...captionProps} fieldKey={carouselShowsCaptions} styleProvider={this.captionStyleProvider} Document={curDoc.layout} TemplateDataDocument={undefined} />
+                        <FormattedTextBox key={index} xPadding={10} yPadding={10} {...captionProps} fieldKey={carouselShowsCaptions} styleProvider={this.captionStyleProvider} Document={curDoc} TemplateDataDocument={undefined} />
                     </div>
                 )}
             </>
         );
     }
     @computed get buttons() {
-        if (!this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)]) return null;
+        if (!this.carouselItems?.[this.carouselIndex]) return null;
         return (
             <>
                 <div key="back" className="carouselView-back" onClick={this.goback}>
@@ -196,7 +242,7 @@ export class CollectionCarouselView extends CollectionSubView() {
                     <FontAwesomeIcon icon="chevron-right" size="2x" />
                 </div>
                 <div key="star" className="carouselView-star" onClick={this.star}>
-                    <FontAwesomeIcon icon="star" color={this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)].layout[this.starField] ? 'yellow' : 'gray'} size="1x" />
+                    <FontAwesomeIcon icon="star" color={this.carouselItems?.[this.carouselIndex][this.starField] ? 'yellow' : 'gray'} size="1x" />
                 </div>
                 <div key="remove" className="carouselView-remove" onClick={e => this.setPracticeVal(e, practiceVal.MISSED)} style={{ visibility: this.layoutDoc.filterOp === cardMode.PRACTICE ? 'visible' : 'hidden' }}>
                     <FontAwesomeIcon icon="xmark" color="red" size="1x" />
@@ -208,6 +254,24 @@ export class CollectionCarouselView extends CollectionSubView() {
         );
     }
 
+    /**
+     * Prompts user to add more flashcaards if  they are in practice mode but there are no flashcards
+     */
+    renderAddFlashcards = () => <p
+            className="collectionCarouselView-addFlashcards"
+            style={{display: !this.carouselItems?.[this.carouselIndex] && this.layoutDoc.filterOp === cardMode.PRACTICE ? 'flex' : 'none'}}>
+            Add flashcards!
+        </p> // prettier-ignore
+
+    /**
+     *  Displays message that a flashcard was recently missed if it had previously been marked as wrong.
+     *  */
+    renderRecentlyMissed = () => <p
+            className="collectionCarouselView-recentlyMissed"
+            style={{display: this.carouselItems?.[this.carouselIndex]?.[this.practiceField] === practiceVal.MISSED ? 'block' : 'none'}}>
+            Recently missed!
+        </p> // prettier-ignore
+
     render() {
         return (
             <div
@@ -219,29 +283,8 @@ export class CollectionCarouselView extends CollectionSubView() {
                     color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string,
                 }}>
                 {this.content}
-                {/* Displays a message to the user to add more flashcards if they are in practice mode and no flashcards are there. */}
-                <p
-                    style={{
-                        display: !this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)] && this.layoutDoc.filterOp === cardMode.PRACTICE ? 'flex' : 'none',
-                        justifyContent: 'center',
-                        alignItems: 'center',
-                        height: '100%',
-                        zIndex: '-1',
-                    }}>
-                    Add flashcards!
-                </p>
-                {/* Displays a message to the user that a flashcard was recently missed if they had previously gotten it wrong. */}
-                <p
-                    style={{
-                        color: 'red',
-                        zIndex: '999',
-                        position: 'relative',
-                        left: '10px',
-                        top: '10px',
-                        display: this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)] ? (this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)].layout[this.practiceField] === practiceVal.MISSED ? 'block' : 'none') : 'none',
-                    }}>
-                    Recently missed!
-                </p>
+                {this.renderAddFlashcards()}
+                {this.renderRecentlyMissed()}
                 {this.Document._chromeHidden ? null : this.buttons}
             </div>
         );
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index def1ea731..f56ea9d76 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -506,13 +506,13 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
     }
 
     // eslint-disable-next-line @typescript-eslint/no-explicit-any
-    onActiveContentItemChanged = (contentItem: any) => {
+    onActiveContentItemChanged = action((contentItem: any) => {
         if (!contentItem || (this.stack === contentItem.parent && ((contentItem?.tab === this.tab && !this._isActive) || (contentItem?.tab !== this.tab && this._isActive)))) {
             this._activated = this._isActive = !contentItem || contentItem?.tab === this.tab;
             if (!this._view && this.tab?.contentItem?.config?.props?.panelName !== TabDocView.DontSelectOnActivate) setTimeout(() => DocumentView.SelectView(this._view, false));
             !this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one.
         }
-    };
+    });
 
     // adds a tab to the layout based on the locaiton parameter which can be:
     //  close[:{left,right,top,bottom}]  - e.g., "close" will close the tab, "close:left" will close the left tab,
-- 
cgit v1.2.3-70-g09d2


From 0da361d49f063d43a0ad87d13665595f9e383e13 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 24 Sep 2024 17:44:49 -0400
Subject: fixed screen to local for carousel views (especially when in
 lightbox)

---
 .../views/collections/CollectionCarousel3DView.tsx | 35 +++++++++++++++++-----
 .../views/collections/CollectionCarouselView.tsx   |  3 ++
 2 files changed, 30 insertions(+), 8 deletions(-)

(limited to 'src/client/views/collections/CollectionCarousel3DView.tsx')

diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index 54cc02825..139aebb02 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -14,6 +14,7 @@ import { DocumentView } from '../nodes/DocumentView';
 import { FocusViewOptions } from '../nodes/FocusViewOptions';
 import './CollectionCarousel3DView.scss';
 import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
+import { Transform } from '../../util/Transform';
 
 // eslint-disable-next-line @typescript-eslint/no-var-requires
 const { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } = require('../global/globalCssVariables.module.scss');
@@ -46,15 +47,32 @@ export class CollectionCarousel3DView extends CollectionSubView() {
     }
 
     centerScale = Number(CAROUSEL3D_CENTER_SCALE);
+    sideScale = Number(CAROUSEL3D_SIDE_SCALE);
     panelWidth = () => this._props.PanelWidth() / 3;
-    panelHeight = () => this._props.PanelHeight() * Number(CAROUSEL3D_SIDE_SCALE);
+    panelHeight = () => this._props.PanelHeight() * this.sideScale;
     onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
     isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive();
     isChildContentActive = () => !!this.isContentActive();
-    childScreenToLocal = () =>
-        this._props // document's left is the panel shifted by the doc's index * panelWidth/#docs.  But it scales by centerScale around its center, so it's left moves left by the distance of the left from the center (panelwidth/2) * the scale delta (centerScale-1)
-            .ScreenToLocalTransform() // the top behaves the same way ecept it's shifted by the 'top' amount specified for the panel in css and then by the scale factor.
-            .translate(-this.panelWidth() + ((this.centerScale - 1) * this.panelWidth()) / 2, -((Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight()) + ((this.centerScale - 1) * this.panelHeight()) / 2)
+    childScreenLeftToLocal = () =>
+        this._props
+            .ScreenToLocalTransform()
+            .scale(this._props.NativeDimScaling?.() || 1)
+            .translate(-(this.panelWidth() - this.panelWidth() * this.sideScale) / 2, -(this.panelHeight() - this.panelHeight() * this.sideScale) / 2 - (Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight())
+            .scale(1 / this.sideScale);
+    childScreenRightToLocal = () =>
+        this._props
+            .ScreenToLocalTransform()
+            .scale(this._props.NativeDimScaling?.() || 1)
+            .translate(-2 * this.panelWidth() - (this.panelWidth() - this.panelWidth() * this.sideScale) / 2, -(this.panelHeight() - this.panelHeight() * this.sideScale) / 2 - (Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight())
+            .scale(1 / this.sideScale);
+    childCenterScreenToLocal = () =>
+        this._props
+            .ScreenToLocalTransform()
+            .scale(this._props.NativeDimScaling?.() || 1)
+            .translate(
+                -this.panelWidth() + ((this.centerScale - 1) * this.panelWidth()) / 2, // Focused Doc is shifted right by 1/3 panel width then left by increased size percent of center * 1/2 * panel width / 3
+                -((Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight()) + ((this.centerScale - 1) * this.panelHeight()) / 2
+            ) // top is top margin % of panelHeight - increased size percent of center * panelHeight / 2
             .scale(1 / this.centerScale);
 
     focus = (anchor: Doc, options: FocusViewOptions): Opt<number> => {
@@ -66,9 +84,10 @@ export class CollectionCarousel3DView extends CollectionSubView() {
         index !== -1 && (this.layoutDoc._carousel_index = index);
         return undefined;
     };
+
     @computed get content() {
         const currentIndex = NumCast(this.layoutDoc._carousel_index);
-        const displayDoc = (childPair: { layout: Doc; data: Doc }) => (
+        const displayDoc = (childPair: { layout: Doc; data: Doc }, dxf: () => Transform) => (
             <DocumentView
                 // eslint-disable-next-line react/jsx-props-no-spreading
                 {...this._props}
@@ -84,7 +103,7 @@ export class CollectionCarousel3DView extends CollectionSubView() {
                 LayoutTemplate={this._props.childLayoutTemplate}
                 LayoutTemplateString={this._props.childLayoutString}
                 focus={this.focus}
-                ScreenToLocalTransform={this.childScreenToLocal}
+                ScreenToLocalTransform={dxf}
                 isContentActive={this.isChildContentActive}
                 isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
                 PanelWidth={this.panelWidth}
@@ -94,7 +113,7 @@ export class CollectionCarousel3DView extends CollectionSubView() {
 
         return this.carouselItems.map((childPair, index) => (
             <div key={childPair.layout[Id]} className={`collectionCarousel3DView-item${index === currentIndex ? '-active' : ''} ${index}`} style={{ width: this.panelWidth() }}>
-                {displayDoc(childPair)}
+                {displayDoc(childPair, index < currentIndex ? this.childScreenLeftToLocal : index === currentIndex ? this.childCenterScreenToLocal : this.childScreenRightToLocal)}
             </div>
         ));
     }
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 974cd3e36..75886b30d 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -154,6 +154,8 @@ export class CollectionCarouselView extends CollectionSubView() {
                 ? false
                 : undefined;
 
+    childScreenToLocalXf = () => this._props.ScreenToLocalTransform().scale(this._props.NativeDimScaling?.() || 1);
+
     renderDoc = (doc: Doc, showCaptions: boolean, overlayFunc?: (r: DocumentView | null) => void) => {
         return (
             <DocumentView
@@ -165,6 +167,7 @@ export class CollectionCarouselView extends CollectionSubView() {
                 fitWidth={undefined}
                 containerViewPath={this.childContainerViewPath}
                 setContentViewBox={undefined}
+                ScreenToLocalTransform={this.childScreenToLocalXf}
                 onDoubleClickScript={this.onContentDoubleClick}
                 onClickScript={this.onContentClick}
                 isDocumentActive={this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this._props.isContentActive}
-- 
cgit v1.2.3-70-g09d2

