Files
Learn_System/frontend/css/phys8-interactives.css
T
Maxim Dolgolyov 77e4dffb43 feat(phys8): Phase 0 redesign foundation — CSS + JS infrastructure
Закладывает уникальный визуальный язык и engine'ы для редизайна Физики 8.

CSS:
- phys8-design-system.css (12 КБ): 3 темы (thermal/electric/spectrum),
  тематические hero-палитры, watermarks, animations (thermal-shift,
  electric-pulse, spectrum-drift, wm-breathe/flicker/rotate, noise overlay),
  staggered fade-in для виджетов, soft elevation на карточках,
  monospace для физ. величин, topic-aware progress bars,
  mobile responsive (≤768px), prefers-reduced-motion.
- phys8-interactives.css (10 КБ): .p8-draggable + .p8-droptarget с
  hover-effects, .p8-palette (для circuit-builder), .p8-scrubber,
  .p8-readout табло, .p8-tooltip, .p8-sandbox canvas wrapper,
  .p8-thermometer + .p8-compass-needle SVG-композиции, glow-utility.

JS:
- phys8-anim.js (6 КБ): easing-функции (quad/cubic/expo/back/elastic/
  bounce/spring), tween-engine с onUpdate/onComplete, raf-wrapper,
  oscillate, stagger, onVisible (IntersectionObserver). Экспорт P8Anim.
- phys8-drag.js (12 КБ): универсальный drag-engine. P8Drag.attach()
  для DOM/SVG, P8Drag.attachCanvas() для логических объектов с
  hit-test, P8Drag.attachPalette() для drag-from-palette-to-drop,
  constraints (lockX/Y, bounds, snap-to-grid), touch + mouse + pointer.
- phys8-helpers.js (18 КБ): тематические хелперы. P8Helpers.thermal
  (tempColor 0-1, heatFlowArrow, molecule, thermometerSVG,
  convectionCellParticles), .em (chargeSVG, circuitComponent для
  battery/resistor/lamp/ammeter/voltmeter/switch, fieldLineFrom),
  .optics (rayLine, lensSVG converging/diverging, mirrorPlane),
  .svg utils (el, create, linearGradient, radialGradient,
  gradientArrow, labeledText).

Линковка (redesign_p8_phase0.cjs):
- 2 CSS-link после katex CDN
- 3 JS-link после phys.js/xp.js
- body class p8-theme-thermal/electric/spectrum на ch1/ch2/ch3
- hub и lab — без темы (нейтральный пурпурный brand)
2026-05-30 09:55:00 +03:00

373 lines
9.8 KiB
CSS

