@use 'mixins' as *; // ---------- Graphing calculator (/calculator) ---------- // desktop: the plane owns the space beside the sidebar the way the link // map does — a full surface with the function dock and the zoom buttons // floating over it. (The page transition only fades, so no transform // steals the fixed positioning.) .calc-stage { position: fixed; inset: 0 0 0 var(--sidebar-w); transition: left 0.22s ease; z-index: 1; background: var(--bg); } // the plane fills the stage .plot-wrap { position: absolute; inset: 0; overflow: hidden; background: var(--surface); } .plot-canvas { display: block; width: 100%; height: 100%; touch-action: none; // the plane owns drags and pinches, not the page } // the function dock, pinned to the plane's left edge .calc-dock { position: absolute; top: 0; left: 0; bottom: 0; z-index: 2; display: flex; flex-direction: column; width: clamp(260px, 24vw, 320px); padding: 18px 16px; border-right: 1px solid var(--border); background: color-mix(in srgb, var(--bg-elev) 92%, transparent); backdrop-filter: blur(12px); box-shadow: var(--shadow-lg); overflow-y: auto; } .calc-dock-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin: 0 0 14px; } .calc-dock-title { @include eyebrow(0.74rem, 750, 0.09em); margin: 0; } .calc-rows { display: flex; flex-direction: column; gap: 8px; margin: 0; padding: 0; list-style: none; } .calc-row { display: flex; align-items: center; gap: 8px; } // the curve's hue; a press hands it on to the next one in the palette .calc-swatch { flex: none; width: 14px; height: 14px; border-radius: 50%; border: 2px solid color-mix(in srgb, var(--text) 12%, transparent); transition: transform 0.15s; &:hover { transform: scale(1.2); } } .calc-input-box { flex: 1; display: flex; align-items: center; min-width: 0; border: 1px solid var(--border); border-radius: 10px; background: var(--surface); transition: border-color 0.15s, box-shadow 0.15s; &:focus-within { border-color: var(--accent); box-shadow: var(--ring); } .calc-item.invalid & { border-color: color-mix(in srgb, var(--danger) 55%, transparent); } } // the "y =" the field answers to, set like the math it introduces .calc-y { flex: none; padding: 0 2px 0 10px; color: var(--faint); font: italic 0.95rem/1 'Computer Modern', Georgia, serif; } // only a field that reads as a function wears the "y =" gutter; everything // else — a relation, point, slider, or unfinished text — is bare, and the // input takes back the room the gutter stood in .calc-input-box.bare .calc-input { padding-left: 10px; } .calc-input { flex: 1; min-width: 0; padding: 9px 10px 9px 6px; border: 0; outline: none; background: transparent; color: var(--text); font: 0.95rem/1.2 'Computer Modern', Georgia, serif; &.input-error { color: var(--danger); } } // The typeset echo of the field's text (Desmos' way): while the reader // types, the formula sets on its own line under the text; once the field // rests, it takes the text's place beside the "y =" gutter, so the dock // reads as mathematics. A click wakes the text again (the box is a label); // the input only steps out of sight, never out of the tab order — and // Tab waking it draws the text back. An expression that does not read // carries no echo, so it stays plain text and red, as ever. .calc-typeset { display: none; color: var(--text); .katex { font-size: 1rem; } } .calc-input-box.typeset { flex-wrap: wrap; .calc-typeset { display: block; } // typing: the echo takes a line of its own, under the field &:focus-within .calc-typeset { flex: 0 0 100%; padding: 0 10px 8px; } // at rest: the field folds away and the formula stands in its place &:not(:focus-within) { .calc-input { flex: none; width: 0; min-width: 0; padding: 0; opacity: 0; } .calc-typeset { flex: 1; min-width: 0; padding: 8px 10px; overflow-x: auto; } } } // A row that declared a variable ("k", "k = 3"). Once the reader stops // editing it, the field folds away to nothing — the name and value now live // in the slider laid in its place (see .calc-vars.is-inline). The input stays // mounted and focusable (never display:none), so the slider's name can be a // label that wakes it back to edit the declaration; a focused field stands // open as any other. This is how a made variable reads as just a slider, not // a "k = 3" box above a slider. .calc-input-box.var-decl:not(:focus-within) { flex: 0 0 0; min-width: 0; overflow: hidden; border-color: transparent; background: transparent; .calc-input { width: 0; min-width: 0; padding: 0; } } // while the declaration's text is awake, its inline slider steps aside — the // field is the whole row again .calc-input-box.var-decl:focus-within ~ .calc-vars.is-inline { display: none; } .calc-remove:hover { color: var(--danger); background: color-mix(in srgb, var(--danger) 10%, transparent); } .calc-error { display: block; margin: 5px 2px 1px; color: var(--danger); font-size: 0.78rem; } .calc-add { margin-top: 12px; } // free variables ("k", "a", "radius") — each slider hangs just under the // row that claims it. The name sets like the maths it names; the drag does // the rough work, the field the fine .calc-vars { display: flex; flex-direction: column; gap: 4px; margin: 5px 0 2px; padding: 0; list-style: none; } .calc-var { display: flex; align-items: center; gap: 8px; } .calc-var-name { flex: none; min-width: 1.5ch; color: var(--text); font: italic 0.98rem/1 'Computer Modern', Georgia, serif; } // the name of a made variable is the door back to its declaration: it is a // label for the folded field, so clicking it wakes "k = 3" to be edited .calc-var-name.editable { padding: 1px 2px; border-radius: 4px; cursor: text; &:hover { background: var(--surface-2); } } // a made variable's slider laid in the row in place of its field: no hanging // margin, the drag takes the room the field used to hold .calc-vars.is-inline { flex: 1; min-width: 0; margin: 0; gap: 0; } .calc-var-range { flex: 1; min-width: 0; accent-color: var(--accent); } .calc-var-number { flex: none; width: 58px; min-width: 0; padding: 3px 6px; border: 1px solid var(--border); border-radius: 7px; background: var(--bg-elev); color: var(--text); font: 0.8rem/1.3 'Computer Modern', Georgia, serif; font-variant-numeric: tabular-nums; // no spinners on the slider's little twin — the drag is the spinner -moz-appearance: textfield; appearance: textfield; &::-webkit-outer-spin-button, &::-webkit-inner-spin-button { -webkit-appearance: none; appearance: none; margin: 0; } &:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring); } } // the fold: the dock slides out to the left, under the app sidebar, and a // quiet card takes its place at the plane's top-left corner .calc-dock-enter-active, .calc-dock-leave-active { transition: transform 0.22s ease; } .calc-dock-enter-from, .calc-dock-leave-to { transform: translateX(-105%); } .calc-dock-open { position: absolute; top: 10px; left: 12px; z-index: 2; display: flex; align-items: center; gap: 6px; padding: 7px 12px; border: 1px solid var(--border); border-radius: 11px; background: color-mix(in srgb, var(--bg-elev) 84%, transparent); backdrop-filter: blur(10px); box-shadow: var(--shadow-sm); color: var(--muted); font-size: 0.84rem; font-weight: 650; white-space: nowrap; transition: color 0.15s, background-color 0.15s; &:hover { color: var(--text); background: var(--surface-2); } } // the button row floating on the plane's top-right corner — clear of the dock .plot-toolbar { position: absolute; top: 10px; right: 12px; z-index: 2; display: flex; gap: 2px; padding: 4px; border: 1px solid var(--border); border-radius: 11px; background: color-mix(in srgb, var(--bg-elev) 84%, transparent); backdrop-filter: blur(10px); box-shadow: var(--shadow-sm); } // the 3D scene hangs its toolbar from a wrapper so the configuration // card can fold out under it: the wrapper takes the corner, the row // keeps its look on the hinge .plot-tools { position: absolute; top: 10px; right: 12px; z-index: 2; .plot-toolbar { position: static; } // the configuration button while its card is open, the graph menu's way .panel-icon-btn.open { color: var(--text); background: var(--surface-2); } } // the configuration card: the space the surfaces stand in — each edge // editable, the height by default the surfaces' own, the mesh's fineness .plot-config { @include dropdown-panel($z: 20, $overflow: visible); right: 0; min-width: 250px; padding: 10px 12px; } .plot-config-row { display: flex; align-items: baseline; justify-content: space-between; gap: 18px; padding: 3px 0; } .plot-config-axis { color: var(--muted); font: italic 13px 'Computer Modern', Georgia, serif; } .plot-config-label { color: var(--muted); font-size: 0.8rem; font-weight: 600; } .plot-config-value { color: var(--text); font: 0.82rem/1.3 'Computer Modern', Georgia, serif; font-variant-numeric: tabular-nums; white-space: nowrap; } .plot-config-bounds { display: flex; align-items: center; gap: 6px; } .plot-config-dots { color: var(--faint); } .plot-config-input { width: 68px; min-width: 0; padding: 4px 7px; border: 1px solid var(--border); border-radius: 7px; background: var(--surface); color: var(--text); font: 0.82rem/1.2 'Computer Modern', Georgia, serif; font-variant-numeric: tabular-nums; &:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring); } } // the switch that decides whose height z answers to: the surfaces' or // the reader's own two bounds .plot-config-toggle { display: flex; align-items: center; justify-content: space-between; gap: 12px; width: 100%; margin-top: 7px; padding: 7px 0 5px; border-top: 1px solid var(--border); color: var(--muted); font-size: 0.74rem; line-height: 1.35; text-align: left; &:hover { color: var(--text); } } .plot-config-switch { position: relative; flex: none; width: 32px; height: 18px; border-radius: 999px; background: var(--border-strong); transition: background-color 0.15s; .knob { position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 50%; background: var(--bg-elev); box-shadow: var(--shadow-sm); transition: transform 0.15s ease; } &.on { background: var(--accent); .knob { transform: translateX(14px); } } } // where the cursor stands, bottom-right so the dock never covers it .plot-readout { position: absolute; bottom: 10px; right: 12px; padding: 4px 10px; border: 1px solid var(--border); border-radius: 8px; background: color-mix(in srgb, var(--bg-elev) 84%, transparent); backdrop-filter: blur(10px); color: var(--muted); font: 0.78rem/1.3 'Computer Modern', Georgia, serif; font-variant-numeric: tabular-nums; pointer-events: none; } // the top bar is in the flow here, so the calculator folds into panels of // the page: the dock first, then the plane as its own card @media (max-width: 900px) { .calc-stage { position: static; inset: auto; display: flex; flex-direction: column; background: transparent; } .calc-dock { position: static; order: 1; width: auto; margin-bottom: 16px; border: 1px solid var(--border); border-radius: 14px; background: var(--bg-elev); backdrop-filter: none; box-shadow: var(--shadow-sm); overflow: visible; } .plot-wrap { position: static; order: 2; width: 100%; height: min(66dvh, 560px); border: 1px solid var(--border); border-radius: 14px; } .plot-toolbar { top: 8px; right: 8px; } .plot-tools { top: 8px; right: 8px; } // during the fold the dock lifts out of the flow, so the plane takes the // width back at once rather than after the slide .calc-dock-leave-active { position: absolute; left: 0; right: 0; } } // ---------- Laid on a page of the book (ToolPane) ---------- // A pane of the book is a full surface again, at any screen width: the plane // fills the page and the input dock pins to its edge as on the desktop, and // the fold above — panels of the plain /calculator page — does not reach // into the pane. .pane-stage .calc-stage { position: absolute; inset: 0; display: block; background: var(--bg); } .pane-stage .plot-wrap { position: absolute; width: auto; height: auto; border: 0; border-radius: 0; } .pane-stage .calc-dock { position: absolute; top: 0; left: 0; bottom: 0; width: clamp(260px, 24vw, 320px); margin: 0; border: 0; border-right: 1px solid var(--border); border-radius: 0; background: color-mix(in srgb, var(--bg-elev) 92%, transparent); backdrop-filter: blur(12px); box-shadow: var(--shadow-lg); overflow-y: auto; }