@khanacademy/perseus 86.0.1 → 87.1.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 (30) hide show
  1. package/dist/components/button-group.d.ts +0 -2
  2. package/dist/components/drag-and-drop/answer-tile/answer-tile.d.ts +88 -0
  3. package/dist/components/drag-and-drop/answer-tile/index.d.ts +2 -0
  4. package/dist/components/drag-and-drop/blank/blank-component.d.ts +41 -0
  5. package/dist/components/drag-and-drop/blank/index.d.ts +2 -0
  6. package/dist/components/drag-and-drop/choice-bank.d.ts +10 -3
  7. package/dist/components/drag-and-drop/dnd-action-menu/dnd-action-menu.d.ts +39 -0
  8. package/dist/components/drag-and-drop/dnd-action-menu/index.d.ts +2 -0
  9. package/dist/components/drag-and-drop/dnd-action-menu/merged-ref-opener.d.ts +21 -0
  10. package/dist/components/drag-and-drop/drag-ids.d.ts +23 -0
  11. package/dist/components/drag-and-drop/perseus-dnd-provider.d.ts +34 -0
  12. package/dist/components/drag-and-drop/temp-strings.d.ts +36 -0
  13. package/dist/components/drag-and-drop/tile-placements.d.ts +33 -0
  14. package/dist/components/drag-and-drop/use-tile-move-actions.d.ts +28 -0
  15. package/dist/es/index.css +1 -1
  16. package/dist/es/index.css.map +1 -1
  17. package/dist/es/index.js +21 -16
  18. package/dist/es/index.js.map +1 -1
  19. package/dist/index.css +1 -1
  20. package/dist/index.css.map +1 -1
  21. package/dist/index.js +23 -18
  22. package/dist/index.js.map +1 -1
  23. package/dist/widgets/categorizer/categorizer.d.ts +3 -5
  24. package/dist/widgets/dropdown/dropdown.d.ts +3 -4
  25. package/dist/widgets/graded-group/graded-group.d.ts +18 -37
  26. package/dist/widgets/graded-group-set/graded-group-set.d.ts +3 -6
  27. package/dist/widgets/matcher/matcher.d.ts +3 -4
  28. package/dist/widgets/number-line/number-line.d.ts +6 -4
  29. package/dist/widgets/python-program/python-program.d.ts +3 -4
  30. package/package.json +31 -30
@@ -8,7 +8,6 @@ interface Props {
8
8
  title?: string;
9
9
  }>;
10
10
  onChange: (value?: any) => unknown;
11
- allowEmpty: boolean;
12
11
  /**
13
12
  * Customizes the selected button's styling.
14
13
  */
@@ -19,7 +18,6 @@ interface Props {
19
18
  disabled?: boolean;
20
19
  }
21
20
  interface DefaultProps {
22
- allowEmpty: Props["allowEmpty"];
23
21
  value: Props["value"];
24
22
  }