/* phys8-interactives.css
* Стили для drag-and-drop виджетов, scrubbers, tooltips, badges,
* палитр компонентов (для circuit builder), readout-табло.
* Расширяет phys-textbook-widgets.css.
*/
/* === Drag handle (cursor + микро-эффект) === */
.p8-draggable {
cursor: grab;
touch-action: none;
user-select: none;
transition: filter .18s, transform .18s;
}
.p8-draggable:hover {
filter: brightness(1.05);
}
.p8-draggable:active,
.p8-draggable.is-dragging {
cursor: grabbing;
filter: brightness(1.12) drop-shadow(0 6px 12px rgba(0,0,0,.18));
z-index: 10;
}
/* === Drop target (зона приёма) === */
.p8-droptarget {
border: 2px dashed var(--p8-brand-l, #c4b5fd);
border-radius: 12px;
padding: 14px;
background: rgba(124, 58, 237, .04);
transition: border-color .2s, background .2s, transform .2s;
min-height: 60px;
position: relative;
}
.p8-droptarget.p8-drop-over {
border-style: solid;
border-color: var(--p8-brand, #7c3aed);
background: rgba(124, 58, 237, .10);
transform: scale(1.02);
}
.p8-theme-thermal .p8-droptarget.p8-drop-over { border-color: var(--th-mid); background: rgba(249,115,22,.10); }
.p8-theme-electric .p8-droptarget.p8-drop-over { border-color: var(--el-mid); background: rgba(6,182,212,.10); }
.p8-theme-spectrum .p8-droptarget.p8-drop-over { border-color: var(--sp-mid-v); background: rgba(168,85,247,.10); }
/* === Component palette (для circuit builder и т.д.) === */
.p8-palette {
display: flex;
flex-wrap: wrap;
gap: 10px;
padding: 12px;
background: rgba(0,0,0,.04);
border-radius: 12px;
margin-bottom: 14px;
}
.p8-palette-item {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: var(--p8-brand-soft, #ede9fe);
border: 1.5px solid var(--p8-brand-l, #c4b5fd);
border-radius: 10px;
cursor: grab;
font-size: .86rem;
font-weight: 600;
color: var(--p8-brand-d, #5b21b6);
transition: transform .15s, box-shadow .15s, background .15s;
touch-action: none;
}
.p8-palette-item:hover {
transform: translateY(-2px);
box-shadow: 0 6px 14px rgba(0,0,0,.10);
background: #fff;
}
.p8-palette-item:active { cursor: grabbing; }
.p8-palette-item svg { width: 22px; height: 22px; }
/* === Scrubber (диапазонный ползунок с подписью) === */
.p8-scrubber {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 14px;
background: rgba(0,0,0,.03);
border-radius: 10px;
margin-bottom: 10px;
}
.p8-scrubber-label {
font-size: .82rem;
font-weight: 700;
color: var(--p8-text);
min-width: 80px;
}
.p8-scrubber input[type="range"] {
flex: 1;
accent-color: var(--p8-brand, #7c3aed);
height: 6px;
}
.p8-theme-thermal .p8-scrubber input[type="range"] { accent-color: var(--th-mid); }
.p8-theme-electric .p8-scrubber input[type="range"] { accent-color: var(--el-mid); }
.p8-theme-spectrum .p8-scrubber input[type="range"] { accent-color: var(--sp-mid-v); }
.p8-scrubber-value {
font-family: var(--p8-mono);
font-weight: 700;
font-size: .92rem;
color: var(--p8-ink);
min-width: 70px;
text-align: right;
}
.p8-scrubber-value .p8-unit {
margin-left: 3px;
}
/* === Readout табло (для live-данных симуляции) === */
.p8-readout {
display: inline-flex;
align-items: baseline;
gap: 4px;
padding: 6px 12px;
background: var(--p8-ink, #0f172a);
color: #f1f5f9;
border-radius: 8px;
font-family: var(--p8-mono);
font-weight: 700;
font-size: 1rem;
letter-spacing: -.01em;
box-shadow: inset 0 1px 0 rgba(255,255,255,.10);
}
.p8-readout-label {
font-size: .68rem;
font-weight: 600;
letter-spacing: .08em;
text-transform: uppercase;
color: #94a3b8;
margin-right: 6px;
}
.p8-readout-value {
color: #fff;
font-size: 1.1em;
min-width: 1em;
display: inline-block;
transition: color .2s;
}
.p8-readout-value.is-changing {
color: #fde047;
}
.p8-readout-unit {
color: #94a3b8;
font-weight: 600;
font-size: .82em;
margin-left: 3px;
}
/* === Tooltip (всплывающие подсказки на drag-объектах) === */
.p8-tooltip {
position: absolute;
pointer-events: none;
padding: 6px 10px;
background: rgba(15, 23, 42, .94);
color: #fff;
font-size: .76rem;
font-weight: 600;
border-radius: 8px;
z-index: 100;
white-space: nowrap;
box-shadow: 0 6px 14px rgba(0,0,0,.20);
opacity: 0;
transform: translateY(4px);
transition: opacity .15s, transform .15s;
}
.p8-tooltip.is-visible {
opacity: 1;
transform: translateY(0);
}
.p8-tooltip::after {
content: '';
position: absolute;
bottom: -4px; left: 50%;
transform: translateX(-50%) rotate(45deg);
width: 8px; height: 8px;
background: inherit;
}
/* === Badge (тематический значок IV-N, статус) === */
.p8-badge {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 4px 10px;
border-radius: 99px;
font-size: .68rem;
font-weight: 800;
text-transform: uppercase;
letter-spacing: .08em;
font-family: var(--p8-display);
}
.p8-badge.p8-badge-thermal { background: var(--th-soft, #fff7ed); color: var(--th-mid, #f97316); }
.p8-badge.p8-badge-electric { background: var(--el-soft, #ecfeff); color: var(--el-mid, #06b6d4); }
.p8-badge.p8-badge-spectrum { background: var(--sp-soft, #fdf2f8); color: var(--sp-mid-v, #a855f7); }
/* === Sandbox canvas (для drag-сцен) === */
.p8-sandbox {
position: relative;
width: 100%;
background:
linear-gradient(135deg, #fafafa 0%, #f3f4f6 100%);
border: 1.5px solid #e5e7eb;
border-radius: 12px;
overflow: hidden;
cursor: default;
}
html.dark .p8-sandbox {
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
border-color: #334155;
}
.p8-sandbox canvas,
.p8-sandbox svg {
display: block;
width: 100%;
height: 100%;
touch-action: none;
}
.p8-sandbox-toolbar {
position: absolute;
top: 10px; left: 10px;
display: flex;
gap: 6px;
z-index: 5;
}
.p8-sandbox-btn {
padding: 5px 10px;
background: rgba(255,255,255,.92);
border: 1px solid #e5e7eb;
border-radius: 7px;
font-size: .76rem;
font-weight: 600;
cursor: pointer;
font-family: var(--p8-body);
transition: background .15s;
}
.p8-sandbox-btn:hover { background: #fff; }
html.dark .p8-sandbox-btn { background: rgba(15,23,42,.92); border-color: #334155; color: #f1f5f9; }
html.dark .p8-sandbox-btn:hover { background: #1e293b; }
/* === Snap-to-grid индикатор === */
.p8-snap-line {
position: absolute;
pointer-events: none;
background: var(--p8-brand, #7c3aed);
opacity: .35;
z-index: 4;
transition: opacity .12s;
}
.p8-snap-line.horizontal { left: 0; right: 0; height: 1px; }
.p8-snap-line.vertical { top: 0; bottom: 0; width: 1px; }
/* === Thermometer (вертикальный термометр для thermal sandbox) === */
.p8-thermometer {
display: inline-flex;
flex-direction: column;
align-items: center;
position: relative;
}
.p8-thermometer-bulb {
width: 28px; height: 28px;
border-radius: 50%;
background: linear-gradient(135deg, #ef4444, #dc2626);
border: 2px solid #991b1b;
box-shadow: 0 0 12px rgba(239,68,68,.45);
}
.p8-thermometer-tube {
width: 10px;
height: 90px;
margin-bottom: -2px;
background: linear-gradient(to top, #ef4444 var(--p8-temp, 50%), #f3f4f6 var(--p8-temp, 50%));
border: 2px solid #475569;
border-bottom: none;
border-radius: 5px 5px 0 0;
}
.p8-thermometer-scale {
position: absolute;
left: 100%; top: 10px;
margin-left: 4px;
font-family: var(--p8-mono);
font-size: .68rem;
color: var(--p8-muted);
white-space: nowrap;
}
/* === Compass needle (для magnetic compass) === */
.p8-compass {
width: 120px; height: 120px;
position: relative;
border-radius: 50%;
background: radial-gradient(circle, #fff 0%, #f1f5f9 100%);
border: 3px solid #475569;
box-shadow: inset 0 2px 4px rgba(0,0,0,.10), 0 4px 10px rgba(0,0,0,.10);
}
.p8-compass::before,
.p8-compass::after {
content: ''; position: absolute;
left: 50%; top: 50%;
background: #475569;
transform-origin: center;
}
.p8-compass::before { /* N marker */
content: 'N';
top: 6px; left: 50%;
transform: translateX(-50%);
background: transparent;
font-family: var(--p8-display);
font-weight: 900;
font-size: .82rem;
color: #dc2626;
}
.p8-compass-needle {
position: absolute;
left: 50%; top: 50%;
width: 6px;
height: 84px;
transform: translate(-50%, -50%) rotate(var(--p8-needle-angle, 0deg));
transform-origin: center;
transition: transform .35s cubic-bezier(.34, 1.56, .64, 1);
z-index: 2;
pointer-events: none;
}
.p8-compass-needle::before,
.p8-compass-needle::after {
content: ''; position: absolute;
left: 0; width: 100%;
height: 50%;
}
.p8-compass-needle::before {
top: 0;
background: linear-gradient(to bottom, #dc2626, #b91c1c);
clip-path: polygon(50% 0, 100% 100%, 0 100%);
}
.p8-compass-needle::after {
bottom: 0;
background: linear-gradient(to top, #475569, #1e293b);
clip-path: polygon(50% 100%, 100% 0, 0 0);
}
/* === Particle Glow (для частиц газа / искр / лучей) === */
.p8-glow {
filter: drop-shadow(0 0 6px currentColor);
}
/* === Animation utility classes === */
.p8-anim-jitter { animation: p8-jitter .35s ease infinite alternate; }
@keyframes p8-jitter {
0% { transform: translate(0, 0); }
100% { transform: translate(1px, -1px); }
}
.p8-anim-shimmer {
background-image: linear-gradient(105deg, transparent 40%, rgba(255,255,255,.4) 50%, transparent 60%);
background-size: 250% 100%;
animation: p8-shimmer 1.4s linear infinite;
}
@keyframes p8-shimmer {
0% { background-position: 250% 0; }
100% { background-position: -150% 0; }
}
/* === Reduced motion === */
@media (prefers-reduced-motion: reduce) {
.p8-draggable, .p8-droptarget, .p8-tooltip,
.p8-compass-needle, .p8-readout-value,
.p8-anim-jitter, .p8-anim-shimmer {
transition: none !important;
animation: none !important;
}
}