25
23
  /**
@@ -0,0 +1,88 @@
1
+ import * as React from "react";
2
+ import type { MoveTarget } from "../dnd-action-menu";
3
+ /** What a scored tile shows. Each value names a CSS class in the module. */
4
+ export type TileScoring = "correct" | "incorrect" | "unused";
5
+ export interface AnswerTileProps {
6
+ /** The tile that moves, named as the widget's placements name it. */
7
+ tileId: string;
8
+ /**
9
+ * The blank this tile sits in. Omit for a tile in the choice bank.
10
+ * The tile reports both ids to dnd-kit, so a drop knows what moved
11
+ * and where it came from.
12
+ */
13
+ fromBlankId?: string;
14
+ /**
15
+ * Perseus markdown for the tile face: text, TeX, or an image.
16
+ * Use "" for an empty tile.
17
+ */
18
+ content: string;
19
+ /**
20
+ * Labels the tile's menu button for screen readers, as translated
21
+ * plain text.
22
+ */
23
+ label: string;
24
+ /**
25
+ * The result the tile shows once the question is scored. A placed tile
26
+ * is "correct" or "incorrect"; a tile the learner left in the choice
27
+ * bank is "unused", which dims it. Omit it before scoring.
28
+ *
29
+ * A scored tile has no menu and cannot be dragged, whichever result
30
+ * it shows.
31
+ */
32
+ scoring?: TileScoring;
33
+ /**
34
+ * Blanks the tile can move to, which populate the menu.
35
+ * An empty array is valid: a placed tile in a one-blank exercise
36
+ * can only be cleared, due to no other legitimate moveTargets.
37
+ */
38
+ moveTargets: ReadonlyArray<MoveTarget>;
39
+ /** Called with the target blank's id when a move action is selected. */
40
+ onMove: (targetId: string) => void;
41
+ /**
42
+ * Label of the blank/column the tile currently sits in, which is used
43
+ * as part of the aria-label for the "Clear" button in the Action Menu.
44
+ */
45
+ clearFromLabel?: string;
46
+ /** Callback for removing the tile from its blank. */
47
+ onClear?: () => void;
48
+ /** Remaining uses for a multi-use tile, spoken by the menu opener. */
49
+ remainingUses?: number;
50
+ /**
51
+ * This ref points to the menu's opener button. The parent uses it to
52
+ * move focus after a tile moves.
53
+ */
54
+ menuRef?: React.Ref<HTMLButtonElement>;
55
+ /**
56
+ * Set true for a tile whose menu stays hidden until hover or focus.
57
+ * Fill in the Blank uses this for a tile in an inline blank, to keep
58
+ * the line of text short at rest.
59
+ */
60
+ hidesMenuAtRest?: boolean;
61
+ /**
62
+ * Set true for a placed tile that stretches to fill its blank, with
63
+ * the content at the start. The menu stays visible. The Sorter uses
64
+ * this for placed tiles.
65
+ */
66
+ fillsBlank?: boolean;
67
+ /** Called when the tile's content renders (TeX ready, image update). */
68
+ onContentRender?: () => void;
69
+ /**
70
+ * Set true for a tile placed in a sub/superscript blank. The tile
71
+ * rests as a small value chip until hover or focus.
72
+ */
73
+ compact?: boolean;
74
+ /**
75
+ * Display height in pixels for an image tile's image. The schema
76
+ * types this as a plain number; the editor offers 24-96 in steps
77
+ * of 12.
78
+ */
79
+ imageHeight?: number;
80
+ }
81
+ /**
82
+ * AnswerTile is the card that a learner moves into a blank. It is part of
83
+ * the Drag-and-Drop widget family. The tile shows authored markdown
84
+ * content: text, TeX, or an image. It puts the DndActionMenu at its
85
+ * leading edge. The parent widget sets `scoring` once the question is
86
+ * scored.
87
+ */
88
+ export declare function AnswerTile(props: AnswerTileProps): React.ReactElement;
@@ -0,0 +1,2 @@
1
+ export { AnswerTile } from "./answer-tile";
2
+ export type { AnswerTileProps } from "./answer-tile";
@@ -0,0 +1,41 @@
1
+ import * as React from "react";
2
+ /** How a blank renders: full size, or smaller for exponents/indices. */
3
+ export type BlankDisplayType = "normal" | "superscript" | "subscript";
4
+ export interface BlankComponentProps {
5
+ /**
6
+ * Unique identifier of this blank. It doubles as the dnd-kit droppable
7
+ * id and as the target id the widget receives when a tile moves here,
8
+ * so it must be unique within the surrounding PerseusDndProvider.
9
+ * The blank widget passes its renderer-assigned `widgetId`; widgets
10
+ * that use this component directly supply their own ids.
11
+ */
12
+ blankId: string;
13
+ displayType: BlankDisplayType;
14
+ /** The placed answer tile, when one sits in this blank. */
15
+ children?: React.ReactNode;
16
+ /**
17
+ * Minimum width of the empty slot. The FITB spec sizes an empty blank
18
+ * to the widest answer tile so the slot's size does not reveal the
19
+ * answer; the widget computes that width and passes it here. Defaults
20
+ * to the design's 65px minimum. A placed tile still grows the blank
21
+ * beyond this.
22
+ */
23
+ minWidth?: number;
24
+ /**
25
+ * Keeps the empty-slot width when a tile is placed, so the line
26
+ * does not reflow. Without it a filled blank hugs its tile.
27
+ */
28
+ keepsWidthWhenFilled?: boolean;
29
+ /** Extra class for the slot element, for widget-level layout. */
30
+ className?: string;
31
+ testId?: string;
32
+ }
33
+ /**
34
+ * Blank is the drop slot an answer tile can be placed into. It is part of
35
+ * the Drag-and-Drop widget family. It renders the slot's chrome (dashed
36
+ * border while empty) and registers itself as a dnd-kit drop target.
37
+ *
38
+ * Fill in the Blank reaches it through the `blank` widget, which owns the
39
+ * Perseus widget plumbing; other widgets can render it directly.
40
+ */
41
+ export declare function BlankComponent(props: BlankComponentProps): React.ReactElement;
@@ -0,0 +1,2 @@
1
+ export { BlankComponent } from "./blank-component";
2
+ export type { BlankComponentProps } from "./blank-component";
@@ -1,10 +1,17 @@
1
1
  import * as React from "react";
2
+ /**
3
+ * The bank's droppable id: a tile dropped here returns to the bank.
4
+ * Every widget mounts its own PerseusDndProvider, which scopes drag ids,
5
+ * so one shared value stays unique for two widgets on one page.
6
+ */
7
+ export declare const CHOICE_BANK_DROP_ID = "choice-bank";
2
8
  interface ChoiceBankProps {
3
9
  /**
4
- * The answer tiles to lay out. Each renders as its own `<li>`. Pass an
5
- * empty array for an empty bank (e.g. once every tile has been placed).
10
+ * The answer tiles to lay out. Each child renders as its own `<li>`,
11
+ * so pass the tiles as siblings and not inside a fragment. Pass
12
+ * nothing for an empty bank (e.g. once every tile has been placed).
6
13
  */
7
- children: React.ReactNode;
14
+ children?: React.ReactNode;
8
15
  /** Visible label; also names the tile list for assistive tech. */
9
16
  label: string;
10
17
  }
@@ -0,0 +1,39 @@
1
+ import * as React from "react";
2
+ export type MoveTarget = {
3
+ /** id of the intended blank target */
4
+ id: string;
5
+ /** The blank/column's label, e.g. "Blank 1" or an authored column name. */
6
+ label: string;
7
+ };
8
+ export interface DndActionMenuProps {
9
+ /** The tile's value — labels the button via aria-label. */
10
+ tileLabel: string;
11
+ /**
12
+ * Remaining uses for a multi-use tile. When provided, it is spoken as
13
+ * part of the opener's description ("5 remaining. Actions menu").
14
+ */
15
+ remainingUses?: number;
16
+ /**
17
+ * Available Blanks/columns this tile can move to.
18
+ */
19
+ moveTargets: ReadonlyArray<MoveTarget>;
20
+ /** Called with the target blank's id when a move action is selected. */
21
+ onMove: (targetId: string) => void;
22
+ /**
23
+ * The blank's visible label, e.g. "Blank 1", spoken as "Clear from
24
+ * Blank 1". Pass it with onClear for a tile that sits in a blank;
25
+ * omit both for a tile in the choice bank.
26
+ */
27
+ clearFromLabel?: string;
28
+ /** Called when the Clear action is selected. */
29
+ onClear?: () => void;
30
+ }
31
+ /**
32
+ * DndActionMenu is the menu that appears on each Answer Tile in our upcoming
33
+ * Drag-and-Drop widget family. It lists every blank the tile can move to and,
34
+ * when the tile is placed in a blank, a Clear action.
35
+ *
36
+ * This is currently purely presentational, and does not include the dnd-kit wiring
37
+ * or move announcements, which will be tackled in future tickets.
38
+ */
39
+ export declare const DndActionMenu: React.ForwardRefExoticComponent<DndActionMenuProps & React.RefAttributes<HTMLButtonElement>>;
@@ -0,0 +1,2 @@
1
+ export { DndActionMenu } from "./dnd-action-menu";
2
+ export type { DndActionMenuProps, MoveTarget } from "./dnd-action-menu";
@@ -0,0 +1,21 @@
1
+ import * as React from "react";
2
+ /**
3
+ * Props for the custom Action Menu opener
4
+ */
5
+ type MergedRefOpenerProps = {
6
+ /** The parent-facing ref, forwarded by DndActionMenu for focus return. */
7
+ openerRef: React.ForwardedRef<HTMLButtonElement>;
8
+ disabled: boolean;
9
+ "aria-label": string;
10
+ "aria-describedby": string;
11
+ className: string;
12
+ children: React.ReactNode;
13
+ };
14
+ /**
15
+ * Two things need a ref to the opener button: ActionMenu (to restore focus
16
+ * when the menu closes) and our parent (to move focus after a tile moves).
17
+ * ActionMenu overwrites any ref we set directly, so this wrapper catches
18
+ * ActionMenu's ref and writes the button into both.
19
+ */
20
+ export declare const MergedRefOpener: React.ForwardRefExoticComponent<MergedRefOpenerProps & React.RefAttributes<HTMLButtonElement>>;
21
+ export {};
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The payload a dragged tile carries. dnd-kit returns it at drag end as
3
+ * `operation.source.data`, so widgets read the move directly instead of
4
+ * decoding the drag id.
5
+ */
6
+ export type TileDragData = {
7
+ /** The tile that moves. */
8
+ tileId: string;
9
+ /** The blank the tile starts in. Absent for a tile in the bank. */
10
+ fromBlankId?: string;
11
+ };
12
+ /**
13
+ * Builds the drag id for a tile. dnd-kit needs one unique id per
14
+ * draggable, and one tile can show in the bank and in a blank at the
15
+ * same time, so the id includes the location.
16
+ */
17
+ export declare function tileDragId(data: TileDragData): string;
18
+ /**
19
+ * Reads the payload from a dnd-kit drag. dnd-kit types its data field
20
+ * as an open record, so this is where the shape is checked. Returns
21
+ * null for a drag that another component started.
22
+ */
23
+ export declare function readTileDragData(data: Record<string, any> | undefined): TileDragData | null;
@@ -0,0 +1,34 @@
1
+ import * as React from "react";
2
+ import type { DragEndEvent } from "@dnd-kit/react";
3
+ type Props = {
4
+ children: React.ReactNode;
5
+ /**
6
+ * Called when a drag gesture finishes. Read the dropped tile and its
7
+ * target from `event.operation.source` / `event.operation.target`.
8
+ * Temporary surface: the provider will grow an `onMove`/`onClear`
9
+ * translation layer so widgets never touch raw dnd-kit events.
10
+ */
11
+ onDragEnd?: (event: DragEndEvent) => void;
12
+ };
13
+ /**
14
+ * PerseusDndProvider is the drag-and-drop context that the Drag-and-Drop
15
+ * widget family renders around its draggables and droppables. Each widget
16
+ * instance mounts its own provider, so drag state never leaks between two
17
+ * widgets on one page.
18
+ *
19
+ * Dragging is a pointer-device interaction (mouse and touch): keyboard and
20
+ * screen-reader users move tiles through each tile's DndActionMenu, which
21
+ * is the single-pointer, non-dragging alternative WCAG 2.5.7 requires.
22
+ * Two dnd-kit defaults are therefore overridden:
23
+ *
24
+ * - Sensors: only the PointerSensor. dnd-kit's KeyboardSensor would add a
25
+ * second, competing keyboard path.
26
+ * - Plugins: dnd-kit's Accessibility plugin is removed. It would make
27
+ * every tile a focusable `role="button"` (an invalid nested control
28
+ * around the menu's real button) and speak keyboard-drag instructions
29
+ * for a mode that is off. Move announcements are instead the widget's
30
+ * job, through the Wonder Blocks Announcer, for the drag path and the
31
+ * menu path alike — one owner per event.
32
+ */
33
+ export declare const PerseusDndProvider: (props: Props) => React.JSX.Element;
34
+ export {};
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Temporary, untranslated strings for the Drag-and-Drop components.
3
+ *
4
+ * The copy is still in flux during development, so these live here
5
+ * instead of PerseusStrings to avoid churning the frontend's translation
6
+ * surface with every tweak. Each entry mirrors the shape it will have in
7
+ * PerseusStrings.
8
+ *
9
+ * TODO(LEMS-4368): once the copy is finalized, move these entries into
10
+ * PerseusStrings (strings.ts) and swap consumers to usePerseusI18n.
11
+ */
12
+ export declare const tempDndStrings: {
13
+ moveTo: string;
14
+ clear: string;
15
+ moveToTarget: ({ target }: {
16
+ target: string;
17
+ }) => string;
18
+ clearTarget: ({ target }: {
19
+ target: string;
20
+ }) => string;
21
+ actionsMenu: string;
22
+ menuRemaining: ({ num }: {
23
+ num: number;
24
+ }) => string;
25
+ choices: string;
26
+ blankLabel: ({ num }: {
27
+ num: number;
28
+ }) => string;
29
+ movedToTarget: ({ tile, target }: {
30
+ tile: string;
31
+ target: string;
32
+ }) => string;
33
+ returnedToChoices: ({ tile }: {
34
+ tile: string;
35
+ }) => string;
36
+ };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Pure placement transitions for the Drag-and-Drop widget family.
3
+ * The parent widget owns the state. The drag path and the menu path
4
+ * call the same functions.
5
+ *
6
+ * These functions apply the FITB/Sorter model: one tile per blank,
7
+ * swap-to-bank displacement. Stacking models (Categorizer) are out of
8
+ * scope.
9
+ */
10
+ /**
11
+ * Maps blankId to the tileId of the placed tile. A missing key means
12
+ * the blank is empty. With multi-use tiles, one tileId can appear
13
+ * under more than one blank.
14
+ */
15
+ export type TilePlacements = Readonly<Record<string, string>>;
16
+ export type TileUsage = "single" | "multi";
17
+ /**
18
+ * Places a tile into a blank. The occupant of the target blank returns
19
+ * to the bank. Set `fromBlankId` when the tile moves out of a blank.
20
+ */
21
+ export declare function placeTile(placements: TilePlacements, move: {
22
+ tileId: string;
23
+ fromBlankId?: string;
24
+ }, targetBlankId: string, tileUsage: TileUsage): TilePlacements;
25
+ /** Empties a blank. Its tile returns to the bank. */
26
+ export declare function clearBlank(placements: TilePlacements, blankId: string): TilePlacements;
27
+ /**
28
+ * Returns how many more times the tile can be placed. Returns null for
29
+ * a multi-use tile with no cap.
30
+ */
31
+ export declare function remainingUses(placements: TilePlacements, tileId: string, tileUsage: TileUsage, maxUsesPerTile?: number): number | null;
32
+ /** Returns true when the choice bank shows this tile. */
33
+ export declare function isTileInBank(placements: TilePlacements, tileId: string, tileUsage: TileUsage, maxUsesPerTile?: number): boolean;
@@ -0,0 +1,28 @@
1
+ import type { TileDragData } from "./drag-ids";
2
+ import type { TilePlacements, TileUsage } from "./tile-placements";
3
+ import type { DragEndEvent } from "@dnd-kit/react";
4
+ /**
5
+ * Move, clear, and drag-end handling shared by the widgets that place
6
+ * tiles into blanks. Each action updates the controlled placements and
7
+ * announces the result.
8
+ *
9
+ * handleMove and handleClear are for the tiles' menus, and also move
10
+ * focus back to a menu afterwards: pass `firstBankMenuRef` to the
11
+ * bank's first menu button and `placedMenuRef(blankId)` to each placed
12
+ * tile's menu button. handleDragEnd is for the PerseusDndProvider, and
13
+ * leaves focus alone: a drag is a pointer interaction.
14
+ */
15
+ export declare function useTileMoveActions(options: {
16
+ placements: TilePlacements;
17
+ onPlacementsChange: (next: TilePlacements) => void;
18
+ tileUsage: TileUsage;
19
+ getTileLabel: (tileId: string) => string;
20
+ getBlankLabel: (blankId: string) => string;
21
+ blankIds: ReadonlyArray<string>;
22
+ }): {
23
+ handleMove: (move: TileDragData, targetBlankId: string) => void;
24
+ handleClear: (blankId: string) => void;
25
+ handleDragEnd: (event: DragEndEvent) => void;
26
+ firstBankMenuRef: (button: HTMLButtonElement | null) => void;
27
+ placedMenuRef: (blankId: string) => (button: HTMLButtonElement | null) => void;
28
+ };
package/dist/es/index.css CHANGED
@@ -16,7 +16,7 @@
16
16
  --wb-font-heading-size-small
17
17
  );--perseus-multiple-choice-indicator-size:3.2rem;--perseus-multiple-choice-indicator-correct-size:calc(var(--perseus-multiple-choice-indicator-font-size)*1.5 + var(--perseus-multiple-choice-indicator-size));--perseus-multiple-choice-content-margin:calc(var(--perseus-multiple-choice-spacing) + var(--perseus-multiple-choice-indicator-size));--perseus-multiple-choice-fade-direction:90deg;--perseus-multiple-choice-fade-color:var(
18
18
  --perseus-widget-background-color,var(--wb-semanticColor-core-background-base-default)
19
- );box-sizing:border-box;min-width:auto;padding-inline-end:var(--perseus-multiple-choice-spacing);position:relative;transform:translateZ(0)}.perseus_ejJezW-c{list-style:none;margin:0;min-width:fit-content;padding:0;scrollbar-width:thin;width:100%}.perseus_ejJezW-c:dir(rtl){--perseus-multiple-choice-fade-direction:270deg}.perseus_ejJezW-c:before{background:linear-gradient(var(--perseus-multiple-choice-fade-direction),var(--perseus-multiple-choice-fade-color) calc(var(--mobile-font-scale, 1)*(var(--perseus-multiple-choice-content-margin) + (var(--perseus-multiple-choice-spacing)/2))),transparent calc(var(--mobile-font-scale, 1)*(var(--perseus-multiple-choice-content-margin) + var(--perseus-multiple-choice-spacing))) calc(100% - var(--mobile-font-scale, 1)*var(--perseus-multiple-choice-spacing)*1.5),var(--perseus-multiple-choice-fade-color) calc(100% - var(--mobile-font-scale, 1)*var(--perseus-multiple-choice-spacing)));content:"";display:block;height:100%;position:fixed;width:100%}.perseus_zAWs4E3G{--perseus-content-line-height:var(--wb-font-heading-lineHeight-medium);display:flex;flex-direction:column;gap:var(--wb-sizing-size_120);margin-inline-start:var(--perseus-multiple-choice-content-margin);position:relative;z-index:-1}@supports (margin-block-start:1lh){.perseus_zAWs4E3G{--perseus-content-line-height:1lh}}.perseus_zAWs4E3G:not(:has(.paragraph:first-child>.paragraph :is(img,mjx-mfrac,mjx-msqrt,mjx-mroot,mjx-munder,mjx-mover,mjx-munderover))){margin-block-start:calc((var(--perseus-multiple-choice-indicator-size) - var(--perseus-content-line-height))*.4)}@media (prefers-reduced-motion:no-preference){.perseus_zAWs4E3G{transition:all .125s}}.perseus_WyGpYl3q{color:var(--wb-semanticColor-core-foreground-neutral-strong);display:block;font-family:inherit;font-style:normal;font-weight:var(--wb-font-weight-bold);margin-block-end:var(--wb-sizing-size_160)}.perseus_6vMrWVz0,.perseus_WyGpYl3q{font-size:var(--wb-font-body-size-medium);line-height:var(--wb-font-body-lineHeight-medium)}.perseus_6vMrWVz0{font-weight:var(--wb-font-weight-medium)}.perseus_6vMrWVz0.perseus_Zv66BVdg{color:var(--wb-semanticColor-core-foreground-neutral-default)}.perseus_6vMrWVz0 .perseus-renderer{display:flex;flex-direction:column;gap:var(--wb-sizing-size_120)}.perseus_6vMrWVz0 .perseus-renderer .paragraph{margin:0}.perseus_6vMrWVz0 .perseus-renderer>.paragraph:not(:has(*)){display:none}.perseus_hJzSXKwq{--perseus-multiple-choice-content-margin:calc(var(--perseus-multiple-choice-spacing) + var(--perseus-multiple-choice-indicator-correct-size))}.perseus_LCi2Yw7w{background:var(--wb-semanticColor-core-background-base-subtle);border:var(--wb-border-width-thin) dashed var(--wb-semanticColor-core-border-neutral-default);border-radius:var(--wb-border-radius-radius_040);gap:var(--wb-sizing-size_240);height:48px;padding:var(--wb-sizing-size_0);width:65px}.perseus_LCi2Yw7w.perseus_8JKzE83C{height:28px;width:26px}.perseus_5CDFHduT{border-bottom:none;color:var(--wb-semanticColor-core-foreground-instructive-default)}:active>.perseus_5CDFHduT,:focus>.perseus_5CDFHduT,:hover>.perseus_5CDFHduT{border-bottom:var(--wb-border-width-medium) solid var(--wb-semanticColor-core-border-instructive-default)}.perseus_ydfsz2Xj{color:var(--wb-semanticColor-core-foreground-neutral-strong);font-size:var(--wb-font-body-size-medium);font-weight:var(--wb-font-weight-medium);line-height:var(--wb-font-body-lineHeight-medium)}.perseus_3AGWQQ8Z{height:auto;line-height:inherit;margin-left:-.2rem;margin-right:.2rem;padding-left:.2rem}.perseus_HDIlPzEz{border-left:var(--wb-border-width-none) solid var(--wb-semanticColor-core-border-neutral-subtle);display:inline-grid;position:relative}.perseus_NkFQFgcZ{grid-template-columns:0fr;grid-template-rows:0fr;margin-bottom:0;margin-top:0;min-width:0;padding-bottom:0;visibility:hidden}.perseus_XhV-9haB{border-left-width:5px;grid-template-columns:1fr;grid-template-rows:1fr;margin-bottom:1.4rem;margin-left:-2.3rem;margin-top:1.4rem;min-width:100%;padding-bottom:1rem;padding-left:2.3rem;visibility:visible}.perseus_WEJESS6h{overflow:hidden}.perseus_SX0g-1Bs{font-size:var(--wb-font-heading-size-medium);line-height:inherit;margin-right:-.6rem;text-align:left;white-space:normal}@media (prefers-reduced-motion:no-preference){.perseus_sbBrAODD{transition:all .25s step-end,grid-template-rows .25s,margin-top .25s,margin-bottom .25s,padding-bottom .25s}.perseus_Wp72j6qk{transition:grid-template-rows .5s,margin-top .5s,margin-bottom .5s,padding-bottom .5s}}.MafsView .protractor-rotation-handle{cursor:grab;pointer-events:all;transition:transform .15s ease-out}.MafsView .protractor-rotation-handle:hover{transform:scale(1.2)}.MafsView .protractor-rotation-handle-arrow-arc{fill:none;stroke:var(--mafs-blue);stroke-width:8;stroke-linecap:square}.MafsView .protractor-rotation-handle-arrowhead{fill:var(--mafs-blue);stroke:none}.MafsView{--mafs-bg:#000;--mafs-fg:#fff;--mafs-origin-color:var(--mafs-fg);--mafs-line-color:#555;--mafs-line-stroke-dash-style:4,3;--mafs-axis-stroke-width:1px;--grid-line-subdivision-color:#222;--mafs-red:#f11d0e;--mafs-orange:#f14e0e;--mafs-yellow:#ffe44a;--mafs-green:#15e272;--mafs-blue:#58a6ff;--mafs-indigo:#7c58ff;--mafs-violet:#ae58ff;--mafs-pink:#ee00ab;background:var(--mafs-bg);display:block;font-family:inherit;font-variant-numeric:tabular-nums;outline:0;overflow:hidden;touch-action:none;-webkit-user-select:none;user-select:none}.MafsView text{fill:var(--mafs-fg);cursor:default}.MafsView path{stroke:var(--mafs-fg)}.MafsView:focus-visible{border-radius:5px;outline:3px solid #58a6ff}@supports not selector(:focus-visible){.MafsView:focus{border-radius:5px;outline:3px solid #58a6ff}}.mafs-shadow{stroke-width:3px;stroke:var(--mafs-bg);stroke-opacity:.75;stroke-linejoin:round;paint-order:stroke}.mafs-movable-point{cursor:grab;touch-action:none}.mafs-movable-point-dragging{cursor:grabbing}.mafs-movable-point:focus{outline:0}.mafs-movable-point *{fill:none;stroke:none}.mafs-movable-point-hitbox{fill:transparent}.mafs-movable-point-focus{stroke:var(--movable-point-color);stroke-width:2;stroke-opacity:0;fill:none;transition:stroke-opacity .2s ease}.mafs-movable-point-ring{fill:var(--movable-point-color);fill-opacity:.25;stroke:none;transition:r .2s ease}.mafs-movable-point-point{fill:var(--movable-point-color);transition:r .2s ease}.mafs-movable-point:focus-visible .mafs-movable-point-point,.mafs-movable-point:hover .mafs-movable-point-point{r:calc(var(--movable-point-ring-size) - 2px)}.mafs-movable-point:focus-visible .mafs-movable-point-ring,.mafs-movable-point:hover .mafs-movable-point-ring{r:calc(var(--movable-point-ring-size) + 3px)}.mafs-movable-point.mafs-movable-point-dragging .mafs-movable-point-ring{r:var(--movable-point-ring-size)}.mafs-movable-point:focus-visible .mafs-movable-point-focus{stroke-opacity:1}.MafsView{--mafs-bg:transparent;--mafs-fg:var(--wb-semanticColor-core-foreground-neutral-strong);--mafs-line-color:var(--wb-semanticColor-core-border-neutral-subtle);--mafs-axis-stroke-width:2px;--mafs-blue:var(--wb-semanticColor-core-foreground-instructive-default);--mafs-red:var(--wb-semanticColor-core-foreground-critical-default);--mafs-green:var(--wb-semanticColor-core-foreground-success-default);--mafs-violet:var(--wb-semanticColor-mastery-primary);--mafs-yellow:var(--wb-semanticColor-core-foreground-warning-default);--static-gray:var(--wb-semanticColor-core-foreground-disabled-strong);--movable-point-color:var(--mafs-blue);--movable-point-center-radius:6px;--movable-point-ring-radius:calc(2px + var(--movable-point-center-radius));--movable-point-halo-radius:calc(3px + var(--movable-point-ring-radius));--movable-point-hover-expansion:2px;--movable-point-focus-ring-offset:2px;--movable-line-stroke-weight:2px;--movable-line-stroke-weight-active:4px;--movable-asymptote-stroke-weight:2px;--movable-asymptote-stroke-weight-active:4px;--movable-asymptote-dash-length:6;--movable-asymptote-dash-gap:8;--movable-asymptote-dash-length-active:8;--movable-asymptote-dash-gap-active:12;overflow:visible!important;overscroll-behavior-y:contain;touch-action:pan-y}.MafsView .movable-circle,.MafsView .movable-line,.MafsView .movable-point__focusable-handle,.MafsView .movable-polygon{touch-action:none}.MafsView>svg{display:block;overflow:visible}.MafsView .movable-line--hover,.MafsView .movable-line:focus-visible,.MafsView .movable-line:hover,.movable-dragging{--movable-line-stroke-weight:var(--movable-line-stroke-weight-active);--movable-asymptote-stroke-weight:var(
19
+ );box-sizing:border-box;min-width:auto;padding-inline-end:var(--perseus-multiple-choice-spacing);position:relative;transform:translateZ(0)}.perseus_ejJezW-c{list-style:none;margin:0;min-width:fit-content;padding:0;scrollbar-width:thin;width:100%}.perseus_ejJezW-c:dir(rtl){--perseus-multiple-choice-fade-direction:270deg}.perseus_ejJezW-c:before{background:linear-gradient(var(--perseus-multiple-choice-fade-direction),var(--perseus-multiple-choice-fade-color) calc(var(--mobile-font-scale, 1)*(var(--perseus-multiple-choice-content-margin) + (var(--perseus-multiple-choice-spacing)/2))),transparent calc(var(--mobile-font-scale, 1)*(var(--perseus-multiple-choice-content-margin) + var(--perseus-multiple-choice-spacing))) calc(100% - var(--mobile-font-scale, 1)*var(--perseus-multiple-choice-spacing)*1.5),var(--perseus-multiple-choice-fade-color) calc(100% - var(--mobile-font-scale, 1)*var(--perseus-multiple-choice-spacing)));content:"";display:block;height:100%;position:fixed;width:100%}.perseus_zAWs4E3G{--perseus-content-line-height:var(--wb-font-heading-lineHeight-medium);display:flex;flex-direction:column;gap:var(--wb-sizing-size_120);margin-inline-start:var(--perseus-multiple-choice-content-margin);position:relative;z-index:-1}@supports (margin-block-start:1lh){.perseus_zAWs4E3G{--perseus-content-line-height:1lh}}.perseus_zAWs4E3G:not(:has(.paragraph:first-child>.paragraph :is(img,mjx-mfrac,mjx-msqrt,mjx-mroot,mjx-munder,mjx-mover,mjx-munderover))){margin-block-start:calc((var(--perseus-multiple-choice-indicator-size) - var(--perseus-content-line-height))*.4)}@media (prefers-reduced-motion:no-preference){.perseus_zAWs4E3G{transition:all .125s}}.perseus_WyGpYl3q{color:var(--wb-semanticColor-core-foreground-neutral-strong);display:block;font-family:inherit;font-style:normal;font-weight:var(--wb-font-weight-bold);margin-block-end:var(--wb-sizing-size_160)}.perseus_6vMrWVz0,.perseus_WyGpYl3q{font-size:var(--wb-font-body-size-medium);line-height:var(--wb-font-body-lineHeight-medium)}.perseus_6vMrWVz0{font-weight:var(--wb-font-weight-medium)}.perseus_6vMrWVz0.perseus_Zv66BVdg{color:var(--wb-semanticColor-core-foreground-neutral-default)}.perseus_6vMrWVz0 .perseus-renderer{display:flex;flex-direction:column;gap:var(--wb-sizing-size_120)}.perseus_6vMrWVz0 .perseus-renderer .paragraph{margin:0}.perseus_6vMrWVz0 .perseus-renderer>.paragraph:not(:has(*)){display:none}.perseus_hJzSXKwq{--perseus-multiple-choice-content-margin:calc(var(--perseus-multiple-choice-spacing) + var(--perseus-multiple-choice-indicator-correct-size))}.perseus_w5JEPtr3{align-items:center;background:var(--wb-semanticColor-core-background-base-subtle);border:var(--wb-border-width-thin) dashed var(--wb-semanticColor-core-border-neutral-default);border-radius:var(--wb-border-radius-radius_040);box-sizing:border-box;display:inline-flex;justify-content:center;margin-inline:var(--wb-sizing-size_040);min-height:48px;min-width:var(--blank-min-inline-size,65px);padding:var(--wb-sizing-size_0);transition:min-width .15s ease-out;vertical-align:middle}@media (prefers-reduced-motion:reduce){.perseus_w5JEPtr3{transition:none}}.perseus_w5JEPtr3.perseus_YYUlZ0FC{margin-inline:0;min-height:28px;min-width:26px}.perseus_w5JEPtr3.perseus_zuqfZw15{vertical-align:.5em}.perseus_w5JEPtr3.perseus_WBbw9tQT{vertical-align:-.7em}.perseus_w5JEPtr3.perseus_YYUlZ0FC.perseus_1Uom-GrS:focus-within,.perseus_w5JEPtr3.perseus_YYUlZ0FC.perseus_1Uom-GrS:hover{vertical-align:middle}.perseus_w5JEPtr3.perseus_1Uom-GrS{background:transparent;border:none;min-height:0;min-width:0}.perseus_w5JEPtr3.perseus_1Uom-GrS.perseus_djiO5HzC{min-width:var(--blank-min-inline-size,65px)}.perseus_w5JEPtr3.perseus_ZiDXp0R-{background:var(--wb-semanticColor-core-background-instructive-subtle);border-color:transparent;border-radius:var(--wb-border-radius-radius_080);outline:var(--wb-border-width-medium) dashed var(--wb-semanticColor-core-border-instructive-default);outline-offset:calc(var(--wb-border-width-medium)*-1)}@container fitb-answer-zone (width < 504px){.perseus_w5JEPtr3:not(.perseus_YYUlZ0FC){display:flex;inline-size:100cqi;justify-content:flex-start;margin-inline:0}}.perseus_5CDFHduT{border-bottom:none;color:var(--wb-semanticColor-core-foreground-instructive-default)}:active>.perseus_5CDFHduT,:focus>.perseus_5CDFHduT,:hover>.perseus_5CDFHduT{border-bottom:var(--wb-border-width-medium) solid var(--wb-semanticColor-core-border-instructive-default)}.perseus_ydfsz2Xj{color:var(--wb-semanticColor-core-foreground-neutral-strong);font-size:var(--wb-font-body-size-medium);font-weight:var(--wb-font-weight-medium);line-height:var(--wb-font-body-lineHeight-medium)}.perseus_3AGWQQ8Z{height:auto;line-height:inherit;margin-left:-.2rem;margin-right:.2rem;padding-left:.2rem}.perseus_HDIlPzEz{border-left:var(--wb-border-width-none) solid var(--wb-semanticColor-core-border-neutral-subtle);display:inline-grid;position:relative}.perseus_NkFQFgcZ{grid-template-columns:0fr;grid-template-rows:0fr;margin-bottom:0;margin-top:0;min-width:0;padding-bottom:0;visibility:hidden}.perseus_XhV-9haB{border-left-width:5px;grid-template-columns:1fr;grid-template-rows:1fr;margin-bottom:1.4rem;margin-left:-2.3rem;margin-top:1.4rem;min-width:100%;padding-bottom:1rem;padding-left:2.3rem;visibility:visible}.perseus_WEJESS6h{overflow:hidden}.perseus_SX0g-1Bs{font-size:var(--wb-font-heading-size-medium);line-height:inherit;margin-right:-.6rem;text-align:left;white-space:normal}@media (prefers-reduced-motion:no-preference){.perseus_sbBrAODD{transition:all .25s step-end,grid-template-rows .25s,margin-top .25s,margin-bottom .25s,padding-bottom .25s}.perseus_Wp72j6qk{transition:grid-template-rows .5s,margin-top .5s,margin-bottom .5s,padding-bottom .5s}}.MafsView .protractor-rotation-handle{cursor:grab;pointer-events:all;transition:transform .15s ease-out}.MafsView .protractor-rotation-handle:hover{transform:scale(1.2)}.MafsView .protractor-rotation-handle-arrow-arc{fill:none;stroke:var(--mafs-blue);stroke-width:8;stroke-linecap:square}.MafsView .protractor-rotation-handle-arrowhead{fill:var(--mafs-blue);stroke:none}.MafsView{--mafs-bg:#000;--mafs-fg:#fff;--mafs-origin-color:var(--mafs-fg);--mafs-line-color:#555;--mafs-line-stroke-dash-style:4,3;--mafs-axis-stroke-width:1px;--grid-line-subdivision-color:#222;--mafs-red:#f11d0e;--mafs-orange:#f14e0e;--mafs-yellow:#ffe44a;--mafs-green:#15e272;--mafs-blue:#58a6ff;--mafs-indigo:#7c58ff;--mafs-violet:#ae58ff;--mafs-pink:#ee00ab;background:var(--mafs-bg);display:block;font-family:inherit;font-variant-numeric:tabular-nums;outline:0;overflow:hidden;touch-action:none;-webkit-user-select:none;user-select:none}.MafsView text{fill:var(--mafs-fg);cursor:default}.MafsView path{stroke:var(--mafs-fg)}.MafsView:focus-visible{border-radius:5px;outline:3px solid #58a6ff}@supports not selector(:focus-visible){.MafsView:focus{border-radius:5px;outline:3px solid #58a6ff}}.mafs-shadow{stroke-width:3px;stroke:var(--mafs-bg);stroke-opacity:.75;stroke-linejoin:round;paint-order:stroke}.mafs-movable-point{cursor:grab;touch-action:none}.mafs-movable-point-dragging{cursor:grabbing}.mafs-movable-point:focus{outline:0}.mafs-movable-point *{fill:none;stroke:none}.mafs-movable-point-hitbox{fill:transparent}.mafs-movable-point-focus{stroke:var(--movable-point-color);stroke-width:2;stroke-opacity:0;fill:none;transition:stroke-opacity .2s ease}.mafs-movable-point-ring{fill:var(--movable-point-color);fill-opacity:.25;stroke:none;transition:r .2s ease}.mafs-movable-point-point{fill:var(--movable-point-color);transition:r .2s ease}.mafs-movable-point:focus-visible .mafs-movable-point-point,.mafs-movable-point:hover .mafs-movable-point-point{r:calc(var(--movable-point-ring-size) - 2px)}.mafs-movable-point:focus-visible .mafs-movable-point-ring,.mafs-movable-point:hover .mafs-movable-point-ring{r:calc(var(--movable-point-ring-size) + 3px)}.mafs-movable-point.mafs-movable-point-dragging .mafs-movable-point-ring{r:var(--movable-point-ring-size)}.mafs-movable-point:focus-visible .mafs-movable-point-focus{stroke-opacity:1}.MafsView{--mafs-bg:transparent;--mafs-fg:var(--wb-semanticColor-core-foreground-neutral-strong);--mafs-line-color:var(--wb-semanticColor-core-border-neutral-subtle);--mafs-axis-stroke-width:2px;--mafs-blue:var(--wb-semanticColor-core-foreground-instructive-default);--mafs-red:var(--wb-semanticColor-core-foreground-critical-default);--mafs-green:var(--wb-semanticColor-core-foreground-success-default);--mafs-violet:var(--wb-semanticColor-mastery-primary);--mafs-yellow:var(--wb-semanticColor-core-foreground-warning-default);--static-gray:var(--wb-semanticColor-core-foreground-disabled-strong);--movable-point-color:var(--mafs-blue);--movable-point-center-radius:6px;--movable-point-ring-radius:calc(2px + var(--movable-point-center-radius));--movable-point-halo-radius:calc(3px + var(--movable-point-ring-radius));--movable-point-hover-expansion:2px;--movable-point-focus-ring-offset:2px;--movable-line-stroke-weight:2px;--movable-line-stroke-weight-active:4px;--movable-asymptote-stroke-weight:2px;--movable-asymptote-stroke-weight-active:4px;--movable-asymptote-dash-length:6;--movable-asymptote-dash-gap:8;--movable-asymptote-dash-length-active:8;--movable-asymptote-dash-gap-active:12;overflow:visible!important;overscroll-behavior-y:contain;touch-action:pan-y}.MafsView .movable-circle,.MafsView .movable-line,.MafsView .movable-point__focusable-handle,.MafsView .movable-polygon{touch-action:none}.MafsView>svg{display:block;overflow:visible}.MafsView .movable-line--hover,.MafsView .movable-line:focus-visible,.MafsView .movable-line:hover,.movable-dragging{--movable-line-stroke-weight:var(--movable-line-stroke-weight-active);--movable-asymptote-stroke-weight:var(
20
20
  --movable-asymptote-stroke-weight-active
21
21
  );--movable-asymptote-dash-length:var(
22
22
  --movable-asymptote-dash-length-active