├── assets ├── 6vsb.bcif └── ipymolstar_screenshot.png ├── src └── ipymolstar │ ├── panel │ ├── __init__.py │ └── pdbemolstar.py │ ├── __init__.py │ ├── molviewspec.py │ ├── static │ ├── pdbemolstar.js │ └── pdbe-dark.css │ └── pdbemolstar.py ├── .gitignore ├── package.json ├── pyproject.toml ├── examples ├── pdbemolstar │ ├── pyshiny_example.py │ ├── rainbow_residues.py │ ├── click_events.py │ ├── mouseover_highlight.py │ ├── panel_app.py │ ├── solara_app.py │ └── pyhdx_secb_deltaG.csv ├── mwe_solara.py └── molviewspec │ ├── solara_cameracontrol.py │ └── double_hooke.py ├── .github └── workflows │ ├── pypi_main.yml │ ├── pypi_test.yml │ └── build.yml ├── LICENSE ├── tests ├── test_unit.py └── _test_notebook.ipynb ├── js └── molviewspec.js └── README.md /assets/6vsb.bcif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molstar/ipymolstar/HEAD/assets/6vsb.bcif -------------------------------------------------------------------------------- /assets/ipymolstar_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molstar/ipymolstar/HEAD/assets/ipymolstar_screenshot.png -------------------------------------------------------------------------------- /src/ipymolstar/panel/__init__.py: -------------------------------------------------------------------------------- 1 | from ipymolstar.panel.pdbemolstar import PDBeMolstar 2 | 3 | __all__ = ["PDBeMolstar"] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .venv 3 | dist 4 | 5 | # Python 6 | __pycache__ 7 | .ipynb_checkpoints 8 | 9 | dev/ 10 | _pdb/ 11 | src/ipymolstar/static/molviewspec.js 12 | src/ipymolstar/static/molviewspec.css -------------------------------------------------------------------------------- /src/ipymolstar/__init__.py: -------------------------------------------------------------------------------- 1 | from ipymolstar.pdbemolstar import PDBeMolstar 2 | from ipymolstar.molviewspec import MolViewSpec 3 | 4 | __version__ = "0.1.0" 5 | __all__ = ["MolViewSpec", "PDBeMolstar", "__version__"] 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "dev": "npm run build -- --sourcemap=inline --watch", 4 | "build": "esbuild js/molviewspec.js --minify --format=esm --bundle --outdir=src/ipymolstar/static" 5 | }, 6 | "dependencies": { 7 | "molstar": "^4.12.1" 8 | }, 9 | "devDependencies": { 10 | "esbuild": "^0.25.1" 11 | } 12 | } -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "ipymolstar" 7 | license = { file = "LICENSE" } 8 | dependencies = ["anywidget"] 9 | readme = "README.md" 10 | description = "PDBeMolstar as anywidget" 11 | keywords = ["molstar", "anywidget"] 12 | dynamic = ['version'] 13 | 14 | [tool.hatch.version] 15 | path = "src/ipymolstar/__init__.py" 16 | 17 | [tool.hatch.build] 18 | only-packages = true 19 | artifacts = ["src/ipymolstar/static/*"] 20 | exclude = ["**/.venv*"] 21 | -------------------------------------------------------------------------------- /examples/pdbemolstar/pyshiny_example.py: -------------------------------------------------------------------------------- 1 | from ipymolstar import PDBeMolstar 2 | from shiny import reactive 3 | from shiny.express import input, render, ui 4 | from shinywidgets import reactive_read, render_widget 5 | 6 | ui.input_text("molecule_id", "Molecule id", "1qyn") 7 | 8 | 9 | @render_widget 10 | def molstar(): 11 | view = PDBeMolstar(molecule_id="1qyn") 12 | return view 13 | 14 | 15 | @reactive.effect 16 | def _(): 17 | molecule_id = input.molecule_id() 18 | # check for valid pdb id 19 | if len(molecule_id) == 4: 20 | molstar.widget.molecule_id = input.molecule_id() 21 | 22 | 23 | @render.text 24 | def center(): 25 | event = reactive_read(molstar.widget, "mouseover_event") 26 | return f"Mouseover event: {event}" 27 | -------------------------------------------------------------------------------- /.github/workflows/pypi_main.yml: -------------------------------------------------------------------------------- 1 | name: PyPi distribute main release 2 | on: [release] 3 | 4 | jobs: 5 | build-n-publish: 6 | name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI 7 | runs-on: ubuntu-latest 8 | permissions: 9 | id-token: write 10 | 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | with: 15 | fetch-depth: 0 16 | - name: Set up Python 3.10 17 | uses: actions/setup-python@v4 18 | with: 19 | python-version: "3.10" 20 | - name: Install Hatch 21 | run: pip install hatch 22 | - name: Build 23 | run: | 24 | npm install 25 | npm run build 26 | hatch build 27 | - name: Publish distribution 📦 to PyPI 28 | uses: pypa/gh-action-pypi-publish@release/v1 29 | -------------------------------------------------------------------------------- /.github/workflows/pypi_test.yml: -------------------------------------------------------------------------------- 1 | name: PyPi distribute test push 2 | on: [push] 3 | 4 | jobs: 5 | build-n-publish: 6 | name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI 7 | runs-on: ubuntu-latest 8 | permissions: 9 | id-token: write 10 | 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | with: 15 | fetch-depth: 0 # only needed for hatch vcs versioning 16 | - name: Set up Python 3.10 17 | uses: actions/setup-python@v4 18 | with: 19 | python-version: "3.10" 20 | - name: Install Hatch 21 | run: pip install hatch 22 | - name: Build 23 | run: | 24 | npm install 25 | npm run build 26 | hatch build 27 | - name: Publish distribution 📦 to Test PyPI 28 | uses: pypa/gh-action-pypi-publish@release/v1 29 | with: 30 | repository-url: https://test.pypi.org/legacy/ 31 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Test App Packaging 2 | # https://github.com/astral-sh/uv/issues/1386 3 | on: [push] 4 | 5 | jobs: 6 | test-packaging: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | 11 | - name: Set up Python 12 | uses: actions/setup-python@v4 13 | with: 14 | python-version: '3.10' 15 | 16 | - name: Install hatch 17 | run: pip install hatch 18 | 19 | - name: Build project 20 | run: | 21 | npm install 22 | npm run build 23 | hatch build 24 | - name: check the tarball 25 | run: | 26 | tar -tzf dist/*.tar.gz 27 | - name: Install uv 28 | run: pip install uv 29 | 30 | - name: Create and activate venv, install package 31 | run: | 32 | uv venv 33 | source .venv/bin/activate 34 | echo PATH=$PATH >> $GITHUB_ENV 35 | uv pip install pytest molviewspec 36 | uv pip install dist/*.tar.gz 37 | python -m pytest tests 38 | 39 | -------------------------------------------------------------------------------- /src/ipymolstar/molviewspec.py: -------------------------------------------------------------------------------- 1 | import anywidget 2 | import pathlib 3 | import traitlets 4 | 5 | # https://github.com/molstar/molstar/blob/80415a2771fcddbca3cc13ddba4be10c92a1454b/src/apps/viewer/app.ts#L88 6 | DEFAULT_VIEWER_OPTIONS = { 7 | "layoutIsExpanded": False, 8 | "layoutShowControls": False, 9 | } 10 | 11 | # https://github.com/molstar/molstar/blob/80415a2771fcddbca3cc13ddba4be10c92a1454b/src/extensions/mvs/load.ts#L37 12 | DEFAULT_MVS_LOAD_OPTIONS = { 13 | "replaceExisting": True, 14 | "sanityChecks": True, 15 | } 16 | 17 | 18 | class MolViewSpec(anywidget.AnyWidget): 19 | _esm = pathlib.Path(__file__).parent / "static" / "molviewspec.js" 20 | _css = pathlib.Path(__file__).parent / "static" / "molviewspec.css" 21 | 22 | width = traitlets.Unicode("100%").tag(sync=True) 23 | height = traitlets.Unicode("500px").tag(sync=True) 24 | msvj_data = traitlets.Unicode("").tag(sync=True) 25 | 26 | viewer_options = traitlets.Dict(DEFAULT_VIEWER_OPTIONS).tag(sync=True) 27 | mvs_load_options = traitlets.Dict(DEFAULT_MVS_LOAD_OPTIONS).tag(sync=True) 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Jochem Smit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/mwe_solara.py: -------------------------------------------------------------------------------- 1 | import solara 2 | from ipymolstar import PDBeMolstar, MolViewSpec 3 | from molviewspec import create_builder 4 | 5 | 6 | @solara.component 7 | def Page(): 8 | solara.Title("Solara + PDBeMolstar + MolViewSpec") 9 | molecule_id = solara.use_reactive("1qyn") 10 | 11 | builder = create_builder() 12 | ( 13 | builder.download(url=f"https://files.rcsb.org/download/{molecule_id.value}.pdb") 14 | .parse(format="pdb") 15 | .model_structure() 16 | .component() 17 | .representation() 18 | .color(color="blue") 19 | ) 20 | 21 | with solara.Sidebar(): 22 | solara.InputText(label="Molecule ID", value=molecule_id) 23 | 24 | with solara.Columns(): 25 | with solara.Card("PDBeMolstar"): 26 | PDBeMolstar.element( 27 | hide_controls_icon=True, 28 | hide_expand_icon=True, 29 | hide_settings_icon=True, 30 | hide_selection_icon=True, 31 | molecule_id=molecule_id.value, 32 | ) 33 | with solara.Card("MolViewSpec"): 34 | MolViewSpec.element(msvj_data=builder.get_state().dumps()) 35 | -------------------------------------------------------------------------------- /tests/test_unit.py: -------------------------------------------------------------------------------- 1 | from ipymolstar import PDBeMolstar, MolViewSpec 2 | from molviewspec import create_builder 3 | 4 | 5 | def test_pdbemolstar(): 6 | """Test the PDBeMolstar component""" 7 | # Create a PDBeMolstar instance 8 | pdbe_molstar = PDBeMolstar( 9 | molecule_id="1qyn", 10 | hide_controls_icon=True, 11 | hide_expand_icon=True, 12 | hide_settings_icon=True, 13 | hide_selection_icon=True, 14 | ) 15 | # Check if the instance is created successfully 16 | assert isinstance(pdbe_molstar, PDBeMolstar), ( 17 | "Failed to create PDBeMolstar instance" 18 | ) 19 | 20 | 21 | def test_molviewspec(): 22 | """Test the MolViewSpec component""" 23 | # Create a MolViewSpec instance 24 | builder = create_builder() 25 | ( 26 | builder.download(url=f"https://files.rcsb.org/download/1qyn.pdb") 27 | .parse(format="pdb") 28 | .model_structure() 29 | .component() 30 | .representation() 31 | .color(color="blue") 32 | ) 33 | 34 | molview_spec = MolViewSpec( 35 | msvj_data=builder.get_state().dumps(), 36 | ) 37 | # Check if the instance is created successfully 38 | assert isinstance(molview_spec, MolViewSpec), ( 39 | "Failed to create MolViewSpec instance" 40 | ) 41 | -------------------------------------------------------------------------------- /js/molviewspec.js: -------------------------------------------------------------------------------- 1 | // import "./widget.css"; 2 | import 'molstar/build/viewer/molstar.css'; 3 | import { Viewer, PluginExtensions } from 'molstar/build/viewer/molstar' 4 | 5 | 6 | function updateFromMSVJ(viewer, msvj, options) { 7 | const mvsData = PluginExtensions.mvs.MVSData.fromMVSJ(msvj); 8 | PluginExtensions.mvs.loadMVS(viewer.plugin, mvsData, options); 9 | } 10 | 11 | function render({ model, el }) { 12 | const uniqueId = `viewer_container_${Math.random().toString(36).slice(2, 11)}`; 13 | let viewerContainer = document.createElement("div"); 14 | viewerContainer.id = uniqueId; 15 | 16 | viewerContainer.style.height = model.get("height"); 17 | viewerContainer.style.width = model.get("width"); 18 | 19 | // Make the container responsive 20 | viewerContainer.style.maxWidth = "100%"; 21 | viewerContainer.style.boxSizing = "border-box"; 22 | 23 | let viewer = null; 24 | 25 | const ViewerOptions = model.get("viewer_options"); 26 | 27 | // Initialize the viewer first 28 | Viewer.create(viewerContainer, ViewerOptions).then(v => { 29 | viewer = v; 30 | // If we have an initial schema, load it 31 | const msvj = model.get("msvj_data"); 32 | if (msvj && msvj.trim() !== "") { 33 | const options = model.get('mvs_load_options') 34 | updateFromMSVJ(viewer, msvj, options) 35 | }; 36 | 37 | }); 38 | 39 | el.appendChild(viewerContainer); 40 | 41 | model.on("change:msvj_data", () => { 42 | console.log("Schema changed"); 43 | const msvj = model.get("msvj_data"); 44 | if (msvj && msvj.trim() !== "") { 45 | const options = model.get('mvs_load_options') 46 | updateFromMSVJ(viewer, msvj, options) 47 | }; 48 | }); 49 | 50 | // Watch for changes to dimensions 51 | model.on("change:height", () => { 52 | viewerContainer.style.height = model.get("height"); 53 | }); 54 | 55 | model.on("change:width", () => { 56 | viewerContainer.style.width = model.get("width"); 57 | }); 58 | } 59 | 60 | export default { render }; 61 | -------------------------------------------------------------------------------- /examples/pdbemolstar/rainbow_residues.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import solara 4 | import solara.lab 5 | from Bio.PDB import PDBParser, Structure 6 | from ipymolstar import PDBeMolstar 7 | from matplotlib import colormaps 8 | from matplotlib.colors import Normalize 9 | 10 | AMINO_ACIDS = [ 11 | "ALA", 12 | "ARG", 13 | "ASN", 14 | "ASP", 15 | "CYS", 16 | "GLN", 17 | "GLU", 18 | "GLY", 19 | "HIS", 20 | "ILE", 21 | "LEU", 22 | "LYS", 23 | "MET", 24 | "PHE", 25 | "PRO", 26 | "PYL", 27 | "SEC", 28 | "SER", 29 | "THR", 30 | "TRP", 31 | "TYR", 32 | "VAL", 33 | ] 34 | 35 | # %% 36 | 37 | root = Path(__file__).parent.parent 38 | pdb_path = root / "assets" / "1qyn.pdb" 39 | 40 | parser = PDBParser(QUIET=True) 41 | structure = parser.get_structure("1qyn", pdb_path) 42 | MAX_R = max(r.id[1] for r in structure.get_residues()) 43 | 44 | # %% 45 | 46 | custom_data = {"data": pdb_path.read_bytes(), "format": "pdb", "binary": False} 47 | 48 | 49 | # %% 50 | def color_residues( 51 | structure: Structure.Structure, auth: bool = False, phase: int = 0 52 | ) -> dict: 53 | _, resn, _ = zip( 54 | *[r.id for r in structure.get_residues() if r.get_resname() in AMINO_ACIDS] 55 | ) 56 | 57 | rmin, rmax = min(resn), max(resn) 58 | # todo check for off by one errors 59 | norm = Normalize(vmin=rmin, vmax=rmax) 60 | auth_str = "_auth" if auth else "" 61 | 62 | cmap = colormaps["hsv"] 63 | data = [] 64 | for i in range(rmin, rmax + 1): 65 | range_size = rmax + 1 - rmin 66 | j = rmin + ((i - rmin + phase) % range_size) 67 | r, g, b, a = cmap(norm(i), bytes=True) 68 | color = {"r": int(r), "g": int(g), "b": int(b)} 69 | elem = { 70 | f"start{auth_str}_residue_number": j, 71 | f"end{auth_str}_residue_number": j, 72 | "color": color, 73 | "focus": False, 74 | } 75 | data.append(elem) 76 | 77 | color_data = {"data": data, "nonSelectedColor": None} 78 | return color_data 79 | 80 | 81 | @solara.component 82 | def Page(): 83 | phase = solara.use_reactive(0.0) 84 | color_data = color_residues(structure, auth=True, phase=phase.value) 85 | with solara.Card(): 86 | PDBeMolstar.element( 87 | custom_data=custom_data, hide_water=True, color_data=color_data 88 | ) 89 | solara.FloatSlider(label="Phase", min=0, max=MAX_R, value=phase, step=1) 90 | 91 | 92 | Page() 93 | -------------------------------------------------------------------------------- /examples/pdbemolstar/click_events.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example shows to how use click interactions 3 | View on pycafé: https://app.py.cafe/jhsmit/ipymolstar-click-interaction 4 | """ 5 | 6 | from string import Template 7 | 8 | import solara 9 | from ipymolstar import PDBeMolstar 10 | 11 | url_template = Template( 12 | "https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/CID/$cid/record/SDF?record_type=3d&response_type=save&response_basename=Conformer3D_COMPOUND_CID_$cid" 13 | ) 14 | 15 | 16 | AA_LUT = { 17 | "ARG": 6322, 18 | "HIS": 6274, 19 | "LYS": 5962, 20 | "ASP": 5960, 21 | "GLU": 33032, 22 | "SER": 5951, 23 | "THR": 6288, 24 | "ASN": 6267, 25 | "GLN": 5961, 26 | "CYS": 5862, 27 | "SEC": 6326983, 28 | "GLY": 750, 29 | "PRO": 145742, 30 | "ALA": 5950, 31 | "VAL": 6287, 32 | "ILE": 6306, 33 | "LEU": 6106, 34 | "MET": 6137, 35 | "PHE": 6140, 36 | "TYR": 6057, 37 | "TRP": 6305, 38 | } 39 | 40 | custom_data_initial = dict( 41 | url=url_template.substitute(cid=AA_LUT["TRP"]), 42 | format="sdf", 43 | binary=False, 44 | ) 45 | 46 | 47 | molecule_id = "1QYN" 48 | 49 | # %% 50 | s = """empty 51 | -ISIS- 52 | 53 | 0 0 0 0 0 0 0 0 0 0999 V2000 54 | M END 55 | $$$$ 56 | """ 57 | 58 | b = s.encode() 59 | empty_data = { 60 | "data": b, 61 | "format": "sdf", 62 | "binary": True, 63 | } 64 | 65 | # %% 66 | 67 | 68 | @solara.component 69 | def Page(): 70 | solara.Title("ipymolstar - Click Events") 71 | click_event = solara.use_reactive(None) 72 | custom_data = solara.use_reactive(empty_data) 73 | molecule_id = solara.use_reactive("1QYN") 74 | amino_acid = solara.use_reactive("") 75 | 76 | def on_click(event): 77 | click_event.set(event) 78 | aa_tla = event["comp_id"] 79 | 80 | if aa_tla in AA_LUT: 81 | amino_acid.set(aa_tla) 82 | custom_data.set( 83 | dict( 84 | url=url_template.substitute(cid=AA_LUT[aa_tla]), 85 | format="sdf", 86 | binary=False, 87 | ) 88 | ) 89 | else: 90 | amino_acid.set("") 91 | custom_data.set(empty_data) 92 | 93 | with solara.Sidebar(): 94 | solara.InputText( 95 | label="Molecule ID", value=molecule_id, on_value=molecule_id.set 96 | ) 97 | 98 | with solara.Columns(): 99 | with solara.Card(f"Protein: {molecule_id.value}"): 100 | PDBeMolstar.element( # type: ignore 101 | molecule_id=molecule_id.value.lower(), 102 | hide_controls_icon=True, 103 | hide_expand_icon=True, 104 | hide_settings_icon=True, 105 | hide_selection_icon=False, 106 | select_interaction=False, # dont show local interactions on click 107 | click_focus=False, # dont zoom on click 108 | on_click_event=on_click, 109 | ) 110 | 111 | with solara.Card(f"Amino Acid: {amino_acid.value}"): 112 | PDBeMolstar.element( # type: ignore 113 | molecule_id="", 114 | custom_data=custom_data.value, 115 | hide_controls_icon=True, 116 | hide_expand_icon=True, 117 | hide_settings_icon=True, 118 | hide_selection_icon=False, 119 | click_focus=False, 120 | ) 121 | -------------------------------------------------------------------------------- /tests/_test_notebook.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Manual tests of PDBeMolstar\n", 8 | "\n", 9 | "Run cells and confirm is described effect is observed" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": null, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "from ipymolstar import PDBeMolstar\n", 19 | "from ipywidgets import VBox\n" 20 | ] 21 | }, 22 | { 23 | "cell_type": "markdown", 24 | "metadata": {}, 25 | "source": [ 26 | "## height\n", 27 | "expected: vbox with two viewers with 200/400 px height" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": null, 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "\n", 37 | "v1 = PDBeMolstar(molecule_id='1qyn', height=\"200px\")\n", 38 | "v2 = PDBeMolstar(molecule_id='1qyn', height=\"400px\")\n", 39 | "VBox([v1, v2])\n" 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": null, 45 | "metadata": {}, 46 | "outputs": [], 47 | "source": [ 48 | "#using widgets layout\n", 49 | "PDBeMolstar(molecule_id='1qyn',layout=Layout(width='500px', height='250px')) " 50 | ] 51 | }, 52 | { 53 | "cell_type": "markdown", 54 | "metadata": {}, 55 | "source": [ 56 | "## molecule_id\n", 57 | "expected: Initial load of '1qyn', rerender to '2nnu' on trigger" 58 | ] 59 | }, 60 | { 61 | "cell_type": "code", 62 | "execution_count": null, 63 | "metadata": {}, 64 | "outputs": [], 65 | "source": [ 66 | "v = PDBeMolstar(molecule_id='1qyn', height='150px')\n", 67 | "v" 68 | ] 69 | }, 70 | { 71 | "cell_type": "code", 72 | "execution_count": null, 73 | "metadata": {}, 74 | "outputs": [], 75 | "source": [ 76 | "v.molecule_id = '2nnu'" 77 | ] 78 | }, 79 | { 80 | "cell_type": "markdown", 81 | "metadata": {}, 82 | "source": [ 83 | "# custom_data\n", 84 | "expected: Initial load of `1cbs`, rerender to chainA of `1qyn` on trigger (trigger does not work)" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": null, 90 | "metadata": {}, 91 | "outputs": [], 92 | "source": [ 93 | "custom_data = dict(url='https://www.ebi.ac.uk/pdbe/model-server/v1/1cbs/atoms?label_entity_id=1&auth_asym_id=A&encoding=bcif', format='cif', binary=True)\n", 94 | "v = PDBeMolstar(custom_data=custom_data, height='150px', alphafold_view=True)\n", 95 | "v" 96 | ] 97 | }, 98 | { 99 | "cell_type": "code", 100 | "execution_count": null, 101 | "metadata": {}, 102 | "outputs": [], 103 | "source": [ 104 | "new_custom_data = dict(url='https://www.ebi.ac.uk/pdbe/model-server/v1/1qyn/atoms?label_entity_id=1&auth_asym_id=A&encoding=bcif', format='cif', binary=True)\n", 105 | "v.custom_data = new_custom_data" 106 | ] 107 | }, 108 | { 109 | "cell_type": "code", 110 | "execution_count": null, 111 | "metadata": {}, 112 | "outputs": [], 113 | "source": [ 114 | "\n", 115 | "#alphafold colors and tooltips\n", 116 | "custom_data = dict(url='https://alphafold.ebi.ac.uk/files/AF-Q8I3H7-F1-model_v4.cif', format='cif', binary=False)\n", 117 | "v = PDBeMolstar(custom_data=custom_data, height='150px', alphafold_view=True)\n", 118 | "\n", 119 | "v" 120 | ] 121 | }, 122 | { 123 | "cell_type": "code", 124 | "execution_count": null, 125 | "metadata": {}, 126 | "outputs": [], 127 | "source": [ 128 | "from pathlib import Path \n", 129 | "fpth = Path().resolve().parent / 'assets' / '6vsb.bcif'\n", 130 | "custom_data = {\n", 131 | " 'data': fpth.read_bytes(),\n", 132 | " 'format': 'cif',\n", 133 | " 'binary': True,\n", 134 | " }\n", 135 | "view = PDBeMolstar(\n", 136 | " custom_data=custom_data, \n", 137 | " hide_controls_icon=True, \n", 138 | " hide_expand_icon=True, \n", 139 | " hide_settings_icon=True, \n", 140 | " hide_selection_icon=True, \n", 141 | " hide_animation_icon=True,\n", 142 | " hide_water=True,\n", 143 | " hide_carbs=True,\n", 144 | ")\n", 145 | "view" 146 | ] 147 | } 148 | ], 149 | "metadata": { 150 | "kernelspec": { 151 | "display_name": ".venv", 152 | "language": "python", 153 | "name": "python3" 154 | }, 155 | "language_info": { 156 | "codemirror_mode": { 157 | "name": "ipython", 158 | "version": 3 159 | }, 160 | "file_extension": ".py", 161 | "mimetype": "text/x-python", 162 | "name": "python", 163 | "nbconvert_exporter": "python", 164 | "pygments_lexer": "ipython3", 165 | "version": "3.10.13" 166 | } 167 | }, 168 | "nbformat": 4, 169 | "nbformat_minor": 4 170 | } 171 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ipymolstar 2 | 3 | ![image](https://github.com/Jhsmit/ipymolstar/assets/7881506/589a94d5-2647-4977-90aa-c886c10cacb9) 4 | 5 | 6 | ## Live Demos 7 | 8 | Give `ipymolstar` a spin without even installing python! 9 | 10 | 11 | - Try it in jupyter lab via [JupyterLite](https://github.com/Jhsmit/ipymolstar-demo) 🌍🚀 12 | - Explore the solara ☀️ demo application on [HuggingFace](https://huggingface.co/spaces/Jhsmit/ipymolstar-demo) 🤗 13 | - Grab a cup and play with the [solara](https://app.py.cafe/jhsmit/ipymolstar-solara), [pyshiny](https://py.cafe/jhsmit/ipymolstar-shiny) or [panel](https://app.py.cafe/jhsmit/ipymolstar-panel) live demo's on PyCafé ☕ 14 | - Upload your Alphafold3 .zip result and view plddt or chain colors in the `solarafold` result viewer on [huggingface](https://huggingface.co/spaces/Jhsmit/solarafold) 🤗 15 | - Control the camera 📷 with [MolViewSpec](https://pypi.org/project/molviewspec/) from python 🐍 in a solara ☀️ dashboard on [PyCafé](https://py.cafe/jhsmit/molviewspec-protein-visualization) ☕ 16 | - Use [MolViewSpec](https://pypi.org/project/molviewspec/) to define primitives 🟦🟢 and animations 🎞️, check out the Double Hooke [3DPGA dynamics](https://enki.ws/ganja.js/examples/pga_dyn.html) example on [PyCafé](https://py.cafe/jhsmit/ipymolstar-kingdon-double-hooke) ☕, adapted from the [kingdon](https://github.com/tBuLi/kingdon) teahouse 🍵; 17 | 18 | You can find other examples in the examples directory. 19 | 20 | ## Preview 21 | 22 | You can run a quick preview of `ipymolstar` in a notebook with [juv](https://github.com/manzt/juv): 23 | 24 | ```sh 25 | juv run example.ipynb 26 | ``` 27 | 28 | ## Installation 29 | 30 | ```sh 31 | pip install ipymolstar 32 | ``` 33 | > [!WARNING] 34 | > Make sure you install ipymolstar in an environment that contains your installation of Jupyter. If you have installed Jupyter in a different environment from your project (requiring you to use a named, non-default kernel), you will have to install ipymolstar (or only anywidget) in your Jupyter environment as well. 35 | 36 | 37 | ## Use 38 | 39 | ```python 40 | from ipymolstar import PDBeMolstar 41 | view = PDBeMolstar(molecule_id='1qyn', theme='light', hide_water=True) 42 | view 43 | ``` 44 | 45 | Loading local data, hiding the buttons: 46 | 47 | ```python 48 | from pathlib import Path 49 | fpth = Path().resolve() / 'assets' / '6vsb.bcif' 50 | custom_data = { 51 | 'data': fpth.read_bytes(), 52 | 'format': 'cif', 53 | 'binary': True, 54 | } 55 | view = PDBeMolstar( 56 | custom_data=custom_data, 57 | hide_controls_icon=True, 58 | hide_expand_icon=True, 59 | hide_settings_icon=True, 60 | hide_selection_icon=True, 61 | hide_animation_icon=True, 62 | hide_water=True, 63 | hide_carbs=True, 64 | ) 65 | view 66 | ``` 67 | 68 | See the example notebook for more advanced usage. 69 | Solara example code can be found [here](https://github.com/Jhsmit/ploomber-solara-ipymolstar) 70 | 71 | ## Citing 72 | 73 | `ipymolstar` uses [anywidget](https://github.com/manzt/anywidget) to create a widgets based on [Mol*](https://molstar.org/) 74 | 75 | To cite `anywidget`: 76 | > Trevor Manz, Nezar Abdennur, Nils Gehlenborg: anywidget: reusable widgets for interactive analysis and visualization in computational notebooks. Journal of Open Source Software, 2024; [10.21105/joss.06939](https://doi.org/10.21105/joss.06939) 77 | 78 | To cite Mol*: 79 | > David Sehnal, Sebastian Bittrich, Mandar Deshpande, Radka Svobodová, Karel Berka, Václav Bazgier, Sameer Velankar, Stephen K Burley, Jaroslav Koča, Alexander S Rose: Mol* Viewer: modern web app for 3D visualization and analysis of large biomolecular structures, Nucleic Acids Research, 2021; [10.1093/nar/gkab31](https://doi.org/10.1093/nar/gkab314). 80 | 81 | 82 | ### PDBeMolstar 83 | The PDBeMolstar widget is based on the [PDBe integration](https://github.com/molstar/pdbe-molstar) of Mol*. 84 | 85 | 86 | ### MolViewSpec 87 | 88 | The MolViewSpec widget is based on [MolViewSpec](https://github.com/molstar/mol-view-spec). To cite MolViewSpec: 89 | 90 | > Sebastian Bittrich, Adam Midlik, Mihaly Varadi, Sameer Velankar, Stephen K. Burley, Jasmine Y. Young, David Sehnal, Brinda Vallat: Describing and Sharing Molecular Visualizations Using the MolViewSpec Toolkit, Current Protocols, 2024; [10.1002/cpz1.1099](https://doi.org/10.1002/cpz1.1099) 91 | 92 | See also the [RCSB citation policies](https://www.rcsb.org/pages/policies) for additional citation information. 93 | 94 | ## Development 95 | 96 | 97 | The molviewspec widget front-end code bundles it's JavaScript dependencies. After setting up Python, 98 | make sure to install these dependencies locally: 99 | 100 | ```sh 101 | npm install 102 | ``` 103 | 104 | While developing, you can run the following in a separate terminal to automatically 105 | rebuild JavaScript as you make changes: 106 | 107 | ```sh 108 | npm run dev 109 | ``` 110 | 111 | 112 | ### Creating a new release 113 | 114 | - update `__version__` in `__init__.py` 115 | - create a new release on GitHub, choose as tag 'v' + `__version__`; ie 'v0.0.3' 116 | - GitHub actions should automatically deploy to PyPi 117 | 118 | ### Hot reloading 119 | 120 | To enable anywidget hot reloading, you need to set the env var `ANYWIDGET_HMR` to 1. 121 | 122 | Windows: 123 | ```bash 124 | set ANYWIDGET_HMR=1 125 | jupyter lab 126 | ``` 127 | -------------------------------------------------------------------------------- /examples/molviewspec/solara_cameracontrol.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import math 3 | import warnings 4 | from io import StringIO 5 | 6 | import numpy as np 7 | import requests 8 | import solara 9 | import solara.lab 10 | from Bio.PDB import PDBParser 11 | from Bio.PDB.PDBExceptions import PDBConstructionWarning 12 | from molviewspec import GlobalMetadata, create_builder 13 | 14 | from ipymolstar.molviewspec import MolViewSpec 15 | 16 | # Suppress only PDBConstructionWarnings 17 | warnings.simplefilter("ignore", PDBConstructionWarning) 18 | 19 | 20 | # https://github.com/molstar/mol-view-spec/blob/def8ad6cdc351dbe01e29bf717e58e004bd10408/molviewspec/app/api/examples.py#L1819 21 | def target_spherical_to_tpu( 22 | target: tuple[float, float, float], 23 | phi: float = 0, 24 | theta: float = 0, 25 | radius: float = 100, 26 | ): 27 | x, y, z = target 28 | phi, theta = math.radians(phi), math.radians(theta) 29 | direction = ( 30 | -math.sin(phi) * math.cos(theta), 31 | -math.sin(theta), 32 | -math.cos(phi) * math.cos(theta), 33 | ) 34 | position = ( 35 | x - direction[0] * radius, 36 | y - direction[1] * radius, 37 | z - direction[2] * radius, 38 | ) 39 | up = (0, 1, 0) 40 | return target, position, up 41 | 42 | 43 | def fetch_pdb(pdb_id) -> StringIO: 44 | url = f"https://files.rcsb.org/download/{pdb_id}.pdb" 45 | response = requests.get(url) 46 | if response.status_code == 200: 47 | sio = StringIO(response.text) 48 | sio.seek(0) 49 | return sio 50 | else: 51 | raise requests.HTTPError(f"Failed to download PDB file {pdb_id}") 52 | 53 | 54 | def load_structure(pdb_id: str): 55 | parser = PDBParser() 56 | structure = parser.get_structure(pdb_id, fetch_pdb(pdb_id)) 57 | 58 | return structure 59 | 60 | 61 | # %% 62 | def calculate_com(structure) -> tuple[float, float, float]: 63 | # Collect C-alpha atom coordinates 64 | ca_coords = [] 65 | 66 | for model in structure: 67 | for chain in model: 68 | for residue in chain: 69 | if "CA" in residue: # Check for C-alpha atom 70 | ca_coords.append(residue["CA"].coord) 71 | 72 | # Compute average (center of mass) 73 | ca_coords = np.array(ca_coords) 74 | return tuple(float(f) for f in np.mean(ca_coords, axis=0)) 75 | 76 | 77 | metadata = GlobalMetadata( 78 | title=None, 79 | description=None, 80 | description_format=None, 81 | ) 82 | 83 | 84 | @solara.component 85 | def Page(): 86 | pdb_id = solara.use_reactive("1qyn") 87 | radius = solara.use_reactive(100.0) 88 | phi = solara.use_reactive(0.0) 89 | theta = solara.use_reactive(0.0) 90 | 91 | with solara.AppBar(): 92 | solara.AppBarTitle("MolViewSpec + Solara: Camera control from python") 93 | 94 | def load_structure_and_com(): 95 | builder = create_builder() 96 | ( 97 | builder.download(url=f"https://files.rcsb.org/download/{pdb_id.value}.pdb") 98 | .parse(format="pdb") 99 | .model_structure() 100 | .component() 101 | .representation() 102 | .color(color="blue") 103 | ) 104 | 105 | structure = load_structure(pdb_id.value) 106 | com = calculate_com(structure) 107 | 108 | return builder, com 109 | 110 | load_task = solara.lab.use_task(load_structure_and_com, dependencies=[pdb_id.value]) 111 | 112 | def rot_phi(value): 113 | new_value = (phi.value + value) % 360 114 | phi.set(new_value) 115 | 116 | def rot_theta(value): 117 | new_value = (theta.value + value) % 360 118 | theta.set(new_value) 119 | 120 | with solara.ColumnsResponsive([4, 8]): 121 | with solara.Card("Controls"): 122 | solara.InputText(label="PDB ID", value=pdb_id) 123 | solara.SliderFloat(label="Radius", value=radius, min=50, max=200, step=0.5) 124 | solara.SliderFloat(label="Phi", value=phi, min=0, max=360, step=1) 125 | solara.SliderFloat(label="Theta", value=theta, min=0, max=360, step=1) 126 | with solara.Row(justify="space-between"): 127 | solara.Button(label="-90° y", on_click=lambda: rot_phi(-90)) 128 | solara.Button(label="+90° y", on_click=lambda: rot_phi(90)) 129 | solara.Button(label="-90° x", on_click=lambda: rot_theta(-90)) 130 | solara.Button(label="+90° x", on_click=lambda: rot_theta(90)) 131 | 132 | with solara.Card("Protein view"): 133 | if load_task.latest is None: 134 | solara.Markdown("Loading...") 135 | else: 136 | builder, com = ( 137 | load_task.value if load_task.finished else load_task.latest 138 | ) 139 | 140 | target, position, up = target_spherical_to_tpu( 141 | target=com, 142 | phi=phi.value, 143 | theta=theta.value, 144 | radius=radius.value, 145 | ) 146 | local_builder = builder.model_copy() 147 | local_builder.camera(target=target, position=position, up=up) 148 | 149 | msvj_data = local_builder.get_state().dumps() 150 | with solara.Div(style="opacity: 0.3" if load_task.pending else None): 151 | view = MolViewSpec.element(msvj_data=msvj_data) 152 | -------------------------------------------------------------------------------- /examples/pdbemolstar/mouseover_highlight.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example solara app with two-way hover/highlight between ipymolstar and altair 3 | View on pycafé: https://app.py.cafe/jhsmit/ipymolstar-altair-hover-highlight 4 | """ 5 | 6 | # %% 7 | from pathlib import Path 8 | 9 | import altair as alt 10 | import numpy as np 11 | import pandas as pd 12 | import solara 13 | from cmap import Colormap 14 | from ipymolstar import PDBeMolstar 15 | 16 | # color limits in kJ/mol 17 | VMIN = 10 18 | VMAX = 40 19 | NO_COVERAGE = "#8c8c8c" 20 | HIGHLIGHT_COLOR = "#e933f8" 21 | cmap = Colormap("tol:rainbow_PuRd_r", bad=NO_COVERAGE) 22 | domain = np.linspace(VMIN, VMAX, 256, endpoint=True) 23 | scale = alt.Scale(domain=list(domain), range=cmap.to_altair()) 24 | 25 | # %% 26 | 27 | 28 | def norm(x, vmin=VMIN, vmax=VMAX): 29 | return (x - vmin) / (vmax - vmin) 30 | 31 | 32 | # %% 33 | script_path = Path(__file__).parent 34 | kwargs = {"comment": "#", "header": [0]} 35 | data = pd.read_csv(script_path / "pyhdx_secb_deltaG.csv", **kwargs).rename( 36 | columns={"r_number": "residue"} 37 | ) 38 | data = data.drop(data.index[-1])[["residue", "deltaG"]] 39 | data["deltaG"] *= 1e-3 40 | 41 | 42 | # %% 43 | cmap = Colormap("tol:rainbow_PuRd_r", bad=NO_COVERAGE) 44 | 45 | rgba_array = cmap(norm(data["deltaG"]), bytes=True) 46 | base_v = np.vectorize(np.base_repr) 47 | ints = rgba_array.astype(np.uint8).view(dtype=np.uint32).byteswap() 48 | padded = np.char.rjust(base_v(ints // 2**8, 16), 6, "0") 49 | hex_colors = np.char.add("#", padded).squeeze() 50 | 51 | # %% 52 | color_data = { 53 | "data": [ 54 | {"residue_number": resi, "color": hcolor.lower()} 55 | for resi, hcolor in zip(data["residue"], hex_colors) 56 | ], 57 | "nonSelectedColor": NO_COVERAGE, 58 | } 59 | 60 | # %% 61 | 62 | tooltips = { 63 | "data": [ 64 | { 65 | "residue_number": resi, 66 | "tooltip": f"ΔG: {value:.2f} kJ/mol" 67 | if not np.isnan(value) 68 | else "No coverage", 69 | } 70 | for resi, value in zip(data["residue"], data["deltaG"]) 71 | ] 72 | } 73 | 74 | 75 | # Create a selection that chooses the nearest point & selects based on x-value 76 | nearest = alt.selection_point( 77 | name="point", 78 | nearest=True, 79 | on="pointerover", 80 | fields=["residue"], 81 | empty=False, 82 | clear="mouseout", 83 | ) 84 | 85 | pad = (VMAX - VMIN) * 0.05 86 | 87 | # The basic scatter 88 | scatter = ( 89 | alt.Chart(data) 90 | .mark_circle(interpolate="basis", size=200) 91 | .encode( 92 | x=alt.X("residue:Q", title="Residue Number"), 93 | y=alt.Y( 94 | "deltaG:Q", 95 | title="ΔG (kJ/mol)", 96 | scale=alt.Scale(domain=(VMAX + pad, VMIN - pad)), 97 | ), 98 | color=alt.Color("deltaG:Q", scale=scale, title="ΔG (kJ/mol)"), 99 | ) 100 | ) 101 | 102 | # %% 103 | 104 | # Transparent selectors across the chart. This is what tells us 105 | # the x-value of the cursor 106 | selectors = ( 107 | alt.Chart(data) 108 | .mark_point() 109 | .encode( 110 | x="residue:Q", 111 | opacity=alt.value(0), 112 | ) 113 | .add_params(nearest) 114 | ) 115 | 116 | # Draw a rule at the location of the selection 117 | rule = ( 118 | alt.Chart(data) 119 | .mark_rule(color="gray", size=2) 120 | .encode( 121 | x="residue:Q", 122 | ) 123 | .transform_filter(nearest) 124 | ) 125 | 126 | vline = ( 127 | alt.Chart(pd.DataFrame({"x": [0]})) 128 | .mark_rule(color=HIGHLIGHT_COLOR, size=2) 129 | .encode(x="x:Q") 130 | ) 131 | 132 | 133 | # Put the five layers into a chart and bind the data 134 | chart = ( 135 | alt.layer(scatter, vline, selectors, rule).properties( 136 | width="container", 137 | height=480, # autosize height? 138 | ) 139 | # .configure(autosize="fit") 140 | ) 141 | 142 | spec = chart.to_dict() 143 | data_name = spec["layer"][1]["data"]["name"] 144 | 145 | 146 | @solara.component 147 | def SelectChart(on_selections, line_value): 148 | spec["datasets"][data_name] = [{"x": line_value}] 149 | view = alt.JupyterChart.element( 150 | chart=chart, spec=spec, embed_options={"actions": False} 151 | ) 152 | 153 | def bind(): 154 | real = solara.get_widget(view) 155 | real.selections.observe(on_selections, "point") 156 | 157 | solara.use_effect(bind, []) 158 | 159 | 160 | @solara.component 161 | def Page(): 162 | # residue number to highlight in altair chart 163 | line_number = solara.use_reactive(None) 164 | 165 | # residue number to highlight in protein view 166 | highlight_number = solara.use_reactive(None) 167 | with solara.AppBar(): 168 | solara.AppBarTitle("altair/ipymolstar bidirectional highlight") 169 | solara.Style( 170 | """ 171 | .vega-embed { 172 | overflow: visible; 173 | width: 100% !important; 174 | height: 800px !important; 175 | }""" 176 | ) 177 | 178 | def on_mouseover(value): 179 | r = value.get("residueNumber", None) 180 | line_number.set(r) 181 | 182 | def on_mouseout(value): 183 | on_mouseover({}) 184 | 185 | def on_selections(event): 186 | try: 187 | r = event["new"].value[0]["residue"] 188 | highlight_number.set(r) 189 | except (IndexError, KeyError): 190 | highlight_number.set(None) 191 | 192 | with solara.ColumnsResponsive([4, 8]): 193 | with solara.Card(style={"height": "550px"}): 194 | PDBeMolstar.element( 195 | molecule_id="1qyn", 196 | color_data=color_data, 197 | hide_water=True, 198 | tooltips=tooltips, 199 | height="500px", 200 | highlight={"data": [{"residue_number": int(highlight_number.value)}]} 201 | if highlight_number.value 202 | else None, 203 | highlight_color=HIGHLIGHT_COLOR, 204 | on_mouseover_event=on_mouseover, 205 | on_mouseout_event=on_mouseout, 206 | ) 207 | with solara.Card(style={"height": "550px"}): 208 | SelectChart(on_selections, line_number.value) 209 | 210 | 211 | # %% 212 | -------------------------------------------------------------------------------- /src/ipymolstar/panel/pdbemolstar.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | from typing import Optional 3 | 4 | from ipymolstar.pdbemolstar import THEMES, Color, QueryParam, ResetParam 5 | 6 | try: 7 | import panel as pn 8 | except ImportError: 9 | msg = "To use ipypmolstar as panel AnyWidgetComponent, the panel package needs to be installed" 10 | raise ImportError(msg) 11 | import param 12 | from panel.custom import AnyWidgetComponent 13 | 14 | 15 | class PDBeMolstar(AnyWidgetComponent): 16 | _esm = pathlib.Path(__file__).parent.parent / "static" / "pdbemolstar.js" 17 | _stylesheets = [ 18 | str(pathlib.Path(__file__).parent.parent / "static" / "pdbe-light.css") 19 | ] 20 | 21 | width = param.String(default="800px") 22 | height = param.String(default="500px") 23 | 24 | molecule_id = param.String() 25 | custom_data = param.Dict(default=None, allow_None=True) 26 | assembly_id = param.String(default="") 27 | default_preset = param.Selector( 28 | default="default", objects=["default", "unitcell", "all-models", "supercell"] 29 | ) 30 | ligand_view = param.Dict(default=None) 31 | alphafold_view = param.Boolean(default=False) 32 | superposition = param.Boolean(default=False) 33 | superposition_params = param.Dict(default=None) 34 | visual_style = param.Selector( 35 | default=None, 36 | objects=[ 37 | "cartoon", 38 | "ball-and-stick", 39 | "carbohydrate", 40 | "ellipsoid", 41 | "gaussian-surface", 42 | "molecular-surface", 43 | "point", 44 | "putty", 45 | "spacefill", 46 | ], 47 | ) 48 | hide_polymer = param.Boolean(default=False) 49 | hide_water = param.Boolean(default=False) 50 | hide_heteroatoms = param.Boolean(default=False) 51 | hide_carbs = param.Boolean(default=False) 52 | hide_non_standard = param.Boolean(default=False) 53 | hide_coarse = param.Boolean(default=False) 54 | load_maps = param.Boolean(default=False) 55 | map_settings = param.Dict(default=None) 56 | bg_color = param.String(default="") 57 | highlight_color = param.String(default="#FF6699") 58 | select_color = param.String(default="#33FF19") 59 | lighting = param.Selector( 60 | default=None, objects=["flat", "matte", "glossy", "metallic", "plastic"] 61 | ) 62 | validation_annotation = param.Boolean(default=False) 63 | domain_annotation = param.Boolean(default=False) 64 | symmetry_annotation = param.Boolean(default=False) 65 | pdbe_url = param.String(default="https://www.ebi.ac.uk/pdbe/") 66 | encoding = param.Selector(default="bcif", objects=["bcif", "cif"]) 67 | low_precision_coords = param.Boolean(default=False) 68 | select_interaction = param.Boolean(default=True) 69 | granularity = param.Selector( 70 | default="residue", 71 | objects=[ 72 | "element", 73 | "residue", 74 | "chain", 75 | "entity", 76 | "model", 77 | "operator", 78 | "structure", 79 | "elementInstances", 80 | "residueInstances", 81 | "chainInstances", 82 | ], 83 | ) 84 | subscribe_events = param.Boolean(default=False) 85 | hide_controls = param.Boolean(default=True) 86 | hide_controls_icon = param.Boolean(default=False) 87 | hide_expand_icon = param.Boolean(default=False) 88 | hide_settings_icon = param.Boolean(default=False) 89 | hide_selection_icon = param.Boolean(default=False) 90 | hide_animation_icon = param.Boolean(default=False) 91 | sequence_panel = param.Boolean(default=False) 92 | pdbe_link = param.Boolean(default=True) 93 | loading_overlay = param.Boolean(default=False) 94 | expanded = param.Boolean(default=False) 95 | landscape = param.Boolean(default=False) 96 | reactive = param.Boolean(default=False) 97 | 98 | spin = param.Boolean(default=False) 99 | _focus = param.List(default=None) 100 | highlight = param.Dict(default=None) 101 | _clear_highlight = param.Boolean(default=False) 102 | color_data = param.Dict(default=None) 103 | _clear_selection = param.Boolean(default=False) 104 | tooltips = param.Dict(default=None) 105 | _clear_tooltips = param.Boolean(default=False) 106 | _set_color = param.Dict(default=None) 107 | _reset = param.Dict(default=None) 108 | _update = param.Dict(default=None) 109 | _args = param.Dict(default={}) 110 | 111 | mouseover_event = param.Dict(default={}) 112 | mouseout_event = param.Boolean(default=False) 113 | click_event = param.Dict(default={}) 114 | click_focus = param.Boolean(default=True) 115 | 116 | def __init__(self, theme="light", **params): 117 | _stylesheets = [THEMES[theme]["css"]] 118 | bg_color = params.pop("bg_color", THEMES[theme]["bg_color"]) 119 | self._stylesheets = _stylesheets # shouldnt work but it does 120 | 121 | super().__init__(bg_color=bg_color, **params) 122 | 123 | def color( 124 | self, 125 | data: list[QueryParam], 126 | non_selected_color=None, 127 | keep_colors=False, 128 | keep_representations=False, 129 | ) -> None: 130 | """ 131 | Alias for PDBE Molstar's `select` method. 132 | 133 | See https://github.com/molstar/pdbe-molstar/wiki/3.-Helper-Methods for parameter 134 | details 135 | """ 136 | 137 | self.color_data = { 138 | "data": data, 139 | "nonSelectedColor": non_selected_color, 140 | "keepColors": keep_colors, 141 | "keepRepresentations": keep_representations, 142 | } 143 | self.color_data = None 144 | 145 | def focus(self, data: list[QueryParam]): 146 | self._focus = data 147 | self._focus = None 148 | 149 | def clear_highlight(self): 150 | self._clear_highlight = not self._clear_highlight 151 | 152 | def clear_tooltips(self): 153 | self._clear_tooltips = not self._clear_tooltips 154 | 155 | def clear_selection(self, structure_number=None): 156 | # move payload to the traitlet which triggers the callback 157 | self._args = {"number": structure_number} 158 | self._clear_selection = not self._clear_selection 159 | 160 | # todo make two traits: select_color, hightlight_color 161 | def set_color( 162 | self, highlight: Optional[Color] = None, select: Optional[Color] = None 163 | ): 164 | data = {} 165 | if highlight is not None: 166 | data["highlight"] = highlight 167 | if select is not None: 168 | data["select"] = select 169 | if data: 170 | self._set_color = data 171 | self._set_color = None 172 | 173 | def reset(self, data: ResetParam): 174 | self._reset = data 175 | self._reset = None 176 | 177 | def update(self, data): 178 | self._update = data 179 | self._update = None 180 | -------------------------------------------------------------------------------- /examples/molviewspec/double_hooke.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is an example from the Kingdon Teahouse (https://github.com/tBuLi/teahouse); adapted for ipymolstar. 3 | 4 | Original example is from the ["May The Forque Be With You"](https://enki.ws/ganja.js/examples/pga_dyn.html) implementation supplement to [PGAdyn](https://bivector.net/PGADYN.html). 5 | 6 | """ 7 | 8 | from ipymolstar.molviewspec import DEFAULT_MVS_LOAD_OPTIONS, MolViewSpec 9 | from molviewspec import create_builder, States, GlobalMetadata 10 | from kingdon import Algebra, MultiVector 11 | 12 | from functools import reduce 13 | from operator import add 14 | 15 | 16 | alg = Algebra(3, 0, 1) 17 | globals().update(alg.blades) 18 | bds = alg.blades 19 | # %% 20 | 21 | 22 | def rsum(values): 23 | return reduce(add, values) 24 | 25 | 26 | def dist_pp(P1, P2) -> float: # point to point 27 | return (P1.normalized() & P2.normalized()).norm().e 28 | 29 | 30 | def sort_nn_dist(points: list[MultiVector]) -> list[MultiVector]: 31 | """Sort points by nearest neighbor.""" 32 | p_list = points.copy() 33 | current = p_list.pop(0) 34 | output = [current] 35 | while p_list: 36 | next = min(p_list, key=lambda p: dist_pp(current, p)) 37 | output.append(next) 38 | p_list.remove(next) 39 | current = next 40 | return output 41 | 42 | 43 | def xyz(point: MultiVector) -> tuple: 44 | """Convert a MultiVector to a tuple.""" 45 | return tuple(point.undual().values()[1:]) 46 | 47 | 48 | class Scene: 49 | def __init__(self): 50 | self.builder = create_builder() 51 | 52 | def append(self, mv: MultiVector | list[MultiVector], **kwargs): 53 | """Append a MultiVector to the scene.""" 54 | # self.builder.append(mv, **kwargs) 55 | if isinstance(mv, list): 56 | # all points 57 | if all(m.grades == (3,) for m in mv): 58 | if len(mv) == 2: 59 | # line 60 | points = [tuple(m.undual().values()[1:]) for m in mv] 61 | self.line(points[0], points[1], **kwargs) 62 | if len(mv) == 3: 63 | points = [tuple(m.undual().values()[1:]) for m in mv] 64 | self.triangle(points, **kwargs) 65 | if len(mv) == 4: 66 | self.quadrilateral(mv, **kwargs) 67 | 68 | elif isinstance(mv, MultiVector): 69 | # single point 70 | if mv.grades == (3,): 71 | coords = tuple(mv.undual().values()[1:]) 72 | self.point(*coords, **kwargs) 73 | 74 | def quadrilateral(self, points: list[MultiVector], color="blue", **kwargs): 75 | p_sort = sort_nn_dist(points) 76 | vertices = [c for mv in p_sort for c in xyz(mv)] 77 | indices = [0, 1, 2, 0, 2, 3, 2, 1, 0, 3, 2, 0] 78 | self.primitives().mesh( 79 | vertices=vertices, 80 | indices=indices, 81 | triangle_groups=[0], # All triangles belong to the same group 82 | color=color, 83 | **kwargs, 84 | ) 85 | 86 | def primitives(self, **kwargs): 87 | return self.builder.primitives(**kwargs) 88 | 89 | def axis_arrows(self, opacity=1.0): 90 | return ( 91 | self.primitives(opacity=opacity) 92 | .arrow( 93 | start=(0, 0, 0), 94 | end=(1, 0, 0), 95 | color="red", 96 | tooltip="X", 97 | show_end_cap=True, 98 | ) 99 | .arrow( 100 | start=(0, 0, 0), 101 | end=(0, 1, 0), 102 | color="green", 103 | tooltip="Y", 104 | show_end_cap=True, 105 | ) 106 | .arrow( 107 | start=(0, 0, 0), 108 | end=(0, 0, 1), 109 | color="blue", 110 | tooltip="Z", 111 | show_end_cap=True, 112 | ) 113 | ) 114 | 115 | def point(self, x, y, z, size=0.1, color="red", **kwargs): 116 | """Create a point in 3D space.""" 117 | return self.primitives().sphere( 118 | center=(x, y, z), radius=size, color=color, **kwargs 119 | ) 120 | 121 | def line(self, start: tuple, end: tuple, size=0.02, color="black"): 122 | return self.primitives().tube( 123 | start=start, 124 | end=end, 125 | color=color, 126 | radius=size, 127 | ) 128 | 129 | def triangle(self, points: list[tuple], color="blue"): 130 | """Create a triangle in 3D space.""" 131 | return self.primitives().mesh( 132 | vertices=[coord for point in points for coord in point], 133 | indices=[0, 1, 2, 2, 1, 0], 134 | triangle_groups=[0], 135 | color=color, 136 | show_triangles=True, 137 | show_wireframe=False, 138 | ) 139 | 140 | @property 141 | def msvj_data(self): 142 | return self.builder.get_state() 143 | 144 | 145 | # %% 146 | 147 | 148 | def RK4(f, y, h): 149 | k1 = f(*y) 150 | k2 = f(*[yi + 0.5 * h * k1i for yi, k1i in zip(y, k1)]) 151 | k3 = f(*[yi + 0.5 * h * k2i for yi, k2i in zip(y, k2)]) 152 | k4 = f(*[yi + h * k3i for yi, k3i in zip(y, k3)]) 153 | return [ 154 | yi + (h / 3) * (k2i + k3i + (k1i + k4i) * 0.5) 155 | for yi, k1i, k2i, k3i, k4i in zip(y, k1, k2, k3, k4) 156 | ] 157 | 158 | 159 | # %% 160 | 161 | d = 3 162 | size = [0.2, 1, 0.2] 163 | vertexes = [ 164 | alg.vector([1, *[s * (((i >> j) % 2) - 0.5) for j, s in enumerate(size)]]).dual() 165 | for i in range(2**d) 166 | ] 167 | 168 | attach_1 = vertexes[5] + alg.blades.e2.dual() 169 | attach_2 = vertexes[1] + alg.blades.e2.dual() + 0.5 * alg.blades.e1.dual() 170 | 171 | initial_state = [1 - 0.5 * bds.e02, 38.35 * bds.e13 + 35.27 * bds.e12 - 5 * bds.e01] 172 | 173 | 174 | faces = [ 175 | (0, 1, 2, 3), 176 | (4, 5, 6, 7), 177 | (0, 1, 4, 5), 178 | (2, 3, 6, 7), 179 | (0, 2, 4, 6), 180 | (1, 3, 5, 7), 181 | ] 182 | 183 | face_colors = ["CC00FF", "0400ff", "fbff00", "ffa200", "44ff00", "00aaff"] 184 | 185 | 186 | mass = 1 187 | I = ( 188 | 1 189 | / 12 190 | * mass 191 | * ( 192 | (size[1] ** 2 + size[2] ** 2) * bds.e01 193 | + (size[2] ** 2 + size[0] ** 2) * bds.e02 194 | + (size[0] ** 2 + size[1] ** 2) * bds.e03 195 | + 12 * bds.e12 196 | + 12 * bds.e13 197 | + 12 * bds.e23 198 | ) 199 | ) 200 | 201 | A = lambda B: B.dual().map(lambda k, v: v * getattr(I, alg.bin2canon[k])) 202 | Ai = lambda B: B.map(lambda k, v: v / getattr(I, alg.bin2canon[k])).undual() 203 | 204 | 205 | @alg.register(symbolic=True) 206 | def forques(M, B): 207 | Gravity = (~M >> -9.81 * bds.e02).dual() 208 | Damping = -0.25 * B.grade(2).dual() 209 | Hooke = -8 * (~M >> attach_1) & vertexes[5] 210 | Hooke2 = -8 * (~M >> attach_2) & vertexes[1] 211 | return (Gravity + Hooke + Hooke2 + Damping).grade( 212 | 2 213 | ) # Ensure a pure bivector because in kingdon<=1.1.0, >> doesn't. Maybe this will change in the future. 214 | 215 | 216 | # Change in M and B 217 | dState = lambda M, B: [-0.5 * M * B, Ai(forques(M, B) - A(B).cp(B))] 218 | 219 | # %% 220 | metadata = GlobalMetadata( 221 | title="beam_hook_law", 222 | description="a block dangling from two strings", 223 | description_format=None, 224 | ) 225 | snapshots = [] 226 | 227 | state = initial_state 228 | 229 | for i in range(500): 230 | scene = Scene() 231 | upd_vertexes = [state[0] >> point for point in vertexes] 232 | 233 | for c, face in zip(face_colors, faces): 234 | f_vertices = [upd_vertexes[i] for i in face] 235 | scene.quadrilateral(f_vertices, color=f"#{c.lower()}") 236 | 237 | scene.point( 238 | *xyz(attach_1.normalized()), size=0.1, color="green", tooltip="attach_1" 239 | ) 240 | scene.point( 241 | *xyz(attach_2.normalized()), size=0.1, color="green", tooltip="attach_2" 242 | ) 243 | 244 | scene.append([attach_1, upd_vertexes[0]], color="black", size=0.02) 245 | scene.append([attach_2, upd_vertexes[1]], color="black", size=0.02) 246 | 247 | snapshot = scene.builder.get_snapshot( 248 | title=str(i), linger_duration_ms=2, transition_duration_ms=2 249 | ) 250 | snapshots.append(snapshot) 251 | 252 | # update the state 253 | state = RK4(dState, state, 0.01) 254 | 255 | states = States( 256 | snapshots=snapshots, 257 | metadata=metadata, 258 | ).dumps() 259 | 260 | 261 | # %% 262 | mvs_load_options = {"keepSnapshotCamera": True} | DEFAULT_MVS_LOAD_OPTIONS 263 | view = MolViewSpec(msvj_data=states, mvs_load_options=mvs_load_options) 264 | view 265 | 266 | 267 | # %% 268 | -------------------------------------------------------------------------------- /src/ipymolstar/static/pdbemolstar.js: -------------------------------------------------------------------------------- 1 | import "https://cdn.jsdelivr.net/npm/pdbe-molstar@3.3.2/build/pdbe-molstar-plugin.js"; 2 | 3 | function standardize_color(str) { 4 | var ctx = document.createElement("canvas").getContext("2d"); 5 | ctx.fillStyle = str; 6 | return ctx.fillStyle; 7 | } 8 | function toRgb(color) { 9 | var hex = standardize_color(color); 10 | var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 11 | return result 12 | ? { 13 | r: parseInt(result[1], 16), 14 | g: parseInt(result[2], 16), 15 | b: parseInt(result[3], 16), 16 | } 17 | : null; 18 | } 19 | 20 | function getHideStructure(model) { 21 | var hideStructure = []; 22 | 23 | if (model.get("hide_polymer")) { 24 | hideStructure.push("polymer"); 25 | } 26 | if (model.get("hide_water")) { 27 | hideStructure.push("water"); 28 | } 29 | if (model.get("hide_heteroatoms")) { 30 | hideStructure.push("het"); 31 | } 32 | if (model.get("hide_carbs")) { 33 | hideStructure.push("carbs"); 34 | } 35 | if (model.get("hide_non_standard")) { 36 | hideStructure.push("nonStandard"); 37 | } 38 | if (model.get("hide_coarse")) { 39 | hideStructure.push("coarse"); 40 | } 41 | 42 | return hideStructure; 43 | } 44 | 45 | function getVisibility(model) { 46 | var visibility = { 47 | polymer: !model.get("hide_polymer"), 48 | het: !model.get("hide_heteroatoms"), 49 | water: !model.get("hide_water"), 50 | carbs: !model.get("hide_carbs"), 51 | // maps ? 52 | }; 53 | 54 | return visibility; 55 | } 56 | 57 | function getHideCanvasControls(model) { 58 | var hideCanvasControls = []; 59 | if (model.get("hide_controls_icon")) { 60 | hideCanvasControls.push("controlToggle"); 61 | } 62 | if (model.get("hide_expand_icon")) { 63 | hideCanvasControls.push("expand"); 64 | } 65 | if (model.get("hide_settings_icon")) { 66 | hideCanvasControls.push("controlInfo"); 67 | } 68 | if (model.get("hide_selection_icon")) { 69 | hideCanvasControls.push("selection"); 70 | } 71 | if (model.get("hide_animation_icon")) { 72 | hideCanvasControls.push("animation"); 73 | } 74 | 75 | return hideCanvasControls; 76 | } 77 | 78 | function getCustomData(model) { 79 | var customData = model.get("custom_data"); 80 | 81 | if (customData && 'data' in customData) { 82 | var url = URL.createObjectURL(new Blob([customData.data])); 83 | customData.url = url; 84 | delete customData.data; 85 | } 86 | 87 | return customData; 88 | } 89 | 90 | const EmptyFocusBindings = { 91 | clickCenterFocus: { triggers: [], action: '', description: '' }, 92 | clickCenterFocusSelectMode: { triggers: [], action: '', description: '' }, 93 | clickResetCameraOnEmpty: { triggers: [], action: '', description: '' }, 94 | clickResetCameraOnEmptySelectMode: { triggers: [], action: '', description: '' } 95 | }; 96 | 97 | 98 | function getOptions(model) { 99 | var options = { 100 | moleculeId: model.get("molecule_id"), 101 | customData: getCustomData(model), 102 | assemblyId: model.get("assembly_id"), 103 | defaultPreset: model.get("default_preset"), 104 | ligandView: model.get("ligand_view"), 105 | alphafoldView: model.get("alphafold_view"), 106 | superposition: model.get("superposition"), 107 | superpositionParams: model.get("superposition_params"), 108 | visualStyle: model.get("visual_style"), 109 | loadMaps: model.get("load_maps"), 110 | bgColor: toRgb(model.get("bg_color")), 111 | selectColor: toRgb(model.get("select_color")), 112 | lighting: model.get("lighting"), 113 | validationAnnotation: model.get("validation_annotation"), 114 | symmetryAnnotation: model.get("symmetry_annotation"), 115 | pdbeUrl: model.get("pdbe_url"), 116 | encoding: model.get("encoding"), 117 | lowPrecisionCoords: model.get("low_precision_coords"), 118 | selectInteraction: model.get("select_interaction"), 119 | // selectBindings: EmptySelectBindings, 120 | granularity: model.get("granularity"), 121 | subscribeEvents: model.get("subscribe_events"), 122 | hideControls: model.get("hide_controls"), 123 | hideCanvasControls: getHideCanvasControls(model), 124 | sequencePanel: model.get("sequence_panel"), 125 | pdbeLink: model.get("pdbe_link"), 126 | loadingOverlay: model.get("loading_overlay"), 127 | expanded: model.get("expanded"), 128 | landscape: model.get("landscape"), 129 | reactive: model.get("reactive"), 130 | }; 131 | 132 | if (model.get('click_focus') == false) { 133 | options.focusBindings = EmptyFocusBindings; 134 | } 135 | 136 | return options; 137 | } 138 | 139 | function subscribe(model, name, callback) { 140 | model.on(name, callback); 141 | return () => model.off(name, callback); 142 | } 143 | 144 | function render({ model, el }) { 145 | let viewerContainer = document.createElement("div"); 146 | viewerContainer.id = "viewer_container"; 147 | 148 | viewerContainer.style.height = model.get("height"); 149 | viewerContainer.style.width = model.get("width"); 150 | 151 | // Make the container responsive 152 | viewerContainer.style.maxWidth = "100%"; 153 | viewerContainer.style.boxSizing = "border-box"; 154 | 155 | var viewerInstance = new window.PDBeMolstarPlugin(); 156 | viewerInstance.render(viewerContainer, getOptions(model)); //.then(() => { 157 | el.appendChild(viewerContainer); 158 | 159 | // callbacks to be called after loading is complete 160 | let callbacksLoadComplete = { 161 | "change:spin": () => viewerInstance.visual.toggleSpin(model.get("spin")), 162 | "change:hide_polymer": () => { 163 | viewerInstance.visual.visibility({ polymer: !model.get("hide_polymer") }); 164 | }, 165 | "change:hide_water": () => { 166 | viewerInstance.visual.visibility({ water: !model.get("hide_water") }); 167 | }, 168 | "change:hide_heteroatoms": () => { 169 | viewerInstance.visual.visibility({ het: !model.get("hide_heteroatoms") }); 170 | }, 171 | "change:hide_carbs": () => { 172 | viewerInstance.visual.visibility({ carbs: !model.get("hide_carbs") }); 173 | }, 174 | "change:hide_non_standard": () => { 175 | viewerInstance.visual.visibility({ 176 | nonStandard: !model.get("hide_non_standard"), 177 | }); 178 | }, 179 | "change:hide_coarse": () => { 180 | viewerInstance.visual.visibility({ coarse: !model.get("hide_coarse") }); 181 | }, 182 | "change:color_data": () => { 183 | const selectValue = model.get("color_data"); 184 | if (selectValue !== null) { 185 | viewerInstance.visual.select(selectValue); 186 | } 187 | }, 188 | "change:highlight": () => { 189 | const highlightValue = model.get("highlight"); 190 | if (highlightValue !== null) { 191 | viewerInstance.visual.highlight(highlightValue); 192 | } 193 | }, 194 | "change:highlight_color": () => { 195 | const highlightColorValue = model.get("highlight_color"); 196 | if (highlightColorValue !== null) { 197 | viewerInstance.visual.setColor({ highlight: highlightColorValue }); 198 | } 199 | }, 200 | "change:tooltips": () => { 201 | const tooltipValue = model.get("tooltips"); 202 | if (tooltipValue !== null) { 203 | viewerInstance.visual.tooltips(tooltipValue); 204 | } 205 | }, 206 | }; 207 | 208 | let otherCallbacks = { 209 | "change:molecule_id": () => { 210 | viewerInstance.visual.update(getOptions(model), true); 211 | }, 212 | "change:custom_data": () => { 213 | viewerInstance.visual.update(getOptions(model), true); 214 | }, 215 | "change:visual_style": () => { 216 | viewerInstance.visual.update(getOptions(model), true); 217 | }, 218 | "change:expanded": () => { 219 | viewerInstance.canvas.toggleExpanded(model.get("expanded")); 220 | }, 221 | "change:bg_color": () => { 222 | viewerInstance.canvas.setBgColor(toRgb(model.get("bg_color"))); 223 | }, 224 | "change:_reset": () => { 225 | const resetValue = model.get("_reset"); 226 | if (resetValue !== null) { 227 | viewerInstance.visual.reset(resetValue); 228 | } 229 | }, 230 | "change:_clear_tooltips": () => { 231 | viewerInstance.visual.clearTooltips(); 232 | } 233 | }; 234 | 235 | let combinedCallbacks = Object.assign( 236 | {}, 237 | callbacksLoadComplete, 238 | otherCallbacks 239 | ); 240 | 241 | viewerInstance.events.loadComplete.subscribe(() => { 242 | // trigger callabacks which need to be called after loading 243 | Object.values(callbacksLoadComplete).forEach((callback) => callback()); 244 | }); 245 | 246 | // subscribe to events and collect unsubscribe funcs 247 | let unsubscribes = Object.entries(combinedCallbacks).map(([name, callback]) => 248 | subscribe(model, name, callback) 249 | ); 250 | 251 | document.addEventListener("PDB.molstar.mouseover", (e) => { 252 | const eventData = e.eventData; 253 | model.set("mouseover_event", eventData); 254 | model.save_changes(); 255 | }); 256 | 257 | document.addEventListener("PDB.molstar.mouseout", (e) => { 258 | model.set("mouseout_event", !model.get("mouseout_event") ); 259 | model.save_changes(); 260 | }); 261 | 262 | document.addEventListener("PDB.molstar.click", (e) => { 263 | const eventData = e.eventData; 264 | model.set("click_event", eventData); 265 | model.save_changes(); 266 | }); 267 | 268 | return () => { 269 | unsubscribes.forEach((unsubscribe) => unsubscribe()); 270 | }; 271 | } 272 | 273 | export default { render }; 274 | -------------------------------------------------------------------------------- /src/ipymolstar/pdbemolstar.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | from typing import Any, List, Optional, TypedDict 3 | 4 | import anywidget 5 | import traitlets 6 | 7 | THEMES = { 8 | "light": { 9 | "bg_color": "#F7F7F7", 10 | "css": ( 11 | pathlib.Path(__file__).parent / "static" / "pdbe-light.css" 12 | ).read_text(), 13 | }, 14 | "dark": { 15 | "bg_color": "#111111", 16 | "css": (pathlib.Path(__file__).parent / "static" / "pdbe-dark.css").read_text(), 17 | }, 18 | } 19 | 20 | 21 | class Color(TypedDict): 22 | r: int 23 | g: int 24 | b: int 25 | 26 | 27 | # codeieum translation of QueryParam from 28 | # https://github.com/molstar/pdbe-molstar/blob/master/src/app/helpers.ts#L180 29 | class QueryParam(TypedDict, total=False): 30 | auth_seq_id: Optional[int] 31 | entity_id: Optional[str] 32 | auth_asym_id: Optional[str] 33 | struct_asym_id: Optional[str] 34 | residue_number: Optional[int] 35 | start_residue_number: Optional[int] 36 | end_residue_number: Optional[int] 37 | auth_residue_number: Optional[int] 38 | auth_ins_code_id: Optional[str] 39 | start_auth_residue_number: Optional[int] 40 | start_auth_ins_code_id: Optional[str] 41 | end_auth_residue_number: Optional[int] 42 | end_auth_ins_code_id: Optional[str] 43 | atoms: Optional[List[str]] 44 | label_comp_id: Optional[str] 45 | color: Optional[Color] 46 | sideChain: Optional[bool] 47 | representation: Optional[str] 48 | representationColor: Optional[Color] 49 | focus: Optional[bool] 50 | tooltip: Optional[str] 51 | start: Optional[Any] 52 | end: Optional[Any] 53 | atom_id: Optional[List[int]] 54 | uniprot_accession: Optional[str] 55 | uniprot_residue_number: Optional[int] 56 | start_uniprot_residue_number: Optional[int] 57 | end_uniprot_residue_number: Optional[int] 58 | 59 | 60 | class ResetParam(TypedDict, total=False): 61 | camera: Optional[bool] 62 | theme: Optional[bool] 63 | highlightColor: Optional[bool] 64 | selectColor: Optional[bool] 65 | 66 | 67 | class PDBeMolstar(anywidget.AnyWidget): 68 | _esm = pathlib.Path(__file__).parent / "static" / "pdbemolstar.js" 69 | _css = pathlib.Path(__file__).parent / "static" / "pdbe-light.css" 70 | 71 | width = traitlets.Unicode("100%").tag(sync=True) 72 | height = traitlets.Unicode("500px").tag(sync=True) 73 | 74 | molecule_id = traitlets.Unicode().tag(sync=True) 75 | custom_data = traitlets.Dict(default_value=None, allow_none=True).tag(sync=True) 76 | assembly_id = traitlets.Unicode().tag(sync=True) 77 | default_preset = traitlets.Enum( 78 | ["default", "unitcell", "all-models", "supercell"], 79 | default_value="default", 80 | ).tag(sync=True) 81 | ligand_view = traitlets.Dict(default_value=None, allow_none=True).tag(sync=True) 82 | alphafold_view = traitlets.Bool(default_value=False).tag(sync=True) 83 | superposition = traitlets.Bool(default_value=False).tag(sync=True) 84 | superposition_params = traitlets.Dict(default_value=None, allow_none=True).tag( 85 | sync=True 86 | ) 87 | visual_style = traitlets.Enum( 88 | [ 89 | "cartoon", 90 | "ball-and-stick", 91 | "carbohydrate", 92 | "ellipsoid", 93 | "gaussian-surface", 94 | "molecular-surface", 95 | "point", 96 | "putty", 97 | "spacefill", 98 | ], 99 | default_value=None, 100 | allow_none=True, 101 | ).tag(sync=True) 102 | hide_polymer = traitlets.Bool(False).tag(sync=True) 103 | hide_water = traitlets.Bool(False).tag(sync=True) 104 | hide_heteroatoms = traitlets.Bool(False).tag(sync=True) 105 | hide_carbs = traitlets.Bool(False).tag(sync=True) 106 | hide_non_standard = traitlets.Bool(False).tag(sync=True) 107 | hide_coarse = traitlets.Bool(False).tag(sync=True) 108 | load_maps = traitlets.Bool(False).tag(sync=True) 109 | map_settings = traitlets.Dict(default_value=None, allow_none=True).tag(sync=True) 110 | bg_color = traitlets.Unicode().tag(sync=True) 111 | highlight_color = traitlets.Unicode("#FF6699").tag(sync=True) 112 | select_color = traitlets.Unicode("#33FF19").tag(sync=True) 113 | lighting = traitlets.Enum( 114 | ["flat", "matte", "glossy", "metallic", "plastic"], 115 | default_value=None, 116 | allow_none=True, 117 | ).tag(sync=True) 118 | validation_annotation = traitlets.Bool(False).tag(sync=True) 119 | domain_annotation = traitlets.Bool(False).tag(sync=True) 120 | symmetry_annotation = traitlets.Bool(False).tag(sync=True) 121 | pdbe_url = traitlets.Unicode("https://www.ebi.ac.uk/pdbe/").tag(sync=True) 122 | encoding = traitlets.Enum(["bcif", "cif"], default_value="bcif").tag(sync=True) 123 | low_precision_coords = traitlets.Bool(False).tag(sync=True) 124 | select_interaction = traitlets.Bool(True).tag(sync=True) 125 | granularity = traitlets.Enum( 126 | [ 127 | "element", 128 | "residue", 129 | "chain", 130 | "entity", 131 | "model", 132 | "operator", 133 | "structure", 134 | "elementInstances", 135 | "residueInstances", 136 | "chainInstances", 137 | ], 138 | default_value="residue", 139 | ).tag(sync=True) 140 | subscribe_events = traitlets.Bool(False).tag(sync=True) 141 | hide_controls = traitlets.Bool(True).tag(sync=True) 142 | hide_controls_icon = traitlets.Bool(False).tag(sync=True) 143 | hide_expand_icon = traitlets.Bool(False).tag(sync=True) 144 | hide_settings_icon = traitlets.Bool(False).tag(sync=True) 145 | hide_selection_icon = traitlets.Bool(False).tag(sync=True) 146 | hide_animation_icon = traitlets.Bool(False).tag(sync=True) 147 | sequence_panel = traitlets.Bool(False).tag(sync=True) 148 | pdbe_link = traitlets.Bool(True).tag(sync=True) 149 | loading_overlay = traitlets.Bool(False).tag(sync=True) 150 | expanded = traitlets.Bool(False).tag(sync=True) 151 | landscape = traitlets.Bool(False).tag(sync=True) 152 | reactive = traitlets.Bool(False).tag(sync=True) 153 | 154 | spin = traitlets.Bool(False).tag(sync=True) 155 | _focus = traitlets.List(default_value=None, allow_none=True).tag(sync=True) 156 | highlight = traitlets.Dict(default_value=None, allow_none=True).tag(sync=True) 157 | _clear_highlight = traitlets.Bool(default_value=False).tag(sync=True) 158 | color_data = traitlets.Dict(default_value=None, allow_none=True).tag(sync=True) 159 | _clear_selection = traitlets.Bool(default_value=False).tag(sync=True) 160 | tooltips = traitlets.Dict(default_value=None, allow_none=True).tag(sync=True) 161 | _clear_tooltips = traitlets.Bool(default_value=False).tag(sync=True) 162 | _set_color = traitlets.Dict(default_value=None, allow_none=True).tag(sync=True) 163 | _reset = traitlets.Dict(allow_none=True, default_value=None).tag(sync=True) 164 | _update = traitlets.Dict(allow_none=True, default_value=None).tag(sync=True) 165 | 166 | _args = traitlets.Dict().tag(sync=True) 167 | 168 | mouseover_event = traitlets.Dict().tag(sync=True) 169 | mouseout_event = traitlets.Bool().tag(sync=True) 170 | click_event = traitlets.Dict().tag(sync=True) 171 | click_focus = traitlets.Bool(True).tag(sync=True) 172 | 173 | def __init__(self, theme="light", **kwargs): 174 | _css = THEMES[theme]["css"] 175 | bg_color = kwargs.pop("bg_color", THEMES[theme]["bg_color"]) 176 | super().__init__(_css=_css, bg_color=bg_color, **kwargs) 177 | 178 | def color( 179 | self, 180 | data: list[QueryParam], 181 | non_selected_color=None, 182 | keep_colors=False, 183 | keep_representations=False, 184 | ) -> None: 185 | """ 186 | Alias for PDBE Molstar's `select` method. 187 | 188 | See https://github.com/molstar/pdbe-molstar/wiki/3.-Helper-Methods for parameter 189 | details 190 | """ 191 | 192 | self.color_data = { 193 | "data": data, 194 | "nonSelectedColor": non_selected_color, 195 | "keepColors": keep_colors, 196 | "keepRepresentations": keep_representations, 197 | } 198 | self.color_data = None 199 | 200 | def focus(self, data: list[QueryParam]): 201 | self._focus = data 202 | self._focus = None 203 | 204 | def clear_highlight(self): 205 | self._clear_highlight = not self._clear_highlight 206 | 207 | def clear_tooltips(self): 208 | self._clear_tooltips = not self._clear_tooltips 209 | 210 | def clear_selection(self, structure_number=None): 211 | # move payload to the traitlet which triggers the callback 212 | self._args = {"number": structure_number} 213 | self._clear_selection = not self._clear_selection 214 | 215 | # todo make two traits: select_color, hightlight_color 216 | def set_color( 217 | self, highlight: Optional[Color] = None, select: Optional[Color] = None 218 | ): 219 | data = {} 220 | if highlight is not None: 221 | data["highlight"] = highlight 222 | if select is not None: 223 | data["select"] = select 224 | if data: 225 | self._set_color = data 226 | self._set_color = None 227 | 228 | def reset(self, data: ResetParam): 229 | self._reset = data 230 | self._reset = None 231 | 232 | def update(self, data): 233 | self._update = data 234 | self._update = None 235 | -------------------------------------------------------------------------------- /examples/pdbemolstar/panel_app.py: -------------------------------------------------------------------------------- 1 | import statistics 2 | from io import StringIO 3 | 4 | import panel as pn 5 | import param 6 | import requests 7 | from Bio.PDB import PDBParser, Residue, Structure 8 | from ipymolstar.panel import PDBeMolstar 9 | from matplotlib import colormaps 10 | from matplotlib.colors import Normalize 11 | 12 | theme = "light" if pn.config.theme == "default" else "dark" 13 | 14 | parser = PDBParser(QUIET=True) 15 | CHAIN_COLORS = [ 16 | "#1f77b4", 17 | "#ff7f0e", 18 | "#2ca02c", 19 | "#d62728", 20 | "#9467bd", 21 | "#8c564b", 22 | "#e377c2", 23 | "#7f7f7f", 24 | "#bcbd22", 25 | "#17becf", 26 | ] 27 | AMINO_ACIDS = [ 28 | "ALA", 29 | "ARG", 30 | "ASN", 31 | "ASP", 32 | "CYS", 33 | "GLN", 34 | "GLU", 35 | "GLY", 36 | "HIS", 37 | "ILE", 38 | "LEU", 39 | "LYS", 40 | "MET", 41 | "PHE", 42 | "PRO", 43 | "PYL", 44 | "SEC", 45 | "SER", 46 | "THR", 47 | "TRP", 48 | "TYR", 49 | "VAL", 50 | ] 51 | # use auth residue numbers or not 52 | AUTH_RESIDUE_NUMBERS = { 53 | "1QYN": False, 54 | "2PE4": True, 55 | } 56 | 57 | pdb_ids = ["1QYN", "2PE4"] 58 | 59 | 60 | def fetch_pdb(pdb_id) -> StringIO: 61 | url = f"https://files.rcsb.org/download/{pdb_id}.pdb" 62 | response = requests.get(url) 63 | if response.status_code == 200: 64 | sio = StringIO(response.text) 65 | sio.seek(0) 66 | return sio 67 | else: 68 | raise requests.HTTPError(f"Failed to download PDB file {pdb_id}") 69 | 70 | 71 | structures = {p_id: parser.get_structure(p_id, fetch_pdb(p_id)) for p_id in pdb_ids} 72 | 73 | 74 | def color_chains(structure: Structure.Structure) -> dict: 75 | data = [ 76 | { 77 | "struct_asym_id": chain.id, 78 | "color": hex_color, 79 | } 80 | for hex_color, chain in zip(CHAIN_COLORS, structure.get_chains()) 81 | ] 82 | 83 | color_data = {"data": data, "nonSelectedColor": None} 84 | return color_data 85 | 86 | 87 | def color_residues(structure: Structure.Structure, auth: bool = False) -> dict: 88 | _, resn, _ = zip( 89 | *[r.id for r in structure.get_residues() if r.get_resname() in AMINO_ACIDS] 90 | ) 91 | 92 | rmin, rmax = min(resn), max(resn) 93 | norm = Normalize(vmin=rmin, vmax=rmax) 94 | auth_str = "auth_" if auth else "" 95 | 96 | cmap = colormaps["rainbow"] 97 | data = [] 98 | for i in range(rmin, rmax): 99 | r, g, b, a = cmap(norm(i), bytes=True) 100 | color = {"r": int(r), "g": int(g), "b": int(b)} 101 | elem = { 102 | f"{auth_str}residue_number": i, 103 | "color": color, 104 | "focus": False, 105 | } 106 | data.append(elem) 107 | 108 | color_data = {"data": data, "nonSelectedColor": None} 109 | return color_data 110 | 111 | 112 | def get_bfactor(residue: Residue.Residue): 113 | """returns the residue-average b-factor""" 114 | return statistics.mean([atom.get_bfactor() for atom in residue]) 115 | 116 | 117 | def color_bfactor(structure: Structure.Structure, auth: bool = False) -> dict: 118 | auth_str = "auth_" if auth else "" 119 | value_data = [] 120 | for chain in structure.get_chains(): 121 | for r in chain.get_residues(): 122 | if r.get_resname() in AMINO_ACIDS: 123 | bfactor = get_bfactor(r) 124 | elem = { 125 | f"{auth_str}residue_number": r.id[1], 126 | "struct_asym_id": chain.id, 127 | "value": bfactor, 128 | } 129 | value_data.append(elem) 130 | 131 | all_values = [d["value"] for d in value_data] 132 | vmin, vmax = min(all_values), max(all_values) 133 | 134 | norm = Normalize(vmin=vmin, vmax=vmax) 135 | cmap = colormaps["inferno"] 136 | data = [] 137 | for v_elem in value_data: 138 | elem = v_elem.copy() 139 | r, g, b, a = cmap(norm(elem.pop("value")), bytes=True) 140 | elem["color"] = {"r": int(r), "g": int(g), "b": int(b)} 141 | data.append(elem) 142 | 143 | color_data = {"data": data, "nonSelectedColor": None} 144 | return color_data 145 | 146 | 147 | def apply_coloring(pdb_id: str, color_mode: str): 148 | structure = structures[pdb_id] 149 | auth = AUTH_RESIDUE_NUMBERS[pdb_id] 150 | if color_mode == "Chain": 151 | return color_chains(structure) 152 | elif color_mode == "Residue": 153 | return color_residues(structure, auth) 154 | elif color_mode == "β-factor": 155 | return color_bfactor(structure, auth) 156 | else: 157 | raise ValueError(f"Invalid color mode: {color_mode}") 158 | 159 | 160 | protein_store = ["1QYN", "2PE4"] 161 | molecule_store = { 162 | "Glucose": dict( 163 | url="https://pubchem.ncbi.nlm.nih.gov/rest/pug/conformers/000016A100000001/SDF?response_type=save&response_basename=Conformer3D_COMPOUND_CID_5793", 164 | format="sdf", 165 | binary=False, 166 | ), 167 | "ATP": dict( 168 | url="https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/CID/5957/record/SDF?record_type=3d&response_type=save&response_basename=Conformer3D_COMPOUND_CID_5957", 169 | format="sdf", 170 | binary=False, 171 | ), 172 | "Caffeine": dict( 173 | url="https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/CID/2519/record/SDF?record_type=3d&response_type=save&response_basename=Conformer3D_COMPOUND_CID_2519", 174 | format="sdf", 175 | binary=False, 176 | ), 177 | "Strychnine": dict( 178 | url=" https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/CID/441071/record/SDF?record_type=3d&response_type=save&response_basename=Conformer3D_COMPOUND_CID_441071 ", 179 | format="sdf", 180 | binary=False, 181 | ), 182 | } 183 | 184 | 185 | class Controller(param.Parameterized): 186 | structure_type = param.Selector( 187 | default="Protein", 188 | allow_None=False, 189 | objects=["Protein", "Molecule"], 190 | doc="Choose to display protein or a small molecule", 191 | ) 192 | 193 | protein_id = param.Selector( 194 | default=protein_store[0], 195 | objects=protein_store, 196 | doc="Protein to display", 197 | ) 198 | 199 | molecule_id = param.Selector( 200 | default="Glucose", 201 | objects=list(molecule_store.keys()), 202 | doc="Molecule to display", 203 | ) 204 | 205 | color_mode = param.Selector( 206 | default="Chain", 207 | objects=["Chain", "Residue", "β-factor"], 208 | doc="Coloring mode", 209 | ) 210 | 211 | def __init__(self, molstar_view: PDBeMolstar, **params): 212 | self.molstar_view = molstar_view 213 | super().__init__(**params) 214 | 215 | @param.depends("structure_type") 216 | def secondary_selector(self): 217 | if self.structure_type == "Protein": 218 | return pn.widgets.Select.from_param(self.param.protein_id, name="Protein") 219 | elif self.structure_type == "Molecule": 220 | return pn.widgets.Select.from_param(self.param.molecule_id, name="Molecule") 221 | else: 222 | return pn.pane.Str("Please make a selection") 223 | 224 | @param.depends("structure_type") 225 | def color_selector(self): 226 | if self.structure_type == "Protein": 227 | return pn.widgets.Select.from_param( 228 | self.param.color_mode, name="Color mode" 229 | ) 230 | return None 231 | 232 | @param.depends( 233 | "structure_type", "protein_id", "molecule_id", "color_mode", watch=True 234 | ) 235 | def update_molecule_data(self): 236 | if self.structure_type == "Protein": 237 | molecule_id = self.protein_id.lower() 238 | custom_data = None 239 | color_data = apply_coloring(self.protein_id, self.color_mode) 240 | else: 241 | molecule_id = "" 242 | custom_data = molecule_store[self.molecule_id] 243 | color_data = {"data": [], "nonSelectedColor": None} 244 | 245 | self.molstar_view.param.update( 246 | molecule_id=molecule_id, custom_data=custom_data, color_data=color_data 247 | ) 248 | 249 | 250 | color_data = apply_coloring(pdb_ids[0], "Chain") # initial value 251 | molstar = PDBeMolstar( 252 | molecule_id="1qyn", theme=theme, sizing_mode="stretch_width", color_data=color_data 253 | ) # , width="1150px") 254 | 255 | 256 | parameters = pn.Param( 257 | molstar, 258 | parameters=[ 259 | "spin", 260 | "visual_style", 261 | "hide_water", 262 | "hide_polymer", 263 | "hide_heteroatoms", 264 | "hide_carbs", 265 | "hide_non_standard", 266 | "hide_coarse", 267 | "bg_color", 268 | ], 269 | widgets={ 270 | "bg_color": {"type": pn.widgets.ColorPicker, "sizing_mode": "stretch_width"} 271 | }, 272 | show_name=False, 273 | ) 274 | 275 | ctrl = Controller(molstar) 276 | 277 | 278 | settings = pn.Column( 279 | pn.pane.Markdown("## Controls"), 280 | pn.widgets.Select.from_param(ctrl.param.structure_type, name="Structure type"), 281 | ctrl.secondary_selector, 282 | ctrl.color_selector, 283 | *parameters, 284 | ) 285 | view = pn.Column(molstar, sizing_mode="stretch_width") 286 | 287 | template = pn.template.FastListTemplate( 288 | title="ipymolstar - Panel", 289 | sidebar=[settings], 290 | main_max_width="1200px", 291 | ) 292 | 293 | template.main.append(view) 294 | 295 | if pn.state.served: 296 | template.servable() 297 | -------------------------------------------------------------------------------- /examples/pdbemolstar/solara_app.py: -------------------------------------------------------------------------------- 1 | import statistics 2 | from dataclasses import asdict, dataclass 3 | from io import StringIO 4 | from pathlib import Path 5 | 6 | import requests 7 | import solara 8 | import solara.lab 9 | from Bio.PDB import PDBParser, Residue, Structure 10 | from ipymolstar import PDBeMolstar 11 | from ipymolstar.pdbemolstar import THEMES 12 | from matplotlib import colormaps 13 | from matplotlib.colors import Normalize 14 | from solara.alias import rv 15 | 16 | parser = PDBParser(QUIET=True) 17 | CHAIN_COLORS = [ 18 | "#1f77b4", 19 | "#ff7f0e", 20 | "#2ca02c", 21 | "#d62728", 22 | "#9467bd", 23 | "#8c564b", 24 | "#e377c2", 25 | "#7f7f7f", 26 | "#bcbd22", 27 | "#17becf", 28 | ] 29 | AMINO_ACIDS = [ 30 | "ALA", 31 | "ARG", 32 | "ASN", 33 | "ASP", 34 | "CYS", 35 | "GLN", 36 | "GLU", 37 | "GLY", 38 | "HIS", 39 | "ILE", 40 | "LEU", 41 | "LYS", 42 | "MET", 43 | "PHE", 44 | "PRO", 45 | "PYL", 46 | "SEC", 47 | "SER", 48 | "THR", 49 | "TRP", 50 | "TYR", 51 | "VAL", 52 | ] 53 | # use auth residue numbers or not 54 | AUTH_RESIDUE_NUMBERS = { 55 | "1QYN": False, 56 | "2PE4": True, 57 | } 58 | 59 | pdb_ids = ["1QYN", "2PE4"] 60 | 61 | 62 | def fetch_pdb(pdb_id) -> StringIO: 63 | url = f"https://files.rcsb.org/download/{pdb_id}.pdb" 64 | response = requests.get(url) 65 | if response.status_code == 200: 66 | sio = StringIO(response.text) 67 | sio.seek(0) 68 | return sio 69 | else: 70 | raise requests.HTTPError(f"Failed to download PDB file {pdb_id}") 71 | 72 | 73 | structures = {p_id: parser.get_structure(p_id, fetch_pdb(p_id)) for p_id in pdb_ids} 74 | 75 | 76 | @dataclass 77 | class PDBeData: 78 | molecule_id: str = "1qyn" 79 | custom_data: dict | None = None 80 | color_data: dict | None = None 81 | bg_color: str = "#F7F7F7" 82 | spin: bool = False 83 | hide_polymer: bool = False 84 | hide_water: bool = False 85 | hide_heteroatoms: bool = False 86 | hide_carbs: bool = False 87 | hide_non_standard: bool = False 88 | hide_coarse: bool = False 89 | 90 | height: str = "700px" 91 | 92 | 93 | visibility_cbs = ["polymer", "water", "heteroatoms", "carbs"] 94 | 95 | 96 | def color_chains(structure: Structure.Structure) -> dict: 97 | data = [ 98 | { 99 | "struct_asym_id": chain.id, 100 | "color": hex_color, 101 | } 102 | for hex_color, chain in zip(CHAIN_COLORS, structure.get_chains()) 103 | ] 104 | 105 | color_data = {"data": data, "nonSelectedColor": None} 106 | return color_data 107 | 108 | 109 | def color_residues(structure: Structure.Structure, auth: bool = False) -> dict: 110 | _, resn, _ = zip( 111 | *[r.id for r in structure.get_residues() if r.get_resname() in AMINO_ACIDS] 112 | ) 113 | 114 | rmin, rmax = min(resn), max(resn) 115 | norm = Normalize(vmin=rmin, vmax=rmax) 116 | auth_str = "auth_" if auth else "" 117 | 118 | cmap = colormaps["rainbow"] 119 | data = [] 120 | for i in range(rmin, rmax): 121 | r, g, b, a = cmap(norm(i), bytes=True) 122 | color = {"r": int(r), "g": int(g), "b": int(b)} 123 | elem = { 124 | f"{auth_str}residue_number": i, 125 | "color": color, 126 | "focus": False, 127 | } 128 | data.append(elem) 129 | 130 | color_data = {"data": data, "nonSelectedColor": None} 131 | return color_data 132 | 133 | 134 | def get_bfactor(residue: Residue.Residue): 135 | """returns the residue-average b-factor""" 136 | return statistics.mean([atom.get_bfactor() for atom in residue]) 137 | 138 | 139 | def color_bfactor(structure: Structure.Structure, auth: bool = False) -> dict: 140 | auth_str = "auth_" if auth else "" 141 | value_data = [] 142 | for chain in structure.get_chains(): 143 | for r in chain.get_residues(): 144 | if r.get_resname() in AMINO_ACIDS: 145 | bfactor = get_bfactor(r) 146 | elem = { 147 | f"{auth_str}residue_number": r.id[1], 148 | "struct_asym_id": chain.id, 149 | "value": bfactor, 150 | } 151 | value_data.append(elem) 152 | 153 | all_values = [d["value"] for d in value_data] 154 | vmin, vmax = min(all_values), max(all_values) 155 | 156 | norm = Normalize(vmin=vmin, vmax=vmax) 157 | cmap = colormaps["inferno"] 158 | data = [] 159 | for v_elem in value_data: 160 | elem = v_elem.copy() 161 | r, g, b, a = cmap(norm(elem.pop("value")), bytes=True) 162 | elem["color"] = {"r": int(r), "g": int(g), "b": int(b)} 163 | data.append(elem) 164 | 165 | color_data = {"data": data, "nonSelectedColor": None} 166 | return color_data 167 | 168 | 169 | def apply_coloring(pdb_id: str, color_mode: str): 170 | structure = structures[pdb_id] 171 | auth = AUTH_RESIDUE_NUMBERS[pdb_id] 172 | if color_mode == "Chain": 173 | return color_chains(structure) 174 | elif color_mode == "Residue": 175 | return color_residues(structure, auth) 176 | elif color_mode == "β-factor": 177 | return color_bfactor(structure, auth) 178 | else: 179 | raise ValueError(f"Invalid color mode: {color_mode}") 180 | 181 | 182 | color_options = ["Chain", "Residue", "β-factor"] 183 | color_data = apply_coloring(pdb_ids[0], color_options[0]) 184 | data = solara.Reactive(PDBeData(color_data=color_data)) 185 | 186 | molecule_store = { 187 | "Glucose": dict( 188 | url="https://pubchem.ncbi.nlm.nih.gov/rest/pug/conformers/000016A100000001/SDF?response_type=save&response_basename=Conformer3D_COMPOUND_CID_5793", 189 | format="sdf", 190 | binary=False, 191 | ), 192 | "ATP": dict( 193 | url="https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/CID/5957/record/SDF?record_type=3d&response_type=save&response_basename=Conformer3D_COMPOUND_CID_5957", 194 | format="sdf", 195 | binary=False, 196 | ), 197 | "Caffeine": dict( 198 | url="https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/CID/2519/record/SDF?record_type=3d&response_type=save&response_basename=Conformer3D_COMPOUND_CID_2519", 199 | format="sdf", 200 | binary=False, 201 | ), 202 | "Strychnine": dict( 203 | url="https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/CID/441071/record/SDF?record_type=3d&response_type=save&response_basename=Conformer3D_COMPOUND_CID_441071 ", 204 | format="sdf", 205 | binary=False, 206 | ), 207 | } 208 | 209 | 210 | def download_pdb(pdb_id, fpath: Path): 211 | url = f"https://files.rcsb.org/download/{pdb_id}.pdb" 212 | response = requests.get(url) 213 | if response.status_code == 200: 214 | fpath.write_bytes(response.content) 215 | return f"{pdb_id}.pdb" 216 | else: 217 | print("Failed to download PDB file") 218 | return None 219 | 220 | 221 | @solara.component 222 | def ProteinView(dark_effective: bool): 223 | with solara.Card("PDBeMol*"): 224 | theme = "dark" if dark_effective else "light" 225 | PDBeMolstar.element(**asdict(data.value), theme=theme) 226 | 227 | 228 | @solara.component 229 | def Page(): 230 | solara.Title("ipymolstar - Solara") 231 | counter, set_counter = solara.use_state(0) 232 | dark_effective = solara.lab.use_dark_effective() 233 | dark_effective_previous = solara.use_previous(dark_effective) 234 | 235 | structure_type = solara.use_reactive("Protein") 236 | color_mode = solara.use_reactive(color_options[0]) 237 | protein_id = solara.use_reactive(pdb_ids[0]) 238 | molecule_key = solara.use_reactive(next(iter(molecule_store.keys()))) 239 | 240 | if dark_effective != dark_effective_previous: 241 | if dark_effective: 242 | data.update(bg_color=THEMES["dark"]["bg_color"]) 243 | else: 244 | data.update(bg_color=THEMES["light"]["bg_color"]) 245 | 246 | def update_protein_id(value: str): 247 | protein_id.set(value) 248 | color_data = apply_coloring(protein_id.value, color_mode.value) 249 | data.update(color_data=color_data, molecule_id=protein_id.value.lower()) 250 | 251 | def update_molecule_key(value: str): 252 | molecule_key.set(value) 253 | data.update(custom_data=molecule_store[molecule_key.value]) 254 | 255 | def update_structure_type(value: str): 256 | structure_type.set(value) 257 | if structure_type.value == "Protein": 258 | color_data = apply_coloring(protein_id.value, color_mode.value) 259 | data.update( 260 | color_data=color_data, 261 | custom_data=None, 262 | molecule_id=protein_id.value.lower(), 263 | ) 264 | else: 265 | color_data = {"data": [], "nonSelectedColor": None} 266 | data.update( 267 | molecule_id="", 268 | custom_data=molecule_store[molecule_key.value], 269 | color_data=color_data, # used to reset colors 270 | ) 271 | 272 | def update_color_mode(value: str): 273 | color_data = apply_coloring(protein_id.value, value) 274 | color_mode.set(value) 275 | data.update(color_data=color_data) 276 | 277 | with solara.AppBar(): 278 | solara.lab.ThemeToggle() 279 | 280 | with solara.ColumnsResponsive([4, 8]): 281 | with solara.Card("Controls"): 282 | with solara.ToggleButtonsSingle( 283 | value=structure_type.value, 284 | on_value=update_structure_type, 285 | classes=["d-flex", "flex-row"], 286 | ): 287 | solara.Button(label="Protein", classes=["flex-grow-1"]) 288 | solara.Button(label="Molecule", classes=["flex-grow-1"]) 289 | 290 | solara.Div(style="height: 20px") 291 | 292 | if structure_type.value == "Protein": 293 | solara.Select( 294 | label="PDB id", 295 | value=protein_id.value, 296 | values=pdb_ids, 297 | on_value=update_protein_id, 298 | ) 299 | 300 | solara.Select( 301 | label="Color mode", 302 | value=color_mode.value, 303 | on_value=update_color_mode, 304 | values=color_options, 305 | ) 306 | else: 307 | solara.Select( 308 | label="Molecule", 309 | value=molecule_key.value, 310 | values=list(molecule_store.keys()), 311 | on_value=update_molecule_key, 312 | ) 313 | 314 | solara.Checkbox( 315 | label="spin", 316 | value=data.value.spin, 317 | on_value=lambda x: data.update(spin=x), 318 | ) 319 | 320 | for struc_elem in visibility_cbs: 321 | attr = f"hide_{struc_elem}" 322 | 323 | def on_value(x, attr=attr): 324 | data.update(**{attr: x}) 325 | 326 | solara.Checkbox( 327 | label=f"hide {struc_elem}", 328 | value=getattr(data.value, attr), 329 | on_value=on_value, 330 | ) 331 | 332 | btn = solara.Button("background color", block=True) 333 | with solara.lab.Menu(activator=btn, close_on_content_click=False): 334 | rv.ColorPicker( 335 | v_model=data.value.bg_color, 336 | on_v_model=lambda x: data.update(bg_color=x), 337 | ) 338 | 339 | solara.Div(style="height: 20px") 340 | solara.Button( 341 | "redraw", on_click=lambda: set_counter(counter + 1), block=True 342 | ) 343 | 344 | key = f"{counter}_{dark_effective}" 345 | ProteinView(dark_effective).key(key) 346 | 347 | 348 | @solara.component 349 | def Layout(children): 350 | dark_effective = solara.lab.use_dark_effective() 351 | return solara.AppLayout(children=children, toolbar_dark=dark_effective, color=None) 352 | -------------------------------------------------------------------------------- /examples/pdbemolstar/pyhdx_secb_deltaG.csv: -------------------------------------------------------------------------------- 1 | # PyHDX v0.4.0b2+34.gcb83492 2 | # 2021/08/20 11:32:33 (1629451953) 3 | # {"r1": 2, "epochs": 200000, "patience": 100, "stop_loss": 1e-05, "optimizer": "SGD", "lr": 10000.0, "momentum": 0.5, "nesterov": true, "model_name": "DeltaGFit", "total_loss": 1.72128356163797, "mse_loss": 1.3044053161108615, "reg_loss": 0.4168782455271085, "regularization_percentage": 24.219033680331435, "epochs_run": 66406} 4 | # {"comment": "#", "header": [0], "index_col": 0} 5 | r_number,sequence,_deltaG,deltaG,pfact,k_obs,covariance 6 | 10,T,20919.430436051756,20919.430436051756,4022.3351440972306,2.4547916966364984,2199.361913446584 7 | 11,F,20919.412154717586,20919.412154717586,4022.3059703101985,2.0418216044135877,2271.551332671181 8 | 12,Q,20919.44082172526,20919.44082172526,4022.3517178957886,2.951296593638542,1528.8012869134923 9 | 13,I,21386.831723581752,21386.831723581752,4841.8668761799245,0.5489064390732655,1399.8350238032224 10 | 14,Q,21386.8224886335,21386.8224886335,4841.849136133268,1.2574766119631515,1603.8319233328189 11 | 15,R,21059.19763359226,21059.19763359226,4251.679754775063,4.035879368140441,1865.73481412718 12 | 16,I,21059.164750663786,21059.164750663786,4251.624287549613,0.6545515283426903,1470.3584191504274 13 | 17,Y,21059.170923250273,21059.170923250273,4251.634699469968,0.6697963521460933,1481.3185274088164 14 | 18,T,20693.032146224697,,,, 15 | 19,K,20692.997641104288,20692.997641104288,3676.742619355029,3.540132555080082,1576.3747429038096 16 | 20,D,20693.035167457594,20693.035167457594,3676.7973601459116,4.0657708833246184,1486.0574538989097 17 | 21,I,21360.490530363208,21360.490530363208,4791.529662130848,0.23134588353839522,5986.358829054358 18 | 22,S,35766.64249139626,35766.64249139626,1454439.8024162317,0.008548793110493598,2859.2003704705885 19 | 23,F,37061.97643529757,37061.97643529757,2431563.0143466685,0.004253193573346486,2242.247939793355 20 | 24,E,37061.96631403077,37061.96631403077,2431553.2503466266,0.0033027199089483506,2251.420648907708 21 | 25,A,20813.342791325536,20813.342791325536,3856.5507081213314,1.654236511048838,1607.9204046269394 22 | 26,P,20813.387886890752,,,, 23 | 27,N,18149.998736732974,18149.998736732974,1340.593708979362,11.939326898781227,1414.4500438249765 24 | 28,A,18150.005460981174,18150.005460981174,1340.5972854094032,14.027448586679636,1357.232499614844 25 | 29,P,18534.163621054628,,,, 26 | 30,H,18031.826501806907,18031.826501806907,1279.1920740457272,3.949226556493646,2358.6347356693536 27 | 31,V,34971.949977670265,34971.949977670265,1061128.396762391,0.0026371315086005666,1669.2917177140157 28 | 32,F,29243.181159656666,29243.181159656666,109316.20134441808,0.03434886188829527,357.703018200999 29 | 33,Q,16809.57074962795,16809.57074962795,787.657776938731,15.056092220540023,1556.6671030537907 30 | 34,K,16809.6083372667,16809.6083372667,787.6695230494516,16.508430965688692,2004.5041090215273 31 | 35,D,17254.54663502072,17254.54663502072,939.7410429332765,15.895002704490233,3133.463816091973 32 | 36,W,25415.39983917216,25415.39983917216,23941.246625989035,0.09675238640252719,1070.861350789624 33 | 37,Q,16343.510915312785,16343.510915312785,654.6875927580811,12.243452216995303,3555.338692727679 34 | 38,P,16343.47746288956,,,, 35 | 39,E,16343.537335444113,16343.537335444113,654.6944552145262,6.138386030065746,1362.5326952741855 36 | 40,V,20964.408403054615,20964.408403054615,4094.7565574927858,0.31086734953708856,1398.076164655746 37 | 41,K,15305.883616868221,15305.883616868221,433.75843330767725,13.688404847244868,3012.652445521661 38 | 42,L,15305.819245718982,15305.819245718982,433.7473558042994,7.183960027772581,2293.157055443234 39 | 43,D,14824.745335403722,14824.745335403722,358.3821881515886,19.461403162147157,1620.2126520444083 40 | 44,L,14745.752092739818,14745.752092739818,347.3246870215025,4.496165928583153,1234.0661726400788 41 | 45,D,14745.7480368854,14745.7480368854,347.3241281320621,20.07923393196863,1330.991074925188 42 | 46,T,18420.5268231792,18420.5268231792,1492.484904728409,3.3933272785866087,734.5796499583479 43 | 47,A,37591.36113684838,37591.36113684838,2999853.4654005547,0.004758835257313989,1471.75656217688 44 | 48,S,37591.32862589079,37591.32862589079,2999814.7721474436,0.007038923937679549,1580.8413386090645 45 | 49,S,32028.591208089692,32028.591208089692,330079.630170158,0.12763824282087013,1727.8668697183139 46 | 50,Q,36320.34866276733,36320.34866276733,1811761.0665843543,0.011389369793521746,2305.2280577127667 47 | 51,L,35824.38584117635,35824.38584117635,1488144.4769514224,0.002523222029800536,1612.3035051968263 48 | 52,A,18443.7739792064,18443.7739792064,1506.3139543665934,3.684657598492488,1584.3043893956876 49 | 53,D,17369.504974145206,17369.504974145206,983.5939497352186,11.520558549949104,1894.6929695107722 50 | 54,D,17369.537881463995,17369.537881463995,983.6067913436877,7.615415454300077,4071.652566875675 51 | 55,V,19722.95345724863,19722.95345724863,2502.1988268169202,0.4746038306651238,1460.2188730606017 52 | 56,Y,19722.976953583093,19722.976953583093,2502.222152383326,1.3999143423922167,2641.9472798986594 53 | 57,E,19722.94137510081,19722.94137510081,2502.186832565106,3.1351797277427793,2440.666490250231 54 | 58,V,19817.645823666517,,,, 55 | 59,V,20443.90324338757,,,, 56 | 60,L,21094.215835522362,,,, 57 | 61,R,21744.52842765715,,,, 58 | 62,V,33286.110561087175,33286.110561087175,543616.886313745,0.005486648255651761,2241.0514977604494 59 | 63,T,34262.50322691518,34262.50322691518,800807.650745016,0.006935409363845657,3471.749531744862 60 | 64,V,35183.67277357366,35183.67277357366,1154113.1402045572,0.0024680394334270907,2344.2913945447503 61 | 65,T,36184.558016831186,36184.558016831186,1716737.138673367,0.003235167722968787,1774.2023430999427 62 | 66,A,36184.61155893592,36184.61155893592,1716773.6066860207,0.008315484829028495,3044.303792906657 63 | 67,S,31965.739137051878,31965.739137051878,321950.5199066919,0.06558588402787163,1379.6936295092294 64 | 68,L,28175.665397740027,28175.665397740027,71572.8785865007,0.06604596714994004,1362.1988662523968 65 | 69,G,18049.636343421433,18049.636343421433,1288.2627427779237,4.020308905144217,1540.8920503317502 66 | 70,E,17480.20797232154,17480.20797232154,1027.7566483143535,10.056411895699735,1673.1779467423257 67 | 71,E,17480.18819570885,17480.18819570885,1027.748584347357,4.816758212488473,1879.27845348462 68 | 72,T,18034.703600071225,18034.703600071225,1280.6530618376737,4.237783505289487,1320.4964598132806 69 | 73,A,18034.708121283336,18034.708121283336,1280.6553590175677,11.138574107552488,2055.4342303713393 70 | 74,F,20633.15891003089,20633.15891003089,3590.4827387090454,1.4432018369299298,1595.7249045922301 71 | 75,L,22977.832474821,22977.832474821,9102.210332908211,0.2988176727632178,1424.8094204705324 72 | 76,C,38055.8606966381,38055.8606966381,3606904.6041440717,0.006418980847125078,6187.955049249469 73 | 77,E,38055.86304583672,38055.86304583672,3606907.9658741355,0.006880508355236782,7822.1597818519895 74 | 78,V,35959.84796624756,35959.84796624756,1570310.779939945,0.0008108179545246907,7833.572826668836 75 | 79,Q,36714.51290101607,36714.51290101607,2118441.9060547557,0.0035365849348005223,6155.599716681484 76 | 80,Q,37056.3704517837,37056.3704517837,2426160.913175716,0.0067558667591728055,7083.195225987243 77 | 81,G,37056.415745414335,37056.415745414335,2426204.511407945,0.005491275986559386,12031.078784124582 78 | 82,G,37056.343758330426,37056.343758330426,2426135.219259382,0.005124903298071557,10916.014935637273 79 | 83,I,34189.772843035185,34189.772843035185,778030.3857223899,0.0031886271505492123,5915.103525543223 80 | 84,F,34187.314347557425,34187.314347557425,777271.8737694325,0.003926696587710748,6581.891207965823 81 | 85,S,24398.96224795455,,,, 82 | 86,I,18502.891000433552,18502.891000433552,1542.0609259951618,2.168791399877809,1909.6439837732762 83 | 87,A,17370.72730027929,17370.72730027929,984.0710575144565,5.384350180826554,1611.2677668523293 84 | 88,G,17370.736126437725,17370.736126437725,984.0745034502467,8.533590095632096,2830.6274635774294 85 | 89,I,17370.770853627477,17370.770853627477,984.0880618630993,2.518406319737342,1674.9914512798407 86 | 90,E,17370.772266360487,17370.772266360487,984.0886134353644,4.181002322749086,1471.719415677227 87 | 91,G,17370.777818023937,17370.777818023937,984.0907809700608,6.045510654785301,1744.7874892721293 88 | 92,T,18454.212918661615,18454.212918661615,1512.5653899518584,7.492032766549868,2214.163067154405 89 | 93,Q,18565.580500180018,18565.580500180018,1580.895322819498,10.36151152679036,5802.230049438161 90 | 94,M,18650.68822923237,18650.68822923237,1635.186985747294,8.526443576884947,5016.776144280829 91 | 95,A,20883.27291810903,,,, 92 | 96,H,23907.980116388553,,,, 93 | 97,C,26932.68731466808,,,, 94 | 98,L,29957.394512947605,,,, 95 | 99,G,32157.84975720566,,,, 96 | 100,A,32157.828467489442,32157.828467489442,347445.4885546223,0.038345369726348395,1331.149551118488 97 | 101,Y,33944.833774070474,33944.833774070474,705980.7347118829,0.006851842814223264,2948.4702936197095 98 | 102,C,36724.94262594434,36724.94262594434,2127225.9904860826,0.01980555521932716,1269.2141478508795 99 | 103,P,36810.77452984988,,,, 100 | 104,N,36810.766334191896,36810.766334191896,2200905.031488488,0.007277787251107704,2478.9459565426732 101 | 105,I,34036.66778660016,34036.66778660016,732177.0558956296,0.004786126223067703,2756.184669090075 102 | 106,L,34036.62736599442,34036.62736599442,732165.3143926405,0.0019054217247063178,5400.099638580072 103 | 107,F,34036.66050959576,34036.66050959576,732174.942034909,0.004365011596196762,6830.932109651076 104 | 108,P,34412.55116980607,,,, 105 | 109,Y,35677.89991058932,35677.89991058932,1404122.983391218,0.0019824187606506246,6032.587161999365 106 | 110,A,36736.95068487817,36736.95068487817,2137384.490977934,0.004728441272774879,12586.58616014294 107 | 111,R,37755.1440496577,37755.1440496577,3201255.29505735,0.0033828318504479597,7286.13209998546 108 | 112,E,37755.216431347755,37755.216431347755,3201347.2263797494,0.003625958180277894,7986.939594956758 109 | 113,C,37755.19195063535,37755.19195063535,3201316.133355422,0.008309608189782172,8181.308303959258 110 | 114,I,37219.94106038409,,,, 111 | 115,T,37219.99342127409,37219.99342127409,2588882.332336185,0.0017437666884690065,2231.95118108176 112 | 116,S,39102.217967293866,39102.217967293866,5462922.438286001,0.006125982367458822,1819.6296246184863 113 | 117,M,39102.22333612587,39102.22333612587,5462934.074549683,0.003214954399810702,1756.1083225687696 114 | 118,V,34775.20101549821,34775.20101549821,981448.515948399,0.00235902688976224,1772.9736350929495 115 | 119,S,31712.02125939371,31712.02125939371,291120.51309290633,0.05254441444643108,1442.7855645406123 116 | 120,R,29873.97299648735,29873.97299648735,140401.51249202696,0.15389551982272456,839.440538021873 117 | 121,G,29874.001705835035,29874.001705835035,140403.1117032424,0.09936216144977028,1126.6317611291659 118 | 122,T,29874.057032199416,29874.057032199416,140406.19362688548,0.08076282420378642,1323.39802357682 119 | 123,F,29874.045786474024,29874.045786474024,140405.56718467432,0.058507755129008,1491.7770622412565 120 | 124,P,27180.865410127568,,,, 121 | 125,Q,24940.676339827005,24940.676339827005,19831.270168220122,0.3000740407134874,714.5709876481052 122 | 126,L,24944.253113893177,24944.253113893177,19859.431841919537,0.1890654483688677,831.8619334265086 123 | 127,N,17069.58370709346,17069.58370709346,873.2499079629926,19.632032357449095,1552.620936347317 124 | 128,L,14041.566570657673,14041.566570657673,262.6651498004279,18.77362445992593,1593.1155837640247 125 | 129,A,14041.537793171956,14041.537793171956,262.66215090677565,21.064592684880132,2562.5574829086654 126 | 130,P,13719.961984919912,,,, 127 | 131,V,13107.866705440014,13107.866705440014,181.35238549218082,5.671388620158085,1858.0264623099183 128 | 132,N,16001.759959888463,16001.759959888463,571.6753078967847,35.21213826098592,4731.509974566803 129 | 133,F,16001.735399184396,16001.735399184396,571.6697373686366,18.91022180797328,2402.5108334346082 130 | 134,D,16001.767890625099,,,, 131 | 135,A,16001.753465642932,,,, 132 | 136,L,15445.976927574618,,,, 133 | 137,F,15445.960152271085,,,, 134 | 138,M,15445.920041689666,15445.920041689666,458.53935089828894,21.992679737845545,1659.3476471682227 135 | 139,N,15445.916900977076,15445.916900977076,458.5387795344132,78.03306938267686,34224.96320926817 136 | 140,Y,15445.876530200469,15445.876530200469,458.53143527617556,21.993058571161768,1154.755721966316 137 | 141,L,10004.632777289324,10004.632777289324,52.9452628232315,49.27737252182043,4278.159298908949 138 | 142,Q,10004.66782322247,10004.66782322247,52.94599898964294,118.2065702567813,482392.73647053854 139 | 143,Q,10136.85278903163,10136.85278903163,55.79676219857428,288.58734172713747,293341008925.16125 140 | 144,Q,10136.856729918336,10136.856729918336,55.79684943774523,288.5868984609935,293330599026.5491 141 | 145,A,10136.85624598459,10136.85624598459,55.79683872492582,251.34872850885395,15053511864.55236 142 | 146,G,10058.255380131106,10058.255380131106,54.08370531273599,152.60814388532276,6565657.453759824 143 | 147,E,10058.146362485824,10058.146362485824,54.081366141120284,187.8239651751353,100584866.22360535 144 | 148,G,10058.13118513682,10058.13118513682,54.081040491549984,108.12026713981362,227650.0767580462 145 | 149,T,10058.126693897279,10058.126693897279,54.08094412659722,205.87304149638751,413613238.5413494 146 | 150,E,10058.089738512477,10058.089738512477,54.08015121083325,201.26144389605025,287969589.8329091 147 | 151,E,10058.04630342949,10058.04630342949,54.079219281346546,89.96556699414205,60433.52536198467 148 | 152,H,10058.001570477172,10058.001570477172,54.07825952196978,113.00960682114687,365840.3022100215 149 | 153,Q,10057.987395708305,10057.987395708305,54.077955401457864,292.36294221033376,443907616929.0691 150 | 154,D,10057.946780954537,10057.946780954537,54.07708401896283,326.4072218268354,6816460454893.367 151 | 155,A,4848.998941539874,4848.998941539874,6.8469398956720475,12.026141759383467,1173.980996585565 152 | -------------------------------------------------------------------------------- /src/ipymolstar/static/pdbe-dark.css: -------------------------------------------------------------------------------- 1 | /** 2 | * pdbe-molstar 3 | * @version 3.3.2 4 | * @link https://github.com/PDBeurope/pdbe-molstar 5 | * @license Apache 2.0 6 | */ 7 | 8 | .msp-plugin { 9 | font-family: "Helvetica Neue", "Segoe UI", Helvetica, "Source Sans Pro", Arial, sans-serif; 10 | font-size: 14px; 11 | line-height: 1.42857143; 12 | position: absolute; 13 | left: 0; 14 | top: 0; 15 | right: 0; 16 | bottom: 0; 17 | /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ 18 | } 19 | 20 | .msp-plugin * { 21 | box-sizing: border-box 22 | } 23 | 24 | .msp-plugin [hidden], 25 | .msp-plugin template { 26 | display: none 27 | } 28 | 29 | .msp-plugin a { 30 | background-color: rgba(0, 0, 0, 0) 31 | } 32 | 33 | .msp-plugin a:active, 34 | .msp-plugin a:hover { 35 | outline: 0 36 | } 37 | 38 | .msp-plugin abbr[title] { 39 | border-bottom: 1px dotted 40 | } 41 | 42 | .msp-plugin b, 43 | .msp-plugin strong { 44 | font-weight: bold 45 | } 46 | 47 | .msp-plugin small { 48 | font-size: 80% 49 | } 50 | 51 | .msp-plugin img { 52 | border: 0 53 | } 54 | 55 | .msp-plugin svg:not(:root) { 56 | overflow: hidden 57 | } 58 | 59 | .msp-plugin button, 60 | .msp-plugin input, 61 | .msp-plugin optgroup, 62 | .msp-plugin select, 63 | .msp-plugin textarea { 64 | color: inherit; 65 | font: inherit; 66 | margin: 0 67 | } 68 | 69 | .msp-plugin button { 70 | overflow: visible 71 | } 72 | 73 | .msp-plugin button, 74 | .msp-plugin select { 75 | text-transform: none 76 | } 77 | 78 | .msp-plugin button, 79 | .msp-plugin html input[type=button], 80 | .msp-plugin input[type=reset], 81 | .msp-plugin input[type=submit] { 82 | -webkit-appearance: button; 83 | cursor: pointer 84 | } 85 | 86 | .msp-plugin button[disabled], 87 | .msp-plugin html input[disabled] { 88 | cursor: default 89 | } 90 | 91 | .msp-plugin button::-moz-focus-inner, 92 | .msp-plugin input::-moz-focus-inner { 93 | border: 0; 94 | padding: 0 95 | } 96 | 97 | .msp-plugin input { 98 | line-height: normal 99 | } 100 | 101 | .msp-plugin input[type=checkbox], 102 | .msp-plugin input[type=radio] { 103 | box-sizing: border-box; 104 | padding: 0 105 | } 106 | 107 | .msp-plugin input[type=number]::-webkit-inner-spin-button, 108 | .msp-plugin input[type=number]::-webkit-outer-spin-button { 109 | height: auto 110 | } 111 | 112 | .msp-plugin textarea { 113 | overflow: auto 114 | } 115 | 116 | .msp-plugin .msp-layout-expanded, 117 | .msp-plugin .msp-layout-standard { 118 | left: 0; 119 | right: 0; 120 | top: 0; 121 | bottom: 0 122 | } 123 | 124 | .msp-plugin .msp-layout-standard { 125 | border: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049) 126 | } 127 | 128 | .msp-plugin .msp-layout-region { 129 | overflow: hidden 130 | } 131 | 132 | .msp-plugin .msp-layout-static, 133 | .msp-plugin .msp-layout-scrollable { 134 | position: absolute 135 | } 136 | 137 | .msp-plugin .msp-scrollable { 138 | overflow-y: auto 139 | } 140 | 141 | .msp-plugin .msp-scrollable-container { 142 | position: absolute; 143 | left: 0; 144 | right: 0; 145 | top: 0; 146 | bottom: 0; 147 | overflow-y: auto 148 | } 149 | 150 | .msp-plugin .msp-layout-static { 151 | overflow: hidden 152 | } 153 | 154 | .msp-plugin .msp-layout-top .msp-layout-static, 155 | .msp-plugin .msp-layout-main .msp-layout-static, 156 | .msp-plugin .msp-layout-bottom .msp-layout-static { 157 | left: 0; 158 | right: 0; 159 | top: 0; 160 | bottom: 0 161 | } 162 | 163 | .msp-plugin .msp-layout-right .msp-layout-static { 164 | left: 0; 165 | right: 0; 166 | top: 0; 167 | bottom: 0 168 | } 169 | 170 | .msp-plugin .msp-layout-right .msp-layout-scrollable { 171 | left: 0; 172 | right: 0; 173 | top: 43px; 174 | bottom: 0 175 | } 176 | 177 | .msp-plugin .msp-layout-left .msp-layout-static { 178 | left: 0; 179 | right: 0; 180 | bottom: 0; 181 | top: 0 182 | } 183 | 184 | .msp-plugin .msp-layout-standard-outside { 185 | position: absolute 186 | } 187 | 188 | .msp-plugin .msp-layout-standard-outside .msp-layout-main { 189 | position: absolute; 190 | left: 0; 191 | right: 0; 192 | bottom: 0; 193 | top: 0 194 | } 195 | 196 | .msp-plugin .msp-layout-standard-outside .msp-layout-top { 197 | position: absolute; 198 | right: 0; 199 | height: 97px; 200 | top: -97px; 201 | width: 50%; 202 | border-left: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049); 203 | border-bottom: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049) 204 | } 205 | 206 | .msp-plugin .msp-layout-standard-outside .msp-layout-bottom { 207 | position: absolute; 208 | left: 0; 209 | right: 0; 210 | height: 97px; 211 | top: -97px; 212 | width: 50%; 213 | border-bottom: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049) 214 | } 215 | 216 | .msp-plugin .msp-layout-standard-outside .msp-layout-right { 217 | position: absolute; 218 | width: 50%; 219 | right: 0; 220 | bottom: -295px; 221 | height: 295px; 222 | border-left: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049); 223 | border-top: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049) 224 | } 225 | 226 | .msp-plugin .msp-layout-standard-outside .msp-layout-left { 227 | position: absolute; 228 | width: 50%; 229 | left: 0; 230 | bottom: 0; 231 | bottom: -295px; 232 | height: 295px; 233 | border-top: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049) 234 | } 235 | 236 | .msp-plugin .msp-layout-standard-outside .msp-layout-hide-right .msp-layout-right { 237 | display: none 238 | } 239 | 240 | .msp-plugin .msp-layout-standard-outside .msp-layout-hide-right .msp-layout-left { 241 | width: 100% 242 | } 243 | 244 | .msp-plugin .msp-layout-standard-outside .msp-layout-hide-left .msp-layout-left { 245 | display: none 246 | } 247 | 248 | .msp-plugin .msp-layout-standard-outside .msp-layout-hide-left .msp-layout-right { 249 | width: 100%; 250 | border-left: none 251 | } 252 | 253 | .msp-plugin .msp-layout-standard-outside .msp-layout-collapse-left .msp-layout-left { 254 | width: 32px 255 | } 256 | 257 | .msp-plugin .msp-layout-standard-outside .msp-layout-collapse-left .msp-layout-right { 258 | left: 32px; 259 | width: auto 260 | } 261 | 262 | .msp-plugin .msp-layout-standard-outside .msp-layout-hide-top .msp-layout-top { 263 | display: none 264 | } 265 | 266 | .msp-plugin .msp-layout-standard-outside .msp-layout-hide-top .msp-layout-bottom { 267 | width: 100%; 268 | border-left: none 269 | } 270 | 271 | .msp-plugin .msp-layout-standard-outside .msp-layout-hide-bottom .msp-layout-bottom { 272 | display: none 273 | } 274 | 275 | .msp-plugin .msp-layout-standard-outside .msp-layout-hide-bottom .msp-layout-top { 276 | width: 100%; 277 | border-left: none 278 | } 279 | 280 | .msp-plugin .msp-layout-standard-landscape { 281 | position: absolute 282 | } 283 | 284 | .msp-plugin .msp-layout-standard-landscape .msp-layout-main { 285 | position: absolute; 286 | left: 330px; 287 | right: 300px; 288 | bottom: 70px; 289 | top: 100px 290 | } 291 | 292 | .msp-plugin .msp-layout-standard-landscape .msp-layout-top { 293 | position: absolute; 294 | left: 330px; 295 | right: 300px; 296 | height: 100px; 297 | top: 0; 298 | border-bottom: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049) 299 | } 300 | 301 | .msp-plugin .msp-layout-standard-landscape .msp-layout-bottom { 302 | position: absolute; 303 | left: 330px; 304 | right: 300px; 305 | height: 70px; 306 | bottom: 0; 307 | border-top: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049) 308 | } 309 | 310 | .msp-plugin .msp-layout-standard-landscape .msp-layout-right { 311 | position: absolute; 312 | width: 300px; 313 | right: 0; 314 | bottom: 0; 315 | top: 0; 316 | border-left: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049) 317 | } 318 | 319 | .msp-plugin .msp-layout-standard-landscape .msp-layout-left { 320 | position: absolute; 321 | width: 330px; 322 | left: 0; 323 | bottom: 0; 324 | top: 0; 325 | border-right: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049) 326 | } 327 | 328 | .msp-plugin .msp-layout-standard-landscape .msp-layout-hide-right .msp-layout-right { 329 | display: none 330 | } 331 | 332 | .msp-plugin .msp-layout-standard-landscape .msp-layout-hide-right .msp-layout-main, 333 | .msp-plugin .msp-layout-standard-landscape .msp-layout-hide-right .msp-layout-top, 334 | .msp-plugin .msp-layout-standard-landscape .msp-layout-hide-right .msp-layout-bottom { 335 | right: 0 336 | } 337 | 338 | .msp-plugin .msp-layout-standard-landscape .msp-layout-hide-left .msp-layout-left { 339 | display: none 340 | } 341 | 342 | .msp-plugin .msp-layout-standard-landscape .msp-layout-hide-left .msp-layout-main, 343 | .msp-plugin .msp-layout-standard-landscape .msp-layout-hide-left .msp-layout-top, 344 | .msp-plugin .msp-layout-standard-landscape .msp-layout-hide-left .msp-layout-bottom { 345 | left: 0 346 | } 347 | 348 | .msp-plugin .msp-layout-standard-landscape .msp-layout-collapse-left .msp-layout-left { 349 | width: 32px 350 | } 351 | 352 | .msp-plugin .msp-layout-standard-landscape .msp-layout-collapse-left .msp-layout-main, 353 | .msp-plugin .msp-layout-standard-landscape .msp-layout-collapse-left .msp-layout-top, 354 | .msp-plugin .msp-layout-standard-landscape .msp-layout-collapse-left .msp-layout-bottom { 355 | left: 32px 356 | } 357 | 358 | .msp-plugin .msp-layout-standard-landscape .msp-layout-hide-bottom .msp-layout-bottom { 359 | display: none 360 | } 361 | 362 | .msp-plugin .msp-layout-standard-landscape .msp-layout-hide-bottom .msp-layout-main { 363 | bottom: 0 364 | } 365 | 366 | .msp-plugin .msp-layout-standard-landscape .msp-layout-hide-top .msp-layout-top { 367 | display: none 368 | } 369 | 370 | .msp-plugin .msp-layout-standard-landscape .msp-layout-hide-top .msp-layout-main { 371 | top: 0 372 | } 373 | 374 | .msp-plugin .msp-layout-standard-portrait { 375 | position: absolute 376 | } 377 | 378 | .msp-plugin .msp-layout-standard-portrait .msp-layout-main { 379 | position: absolute; 380 | left: 0; 381 | right: 0; 382 | bottom: 361px; 383 | top: 97px 384 | } 385 | 386 | .msp-plugin .msp-layout-standard-portrait .msp-layout-top { 387 | position: absolute; 388 | right: 0; 389 | height: 97px; 390 | top: 0; 391 | width: 50%; 392 | border-left: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049); 393 | border-bottom: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049) 394 | } 395 | 396 | .msp-plugin .msp-layout-standard-portrait .msp-layout-bottom { 397 | position: absolute; 398 | left: 0; 399 | right: 0; 400 | height: 97px; 401 | width: 50%; 402 | border-bottom: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049) 403 | } 404 | 405 | .msp-plugin .msp-layout-standard-portrait .msp-layout-right { 406 | position: absolute; 407 | width: 50%; 408 | right: 0; 409 | bottom: 0; 410 | height: 361px; 411 | border-left: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049); 412 | border-top: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049) 413 | } 414 | 415 | .msp-plugin .msp-layout-standard-portrait .msp-layout-left { 416 | position: absolute; 417 | width: 50%; 418 | left: 0; 419 | bottom: 0; 420 | height: 361px; 421 | border-top: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049) 422 | } 423 | 424 | .msp-plugin .msp-layout-standard-portrait .msp-layout-hide-right .msp-layout-right { 425 | display: none 426 | } 427 | 428 | .msp-plugin .msp-layout-standard-portrait .msp-layout-hide-right .msp-layout-left { 429 | width: 100% 430 | } 431 | 432 | .msp-plugin .msp-layout-standard-portrait .msp-layout-hide-left .msp-layout-left { 433 | display: none 434 | } 435 | 436 | .msp-plugin .msp-layout-standard-portrait .msp-layout-hide-left .msp-layout-right { 437 | width: 100%; 438 | border-left: none 439 | } 440 | 441 | .msp-plugin .msp-layout-standard-portrait .msp-layout-hide-right.msp-layout-hide-left .msp-layout-main { 442 | bottom: 0 443 | } 444 | 445 | .msp-plugin .msp-layout-standard-portrait .msp-layout-collapse-left .msp-layout-left { 446 | width: 32px 447 | } 448 | 449 | .msp-plugin .msp-layout-standard-portrait .msp-layout-collapse-left .msp-layout-right { 450 | left: 32px; 451 | width: auto 452 | } 453 | 454 | .msp-plugin .msp-layout-standard-portrait .msp-layout-hide-top .msp-layout-top { 455 | display: none 456 | } 457 | 458 | .msp-plugin .msp-layout-standard-portrait .msp-layout-hide-top .msp-layout-bottom { 459 | width: 100%; 460 | border-left: none 461 | } 462 | 463 | .msp-plugin .msp-layout-standard-portrait .msp-layout-hide-bottom .msp-layout-bottom { 464 | display: none 465 | } 466 | 467 | .msp-plugin .msp-layout-standard-portrait .msp-layout-hide-bottom .msp-layout-top { 468 | width: 100%; 469 | border-left: none 470 | } 471 | 472 | .msp-plugin .msp-layout-standard-portrait .msp-layout-hide-top.msp-layout-hide-bottom .msp-layout-main { 473 | top: 0 474 | } 475 | 476 | .msp-plugin .msp-layout-standard-reactive { 477 | position: absolute 478 | } 479 | 480 | @media(orientation: landscape), (min-width: 1000px) { 481 | .msp-plugin .msp-layout-standard-reactive .msp-layout-main { 482 | position: absolute; 483 | left: 330px; 484 | right: 300px; 485 | bottom: 70px; 486 | top: 100px 487 | } 488 | 489 | .msp-plugin .msp-layout-standard-reactive .msp-layout-top { 490 | position: absolute; 491 | left: 330px; 492 | right: 300px; 493 | height: 100px; 494 | top: 0; 495 | border-bottom: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049) 496 | } 497 | 498 | .msp-plugin .msp-layout-standard-reactive .msp-layout-bottom { 499 | position: absolute; 500 | left: 330px; 501 | right: 300px; 502 | height: 70px; 503 | bottom: 0; 504 | border-top: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049) 505 | } 506 | 507 | .msp-plugin .msp-layout-standard-reactive .msp-layout-right { 508 | position: absolute; 509 | width: 300px; 510 | right: 0; 511 | bottom: 0; 512 | top: 0; 513 | border-left: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049) 514 | } 515 | 516 | .msp-plugin .msp-layout-standard-reactive .msp-layout-left { 517 | position: absolute; 518 | width: 330px; 519 | left: 0; 520 | bottom: 0; 521 | top: 0; 522 | border-right: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049) 523 | } 524 | 525 | .msp-plugin .msp-layout-standard-reactive .msp-layout-hide-right .msp-layout-right { 526 | display: none 527 | } 528 | 529 | .msp-plugin .msp-layout-standard-reactive .msp-layout-hide-right .msp-layout-main, 530 | .msp-plugin .msp-layout-standard-reactive .msp-layout-hide-right .msp-layout-top, 531 | .msp-plugin .msp-layout-standard-reactive .msp-layout-hide-right .msp-layout-bottom { 532 | right: 0 533 | } 534 | 535 | .msp-plugin .msp-layout-standard-reactive .msp-layout-hide-left .msp-layout-left { 536 | display: none 537 | } 538 | 539 | .msp-plugin .msp-layout-standard-reactive .msp-layout-hide-left .msp-layout-main, 540 | .msp-plugin .msp-layout-standard-reactive .msp-layout-hide-left .msp-layout-top, 541 | .msp-plugin .msp-layout-standard-reactive .msp-layout-hide-left .msp-layout-bottom { 542 | left: 0 543 | } 544 | 545 | .msp-plugin .msp-layout-standard-reactive .msp-layout-collapse-left .msp-layout-left { 546 | width: 32px 547 | } 548 | 549 | .msp-plugin .msp-layout-standard-reactive .msp-layout-collapse-left .msp-layout-main, 550 | .msp-plugin .msp-layout-standard-reactive .msp-layout-collapse-left .msp-layout-top, 551 | .msp-plugin .msp-layout-standard-reactive .msp-layout-collapse-left .msp-layout-bottom { 552 | left: 32px 553 | } 554 | 555 | .msp-plugin .msp-layout-standard-reactive .msp-layout-hide-bottom .msp-layout-bottom { 556 | display: none 557 | } 558 | 559 | .msp-plugin .msp-layout-standard-reactive .msp-layout-hide-bottom .msp-layout-main { 560 | bottom: 0 561 | } 562 | 563 | .msp-plugin .msp-layout-standard-reactive .msp-layout-hide-top .msp-layout-top { 564 | display: none 565 | } 566 | 567 | .msp-plugin .msp-layout-standard-reactive .msp-layout-hide-top .msp-layout-main { 568 | top: 0 569 | } 570 | } 571 | 572 | @media(orientation: portrait)and (max-width: 1000px) { 573 | .msp-plugin .msp-layout-standard-reactive .msp-layout-main { 574 | position: absolute; 575 | left: 0; 576 | right: 0; 577 | bottom: 361px; 578 | top: 97px 579 | } 580 | 581 | .msp-plugin .msp-layout-standard-reactive .msp-layout-top { 582 | position: absolute; 583 | right: 0; 584 | height: 97px; 585 | top: 0; 586 | width: 50%; 587 | border-left: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049); 588 | border-bottom: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049) 589 | } 590 | 591 | .msp-plugin .msp-layout-standard-reactive .msp-layout-bottom { 592 | position: absolute; 593 | left: 0; 594 | right: 0; 595 | height: 97px; 596 | width: 50%; 597 | border-bottom: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049) 598 | } 599 | 600 | .msp-plugin .msp-layout-standard-reactive .msp-layout-right { 601 | position: absolute; 602 | width: 50%; 603 | right: 0; 604 | bottom: 0; 605 | height: 361px; 606 | border-left: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049); 607 | border-top: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049) 608 | } 609 | 610 | .msp-plugin .msp-layout-standard-reactive .msp-layout-left { 611 | position: absolute; 612 | width: 50%; 613 | left: 0; 614 | bottom: 0; 615 | height: 361px; 616 | border-top: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049) 617 | } 618 | 619 | .msp-plugin .msp-layout-standard-reactive .msp-layout-hide-right .msp-layout-right { 620 | display: none 621 | } 622 | 623 | .msp-plugin .msp-layout-standard-reactive .msp-layout-hide-right .msp-layout-left { 624 | width: 100% 625 | } 626 | 627 | .msp-plugin .msp-layout-standard-reactive .msp-layout-hide-left .msp-layout-left { 628 | display: none 629 | } 630 | 631 | .msp-plugin .msp-layout-standard-reactive .msp-layout-hide-left .msp-layout-right { 632 | width: 100%; 633 | border-left: none 634 | } 635 | 636 | .msp-plugin .msp-layout-standard-reactive .msp-layout-hide-right.msp-layout-hide-left .msp-layout-main { 637 | bottom: 0 638 | } 639 | 640 | .msp-plugin .msp-layout-standard-reactive .msp-layout-collapse-left .msp-layout-left { 641 | width: 32px 642 | } 643 | 644 | .msp-plugin .msp-layout-standard-reactive .msp-layout-collapse-left .msp-layout-right { 645 | left: 32px; 646 | width: auto 647 | } 648 | 649 | .msp-plugin .msp-layout-standard-reactive .msp-layout-hide-top .msp-layout-top { 650 | display: none 651 | } 652 | 653 | .msp-plugin .msp-layout-standard-reactive .msp-layout-hide-top .msp-layout-bottom { 654 | width: 100%; 655 | border-left: none 656 | } 657 | 658 | .msp-plugin .msp-layout-standard-reactive .msp-layout-hide-bottom .msp-layout-bottom { 659 | display: none 660 | } 661 | 662 | .msp-plugin .msp-layout-standard-reactive .msp-layout-hide-bottom .msp-layout-top { 663 | width: 100%; 664 | border-left: none 665 | } 666 | 667 | .msp-plugin .msp-layout-standard-reactive .msp-layout-hide-top.msp-layout-hide-bottom .msp-layout-main { 668 | top: 0 669 | } 670 | } 671 | 672 | .msp-plugin .msp-layout-expanded { 673 | position: fixed 674 | } 675 | 676 | @media(orientation: landscape) { 677 | .msp-plugin .msp-layout-expanded .msp-layout-main { 678 | position: absolute; 679 | left: 330px; 680 | right: 300px; 681 | bottom: 70px; 682 | top: 100px 683 | } 684 | 685 | .msp-plugin .msp-layout-expanded .msp-layout-top { 686 | position: absolute; 687 | left: 330px; 688 | right: 300px; 689 | height: 100px; 690 | top: 0; 691 | border-bottom: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049) 692 | } 693 | 694 | .msp-plugin .msp-layout-expanded .msp-layout-bottom { 695 | position: absolute; 696 | left: 330px; 697 | right: 300px; 698 | height: 70px; 699 | bottom: 0; 700 | border-top: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049) 701 | } 702 | 703 | .msp-plugin .msp-layout-expanded .msp-layout-right { 704 | position: absolute; 705 | width: 300px; 706 | right: 0; 707 | bottom: 0; 708 | top: 0; 709 | border-left: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049) 710 | } 711 | 712 | .msp-plugin .msp-layout-expanded .msp-layout-left { 713 | position: absolute; 714 | width: 330px; 715 | left: 0; 716 | bottom: 0; 717 | top: 0; 718 | border-right: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049) 719 | } 720 | 721 | .msp-plugin .msp-layout-expanded .msp-layout-hide-right .msp-layout-right { 722 | display: none 723 | } 724 | 725 | .msp-plugin .msp-layout-expanded .msp-layout-hide-right .msp-layout-main, 726 | .msp-plugin .msp-layout-expanded .msp-layout-hide-right .msp-layout-top, 727 | .msp-plugin .msp-layout-expanded .msp-layout-hide-right .msp-layout-bottom { 728 | right: 0 729 | } 730 | 731 | .msp-plugin .msp-layout-expanded .msp-layout-hide-left .msp-layout-left { 732 | display: none 733 | } 734 | 735 | .msp-plugin .msp-layout-expanded .msp-layout-hide-left .msp-layout-main, 736 | .msp-plugin .msp-layout-expanded .msp-layout-hide-left .msp-layout-top, 737 | .msp-plugin .msp-layout-expanded .msp-layout-hide-left .msp-layout-bottom { 738 | left: 0 739 | } 740 | 741 | .msp-plugin .msp-layout-expanded .msp-layout-collapse-left .msp-layout-left { 742 | width: 32px 743 | } 744 | 745 | .msp-plugin .msp-layout-expanded .msp-layout-collapse-left .msp-layout-main, 746 | .msp-plugin .msp-layout-expanded .msp-layout-collapse-left .msp-layout-top, 747 | .msp-plugin .msp-layout-expanded .msp-layout-collapse-left .msp-layout-bottom { 748 | left: 32px 749 | } 750 | 751 | .msp-plugin .msp-layout-expanded .msp-layout-hide-bottom .msp-layout-bottom { 752 | display: none 753 | } 754 | 755 | .msp-plugin .msp-layout-expanded .msp-layout-hide-bottom .msp-layout-main { 756 | bottom: 0 757 | } 758 | 759 | .msp-plugin .msp-layout-expanded .msp-layout-hide-top .msp-layout-top { 760 | display: none 761 | } 762 | 763 | .msp-plugin .msp-layout-expanded .msp-layout-hide-top .msp-layout-main { 764 | top: 0 765 | } 766 | } 767 | 768 | @media(orientation: portrait) { 769 | .msp-plugin .msp-layout-expanded .msp-layout-main { 770 | position: absolute; 771 | left: 0; 772 | right: 0; 773 | bottom: 361px; 774 | top: 97px 775 | } 776 | 777 | .msp-plugin .msp-layout-expanded .msp-layout-top { 778 | position: absolute; 779 | right: 0; 780 | height: 97px; 781 | top: 0; 782 | width: 50%; 783 | border-left: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049); 784 | border-bottom: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049) 785 | } 786 | 787 | .msp-plugin .msp-layout-expanded .msp-layout-bottom { 788 | position: absolute; 789 | left: 0; 790 | right: 0; 791 | height: 97px; 792 | width: 50%; 793 | border-bottom: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049) 794 | } 795 | 796 | .msp-plugin .msp-layout-expanded .msp-layout-right { 797 | position: absolute; 798 | width: 50%; 799 | right: 0; 800 | bottom: 0; 801 | height: 361px; 802 | border-left: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049); 803 | border-top: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049) 804 | } 805 | 806 | .msp-plugin .msp-layout-expanded .msp-layout-left { 807 | position: absolute; 808 | width: 50%; 809 | left: 0; 810 | bottom: 0; 811 | height: 361px; 812 | border-top: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049) 813 | } 814 | 815 | .msp-plugin .msp-layout-expanded .msp-layout-hide-right .msp-layout-right { 816 | display: none 817 | } 818 | 819 | .msp-plugin .msp-layout-expanded .msp-layout-hide-right .msp-layout-left { 820 | width: 100% 821 | } 822 | 823 | .msp-plugin .msp-layout-expanded .msp-layout-hide-left .msp-layout-left { 824 | display: none 825 | } 826 | 827 | .msp-plugin .msp-layout-expanded .msp-layout-hide-left .msp-layout-right { 828 | width: 100%; 829 | border-left: none 830 | } 831 | 832 | .msp-plugin .msp-layout-expanded .msp-layout-hide-right.msp-layout-hide-left .msp-layout-main { 833 | bottom: 0 834 | } 835 | 836 | .msp-plugin .msp-layout-expanded .msp-layout-collapse-left .msp-layout-left { 837 | width: 32px 838 | } 839 | 840 | .msp-plugin .msp-layout-expanded .msp-layout-collapse-left .msp-layout-right { 841 | left: 32px; 842 | width: auto 843 | } 844 | 845 | .msp-plugin .msp-layout-expanded .msp-layout-hide-top .msp-layout-top { 846 | display: none 847 | } 848 | 849 | .msp-plugin .msp-layout-expanded .msp-layout-hide-top .msp-layout-bottom { 850 | width: 100%; 851 | border-left: none 852 | } 853 | 854 | .msp-plugin .msp-layout-expanded .msp-layout-hide-bottom .msp-layout-bottom { 855 | display: none 856 | } 857 | 858 | .msp-plugin .msp-layout-expanded .msp-layout-hide-bottom .msp-layout-top { 859 | width: 100%; 860 | border-left: none 861 | } 862 | 863 | .msp-plugin .msp-layout-expanded .msp-layout-hide-top.msp-layout-hide-bottom .msp-layout-main { 864 | top: 0 865 | } 866 | } 867 | 868 | .msp-plugin ::-webkit-scrollbar { 869 | width: 10px; 870 | height: 10px 871 | } 872 | 873 | .msp-plugin ::-webkit-scrollbar-track { 874 | border-radius: 0; 875 | background-color: rgb(22.2865853659, 24.9085365854, 31.4634146341) 876 | } 877 | 878 | .msp-plugin ::-webkit-scrollbar-thumb { 879 | border-radius: 0; 880 | background-color: rgb(13.8280487805, 15.4548780488, 19.5219512195) 881 | } 882 | 883 | .msp-plugin .msp-form-control, 884 | .msp-plugin .msp-control-row select, 885 | .msp-plugin .msp-control-row button, 886 | .msp-plugin .msp-control-row input[type=text], 887 | .msp-plugin .msp-btn { 888 | display: block; 889 | width: 100%; 890 | background: rgb(11.7134146341, 13.0914634146, 16.5365853659); 891 | border: none; 892 | padding: 0 10px; 893 | line-height: 30px; 894 | height: 32px; 895 | -webkit-appearance: none; 896 | -moz-appearance: none; 897 | appearance: none; 898 | -webkit-box-shadow: none; 899 | box-shadow: none; 900 | background-image: none 901 | } 902 | 903 | .msp-plugin .msp-form-control::-moz-placeholder, 904 | .msp-plugin .msp-control-row select::-moz-placeholder, 905 | .msp-plugin .msp-control-row button::-moz-placeholder, 906 | .msp-plugin .msp-control-row input[type=text]::-moz-placeholder, 907 | .msp-plugin .msp-btn::-moz-placeholder { 908 | color: hsl(216, 24.3902439024%, 50.9215686275%); 909 | opacity: 1 910 | } 911 | 912 | .msp-plugin .msp-form-control:-ms-input-placeholder, 913 | .msp-plugin .msp-control-row select:-ms-input-placeholder, 914 | .msp-plugin .msp-control-row button:-ms-input-placeholder, 915 | .msp-plugin .msp-control-row input[type=text]:-ms-input-placeholder, 916 | .msp-plugin .msp-btn:-ms-input-placeholder { 917 | color: hsl(216, 24.3902439024%, 50.9215686275%) 918 | } 919 | 920 | .msp-plugin .msp-form-control::-webkit-input-placeholder, 921 | .msp-plugin .msp-control-row select::-webkit-input-placeholder, 922 | .msp-plugin .msp-control-row button::-webkit-input-placeholder, 923 | .msp-plugin .msp-control-row input[type=text]::-webkit-input-placeholder, 924 | .msp-plugin .msp-btn::-webkit-input-placeholder { 925 | color: hsl(216, 24.3902439024%, 50.9215686275%) 926 | } 927 | 928 | .msp-plugin .msp-form-control:hover, 929 | .msp-plugin .msp-control-row select:hover, 930 | .msp-plugin .msp-control-row button:hover, 931 | .msp-plugin .msp-control-row input[type=text]:hover, 932 | .msp-plugin .msp-btn:hover { 933 | color: #51a2fb; 934 | background-color: rgb(22.2865853659, 24.9085365854, 31.4634146341); 935 | border: none; 936 | outline-offset: -1px !important; 937 | outline: 1px solid rgb(54.006097561, 60.3597560976, 76.243902439) !important 938 | } 939 | 940 | .msp-plugin .msp-form-control:active, 941 | .msp-plugin .msp-control-row select:active, 942 | .msp-plugin .msp-control-row button:active, 943 | .msp-plugin .msp-control-row input[type=text]:active, 944 | .msp-plugin .msp-btn:active, 945 | .msp-plugin .msp-form-control:focus, 946 | .msp-plugin .msp-control-row select:focus, 947 | .msp-plugin .msp-control-row button:focus, 948 | .msp-plugin .msp-control-row input[type=text]:focus, 949 | .msp-plugin .msp-btn:focus { 950 | color: #ccd4e0; 951 | background-color: rgb(11.7134146341, 13.0914634146, 16.5365853659); 952 | border: none; 953 | outline-offset: 0; 954 | outline: none 955 | } 956 | 957 | .msp-plugin .msp-form-control[disabled], 958 | .msp-plugin .msp-control-row select[disabled], 959 | .msp-plugin .msp-control-row button[disabled], 960 | .msp-plugin .msp-control-row input[disabled][type=text], 961 | .msp-plugin [disabled].msp-btn, 962 | .msp-plugin .msp-form-control[readonly], 963 | .msp-plugin .msp-control-row select[readonly], 964 | .msp-plugin .msp-control-row button[readonly], 965 | .msp-plugin .msp-control-row input[readonly][type=text], 966 | .msp-plugin [readonly].msp-btn, 967 | fieldset[disabled] .msp-plugin .msp-form-control, 968 | fieldset[disabled] .msp-plugin .msp-control-row select, 969 | fieldset[disabled] .msp-plugin .msp-control-row button, 970 | fieldset[disabled] .msp-plugin .msp-control-row input[type=text], 971 | fieldset[disabled] .msp-plugin .msp-btn { 972 | background: #111318; 973 | opacity: .35 974 | } 975 | 976 | .msp-plugin .msp-btn, 977 | .msp-plugin .msp-control-row button { 978 | display: inline-block; 979 | margin-bottom: 0; 980 | text-align: center; 981 | touch-action: manipulation; 982 | cursor: pointer; 983 | background-image: none; 984 | white-space: nowrap; 985 | -webkit-user-select: none; 986 | -moz-user-select: none; 987 | -ms-user-select: none; 988 | user-select: none; 989 | padding: 0 10px; 990 | line-height: 32px; 991 | border: none; 992 | -moz-box-sizing: border-box; 993 | box-sizing: border-box 994 | } 995 | 996 | .msp-plugin .msp-btn[disabled], 997 | .msp-plugin .msp-control-row button[disabled] { 998 | background: #111318; 999 | opacity: .35 1000 | } 1001 | 1002 | .msp-plugin .msp-btn-block, 1003 | .msp-plugin .msp-control-row button { 1004 | display: block; 1005 | width: 100% 1006 | } 1007 | 1008 | .msp-plugin .msp-btn, 1009 | .msp-plugin .msp-control-row button, 1010 | .msp-plugin .msp-btn:active, 1011 | .msp-plugin .msp-btn-link:focus, 1012 | .msp-plugin .msp-btn:hover { 1013 | outline: none 1014 | } 1015 | 1016 | .msp-plugin .msp-material-icon svg { 1017 | display: inline-flex; 1018 | vertical-align: middle; 1019 | font-size: 1.2em; 1020 | margin-bottom: 3px; 1021 | fill: currentColor; 1022 | width: 1em; 1023 | height: 1em; 1024 | flex-shrink: 0; 1025 | user-select: none 1026 | } 1027 | 1028 | .msp-plugin .msp-btn-block>.msp-material-icon, 1029 | .msp-plugin .msp-control-row button>.msp-material-icon { 1030 | margin-left: 0; 1031 | margin-right: .4em 1032 | } 1033 | 1034 | .msp-plugin .msp-btn-childless>.msp-material-icon { 1035 | margin-left: 0; 1036 | margin-right: 0 1037 | } 1038 | 1039 | .msp-plugin .msp-btn-icon { 1040 | border: none; 1041 | height: 32px; 1042 | width: 32px; 1043 | line-height: 32px; 1044 | padding: 0; 1045 | text-align: center 1046 | } 1047 | 1048 | .msp-plugin .msp-btn-icon:hover { 1049 | color: #51a2fb; 1050 | background-color: rgb(22.2865853659, 24.9085365854, 31.4634146341); 1051 | border: none; 1052 | outline-offset: -1px !important; 1053 | outline: 1px solid rgb(54.006097561, 60.3597560976, 76.243902439) !important 1054 | } 1055 | 1056 | .msp-plugin .msp-btn-icon[disabled], 1057 | .msp-plugin .msp-btn-icon[disabled]:hover, 1058 | .msp-plugin .msp-btn-icon[disabled]:active { 1059 | color: hsl(216, 24.3902439024%, 50.9215686275%) 1060 | } 1061 | 1062 | .msp-plugin .msp-btn-icon-small { 1063 | border: none; 1064 | height: 32px; 1065 | width: 20px; 1066 | line-height: 32px; 1067 | padding: 0; 1068 | text-align: center 1069 | } 1070 | 1071 | .msp-plugin .msp-btn-icon-small:hover { 1072 | color: #51a2fb; 1073 | background-color: rgb(22.2865853659, 24.9085365854, 31.4634146341); 1074 | border: none; 1075 | outline-offset: -1px !important; 1076 | outline: 1px solid rgb(54.006097561, 60.3597560976, 76.243902439) !important 1077 | } 1078 | 1079 | .msp-plugin .msp-btn-icon-small[disabled], 1080 | .msp-plugin .msp-btn-icon-small[disabled]:hover, 1081 | .msp-plugin .msp-btn-icon-small[disabled]:active { 1082 | color: hsl(216, 24.3902439024%, 50.9215686275%) 1083 | } 1084 | 1085 | .msp-plugin .msp-btn-link { 1086 | font-weight: normal; 1087 | border-radius: 0 1088 | } 1089 | 1090 | .msp-plugin .msp-btn-link, 1091 | .msp-plugin .msp-btn-link:active, 1092 | .msp-plugin .msp-btn-link.active, 1093 | .msp-plugin .msp-btn-link[disabled], 1094 | fieldset[disabled] .msp-plugin .msp-btn-link { 1095 | background-color: rgba(0, 0, 0, 0); 1096 | -webkit-box-shadow: none; 1097 | box-shadow: none 1098 | } 1099 | 1100 | .msp-plugin .msp-btn-link, 1101 | .msp-plugin .msp-btn-link:hover, 1102 | .msp-plugin .msp-btn-link:focus, 1103 | .msp-plugin .msp-btn-link:active { 1104 | border-color: rgba(0, 0, 0, 0) 1105 | } 1106 | 1107 | .msp-plugin .msp-btn-link:hover, 1108 | .msp-plugin .msp-btn-link:focus { 1109 | text-decoration: none; 1110 | background-color: rgba(0, 0, 0, 0) 1111 | } 1112 | 1113 | .msp-plugin .msp-btn-link[disabled]:hover, 1114 | .msp-plugin .msp-btn-link[disabled]:focus, 1115 | fieldset[disabled] .msp-plugin .msp-btn-link:hover, 1116 | fieldset[disabled] .msp-plugin .msp-btn-link:focus { 1117 | text-decoration: none 1118 | } 1119 | 1120 | .msp-plugin .msp-btn-link .msp-icon { 1121 | font-size: 100% 1122 | } 1123 | 1124 | .msp-plugin .msp-btn-link, 1125 | .msp-plugin .msp-btn-link:active, 1126 | .msp-plugin .msp-btn-link:focus { 1127 | color: #ccd4e0; 1128 | text-decoration: none 1129 | } 1130 | 1131 | .msp-plugin .msp-btn-link:hover { 1132 | color: #51a2fb; 1133 | text-decoration: none 1134 | } 1135 | 1136 | .msp-plugin .msp-btn-link-toggle-on { 1137 | color: #ccd4e0 1138 | } 1139 | 1140 | .msp-plugin .msp-btn-link-toggle-off, 1141 | .msp-plugin .msp-btn-link-toggle-off:active, 1142 | .msp-plugin .msp-btn-link-toggle-off:focus { 1143 | color: hsl(216, 24.3902439024%, 50.9215686275%) !important 1144 | } 1145 | 1146 | .msp-plugin .msp-btn-link-toggle-off:hover, 1147 | .msp-plugin .msp-btn-link-toggle-on:hover { 1148 | color: #51a2fb !important 1149 | } 1150 | 1151 | .msp-plugin .msp-btn-action, 1152 | .msp-plugin .msp-btn-action:active, 1153 | .msp-plugin .msp-btn-action:focus { 1154 | color: #ccd4e0; 1155 | background: rgb(11.7134146341, 13.0914634146, 16.5365853659) 1156 | } 1157 | 1158 | .msp-plugin .msp-btn-action:hover { 1159 | color: #51a2fb; 1160 | background: rgb(6.4268292683, 7.1829268293, 9.0731707317) 1161 | } 1162 | 1163 | .msp-plugin .msp-btn-action[disabled], 1164 | .msp-plugin .msp-btn-action[disabled]:hover, 1165 | .msp-plugin .msp-btn-action[disabled]:active, 1166 | .msp-plugin .msp-btn-action[disabled]:focus { 1167 | color: hsl(216, 24.3902439024%, 82.9215686275%) 1168 | } 1169 | 1170 | .msp-plugin .msp-btn-commit-on, 1171 | .msp-plugin .msp-btn-commit-on:active, 1172 | .msp-plugin .msp-btn-commit-on:focus { 1173 | color: #68befd; 1174 | background: rgb(12.7707317073, 14.2731707317, 18.0292682927) 1175 | } 1176 | 1177 | .msp-plugin .msp-btn-commit-on:hover { 1178 | color: #51a2fb; 1179 | background: rgb(7.4841463415, 8.3646341463, 10.5658536585) 1180 | } 1181 | 1182 | .msp-plugin .msp-btn-commit-on[disabled], 1183 | .msp-plugin .msp-btn-commit-on[disabled]:hover, 1184 | .msp-plugin .msp-btn-commit-on[disabled]:active, 1185 | .msp-plugin .msp-btn-commit-on[disabled]:focus { 1186 | color: hsl(205.3691275168, 97.385620915%, 69%) 1187 | } 1188 | 1189 | .msp-plugin .msp-btn-commit-off, 1190 | .msp-plugin .msp-btn-commit-off:active, 1191 | .msp-plugin .msp-btn-commit-off:focus { 1192 | color: #ccd4e0; 1193 | background: rgb(8.5414634146, 9.5463414634, 12.0585365854) 1194 | } 1195 | 1196 | .msp-plugin .msp-btn-commit-off:hover { 1197 | color: #51a2fb; 1198 | background: rgb(3.2548780488, 3.637804878, 4.5951219512) 1199 | } 1200 | 1201 | .msp-plugin .msp-btn-commit-off[disabled], 1202 | .msp-plugin .msp-btn-commit-off[disabled]:hover, 1203 | .msp-plugin .msp-btn-commit-off[disabled]:active, 1204 | .msp-plugin .msp-btn-commit-off[disabled]:focus { 1205 | color: hsl(216, 24.3902439024%, 82.9215686275%) 1206 | } 1207 | 1208 | .msp-plugin .msp-btn-remove:hover { 1209 | color: #f2f4f7 1210 | } 1211 | 1212 | .msp-plugin .msp-btn-commit-on:hover { 1213 | color: hsl(205.3691275168, 97.385620915%, 50%) 1214 | } 1215 | 1216 | .msp-plugin .msp-btn-action { 1217 | height: 32px; 1218 | line-height: 32px 1219 | } 1220 | 1221 | .msp-plugin input[type=file] { 1222 | display: block 1223 | } 1224 | 1225 | .msp-plugin input[type=range] { 1226 | display: block; 1227 | width: 100% 1228 | } 1229 | 1230 | .msp-plugin select[multiple], 1231 | .msp-plugin select[size] { 1232 | height: auto 1233 | } 1234 | 1235 | .msp-plugin textarea.msp-form-control, 1236 | .msp-plugin textarea.msp-btn { 1237 | height: auto 1238 | } 1239 | 1240 | .msp-plugin .msp-control-top-offset { 1241 | margin-top: 1px 1242 | } 1243 | 1244 | .msp-plugin .msp-btn-commit { 1245 | text-align: right; 1246 | padding-top: 0; 1247 | padding-bottom: 0; 1248 | padding-right: 10px; 1249 | padding-left: 0; 1250 | line-height: 32px; 1251 | border: none; 1252 | overflow: hidden; 1253 | font-weight: bold 1254 | } 1255 | 1256 | .msp-plugin .msp-btn-commit .msp-icon { 1257 | display: block-inline; 1258 | line-height: 32px; 1259 | width: 32px; 1260 | text-align: center 1261 | } 1262 | 1263 | .msp-plugin select.msp-form-control, 1264 | .msp-plugin .msp-control-row select, 1265 | .msp-plugin select.msp-btn { 1266 | background: none; 1267 | background-color: rgb(11.7134146341, 13.0914634146, 16.5365853659); 1268 | background-size: 8px 12px; 1269 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAUCAMAAACzvE1FAAAADFBMVEUzMzMzMzMzMzMzMzMKAG/3AAAAA3RSTlMAf4C/aSLHAAAAPElEQVR42q3NMQ4AIAgEQTn//2cLdRKppSGzBYwzVXvznNWs8C58CiussPJj8h6NwgorrKRdTvuV9v16Afn0AYFOB7aYAAAAAElFTkSuQmCC); 1270 | background-repeat: no-repeat; 1271 | background-position: right 10px center; 1272 | padding-right: 24px 1273 | } 1274 | 1275 | .msp-plugin select.msp-form-control:-moz-focusring, 1276 | .msp-plugin .msp-control-row select:-moz-focusring, 1277 | .msp-plugin select.msp-btn:-moz-focusring { 1278 | color: rgba(0, 0, 0, 0); 1279 | text-shadow: 0 0 0 #ccd4e0 1280 | } 1281 | 1282 | .msp-plugin .msp-default-bg { 1283 | background: #111318 1284 | } 1285 | 1286 | .msp-plugin .msp-transparent-bg { 1287 | background: rgba(0, 0, 0, 0) 1288 | } 1289 | 1290 | .msp-plugin .msp-no-hover-outline:hover { 1291 | color: #51a2fb; 1292 | background-color: inherit; 1293 | border: none; 1294 | outline-offset: 0 !important; 1295 | outline: none !important 1296 | } 1297 | 1298 | .msp-plugin .msp-icon-inline { 1299 | margin-right: 8px 1300 | } 1301 | 1302 | .msp-plugin .msp-control-row { 1303 | position: relative; 1304 | height: 32px; 1305 | background: #111318; 1306 | margin-top: 1px 1307 | } 1308 | 1309 | .msp-plugin .msp-control-row>span.msp-control-row-label, 1310 | .msp-plugin .msp-control-row>button.msp-control-button-label { 1311 | line-height: 32px; 1312 | display: block; 1313 | width: 120px; 1314 | text-align: right; 1315 | padding: 0 10px; 1316 | color: hsl(216, 24.3902439024%, 68.9215686275%); 1317 | overflow: hidden; 1318 | text-overflow: ellipsis; 1319 | white-space: nowrap; 1320 | position: relative; 1321 | -webkit-user-select: none; 1322 | -moz-user-select: none; 1323 | -ms-user-select: none; 1324 | -o-user-select: none; 1325 | user-select: none; 1326 | cursor: default 1327 | } 1328 | 1329 | .msp-plugin .msp-control-row>button.msp-control-button-label { 1330 | background: #111318; 1331 | cursor: pointer 1332 | } 1333 | 1334 | .msp-plugin .msp-control-row .msp-control-current { 1335 | background: #111318 1336 | } 1337 | 1338 | .msp-plugin .msp-control-row>div.msp-control-row-ctrl { 1339 | position: absolute; 1340 | left: 120px; 1341 | top: 0; 1342 | right: 0; 1343 | bottom: 0 1344 | } 1345 | 1346 | .msp-plugin .msp-control-row>div { 1347 | background: rgb(11.7134146341, 13.0914634146, 16.5365853659) 1348 | } 1349 | 1350 | .msp-plugin .msp-control-row>.msp-flex-row, 1351 | .msp-plugin .msp-control-row>.msp-state-image-row { 1352 | background: #111318 1353 | } 1354 | 1355 | .msp-plugin .msp-control-label-short>span { 1356 | width: 80px !important 1357 | } 1358 | 1359 | .msp-plugin .msp-control-label-short>div:nth-child(2) { 1360 | left: 80px !important 1361 | } 1362 | 1363 | .msp-plugin .msp-control-col-2 { 1364 | float: left; 1365 | width: 50% 1366 | } 1367 | 1368 | .msp-plugin .msp-control-twoline { 1369 | height: 64px !important 1370 | } 1371 | 1372 | .msp-plugin .msp-control-group { 1373 | position: relative 1374 | } 1375 | 1376 | .msp-plugin .msp-toggle-button .msp-icon { 1377 | display: inline-block; 1378 | margin-right: 6px 1379 | } 1380 | 1381 | .msp-plugin .msp-toggle-button>div>button:hover { 1382 | border-color: rgb(22.2865853659, 24.9085365854, 31.4634146341) !important; 1383 | border: none; 1384 | outline-offset: -1px !important; 1385 | outline: 1px solid rgb(54.006097561, 60.3597560976, 76.243902439) !important 1386 | } 1387 | 1388 | .msp-plugin .msp-slider>div:first-child { 1389 | position: absolute; 1390 | top: 0; 1391 | left: 18px; 1392 | bottom: 0; 1393 | right: 62px; 1394 | display: flex 1395 | } 1396 | 1397 | .msp-plugin .msp-slider>div:last-child { 1398 | position: absolute; 1399 | height: 32px; 1400 | line-height: 32px; 1401 | text-align: center; 1402 | right: 0; 1403 | width: 50px; 1404 | top: 0; 1405 | bottom: 0 1406 | } 1407 | 1408 | .msp-plugin .msp-slider input[type=text] { 1409 | padding-right: 6px; 1410 | padding-left: 4px; 1411 | font-size: 80%; 1412 | text-align: right 1413 | } 1414 | 1415 | .msp-plugin .msp-slider2>div:first-child { 1416 | position: absolute; 1417 | height: 32px; 1418 | line-height: 32px; 1419 | text-align: center; 1420 | left: 0; 1421 | width: 25px; 1422 | top: 0; 1423 | bottom: 0; 1424 | font-size: 80% 1425 | } 1426 | 1427 | .msp-plugin .msp-slider2>div:nth-child(2) { 1428 | position: absolute; 1429 | top: 0; 1430 | left: 35px; 1431 | bottom: 0; 1432 | right: 37px; 1433 | display: flex 1434 | } 1435 | 1436 | .msp-plugin .msp-slider2>div:last-child { 1437 | position: absolute; 1438 | height: 32px; 1439 | line-height: 32px; 1440 | text-align: center; 1441 | right: 0; 1442 | width: 25px; 1443 | top: 0; 1444 | bottom: 0; 1445 | font-size: 80% 1446 | } 1447 | 1448 | .msp-plugin .msp-slider2 input[type=text] { 1449 | padding-right: 4px; 1450 | padding-left: 4px; 1451 | font-size: 80%; 1452 | text-align: center 1453 | } 1454 | 1455 | .msp-plugin .msp-toggle-color-picker button { 1456 | border: 10px solid rgb(11.7134146341, 13.0914634146, 16.5365853659) !important; 1457 | margin: 0; 1458 | text-align: center; 1459 | padding-right: 10px; 1460 | padding-left: 10px 1461 | } 1462 | 1463 | .msp-plugin .msp-toggle-color-picker button:hover { 1464 | border-color: rgb(22.2865853659, 24.9085365854, 31.4634146341) !important; 1465 | border: none; 1466 | outline-offset: -1px !important; 1467 | outline: 1px solid rgb(54.006097561, 60.3597560976, 76.243902439) !important 1468 | } 1469 | 1470 | .msp-plugin .msp-toggle-color-picker .msp-color-picker { 1471 | position: absolute; 1472 | z-index: 100000; 1473 | background: #111318; 1474 | border-top: 1px solid #111318; 1475 | padding-bottom: 5px; 1476 | width: 100% 1477 | } 1478 | 1479 | .msp-plugin .msp-toggle-color-picker-above .msp-color-picker { 1480 | top: -85px; 1481 | height: 85px 1482 | } 1483 | 1484 | .msp-plugin .msp-toggle-color-picker-below .msp-color-picker { 1485 | top: 32px; 1486 | height: 80px 1487 | } 1488 | 1489 | .msp-plugin .msp-control-offset { 1490 | padding-left: 10px 1491 | } 1492 | 1493 | .msp-plugin .msp-accent-offset { 1494 | padding-left: 1px; 1495 | margin-left: 8px; 1496 | border-left: 2px solid rgb(214.262195122, 113.4329268293, 24.237804878) 1497 | } 1498 | 1499 | .msp-plugin .msp-control-group-wrapper { 1500 | margin-bottom: 0px; 1501 | margin-top: 1px 1502 | } 1503 | 1504 | .msp-plugin .msp-control-group-header { 1505 | background: #111318 1506 | } 1507 | 1508 | .msp-plugin .msp-control-group-header>button, 1509 | .msp-plugin .msp-control-group-header div { 1510 | padding-left: 4px; 1511 | text-align: left; 1512 | height: 24px !important; 1513 | line-height: 24px !important; 1514 | font-size: 85% !important; 1515 | background: #111318 !important; 1516 | color: hsl(216, 24.3902439024%, 68.9215686275%) 1517 | } 1518 | 1519 | .msp-plugin .msp-control-group-header .msp-icon { 1520 | height: 24px !important; 1521 | line-height: 24px !important 1522 | } 1523 | 1524 | .msp-plugin .msp-control-group-header>span { 1525 | padding-left: 5px; 1526 | line-height: 21.3333333333px; 1527 | font-size: 70%; 1528 | background: #111318; 1529 | color: hsl(216, 24.3902439024%, 68.9215686275%) 1530 | } 1531 | 1532 | .msp-plugin .msp-control-current { 1533 | background: #111318 1534 | } 1535 | 1536 | .msp-plugin .msp-control-group-footer { 1537 | background: rgb(27.5731707317, 30.8170731707, 38.9268292683); 1538 | height: 5px; 1539 | font-size: 1px; 1540 | margin-top: 1px 1541 | } 1542 | 1543 | .msp-plugin .msp-control-group-expander { 1544 | display: block; 1545 | position: absolute; 1546 | line-height: 32px; 1547 | padding: 0; 1548 | left: 0; 1549 | top: 0; 1550 | width: 120px; 1551 | text-align: left; 1552 | background: rgba(0, 0, 0, 0) 1553 | } 1554 | 1555 | .msp-plugin .msp-control-group-expander .msp-icon { 1556 | line-height: 29px; 1557 | width: 31px; 1558 | text-align: center; 1559 | font-size: 100% 1560 | } 1561 | 1562 | .msp-plugin .msp-plugin-layout_controls { 1563 | position: absolute; 1564 | left: 10px; 1565 | top: 10px 1566 | } 1567 | 1568 | .msp-plugin .msp-plugin-layout_controls>button:first-child { 1569 | margin-right: 6px 1570 | } 1571 | 1572 | .msp-plugin .msp-empty-control { 1573 | display: none 1574 | } 1575 | 1576 | .msp-plugin .msp-control .msp-btn-block, 1577 | .msp-plugin .msp-control .msp-control-row button, 1578 | .msp-plugin .msp-control-row .msp-control button { 1579 | margin-bottom: 0px; 1580 | margin-top: 0px 1581 | } 1582 | 1583 | .msp-plugin .msp-row-text { 1584 | height: 32px; 1585 | position: relative; 1586 | background: #111318; 1587 | margin-top: 1px 1588 | } 1589 | 1590 | .msp-plugin .msp-row-text>div { 1591 | line-height: 32px; 1592 | text-align: center; 1593 | color: hsl(216, 24.3902439024%, 68.9215686275%) 1594 | } 1595 | 1596 | .msp-plugin .msp-help span { 1597 | display: none 1598 | } 1599 | 1600 | .msp-plugin .msp-help:hover span { 1601 | display: inline-block; 1602 | background: linear-gradient(#111318, rgba(17, 19, 24, 0.8)) 1603 | } 1604 | 1605 | .msp-plugin .msp-help-text { 1606 | position: relative; 1607 | background: #111318; 1608 | margin-top: 1px 1609 | } 1610 | 1611 | .msp-plugin .msp-help-text>div { 1612 | padding: 5px 10px; 1613 | text-align: left; 1614 | color: hsl(216, 24.3902439024%, 68.9215686275%) 1615 | } 1616 | 1617 | .msp-plugin .msp-help-text>p { 1618 | padding: 5px 10px; 1619 | text-align: left; 1620 | color: hsl(216, 24.3902439024%, 68.9215686275%) 1621 | } 1622 | 1623 | .msp-plugin .msp-help-description { 1624 | font-style: italic 1625 | } 1626 | 1627 | .msp-plugin .msp-help-legend { 1628 | padding-top: 10px 1629 | } 1630 | 1631 | .msp-plugin .msp-scale-legend>div { 1632 | width: 100%; 1633 | height: 30px 1634 | } 1635 | 1636 | .msp-plugin .msp-scale-legend>div>span { 1637 | padding: 5px; 1638 | color: #fff; 1639 | font-weight: bold; 1640 | background-color: rgba(0, 0, 0, .2) 1641 | } 1642 | 1643 | .msp-plugin .msp-table-legend>div { 1644 | margin-right: 5px; 1645 | display: inline-flex 1646 | } 1647 | 1648 | .msp-plugin .msp-table-legend>div .msp-table-legend-color { 1649 | width: 30px; 1650 | height: 20px 1651 | } 1652 | 1653 | .msp-plugin .msp-table-legend>div .msp-table-legend-text { 1654 | margin: 0 5px 1655 | } 1656 | 1657 | .msp-plugin .msp-image-preview { 1658 | position: relative; 1659 | background: #111318; 1660 | margin-top: 1px; 1661 | padding: 10px 1662 | } 1663 | 1664 | .msp-plugin .msp-image-preview canvas { 1665 | -webkit-user-select: none; 1666 | -moz-user-select: none; 1667 | -ms-user-select: none; 1668 | user-select: none 1669 | } 1670 | 1671 | .msp-plugin .msp-image-preview>span { 1672 | margin-top: 6px; 1673 | display: block; 1674 | text-align: center; 1675 | font-size: 80%; 1676 | line-height: 15px 1677 | } 1678 | 1679 | .msp-plugin .msp-copy-image-wrapper { 1680 | position: relative 1681 | } 1682 | 1683 | .msp-plugin .msp-copy-image-wrapper div { 1684 | font-weight: bold; 1685 | padding: 3px; 1686 | margin: 1px 0; 1687 | width: 100%; 1688 | background: rgb(11.7134146341, 13.0914634146, 16.5365853659); 1689 | text-align: center 1690 | } 1691 | 1692 | .msp-plugin .msp-copy-image-wrapper img { 1693 | margin-top: 1px 1694 | } 1695 | 1696 | .msp-plugin .msp-control-text-area-wrapper, 1697 | .msp-plugin .msp-text-area-wrapper { 1698 | position: relative 1699 | } 1700 | 1701 | .msp-plugin .msp-control-text-area-wrapper textarea, 1702 | .msp-plugin .msp-text-area-wrapper textarea { 1703 | border: none; 1704 | width: 100%; 1705 | height: 100%; 1706 | background: rgb(11.7134146341, 13.0914634146, 16.5365853659); 1707 | padding: 5px 10px; 1708 | resize: none; 1709 | font-size: 12px; 1710 | line-height: 16px 1711 | } 1712 | 1713 | .msp-plugin .msp-control-text-area-wrapper { 1714 | height: 64px !important 1715 | } 1716 | 1717 | .msp-plugin .msp-text-area-wrapper { 1718 | height: 96px !important 1719 | } 1720 | 1721 | .msp-plugin .msp-slider-base { 1722 | position: relative; 1723 | height: 14px; 1724 | padding: 5px 0; 1725 | width: 100%; 1726 | border-radius: 6px; 1727 | align-self: center; 1728 | box-sizing: border-box; 1729 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0) 1730 | } 1731 | 1732 | .msp-plugin .msp-slider-base * { 1733 | box-sizing: border-box; 1734 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0) 1735 | } 1736 | 1737 | .msp-plugin .msp-slider-base-rail { 1738 | position: absolute; 1739 | width: 100%; 1740 | background-color: rgb(30.7451219512, 34.362195122, 43.4048780488); 1741 | height: 4px; 1742 | border-radius: 2px 1743 | } 1744 | 1745 | .msp-plugin .msp-slider-base-track { 1746 | position: absolute; 1747 | left: 0; 1748 | height: 4px; 1749 | border-radius: 6px; 1750 | background-color: tint(#ccd4e0, 60%) 1751 | } 1752 | 1753 | .msp-plugin .msp-slider-base-handle { 1754 | position: absolute; 1755 | margin-left: -11px; 1756 | margin-top: -9px; 1757 | width: 22px; 1758 | height: 22px; 1759 | cursor: pointer; 1760 | border-radius: 50%; 1761 | background-color: #ccd4e0; 1762 | border: 4px solid rgb(30.7451219512, 34.362195122, 43.4048780488) 1763 | } 1764 | 1765 | .msp-plugin .msp-slider-base-handle:hover { 1766 | background-color: #51a2fb 1767 | } 1768 | 1769 | .msp-plugin .msp-slider-base-mark { 1770 | position: absolute; 1771 | top: 18px; 1772 | left: 0; 1773 | width: 100%; 1774 | font-size: 12px 1775 | } 1776 | 1777 | .msp-plugin .msp-slider-base-mark-text { 1778 | position: absolute; 1779 | display: inline-block; 1780 | vertical-align: middle; 1781 | text-align: center; 1782 | cursor: pointer; 1783 | color: #999 1784 | } 1785 | 1786 | .msp-plugin .msp-slider-base-mark-text-active { 1787 | color: #666 1788 | } 1789 | 1790 | .msp-plugin .msp-slider-base-step { 1791 | position: absolute; 1792 | width: 100%; 1793 | height: 4px; 1794 | background: rgba(0, 0, 0, 0) 1795 | } 1796 | 1797 | .msp-plugin .msp-slider-base-dot { 1798 | position: absolute; 1799 | bottom: -2px; 1800 | margin-left: -4px; 1801 | width: 8px; 1802 | height: 8px; 1803 | border: 2px solid #e9e9e9; 1804 | background-color: #fff; 1805 | cursor: pointer; 1806 | border-radius: 50%; 1807 | vertical-align: middle 1808 | } 1809 | 1810 | .msp-plugin .msp-slider-base-dot:first-child { 1811 | margin-left: -4px 1812 | } 1813 | 1814 | .msp-plugin .msp-slider-base-dot:last-child { 1815 | margin-left: -4px 1816 | } 1817 | 1818 | .msp-plugin .msp-slider-base-dot-active { 1819 | border-color: tint(#ccd4e0, 50%) 1820 | } 1821 | 1822 | .msp-plugin .msp-slider-base-disabled { 1823 | background: #111318; 1824 | opacity: .35 1825 | } 1826 | 1827 | .msp-plugin .msp-slider-base-disabled .msp-slider-base-handle, 1828 | .msp-plugin .msp-slider-base-disabled .msp-slider-base-dot { 1829 | cursor: not-allowed 1830 | } 1831 | 1832 | .msp-plugin .msp-slider-base-disabled .msp-slider-base-mark-text, 1833 | .msp-plugin .msp-slider-base-disabled .msp-slider-base-dot { 1834 | cursor: not-allowed !important 1835 | } 1836 | 1837 | .msp-plugin .msp-description { 1838 | padding: 10px; 1839 | font-size: 85%; 1840 | background: #111318; 1841 | text-align: center; 1842 | -webkit-user-select: none; 1843 | -moz-user-select: none; 1844 | -ms-user-select: none; 1845 | -o-user-select: none; 1846 | user-select: none; 1847 | font-weight: light; 1848 | cursor: default 1849 | } 1850 | 1851 | .msp-plugin .msp-description:not(:first-child) { 1852 | border-top: 1px solid rgb(30.7451219512, 34.362195122, 43.4048780488) 1853 | } 1854 | 1855 | .msp-plugin .msp-color-picker input { 1856 | color: #000 !important 1857 | } 1858 | 1859 | .msp-plugin .msp-no-webgl { 1860 | position: absolute; 1861 | width: 100%; 1862 | height: 100%; 1863 | left: 0; 1864 | top: 0; 1865 | display: table; 1866 | text-align: center; 1867 | background: #111318 1868 | } 1869 | 1870 | .msp-plugin .msp-no-webgl>div b { 1871 | font-size: 120% 1872 | } 1873 | 1874 | .msp-plugin .msp-no-webgl>div { 1875 | display: table-cell; 1876 | vertical-align: middle; 1877 | text-align: center; 1878 | width: 100%; 1879 | height: 100% 1880 | } 1881 | 1882 | .msp-plugin .msp-loader-msp-btn-file { 1883 | position: relative; 1884 | overflow: hidden 1885 | } 1886 | 1887 | .msp-plugin .msp-loader-msp-btn-file input[type=file] { 1888 | position: absolute; 1889 | top: 0; 1890 | right: 0; 1891 | min-width: 100%; 1892 | min-height: 100%; 1893 | font-size: 100px; 1894 | text-align: right; 1895 | filter: alpha(opacity=0); 1896 | opacity: 0; 1897 | outline: none; 1898 | background: #fff; 1899 | cursor: inherit; 1900 | display: block 1901 | } 1902 | 1903 | .msp-plugin .msp-controls-section { 1904 | margin-bottom: 10px 1905 | } 1906 | 1907 | .msp-plugin .msp-combined-color-button { 1908 | border: 4px solid rgb(11.7134146341, 13.0914634146, 16.5365853659) !important; 1909 | margin: 0; 1910 | text-align: center; 1911 | padding-right: 10px; 1912 | padding-left: 10px 1913 | } 1914 | 1915 | .msp-plugin .msp-combined-color-button:hover { 1916 | border-color: rgb(22.2865853659, 24.9085365854, 31.4634146341) !important; 1917 | border: none; 1918 | outline-offset: -1px !important; 1919 | outline: 1px solid rgb(54.006097561, 60.3597560976, 76.243902439) !important 1920 | } 1921 | 1922 | .msp-plugin .msp-combined-color-swatch { 1923 | width: 100%; 1924 | display: grid; 1925 | grid-gap: 1px; 1926 | grid-template-columns: repeat(6, auto) 1927 | } 1928 | 1929 | .msp-plugin .msp-combined-color-swatch .msp-btn:hover, 1930 | .msp-plugin .msp-combined-color-swatch .msp-control-row button:hover, 1931 | .msp-plugin .msp-control-row .msp-combined-color-swatch button:hover { 1932 | outline-offset: -1px !important; 1933 | outline: 1px solid rgb(54.006097561, 60.3597560976, 76.243902439) !important 1934 | } 1935 | 1936 | .msp-plugin .msp-action-select { 1937 | position: relative 1938 | } 1939 | 1940 | .msp-plugin .msp-action-select select { 1941 | padding-left: 42px 1942 | } 1943 | 1944 | .msp-plugin .msp-action-select option:first-child { 1945 | color: hsl(216, 24.3902439024%, 68.9215686275%) 1946 | } 1947 | 1948 | .msp-plugin .msp-action-select>.msp-icon { 1949 | display: block; 1950 | top: 0; 1951 | left: 10px; 1952 | position: absolute; 1953 | line-height: 32px 1954 | } 1955 | 1956 | .msp-plugin .msp-simple-help-section { 1957 | height: 28px; 1958 | line-height: 28px; 1959 | margin-top: 5px; 1960 | margin-bottom: 5px; 1961 | padding: 0 10px; 1962 | font-weight: 500; 1963 | background: #111318; 1964 | color: #ccd4e0 1965 | } 1966 | 1967 | .msp-plugin .msp-left-panel-controls-buttons { 1968 | position: absolute; 1969 | width: 32px; 1970 | top: 0; 1971 | bottom: 0; 1972 | padding-top: 10px; 1973 | background: #111318 1974 | } 1975 | 1976 | .msp-plugin .msp-left-panel-controls-buttons-bottom { 1977 | position: absolute; 1978 | bottom: 0 1979 | } 1980 | 1981 | .msp-plugin .msp-left-panel-controls-button-data-dirty { 1982 | position: absolute; 1983 | width: 6px; 1984 | height: 6px; 1985 | background: rgb(214.262195122, 113.4329268293, 24.237804878); 1986 | border-radius: 3px; 1987 | right: 6px; 1988 | bottom: 6px 1989 | } 1990 | 1991 | .msp-plugin .msp-left-panel-controls .msp-scrollable-container { 1992 | left: 33px 1993 | } 1994 | 1995 | .msp-plugin .msp-mapped-parameter-group { 1996 | position: relative 1997 | } 1998 | 1999 | .msp-plugin .msp-mapped-parameter-group>.msp-control-row:first-child>div:nth-child(2) { 2000 | right: 33px 2001 | } 2002 | 2003 | .msp-plugin .msp-mapped-parameter-group>button:first-child { 2004 | right: 33px 2005 | } 2006 | 2007 | .msp-plugin .msp-mapped-parameter-group>.msp-btn-icon { 2008 | position: absolute; 2009 | right: 0; 2010 | width: 32px; 2011 | top: 0; 2012 | padding: 0 2013 | } 2014 | 2015 | .msp-plugin .msp-shape-filled { 2016 | fill: #ccd4e0; 2017 | stroke: #ccd4e0 2018 | } 2019 | 2020 | .msp-plugin .msp-shape-empty { 2021 | fill: none; 2022 | stroke: #ccd4e0 2023 | } 2024 | 2025 | .msp-plugin .msp-no-overflow { 2026 | overflow: hidden; 2027 | text-overflow: ellipsis; 2028 | white-space: nowrap 2029 | } 2030 | 2031 | .msp-plugin .msp-25-lower-contrast-text { 2032 | color: hsl(216, 24.3902439024%, 58.9215686275%) 2033 | } 2034 | 2035 | .msp-plugin .msp-expandable-group-color-stripe { 2036 | position: absolute; 2037 | left: 0; 2038 | top: 30px; 2039 | width: 120px; 2040 | height: 2px 2041 | } 2042 | 2043 | .msp-plugin .msp-section-header { 2044 | height: 32px; 2045 | line-height: 32px; 2046 | margin-top: 10px; 2047 | margin-bottom: 10px; 2048 | text-align: right; 2049 | padding: 0 10px; 2050 | font-weight: bold; 2051 | background: #111318; 2052 | overflow: hidden; 2053 | cursor: default 2054 | } 2055 | 2056 | .msp-plugin .msp-section-header>.msp-icon { 2057 | display: block; 2058 | float: left 2059 | } 2060 | 2061 | .msp-plugin .msp-section-header>small { 2062 | font-weight: normal 2063 | } 2064 | 2065 | .msp-plugin .msp-current-header { 2066 | height: 32px; 2067 | line-height: 32px; 2068 | margin-bottom: 10px; 2069 | text-align: center; 2070 | font-weight: bold; 2071 | background: #111318 2072 | } 2073 | 2074 | .msp-plugin .msp-flex-row, 2075 | .msp-plugin .msp-state-image-row { 2076 | margin-top: 1px; 2077 | background: #111318; 2078 | display: flex; 2079 | flex-direction: row; 2080 | width: inherit; 2081 | height: 32px 2082 | } 2083 | 2084 | .msp-plugin .msp-flex-row>.msp-flex-item, 2085 | .msp-plugin .msp-state-image-row>.msp-flex-item { 2086 | margin: 0; 2087 | flex: 1 1 auto; 2088 | margin-right: 1px; 2089 | overflow: hidden 2090 | } 2091 | 2092 | .msp-plugin .msp-flex-row>.msp-flex-item:last-child, 2093 | .msp-plugin .msp-state-image-row>.msp-flex-item:last-child { 2094 | margin-right: 0 2095 | } 2096 | 2097 | .msp-plugin .msp-flex-row>select, 2098 | .msp-plugin .msp-state-image-row>select, 2099 | .msp-plugin .msp-flex-row>button, 2100 | .msp-plugin .msp-state-image-row>button { 2101 | margin: 0; 2102 | flex: 1 1 auto; 2103 | margin-right: 1px; 2104 | height: 32px; 2105 | overflow: hidden 2106 | } 2107 | 2108 | .msp-plugin .msp-flex-row .msp-btn-icon, 2109 | .msp-plugin .msp-state-image-row .msp-btn-icon, 2110 | .msp-plugin .msp-flex-row .msp-btn-icon-small, 2111 | .msp-plugin .msp-state-image-row .msp-btn-icon-small { 2112 | flex: 0 0 32px; 2113 | max-width: 32px 2114 | } 2115 | 2116 | .msp-plugin .msp-flex-row>select, 2117 | .msp-plugin .msp-state-image-row>select { 2118 | background: none 2119 | } 2120 | 2121 | .msp-plugin .msp-flex-row>select>option[value=_], 2122 | .msp-plugin .msp-state-image-row>select>option[value=_] { 2123 | display: none 2124 | } 2125 | 2126 | .msp-plugin .msp-flex-row>select:last-child, 2127 | .msp-plugin .msp-state-image-row>select:last-child, 2128 | .msp-plugin .msp-flex-row>button:last-child, 2129 | .msp-plugin .msp-state-image-row>button:last-child { 2130 | margin-right: 0 2131 | } 2132 | 2133 | .msp-plugin .msp-flex-row>button.msp-control-button-label, 2134 | .msp-plugin .msp-state-image-row>button.msp-control-button-label { 2135 | background: #111318 2136 | } 2137 | 2138 | .msp-plugin .msp-state-list { 2139 | list-style: none 2140 | } 2141 | 2142 | .msp-plugin .msp-state-list>li { 2143 | position: relative; 2144 | overflow: hidden 2145 | } 2146 | 2147 | .msp-plugin .msp-state-list>li>button:first-child { 2148 | text-align: left; 2149 | border-left: 10px solid rgb(42.3756097561, 47.3609756098, 59.8243902439) !important 2150 | } 2151 | 2152 | .msp-plugin .msp-state-list>li>div { 2153 | position: absolute; 2154 | right: 0; 2155 | top: 0 2156 | } 2157 | 2158 | .msp-plugin .msp-state-image-row { 2159 | height: 96px; 2160 | margin-top: 0px 2161 | } 2162 | 2163 | .msp-plugin .msp-state-image-row>button { 2164 | height: 96px; 2165 | padding: 0px 2166 | } 2167 | 2168 | .msp-plugin .msp-state-image-row>button>img { 2169 | min-height: 96px; 2170 | width: inherit; 2171 | transform: translateY(-50%); 2172 | top: 50%; 2173 | position: relative 2174 | } 2175 | 2176 | .msp-plugin .msp-tree-row { 2177 | position: relative; 2178 | margin-top: 0; 2179 | margin-bottom: 1px; 2180 | background: rgba(0, 0, 0, 0) 2181 | } 2182 | 2183 | .msp-plugin .msp-tree-row-current .msp-btn-tree-label>span { 2184 | font-weight: bold 2185 | } 2186 | 2187 | .msp-plugin .msp-tree-row-current .msp-btn-tree-label { 2188 | border-radius: 0 !important 2189 | } 2190 | 2191 | .msp-plugin .msp-tree-row .msp-btn-tree-label { 2192 | text-align: left; 2193 | border-radius: 0 0 0 8px; 2194 | border-left-width: 4px; 2195 | border-left-style: solid 2196 | } 2197 | 2198 | .msp-plugin .msp-tree-row .msp-btn-tree-label>small { 2199 | color: hsl(216, 24.3902439024%, 63.9215686275%) 2200 | } 2201 | 2202 | .msp-plugin .msp-tree-updates-wrapper .msp-control-group-header:last-child { 2203 | margin-bottom: 1px 2204 | } 2205 | 2206 | .msp-plugin .msp-viewport-top-left-controls { 2207 | position: absolute; 2208 | left: 10px; 2209 | top: 10px 2210 | } 2211 | 2212 | .msp-plugin .msp-viewport-top-left-controls .msp-traj-controls { 2213 | line-height: 32px; 2214 | float: left; 2215 | margin-right: 10px; 2216 | background-color: rgb(11.7134146341, 13.0914634146, 16.5365853659) 2217 | } 2218 | 2219 | .msp-plugin .msp-viewport-top-left-controls .msp-traj-controls>span { 2220 | color: #ccd4e0; 2221 | margin-left: 10px; 2222 | margin-right: 10px; 2223 | font-size: 85%; 2224 | display: inline-block 2225 | } 2226 | 2227 | .msp-plugin .msp-viewport-top-left-controls .msp-state-snapshot-viewport-controls { 2228 | line-height: 32px; 2229 | float: left; 2230 | margin-right: 10px 2231 | } 2232 | 2233 | .msp-plugin .msp-viewport-top-left-controls .msp-state-snapshot-viewport-controls>button { 2234 | background-color: rgb(11.7134146341, 13.0914634146, 16.5365853659) 2235 | } 2236 | 2237 | .msp-plugin .msp-viewport-top-left-controls .msp-state-snapshot-viewport-controls>select { 2238 | display: inline-block; 2239 | width: 200px; 2240 | margin-right: 10px 2241 | } 2242 | 2243 | .msp-plugin .msp-viewport-top-left-controls .msp-animation-viewport-controls { 2244 | line-height: 32px; 2245 | float: left; 2246 | margin-right: 10px; 2247 | position: relative 2248 | } 2249 | 2250 | .msp-plugin .msp-viewport-top-left-controls .msp-animation-viewport-controls>div:first-child { 2251 | position: relative; 2252 | display: inline-block 2253 | } 2254 | 2255 | .msp-plugin .msp-viewport-top-left-controls .msp-animation-viewport-controls>div:first-child>button { 2256 | position: relative 2257 | } 2258 | 2259 | .msp-plugin .msp-viewport-top-left-controls .msp-animation-viewport-controls .msp-animation-viewport-controls-select { 2260 | width: 290px; 2261 | position: absolute; 2262 | left: 0; 2263 | margin-top: 10px; 2264 | background: rgb(30.7451219512, 34.362195122, 43.4048780488) 2265 | } 2266 | 2267 | .msp-plugin .msp-viewport-top-left-controls .msp-animation-viewport-controls .msp-animation-viewport-controls-select .msp-control-row:first-child { 2268 | margin-top: 0 2269 | } 2270 | 2271 | .msp-plugin .msp-selection-viewport-controls { 2272 | position: relative; 2273 | margin: 10px auto 0 auto; 2274 | width: 430px 2275 | } 2276 | 2277 | .msp-plugin .msp-selection-viewport-controls-actions { 2278 | position: absolute; 2279 | width: 100%; 2280 | top: 32px; 2281 | background: rgb(30.7451219512, 34.362195122, 43.4048780488) 2282 | } 2283 | 2284 | .msp-plugin .msp-selection-viewport-controls>.msp-flex-row .msp-btn, 2285 | .msp-plugin .msp-selection-viewport-controls>.msp-state-image-row .msp-btn, 2286 | .msp-plugin .msp-selection-viewport-controls>.msp-flex-row .msp-control-row button, 2287 | .msp-plugin .msp-control-row .msp-selection-viewport-controls>.msp-flex-row button, 2288 | .msp-plugin .msp-selection-viewport-controls>.msp-state-image-row .msp-control-row button, 2289 | .msp-plugin .msp-control-row .msp-selection-viewport-controls>.msp-state-image-row button { 2290 | padding: 0 5px 2291 | } 2292 | 2293 | .msp-plugin .msp-selection-viewport-controls select.msp-form-control, 2294 | .msp-plugin .msp-selection-viewport-controls select.msp-btn, 2295 | .msp-plugin .msp-selection-viewport-controls .msp-control-row select, 2296 | .msp-plugin .msp-control-row .msp-selection-viewport-controls select { 2297 | padding: 0 5px; 2298 | text-align: center; 2299 | background: rgb(11.7134146341, 13.0914634146, 16.5365853659); 2300 | flex: 0 0 80px; 2301 | text-overflow: ellipsis 2302 | } 2303 | 2304 | .msp-plugin .msp-param-object-list-item { 2305 | margin-top: 1px; 2306 | position: relative 2307 | } 2308 | 2309 | .msp-plugin .msp-param-object-list-item>button { 2310 | text-align: left 2311 | } 2312 | 2313 | .msp-plugin .msp-param-object-list-item>button>span { 2314 | font-weight: bold 2315 | } 2316 | 2317 | .msp-plugin .msp-param-object-list-item>div { 2318 | position: absolute; 2319 | right: 0; 2320 | top: 0 2321 | } 2322 | 2323 | .msp-plugin .msp-state-actions .msp-transform-wrapper:last-child { 2324 | margin-bottom: 10px 2325 | } 2326 | 2327 | .msp-plugin .msp-button-row { 2328 | display: flex; 2329 | flex-direction: row; 2330 | height: 32px; 2331 | width: inherit 2332 | } 2333 | 2334 | .msp-plugin .msp-button-row>button { 2335 | margin: 0; 2336 | flex: 1 1 auto; 2337 | margin-right: 1px; 2338 | height: 32px; 2339 | text-align-last: center; 2340 | background: none; 2341 | padding: 0 10px; 2342 | overflow: hidden 2343 | } 2344 | 2345 | .msp-plugin .msp-action-menu-options-no-header, 2346 | .msp-plugin .msp-action-menu-options .msp-control-group-children { 2347 | max-height: 300px; 2348 | overflow: hidden; 2349 | overflow-y: auto 2350 | } 2351 | 2352 | .msp-plugin .msp-action-menu-options .msp-control-row, 2353 | .msp-plugin .msp-action-menu-options button, 2354 | .msp-plugin .msp-action-menu-options .msp-icon, 2355 | .msp-plugin .msp-action-menu-options .msp-flex-row, 2356 | .msp-plugin .msp-action-menu-options .msp-state-image-row { 2357 | height: 24px; 2358 | line-height: 24px 2359 | } 2360 | 2361 | .msp-plugin .msp-action-menu-options button { 2362 | text-align: left 2363 | } 2364 | 2365 | .msp-plugin .msp-action-menu-options .msp-action-menu-button { 2366 | margin-top: 1px; 2367 | display: flex 2368 | } 2369 | 2370 | .msp-plugin .msp-action-menu-options .msp-action-menu-button .msp-icon { 2371 | margin-right: 6px 2372 | } 2373 | 2374 | .msp-plugin .msp-representation-entry { 2375 | position: relative 2376 | } 2377 | 2378 | .msp-plugin .msp-representation-entry>.msp-control-group-header>.msp-btn, 2379 | .msp-plugin .msp-control-row .msp-representation-entry>.msp-control-group-header>button { 2380 | font-weight: bold 2381 | } 2382 | 2383 | .msp-plugin .msp-representation-entry>.msp-control-group-header>.msp-icon, 2384 | .msp-plugin .msp-representation-entry>.msp-control-group-header>.msp-btn-link { 2385 | line-height: 24px; 2386 | height: 24px 2387 | } 2388 | 2389 | .msp-plugin .msp-control-group-presets-wrapper { 2390 | position: absolute; 2391 | right: 0; 2392 | top: 0 2393 | } 2394 | 2395 | .msp-plugin .msp-control-group-presets-wrapper .msp-control-group-header { 2396 | background: rgba(0, 0, 0, 0) 2397 | } 2398 | 2399 | .msp-plugin .msp-control-group-presets-wrapper button { 2400 | background: rgba(0, 0, 0, 0) !important 2401 | } 2402 | 2403 | .msp-plugin .msp-parameter-matrix input { 2404 | flex: 1 1 auto; 2405 | min-width: 0 2406 | } 2407 | 2408 | .msp-plugin .msp-btn-apply-simple { 2409 | text-align: left 2410 | } 2411 | 2412 | .msp-plugin .msp-btn-apply-simple .msp-icon { 2413 | margin-right: 10px 2414 | } 2415 | 2416 | .msp-plugin .msp-type-class-Root { 2417 | border-left-color: #111318 2418 | } 2419 | 2420 | .msp-plugin .msp-type-class-Group { 2421 | border-left-color: rgb(214.262195122, 113.4329268293, 24.237804878) 2422 | } 2423 | 2424 | .msp-plugin .msp-type-class-Data { 2425 | border-left-color: hsl(183.5294117647, 8.7179487179%, 46.7647058824%) 2426 | } 2427 | 2428 | .msp-plugin .msp-type-class-Object { 2429 | border-left-color: rgb(36.616, 162.384, 89.948) 2430 | } 2431 | 2432 | .msp-plugin .msp-type-class-Representation3D { 2433 | border-left-color: rgb(36.9790794979, 139.6987447699, 208.5209205021) 2434 | } 2435 | 2436 | .msp-plugin .msp-type-class-Behavior { 2437 | border-left-color: rgb(127.949790795, 67.1966527197, 152.8033472803) 2438 | } 2439 | 2440 | .msp-plugin .msp-accent-color-cyan { 2441 | color: hsl(183.5294117647, 8.7179487179%, 46.7647058824%) 2442 | } 2443 | 2444 | .msp-plugin .msp-accent-bg-cyan { 2445 | background: hsl(183.5294117647, 8.7179487179%, 46.7647058824%) 2446 | } 2447 | 2448 | .msp-plugin .msp-transform-header-brand-cyan { 2449 | border-bottom: 1px solid hsl(183.5294117647, 8.7179487179%, 46.7647058824%) 2450 | } 2451 | 2452 | .msp-plugin .msp-transform-header-brand-cyan:active, 2453 | .msp-plugin .msp-transform-header-brand-cyan:focus { 2454 | border-bottom: 1px solid hsl(183.5294117647, 8.7179487179%, 46.7647058824%) 2455 | } 2456 | 2457 | .msp-plugin .msp-accent-color-red { 2458 | color: rgb(190.9931506849, 39.1780821918, 23.5068493151) 2459 | } 2460 | 2461 | .msp-plugin .msp-accent-bg-red { 2462 | background: rgb(190.9931506849, 39.1780821918, 23.5068493151) 2463 | } 2464 | 2465 | .msp-plugin .msp-transform-header-brand-red { 2466 | border-bottom: 1px solid rgb(190.9931506849, 39.1780821918, 23.5068493151) 2467 | } 2468 | 2469 | .msp-plugin .msp-transform-header-brand-red:active, 2470 | .msp-plugin .msp-transform-header-brand-red:focus { 2471 | border-bottom: 1px solid rgb(190.9931506849, 39.1780821918, 23.5068493151) 2472 | } 2473 | 2474 | .msp-plugin .msp-accent-color-gray { 2475 | color: rgb(33.8356164384, 47.5, 61.1643835616) 2476 | } 2477 | 2478 | .msp-plugin .msp-accent-bg-gray { 2479 | background: rgb(33.8356164384, 47.5, 61.1643835616) 2480 | } 2481 | 2482 | .msp-plugin .msp-transform-header-brand-gray { 2483 | border-bottom: 1px solid rgb(33.8356164384, 47.5, 61.1643835616) 2484 | } 2485 | 2486 | .msp-plugin .msp-transform-header-brand-gray:active, 2487 | .msp-plugin .msp-transform-header-brand-gray:focus { 2488 | border-bottom: 1px solid rgb(33.8356164384, 47.5, 61.1643835616) 2489 | } 2490 | 2491 | .msp-plugin .msp-accent-color-green { 2492 | color: rgb(36.616, 162.384, 89.948) 2493 | } 2494 | 2495 | .msp-plugin .msp-accent-bg-green { 2496 | background: rgb(36.616, 162.384, 89.948) 2497 | } 2498 | 2499 | .msp-plugin .msp-transform-header-brand-green { 2500 | border-bottom: 1px solid rgb(36.616, 162.384, 89.948) 2501 | } 2502 | 2503 | .msp-plugin .msp-transform-header-brand-green:active, 2504 | .msp-plugin .msp-transform-header-brand-green:focus { 2505 | border-bottom: 1px solid rgb(36.616, 162.384, 89.948) 2506 | } 2507 | 2508 | .msp-plugin .msp-accent-color-purple { 2509 | color: rgb(127.949790795, 67.1966527197, 152.8033472803) 2510 | } 2511 | 2512 | .msp-plugin .msp-accent-bg-purple { 2513 | background: rgb(127.949790795, 67.1966527197, 152.8033472803) 2514 | } 2515 | 2516 | .msp-plugin .msp-transform-header-brand-purple { 2517 | border-bottom: 1px solid rgb(127.949790795, 67.1966527197, 152.8033472803) 2518 | } 2519 | 2520 | .msp-plugin .msp-transform-header-brand-purple:active, 2521 | .msp-plugin .msp-transform-header-brand-purple:focus { 2522 | border-bottom: 1px solid rgb(127.949790795, 67.1966527197, 152.8033472803) 2523 | } 2524 | 2525 | .msp-plugin .msp-accent-color-blue { 2526 | color: rgb(36.9790794979, 139.6987447699, 208.5209205021) 2527 | } 2528 | 2529 | .msp-plugin .msp-accent-bg-blue { 2530 | background: rgb(36.9790794979, 139.6987447699, 208.5209205021) 2531 | } 2532 | 2533 | .msp-plugin .msp-transform-header-brand-blue { 2534 | border-bottom: 1px solid rgb(36.9790794979, 139.6987447699, 208.5209205021) 2535 | } 2536 | 2537 | .msp-plugin .msp-transform-header-brand-blue:active, 2538 | .msp-plugin .msp-transform-header-brand-blue:focus { 2539 | border-bottom: 1px solid rgb(36.9790794979, 139.6987447699, 208.5209205021) 2540 | } 2541 | 2542 | .msp-plugin .msp-accent-color-orange { 2543 | color: rgb(214.262195122, 113.4329268293, 24.237804878) 2544 | } 2545 | 2546 | .msp-plugin .msp-accent-bg-orange { 2547 | background: rgb(214.262195122, 113.4329268293, 24.237804878) 2548 | } 2549 | 2550 | .msp-plugin .msp-transform-header-brand-orange { 2551 | border-bottom: 1px solid rgb(214.262195122, 113.4329268293, 24.237804878) 2552 | } 2553 | 2554 | .msp-plugin .msp-transform-header-brand-orange:active, 2555 | .msp-plugin .msp-transform-header-brand-orange:focus { 2556 | border-bottom: 1px solid rgb(214.262195122, 113.4329268293, 24.237804878) 2557 | } 2558 | 2559 | .msp-plugin .msp-volume-channel-inline-controls>:first-child { 2560 | position: absolute; 2561 | left: 0; 2562 | top: 0; 2563 | height: 32px; 2564 | right: 32px 2565 | } 2566 | 2567 | .msp-plugin .msp-volume-channel-inline-controls .msp-slider>div:first-child { 2568 | right: 42px 2569 | } 2570 | 2571 | .msp-plugin .msp-volume-channel-inline-controls .msp-slider>div:last-child { 2572 | width: 30px 2573 | } 2574 | 2575 | .msp-plugin .msp-volume-channel-inline-controls>button { 2576 | position: absolute; 2577 | right: 0; 2578 | width: 32px; 2579 | top: 0; 2580 | padding: 0 2581 | } 2582 | 2583 | .msp-plugin .msp-volume-channel-inline-controls>button .msp-material-icon { 2584 | margin-right: 0 2585 | } 2586 | 2587 | .msp-plugin .msp-list-unstyled { 2588 | padding-left: 0; 2589 | list-style: none 2590 | } 2591 | 2592 | .msp-plugin .msp-drag-drop-overlay { 2593 | border: 12px dashed #ccd4e0; 2594 | background: rgba(0, 0, 0, .36); 2595 | display: flex; 2596 | align-items: center; 2597 | justify-content: center; 2598 | position: absolute; 2599 | left: 0; 2600 | right: 0; 2601 | top: 0; 2602 | bottom: 0; 2603 | font-size: 48px; 2604 | font-weight: bold 2605 | } 2606 | 2607 | .msp-plugin .msp-task-state { 2608 | line-height: 32px 2609 | } 2610 | 2611 | .msp-plugin .msp-task-state>span { 2612 | -webkit-user-select: none; 2613 | -moz-user-select: none; 2614 | -ms-user-select: none; 2615 | -o-user-select: none; 2616 | user-select: none; 2617 | cursor: default 2618 | } 2619 | 2620 | .msp-plugin .msp-overlay-tasks { 2621 | position: absolute; 2622 | display: flex; 2623 | top: 0; 2624 | left: 0; 2625 | bottom: 0; 2626 | right: 0; 2627 | height: 100%; 2628 | width: 100%; 2629 | z-index: 1000; 2630 | justify-content: center; 2631 | align-items: center; 2632 | background: rgba(0, 0, 0, .25) 2633 | } 2634 | 2635 | .msp-plugin .msp-overlay-tasks .msp-task-state>div { 2636 | height: 32px; 2637 | margin-top: 1px; 2638 | position: relative; 2639 | width: 100%; 2640 | background: #111318 2641 | } 2642 | 2643 | .msp-plugin .msp-overlay-tasks .msp-task-state>div>div { 2644 | height: 32px; 2645 | line-height: 32px; 2646 | display: inline-block; 2647 | padding: 0 10px; 2648 | -webkit-user-select: none; 2649 | -moz-user-select: none; 2650 | -ms-user-select: none; 2651 | -o-user-select: none; 2652 | user-select: none; 2653 | cursor: default; 2654 | white-space: nowrap; 2655 | background: #111318; 2656 | position: absolute 2657 | } 2658 | 2659 | .msp-plugin .msp-overlay-tasks .msp-task-state>div>button { 2660 | display: inline-block; 2661 | margin-top: -3px 2662 | } 2663 | 2664 | .msp-plugin .msp-background-tasks { 2665 | position: absolute; 2666 | left: 0; 2667 | bottom: 0; 2668 | z-index: 1000 2669 | } 2670 | 2671 | .msp-plugin .msp-background-tasks .msp-task-state>div { 2672 | height: 32px; 2673 | margin-top: 1px; 2674 | position: relative; 2675 | width: 100%; 2676 | background: #111318 2677 | } 2678 | 2679 | .msp-plugin .msp-background-tasks .msp-task-state>div>div { 2680 | height: 32px; 2681 | line-height: 32px; 2682 | display: inline-block; 2683 | padding: 0 10px; 2684 | -webkit-user-select: none; 2685 | -moz-user-select: none; 2686 | -ms-user-select: none; 2687 | -o-user-select: none; 2688 | user-select: none; 2689 | cursor: default; 2690 | white-space: nowrap; 2691 | background: #111318; 2692 | position: absolute 2693 | } 2694 | 2695 | .msp-plugin .msp-background-tasks .msp-task-state>div>button { 2696 | display: inline-block; 2697 | margin-top: -3px 2698 | } 2699 | 2700 | .msp-plugin .msp-viewport { 2701 | position: absolute; 2702 | left: 0; 2703 | top: 0; 2704 | right: 0; 2705 | bottom: 0; 2706 | background: #111318 2707 | } 2708 | 2709 | .msp-plugin .msp-viewport .msp-btn-link { 2710 | background: rgba(0, 0, 0, .2) 2711 | } 2712 | 2713 | .msp-plugin .msp-viewport-expanded { 2714 | position: fixed; 2715 | z-index: 1000 2716 | } 2717 | 2718 | .msp-plugin .msp-viewport-host3d { 2719 | position: absolute; 2720 | left: 0; 2721 | top: 0; 2722 | right: 0; 2723 | bottom: 0; 2724 | -webkit-user-select: none; 2725 | user-select: none; 2726 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 2727 | -webkit-touch-callout: none; 2728 | touch-action: manipulation 2729 | } 2730 | 2731 | .msp-plugin .msp-viewport-host3d>canvas { 2732 | background-color: #111318; 2733 | background-image: linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%, lightgrey), linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%, lightgrey); 2734 | background-size: 60px 60px; 2735 | background-position: 0 0, 30px 30px 2736 | } 2737 | 2738 | .msp-plugin .msp-viewport-controls { 2739 | position: absolute; 2740 | right: 10px; 2741 | top: 10px; 2742 | width: 32px 2743 | } 2744 | 2745 | .msp-plugin .msp-viewport-controls-buttons { 2746 | text-align: right; 2747 | position: relative 2748 | } 2749 | 2750 | .msp-plugin .msp-viewport-controls-buttons>div { 2751 | position: relative; 2752 | margin-bottom: 4px 2753 | } 2754 | 2755 | .msp-plugin .msp-viewport-controls-buttons button { 2756 | padding: 0; 2757 | text-align: center; 2758 | width: 32px; 2759 | position: relative 2760 | } 2761 | 2762 | .msp-plugin .msp-viewport-controls-buttons .msp-btn-link-toggle-off { 2763 | color: hsl(216, 24.3902439024%, 50.9215686275%) 2764 | } 2765 | 2766 | .msp-plugin .msp-viewport-controls-buttons .msp-btn-link:hover { 2767 | color: #51a2fb 2768 | } 2769 | 2770 | .msp-plugin .msp-semi-transparent-background { 2771 | background: #111318; 2772 | opacity: .5; 2773 | position: absolute; 2774 | top: 0; 2775 | left: 0; 2776 | width: 100%; 2777 | height: 100% 2778 | } 2779 | 2780 | .msp-plugin .msp-hover-box-wrapper { 2781 | position: relative 2782 | } 2783 | 2784 | .msp-plugin .msp-hover-box-wrapper .msp-hover-box-body { 2785 | visibility: hidden; 2786 | position: absolute; 2787 | right: 36px; 2788 | top: 0; 2789 | width: 100px; 2790 | background-color: #111318 2791 | } 2792 | 2793 | .msp-plugin .msp-hover-box-wrapper .msp-hover-box-spacer { 2794 | visibility: hidden; 2795 | position: absolute; 2796 | right: 32px; 2797 | top: 0; 2798 | width: 4px; 2799 | height: 32px 2800 | } 2801 | 2802 | .msp-plugin .msp-hover-box-wrapper:hover .msp-hover-box-body, 2803 | .msp-plugin .msp-hover-box-wrapper:hover .msp-hover-box-spacer { 2804 | visibility: visible 2805 | } 2806 | 2807 | .msp-plugin .msp-viewport-controls-panel { 2808 | width: 290px; 2809 | top: 0; 2810 | right: 36px; 2811 | position: absolute; 2812 | background: rgb(30.7451219512, 34.362195122, 43.4048780488) 2813 | } 2814 | 2815 | .msp-plugin .msp-viewport-controls-panel .msp-control-group-wrapper:first-child { 2816 | padding-top: 0 2817 | } 2818 | 2819 | .msp-plugin .msp-viewport-controls-panel .msp-viewport-controls-panel-controls { 2820 | overflow-y: auto; 2821 | max-height: 400px 2822 | } 2823 | 2824 | .msp-plugin .msp-highlight-toast-wrapper { 2825 | position: absolute; 2826 | right: 10px; 2827 | bottom: 10px; 2828 | max-width: 95%; 2829 | z-index: 10000 2830 | } 2831 | 2832 | .msp-plugin .msp-highlight-info { 2833 | color: #51a2fb; 2834 | padding: 3px 10px; 2835 | background: #111318; 2836 | opacity: 90%; 2837 | max-width: 400px; 2838 | -webkit-user-select: none; 2839 | -moz-user-select: none; 2840 | -ms-user-select: none; 2841 | -o-user-select: none; 2842 | user-select: none; 2843 | cursor: default 2844 | } 2845 | 2846 | .msp-plugin .msp-highlight-markdown-row { 2847 | padding-left: 10px 2848 | } 2849 | 2850 | .msp-plugin .msp-highlight-simple-row { 2851 | text-align: right 2852 | } 2853 | 2854 | .msp-plugin .msp-highlight-info-hr { 2855 | margin-inline: 0px; 2856 | margin-block: 3px; 2857 | border: none; 2858 | height: 1px; 2859 | background-color: #51a2fb 2860 | } 2861 | 2862 | .msp-plugin .msp-highlight-info-additional { 2863 | font-size: 85%; 2864 | display: inline-block; 2865 | color: rgb(5.1685393258, 109.8314606742, 224.8314606742) 2866 | } 2867 | 2868 | .msp-plugin .msp-snapshot-description-wrapper { 2869 | background: rgba(17, 19, 24, .5); 2870 | position: absolute; 2871 | left: 0; 2872 | top: 42px; 2873 | padding: 6.6px 10px; 2874 | max-height: 224px; 2875 | overflow: hidden; 2876 | overflow-y: auto; 2877 | max-width: 400px 2878 | } 2879 | 2880 | .msp-plugin .msp-snapshot-description-wrapper a { 2881 | text-decoration: underline; 2882 | cursor: pointer; 2883 | color: #ccd4e0 2884 | } 2885 | 2886 | .msp-plugin .msp-snapshot-description-wrapper ul, 2887 | .msp-plugin .msp-snapshot-description-wrapper ol { 2888 | padding-left: 14px 2889 | } 2890 | 2891 | .msp-plugin .msp-log-wrap { 2892 | position: absolute; 2893 | right: 0; 2894 | top: 0; 2895 | left: 0; 2896 | bottom: 0; 2897 | overflow: hidden 2898 | } 2899 | 2900 | .msp-plugin .msp-log { 2901 | position: absolute; 2902 | right: -20px; 2903 | top: 0; 2904 | left: 0; 2905 | bottom: 0; 2906 | overflow-y: scroll; 2907 | overflow-x: hidden; 2908 | font-size: 90%; 2909 | background: rgb(30.7451219512, 34.362195122, 43.4048780488) 2910 | } 2911 | 2912 | .msp-plugin .msp-log { 2913 | font-size: 90% 2914 | } 2915 | 2916 | .msp-plugin .msp-log ul { 2917 | padding: 0; 2918 | margin: 0 2919 | } 2920 | 2921 | .msp-plugin .msp-log { 2922 | color: hsl(216, 24.3902439024%, 78.9215686275%) 2923 | } 2924 | 2925 | .msp-plugin .msp-log li { 2926 | clear: both; 2927 | margin: 0; 2928 | background: #111318; 2929 | position: relative 2930 | } 2931 | 2932 | .msp-plugin .msp-log li:not(:last-child) { 2933 | border-bottom: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049) 2934 | } 2935 | 2936 | .msp-plugin .msp-log .msp-log-entry { 2937 | margin-left: 110px; 2938 | background: rgb(20.1719512195, 22.5451219512, 28.4780487805); 2939 | padding: 3px 25px 3px 10px 2940 | } 2941 | 2942 | .msp-plugin .msp-log .msp-log-timestamp { 2943 | padding: 3px 10px 3px 10px; 2944 | float: left; 2945 | text-align: right; 2946 | width: 110px; 2947 | color: hsl(216, 24.3902439024%, 63.9215686275%); 2948 | font-size: 100% 2949 | } 2950 | 2951 | .msp-plugin .msp-log .msp-log-timestamp small { 2952 | font-size: 100% 2953 | } 2954 | 2955 | .msp-plugin .msp-log .label { 2956 | margin-top: -3px; 2957 | font-size: 7pt 2958 | } 2959 | 2960 | .msp-plugin .msp-log-entry-badge { 2961 | position: absolute; 2962 | left: 0; 2963 | top: 0; 2964 | bottom: 0; 2965 | width: 6px 2966 | } 2967 | 2968 | .msp-plugin .msp-log-entry-message { 2969 | background: #0cca5d 2970 | } 2971 | 2972 | .msp-plugin .msp-log-entry-info { 2973 | background: #5e3673 2974 | } 2975 | 2976 | .msp-plugin .msp-log-entry-error { 2977 | background: #fd354b 2978 | } 2979 | 2980 | .msp-plugin .msp-log-entry-warning { 2981 | background: #fcc937 2982 | } 2983 | 2984 | .msp-plugin .msp-sequence { 2985 | position: absolute; 2986 | right: 0; 2987 | top: 0; 2988 | left: 0; 2989 | bottom: 0; 2990 | background: #111318 2991 | } 2992 | 2993 | .msp-plugin .msp-sequence-select { 2994 | position: relative; 2995 | height: 24px; 2996 | width: 100%; 2997 | margin-bottom: 1px; 2998 | background: rgb(30.7451219512, 34.362195122, 43.4048780488); 2999 | text-align: left 3000 | } 3001 | 3002 | .msp-plugin .msp-sequence-select>span { 3003 | display: inline-block; 3004 | line-height: 24px; 3005 | padding: 0 10px; 3006 | font-size: 85%; 3007 | font-weight: bold; 3008 | cursor: default 3009 | } 3010 | 3011 | .msp-plugin .msp-sequence-select>select { 3012 | display: inline-block; 3013 | max-width: 120px; 3014 | width: auto; 3015 | text-overflow: ellipsis; 3016 | font-size: 85%; 3017 | height: 24px; 3018 | line-height: 24px; 3019 | background-size: 6px 8px; 3020 | background-color: rgb(30.7451219512, 34.362195122, 43.4048780488) 3021 | } 3022 | 3023 | .msp-plugin .msp-sequence-wrapper { 3024 | word-break: break-word; 3025 | padding: 10px 10px 3px 10px; 3026 | user-select: none 3027 | } 3028 | 3029 | .msp-plugin .msp-sequence-wrapper-non-empty { 3030 | font-size: 85%; 3031 | line-height: 180%; 3032 | font-family: "Courier New", monospace; 3033 | background: rgb(11.7134146341, 13.0914634146, 16.5365853659); 3034 | width: 100%; 3035 | overflow-y: auto; 3036 | overflow-x: hidden; 3037 | position: absolute; 3038 | top: 25px; 3039 | left: 0; 3040 | bottom: 0; 3041 | right: 0 3042 | } 3043 | 3044 | .msp-plugin .msp-sequence-chain-label { 3045 | margin-left: 10px; 3046 | margin-top: 10px; 3047 | user-select: none; 3048 | color: #51a2fb; 3049 | font-size: 90%; 3050 | line-height: 90%; 3051 | padding-left: .2em 3052 | } 3053 | 3054 | .msp-plugin .msp-sequence-wrapper span { 3055 | cursor: pointer 3056 | } 3057 | 3058 | .msp-plugin .msp-sequence-wrapper .msp-sequence-residue-long { 3059 | margin: 0em .2em 0em .2em 3060 | } 3061 | 3062 | .msp-plugin .msp-sequence-wrapper .msp-sequence-residue-long-begin { 3063 | margin: 0em .2em 0em 0em 3064 | } 3065 | 3066 | .msp-plugin .msp-sequence-wrapper .msp-sequence-label { 3067 | color: #51a2fb; 3068 | font-size: 90%; 3069 | line-height: 90%; 3070 | padding-bottom: 1em; 3071 | padding-left: .2em 3072 | } 3073 | 3074 | .msp-plugin .msp-sequence-wrapper .msp-sequence-number { 3075 | color: #51a2fb; 3076 | word-break: keep-all; 3077 | cursor: default; 3078 | position: relative; 3079 | top: -1.1em; 3080 | left: 3.1em; 3081 | padding: 0px; 3082 | margin-left: -3em; 3083 | font-size: 80%; 3084 | pointer-events: none 3085 | } 3086 | 3087 | .msp-plugin .msp-sequence-wrapper .msp-sequence-number-long { 3088 | left: 3.3em 3089 | } 3090 | 3091 | .msp-plugin .msp-sequence-wrapper .msp-sequence-number-long-negative { 3092 | left: 2.7em 3093 | } 3094 | 3095 | .msp-plugin .msp-sequence-wrapper .msp-sequence-number-negative { 3096 | left: 2.5em 3097 | } 3098 | 3099 | .msp-plugin .msp-sequence-wrapper .msp-sequence-present { 3100 | color: #ccd4e0 3101 | } 3102 | 3103 | .msp-plugin .msp-sequence-wrapper .msp-sequence-missing { 3104 | color: hsl(216, 24.3902439024%, 38.9215686275%); 3105 | cursor: default 3106 | } 3107 | 3108 | .msp-plugin .msp-transformer .msp-entity-badge { 3109 | position: absolute; 3110 | top: 0; 3111 | right: 0; 3112 | height: 32px; 3113 | line-height: 32px; 3114 | width: 32px 3115 | } 3116 | 3117 | .msp-plugin .msp-layout-right, 3118 | .msp-plugin .msp-layout-left { 3119 | background: rgb(30.7451219512, 34.362195122, 43.4048780488) 3120 | } 3121 | 3122 | .msp-plugin .msp-transformer-wrapper { 3123 | position: relative 3124 | } 3125 | 3126 | .msp-plugin .msp-transformer-wrapper .msp-entity-badge { 3127 | left: 0; 3128 | top: 0 3129 | } 3130 | 3131 | .msp-plugin .msp-transformer-wrapper:first-child .msp-panel-description-content { 3132 | top: 33px 3133 | } 3134 | 3135 | .msp-plugin .msp-transformer-wrapper:not(:first-child) .msp-panel-description-content { 3136 | bottom: 33px 3137 | } 3138 | 3139 | .msp-plugin .msp-transform-wrapper { 3140 | margin-bottom: 10px 3141 | } 3142 | 3143 | .msp-plugin .msp-transform-wrapper-collapsed { 3144 | margin-bottom: 1px 3145 | } 3146 | 3147 | .msp-plugin .msp-transform-update-wrapper { 3148 | margin-bottom: 1px 3149 | } 3150 | 3151 | .msp-plugin .msp-transform-update-wrapper-collapsed { 3152 | margin-bottom: 1px 3153 | } 3154 | 3155 | .msp-plugin .msp-transform-update-wrapper>.msp-transform-header>button, 3156 | .msp-plugin .msp-transform-update-wrapper-collapsed>.msp-transform-header>button { 3157 | text-align: left; 3158 | padding-left: 32px; 3159 | line-height: 24px; 3160 | background: rgb(22.2865853659, 24.9085365854, 31.4634146341) 3161 | } 3162 | 3163 | .msp-plugin .msp-transform-wrapper>.msp-transform-header>button { 3164 | text-align: left; 3165 | background: #111318; 3166 | font-weight: bold; 3167 | padding-right: 5px 3168 | } 3169 | 3170 | .msp-plugin .msp-transform-header { 3171 | position: relative 3172 | } 3173 | 3174 | .msp-plugin .msp-transform-header>button>small { 3175 | font-weight: normal; 3176 | float: right 3177 | } 3178 | 3179 | .msp-plugin .msp-transform-header>button>span:first-child { 3180 | margin-right: 10px 3181 | } 3182 | 3183 | .msp-plugin .msp-transform-header>button:hover { 3184 | color: hsl(216, 24.3902439024%, 68.9215686275%) 3185 | } 3186 | 3187 | .msp-plugin .msp-transform-header-brand { 3188 | margin-bottom: -1px 3189 | } 3190 | 3191 | .msp-plugin .msp-transform-header-brand svg { 3192 | fill: #ccd4e0; 3193 | stroke: #ccd4e0 3194 | } 3195 | 3196 | .msp-plugin .msp-transform-default-params { 3197 | background: #111318; 3198 | position: absolute; 3199 | left: 0; 3200 | top: 0; 3201 | width: 32px; 3202 | padding: 0 3203 | } 3204 | 3205 | .msp-plugin .msp-transform-default-params:hover { 3206 | background: hsl(222.8571428571, 17.0731707317%, -11.9607843137%) 3207 | } 3208 | 3209 | .msp-plugin .msp-transform-apply-wrap { 3210 | position: relative; 3211 | margin-top: 1px; 3212 | width: 100%; 3213 | height: 32px 3214 | } 3215 | 3216 | .msp-plugin .msp-transform-refresh { 3217 | width: 87px; 3218 | margin-left: 33px; 3219 | background: #111318; 3220 | text-align: right 3221 | } 3222 | 3223 | .msp-plugin .msp-transform-apply { 3224 | display: block; 3225 | position: absolute; 3226 | left: 120px; 3227 | right: 0; 3228 | top: 0 3229 | } 3230 | 3231 | .msp-plugin .msp-transform-apply-wider { 3232 | margin-left: 33px 3233 | } 3234 | 3235 | .msp-plugin .msp-data-beh { 3236 | margin: 10px 0 !important 3237 | } 3238 | 3239 | .msp-plugin .msp-toast-container { 3240 | position: relative; 3241 | z-index: 1001 3242 | } 3243 | 3244 | .msp-plugin .msp-toast-container .msp-toast-entry { 3245 | color: #ccd4e0; 3246 | background: rgb(30.7451219512, 34.362195122, 43.4048780488); 3247 | position: relative; 3248 | float: right; 3249 | min-height: 32px; 3250 | margin-top: 10px; 3251 | border: 1px solid rgb(48.7195121951, 54.4512195122, 68.7804878049); 3252 | display: table 3253 | } 3254 | 3255 | .msp-plugin .msp-toast-container .msp-toast-entry .msp-toast-title { 3256 | height: 100%; 3257 | line-height: 32px; 3258 | padding: 0 10px; 3259 | background: #111318; 3260 | font-weight: bold; 3261 | display: table-cell; 3262 | -webkit-user-select: none; 3263 | -moz-user-select: none; 3264 | -ms-user-select: none; 3265 | -o-user-select: none; 3266 | user-select: none; 3267 | font-weight: light; 3268 | cursor: pointer 3269 | } 3270 | 3271 | .msp-plugin .msp-toast-container .msp-toast-entry .msp-toast-message { 3272 | padding: 3px 42px 3px 10px; 3273 | display: table-cell 3274 | } 3275 | 3276 | .msp-plugin .msp-toast-container .msp-toast-entry .msp-toast-message a { 3277 | text-decoration: none; 3278 | color: #68befd; 3279 | font-weight: bold 3280 | } 3281 | 3282 | .msp-plugin .msp-toast-container .msp-toast-entry .msp-toast-message a:hover { 3283 | text-decoration: underline; 3284 | color: hsl(205.3691275168, 97.385620915%, 50%) 3285 | } 3286 | 3287 | .msp-plugin .msp-toast-container .msp-toast-entry .msp-toast-message a:active, 3288 | .msp-plugin .msp-toast-container .msp-toast-entry .msp-toast-message a:focus { 3289 | color: #68befd; 3290 | outline-offset: 0; 3291 | outline: none 3292 | } 3293 | 3294 | .msp-plugin .msp-toast-container .msp-toast-entry .msp-toast-hide { 3295 | position: absolute; 3296 | width: 42px; 3297 | right: 0; 3298 | top: 0; 3299 | bottom: 0 3300 | } 3301 | 3302 | .msp-plugin .msp-toast-container .msp-toast-entry .msp-toast-hide .msp-btn-icon { 3303 | background: rgba(0, 0, 0, 0); 3304 | position: absolute; 3305 | top: 1px; 3306 | right: 0; 3307 | left: 0; 3308 | bottom: 0; 3309 | width: 100%; 3310 | text-align: right; 3311 | padding-right: 5px 3312 | } 3313 | 3314 | .msp-plugin .msp-help-row { 3315 | position: relative; 3316 | height: 32px; 3317 | background: #111318; 3318 | margin-top: 1px; 3319 | display: table; 3320 | width: 100% 3321 | } 3322 | 3323 | .msp-plugin .msp-help-row>span { 3324 | width: 120px; 3325 | text-align: right; 3326 | padding: 3px 10px; 3327 | color: hsl(216, 24.3902439024%, 68.9215686275%); 3328 | display: table-cell; 3329 | font-weight: bold; 3330 | -webkit-user-select: none; 3331 | -moz-user-select: none; 3332 | -ms-user-select: none; 3333 | -o-user-select: none; 3334 | user-select: none; 3335 | cursor: default 3336 | } 3337 | 3338 | .msp-plugin .msp-help-row>div { 3339 | background: rgb(11.7134146341, 13.0914634146, 16.5365853659); 3340 | position: relative; 3341 | padding: 3px 10px; 3342 | display: table-cell 3343 | } 3344 | 3345 | .msp-plugin .msp-canvas { 3346 | width: 100%; 3347 | height: 100%; 3348 | background-color: #f3f2ee 3349 | } 3350 | 3351 | .msp-plugin .msp-canvas text { 3352 | -webkit-touch-callout: none; 3353 | -webkit-user-select: none; 3354 | -khtml-user-select: none; 3355 | -moz-user-select: none; 3356 | -ms-user-select: none; 3357 | user-select: none 3358 | } 3359 | 3360 | .msp-plugin .msp-canvas circle { 3361 | stroke: #000; 3362 | stroke-width: 10; 3363 | stroke-opacity: .3 3364 | } 3365 | 3366 | .msp-plugin .msp-canvas circle:hover { 3367 | fill: #ae5d04; 3368 | stroke: #000; 3369 | stroke-width: 10px 3370 | } 3371 | 3372 | .msp-plugin .msp-canvas .info { 3373 | fill: #fff; 3374 | stroke: #000; 3375 | stroke-width: 3 3376 | } 3377 | 3378 | .msp-plugin .msp-canvas .show { 3379 | visibility: visible 3380 | } 3381 | 3382 | .msp-plugin .msp-canvas .hide { 3383 | visibility: hidden 3384 | } 3385 | 3386 | .msp-plugin .msp-canvas .delete-button rect { 3387 | fill: #ed4337; 3388 | stroke: #000 3389 | } 3390 | 3391 | .msp-plugin .msp-canvas .delete-button text { 3392 | stroke: #fff; 3393 | fill: #fff 3394 | } 3395 | 3396 | .msp-plugin .msp-canvas .delete-button:hover { 3397 | stroke: #000; 3398 | stroke-width: 3; 3399 | fill: #ff6961 3400 | } 3401 | 3402 | .msp-plugin .msp-canvas .infoCircle:hover { 3403 | fill: #4c66b2 3404 | } 3405 | 3406 | .msp-plugin .msp-canvas:focus { 3407 | outline: none 3408 | } 3409 | 3410 | .msp-plugin .msp-logo { 3411 | display: block; 3412 | position: absolute; 3413 | bottom: 10px; 3414 | right: 10px; 3415 | height: 32px; 3416 | width: 100px; 3417 | background-repeat: no-repeat; 3418 | background-position: bottom right; 3419 | background-size: auto; 3420 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFcAAAAgCAYAAABn7+QVAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAACHDwAAjA8AAP1SAACBQAAAfXkAAOmLAAA85QAAGcxzPIV3AAAKL2lDQ1BJQ0MgUHJvZmlsZQAASMedlndUVNcWh8+9d3qhzTACUobeu8AA0nuTXkVhmBlgKAMOMzSxIaICEUVEmiJIUMSA0VAkVkSxEBRUsAckCCgxGEVULG9G1ouurLz38vL746xv7bP3ufvsvc9aFwCSpy+XlwZLAZDKE/CDPJzpEZFRdOwAgAEeYIApAExWRrpfsHsIEMnLzYWeIXICXwQB8HpYvAJw09AzgE4H/5+kWel8geiYABGbszkZLBEXiDglS5Auts+KmBqXLGYYJWa+KEERy4k5YZENPvsssqOY2ak8tojFOaezU9li7hXxtkwhR8SIr4gLM7mcLBHfErFGijCVK+I34thUDjMDABRJbBdwWIkiNhExiR8S5CLi5QDgSAlfcdxXLOBkC8SXcklLz+FzExIFdB2WLt3U2ppB9+RkpXAEAsMAJiuZyWfTXdJS05m8HAAW7/xZMuLa0kVFtjS1trQ0NDMy/apQ/3Xzb0rc20V6Gfi5ZxCt/4vtr/zSGgBgzIlqs/OLLa4KgM4tAMjd+2LTOACApKhvHde/ug9NPC+JAkG6jbFxVlaWEZfDMhIX9A/9T4e/oa++ZyQ+7o/y0F058UxhioAurhsrLSVNyKdnpDNZHLrhn4f4Hwf+dR4GQZx4Dp/DE0WEiaaMy0sQtZvH5gq4aTw6l/efmvgPw/6kxbkWidL4EVBjjIDUdSpAfu0HKAoRINH7xV3/o2+++DAgfnnhKpOLc//vN/1nwaXiJYOb8DnOJSiEzhLyMxf3xM8SoAEBSAIqkAfKQB3oAENgBqyALXAEbsAb+IMQEAlWAxZIBKmAD7JAHtgECkEx2An2gGpQBxpBM2gFx0EnOAXOg0vgGrgBboP7YBRMgGdgFrwGCxAEYSEyRIHkIRVIE9KHzCAGZA+5Qb5QEBQJxUIJEA8SQnnQZqgYKoOqoXqoGfoeOgmdh65Ag9BdaAyahn6H3sEITIKpsBKsBRvDDNgJ9oFD4FVwArwGzoUL4B1wJdwAH4U74PPwNfg2PAo/g+cQgBARGqKKGCIMxAXxR6KQeISPrEeKkAqkAWlFupE+5CYyiswgb1EYFAVFRxmibFGeqFAUC7UGtR5VgqpGHUZ1oHpRN1FjqFnURzQZrYjWR9ugvdAR6AR0FroQXYFuQrejL6JvoyfQrzEYDA2jjbHCeGIiMUmYtZgSzD5MG+YcZhAzjpnDYrHyWH2sHdYfy8QKsIXYKuxR7FnsEHYC+wZHxKngzHDuuCgcD5ePq8AdwZ3BDeEmcQt4Kbwm3gbvj2fjc/Cl+EZ8N/46fgK/QJAmaBPsCCGEJMImQiWhlXCR8IDwkkgkqhGtiYFELnEjsZJ4jHiZOEZ8S5Ih6ZFcSNEkIWkH6RDpHOku6SWZTNYiO5KjyALyDnIz+QL5EfmNBEXCSMJLgi2xQaJGokNiSOK5JF5SU9JJcrVkrmSF5AnJ65IzUngpLSkXKabUeqkaqZNSI1Jz0hRpU2l/6VTpEukj0lekp2SwMloybjJsmQKZgzIXZMYpCEWd4kJhUTZTGikXKRNUDFWb6kVNohZTv6MOUGdlZWSXyYbJZsvWyJ6WHaUhNC2aFy2FVko7ThumvVuitMRpCWfJ9iWtS4aWzMstlXOU48gVybXJ3ZZ7J0+Xd5NPlt8l3yn/UAGloKcQqJClsF/hosLMUupS26WspUVLjy+9pwgr6ikGKa5VPKjYrzinpKzkoZSuVKV0QWlGmabsqJykXK58RnlahaJir8JVKVc5q/KULkt3oqfQK+m99FlVRVVPVaFqveqA6oKatlqoWr5am9pDdYI6Qz1evVy9R31WQ0XDTyNPo0XjniZek6GZqLlXs09zXktbK1xrq1an1pS2nLaXdq52i/YDHbKOg84anQadW7oYXYZusu4+3Rt6sJ6FXqJejd51fVjfUp+rv09/0ABtYG3AM2gwGDEkGToZZhq2GI4Z0Yx8jfKNOo2eG2sYRxnvMu4z/mhiYZJi0mhy31TG1Ns037Tb9HczPTOWWY3ZLXOyubv5BvMu8xfL9Jdxlu1fdseCYuFnsdWix+KDpZUl37LVctpKwyrWqtZqhEFlBDBKGJet0dbO1husT1m/tbG0Edgct/nN1tA22faI7dRy7eWc5Y3Lx+3U7Jh29Xaj9nT7WPsD9qMOqg5MhwaHx47qjmzHJsdJJ12nJKejTs+dTZz5zu3O8y42Lutczrkirh6uRa4DbjJuoW7Vbo/c1dwT3FvcZz0sPNZ6nPNEe/p47vIc8VLyYnk1e816W3mv8+71IfkE+1T7PPbV8+X7dvvBft5+u/0erNBcwVvR6Q/8vfx3+z8M0A5YE/BjICYwILAm8EmQaVBeUF8wJTgm+Ejw6xDnkNKQ+6E6ocLQnjDJsOiw5rD5cNfwsvDRCOOIdRHXIhUiuZFdUdiosKimqLmVbiv3rJyItogujB5epb0qe9WV1QqrU1afjpGMYcaciEXHhsceiX3P9Gc2MOfivOJq42ZZLqy9rGdsR3Y5e5pjxynjTMbbxZfFTyXYJexOmE50SKxInOG6cKu5L5I8k+qS5pP9kw8lf0oJT2lLxaXGpp7kyfCSeb1pymnZaYPp+umF6aNrbNbsWTPL9+E3ZUAZqzK6BFTRz1S/UEe4RTiWaZ9Zk/kmKyzrRLZ0Ni+7P0cvZ3vOZK577rdrUWtZa3vyVPM25Y2tc1pXvx5aH7e+Z4P6hoINExs9Nh7eRNiUvOmnfJP8svxXm8M3dxcoFWwsGN/isaWlUKKQXziy1XZr3TbUNu62ge3m26u2fyxiF10tNimuKH5fwiq5+o3pN5XffNoRv2Og1LJ0/07MTt7O4V0Ouw6XSZfllo3v9tvdUU4vLyp/tSdmz5WKZRV1ewl7hXtHK30ru6o0qnZWva9OrL5d41zTVqtYu712fh9739B+x/2tdUp1xXXvDnAP3Kn3qO9o0GqoOIg5mHnwSWNYY9+3jG+bmxSaips+HOIdGj0cdLi32aq5+YjikdIWuEXYMn00+uiN71y/62o1bK1vo7UVHwPHhMeefh/7/fBxn+M9JxgnWn/Q/KG2ndJe1AF15HTMdiZ2jnZFdg2e9D7Z023b3f6j0Y+HTqmeqjkte7r0DOFMwZlPZ3PPzp1LPzdzPuH8eE9Mz/0LERdu9Qb2Dlz0uXj5kvulC31OfWcv210+dcXmysmrjKud1yyvdfRb9Lf/ZPFT+4DlQMd1q+tdN6xvdA8uHzwz5DB0/qbrzUu3vG5du73i9uBw6PCdkeiR0TvsO1N3U+6+uJd5b+H+xgfoB0UPpR5WPFJ81PCz7s9to5ajp8dcx/ofBz++P84af/ZLxi/vJwqekJ9UTKpMNk+ZTZ2adp++8XTl04ln6c8WZgp/lf619rnO8x9+c/ytfzZiduIF/8Wn30teyr889GrZq565gLlHr1NfL8wXvZF/c/gt423fu/B3kwtZ77HvKz/ofuj+6PPxwafUT5/+BQOY8/xvJtwPAAAACXBIWXMAAC4iAAAuIgGq4t2SAAANMElEQVRoQ92aB1xURx7H/69sY5eOFBELCipESsSC0RCMJRZMrICHGiMmGjWaqDk7YEsuGok5TS6xi56KGtsFG6jBiAYLKhqVc8GGBZG+fd97N+/twNJWFksS7/v5DG/nN/OG/fze/838Z4CA/wMCE9d9W8oQ3mUMBSojBTqWAuBQAweHIC56lanXHw8xJixM6qhQNcX1KuQykluyKzMPVxvF5XUh3hIpgFSiQz8AJBItSKU6sCsX55P9byLxxRKwYl3W5O6dg5o62IMRmcpyBBz87wNYcyH3R4iL+gh3+8MhHaTqYJKUKO2dPYTigIqza1MlLZLnzh3arQ/uZzVn14YOIGRyJWXrqgR5U6VI1kRJS92VBEEry+wrAnC3F04XL3cY4OMF7/p6weC2zSDQzQG3/IlM7dspdPmU0VxtLqYf5haM6HYOBYLVUwcXByQy92JxXioexUzFhT5cySn3TrjrC4WP3EsPHuPfZGJVZg4HCdt/wF0aT8LWUHT/jTpl4fZU3KNBSHytQ0D33uDR0qfjoqg3hmOpQU65d4u2cW4X6NCyJ1ZeIeKSFRC3p1q4kzYdmzr6Zk98p6rsj+rhi0KoFe5gIm53M/ypDhbNJQgC3kbTFUGSi+LiwmgsWyQ5zk9McESCZ8gEVHvF1kneWJI5CJT2SHWDbUQ0vNbEvqr4OClwCyZ+RzSQ+psomqOwUgOL5vL4BIdCi/aBvtJb3AdYsoirs0usnWfH1vbNOmPlFWHmWlve2DFB3t0nhvh0qm2wRRZuG+ksFyUlDe4qcbYRJ0H8v6NxSxVPNZcnPPJDIAlY8PWnXWVYqsPhZb3lDAfzW3T50xbmZ+MfyFhbRcr7yNj1EZ1gdb+O8DFvMKk7it4+ywYjY11k0s1po8KpmA4tITUmnHaWS5HBKJKr0aC5zXw6QJvgNzyhXDIZS3UgCN3UJq3fdLd188PKs3H8+Bjpvn2x/jv2TwnbsOezt3/YPavTss3TXXHzi4U3Vic/+H5gq+7rkLEkmgb5yWwVb3CnNiFAcD+aOtaGaMobmzrqLaoyIwlC11RkNB/JvPGCiGjQXJ43h8QCSRGzEqeG1Xmah77u48QCPdM7NBYrjSPveJg069i7H2UcjUpndWSZrZ3bFRfHlic8nL1TnezcM2Vyh0dLtsbnzdu8JHHW5qVt8G3Pj9qOT4RYluOE/UYllQZPCvFxMik1cbGRSKsbWwlKUPhxhDGxZJ25Ls28oX2X3k60HmZiqQqDTj+rqX8fB7lTC6xYT2569zA9Jb5m7xz8r3aB03uE9fpOFP7WYujZ/TPo22MSDOs1FT4ePBfG9ZvQsod/12kUJf190prli4YnJ6Mt2HOSMKICGLL/5su3Tn6wPxMYZE4lvMH/RAZP6NjaJGBsJSJIi3mrTg6d9bAYem05YSxS6WJgQdR2LFtnLk9oxFigRaKpq2aEuWMJDizu6UlQosltuo3FivU8zgyOkEhkRzz941u2CogDxyYhgMzDrWb4rMXN0Q36vN4TZr43XuTt0WyeoiR/MwqV509JqgzOSx+77zcw8nGM4UMx2r+5qYJpqpByHVztcc3E+QdFXJWx8dE78MgCDaZYldi5eIB/jwj577/+NB9VJ/GajmHj2nYZKpPZNW5aVJ9v2ULDwlaXdsvFYlvzpo1l9PD4yXUoKStAY3MgFjuAexNvcFA4C+32NgqY3HcofHFg18ioH1adRSHyjdBgCQJaQ/y2SFyzAIMKuSkp+1YAepIOGwZ1Bgo9UGu4gCK2z9ZfoEit3yMI1X8XxZwh+B2al2/7jOnfbsKqGaNeB7RYgmsAmvJi2LHkbwaC0baXyElKKpVe7f/JVlpsY4978Abp0PxsvqcSVVZfMGoud3Z44+HZ8vOeG2m3GWOkntNwK8CTgky4eiWJK9fqflUZJRe0jFirZmgvDSPu29or2PmdzhEgpkVC3/ziIpiRvL1ETUua74+NLed3aEnRg4IC3F2Edp6DNx/AmqxcXLMeFK0w3M8L1yxToTfCtCNZUKTRY8VMZv4TyC/VxFiM3OM7N0BudiaMW/g9VgBkto7QIWyYKDstaSEYGdo3dEQNY/n5/EbKJHBq2QPcOozBWk24K00UGgM3QuI2GisA5cVXIOdyYqHeKBo0cEDSaSwLLNu8TJ5968o6LQORI3oMETRPRycI9GrhkHH7Di/UjQpEvzYeQnlZKMQ0rB1Y/25+xO4M2Fl61/KcazTo4W5ONuRcOIUVEx3CI0Fqax8lljsO9w2tuTMuyksHVcHvwKHX2xIcU9aFsgmQEbR5MX50aztQYJzWu19NY3lmjp6pekIrxmbfvv6woLQQqwCBzZujn0SYqfbX5KkLGprVL51IXgMcW5VdgFgqh4DwkaR/WAxBi837Co5j4Hbmj3wucglL9cJy4ENKzRkVf5+q9Bqnpol9WKpDYuR0DfoKabcL8rGCotfBEQ0GLy41ewk81VyWIfYV3lNmXj2NNizVaNvtPfBBc2B1Hl07BKqi2xkkyf0HSxYg0D7eFn9G5rJ69EAYfXj4zgos1QtaYoq16G2qRCYWA0dw5oFqcb9cAyfvPG50ufq4FI/wdPg5t777+VKoNh1ZPzVbIAiWIwl69qm9G9Lad+kJFF5QKFosXCthjXrI/W0jsCw5G62+Tz0D5p8mU3sxrp7FWwClZKYcHWMawvKqvuf6PZh86HwBusW6VY0g/FzlEru0mHAsPB05mnN3X7sHKzNz+K91Df2o+VQIorDBVGz2lpPHvhobdvRy+v7ewT2HYrUmdy/tBU3po5Ren55MP7e+a6MP2F8aHLHXqr9ExO8Y46oQr08bFS6cflkD/1gT+wYLH1aeydGCSD8Q5ox5Ymo1YdUmgqTI2ZkpWziDToMVM0adCpRntrAERc/B0qvFImSsrWAsWdvYx/j1rkRtYNBGo+bbk9gnGKZ19Q0GgzgVlm4yJeQYq8ydsfb4eW158a6LaTuxYkaZuQN0mrLtb39y/KkL2V+Shdved7URrz9Wj7Fn7xfBuAOZuGbiTqkKRu09Y8HgtkFg5A3+qcpgq8zloUT0vItpyUZthXlq0amKQfnbTgNw5AIsvTos3o2SYGL10vAA0r8eY/mdV4nWgBUz26/eqWMwz7JeQeDrbIcM1idgyXpzp6xOyzHoVBuyUrdiBeD6ySQw6DVr+n9+XImlBmE5ggHOiGs8wleg0G7e8urEQwBNEuavywjpYY2BGse8oQ9QHjgM7bK0/ApfiWDslhOGEq1+NZZqwnH526/cOVbdYP7K13OelKcBY/O5ICKsNpeHFJMJ1zL2aVQlBaAqfgDKswdUKIFYhJutAqVqDznDI1xDdbRVFkkc6YzDQ9piqX448HNSmE+jitVq/mkU4OqzERd9sEJnGNJ/W7pgcGalsTp9FDLRdF5QGwJ0wNpEoAhOi0GGao0M8Fe+DkzpIEgYpMY9G2fuxMRj+axBvyrryEbITtsIjNGwcuDnvzzEzVahJ+gsVnURfTK/Vg6uYUDSNH8gVG/0Ltqy6E2FVNajjYf5WFNZ8AhQcvb88zxvsIEZzBvcV4hYYyQsiP4Jt9YPbyAycgcytM2qn4G/moz9qMpYnkaZK0CIv8y9cKQk72JqkYqAZVi1GmlAxXVGX3DdWHYGKwDurSLBxrb1yLRDo/ftTxkflpQyxW5lyhTJ97vm+azYNneWiCJ+HtxtICnCeTZ/wH0m9yaQHHNAEJ6X+ZGHeINLtLpIiIusP2JrwxspJyLyyzVL+WttY3kabe74xCNFBMd+xXDcl2MTfinBcqPggP5Kfe+bqimTomTwWkg8tPaNjLC3bX5CxtKljjqxViGzyfFrFfTFB/3GK3w9zTvd49eyobCsNGPvlCl1ziKeGWQwxI2sYWx2QamwsFWWcQfO4hbM9EgNLIiaK1zrofGRy8PQ34o1mmf+Hyz5/nub9Kprh4qVS4WzBR6SFEOLVv3hze7zYOiAFTDqveUQ03829O0yDJrYm8+Lr9+/AztOn1SxHPNy/xoqklxEi9qAo7kPq0rGvcIBaOIah3s0yDOZO/rro6rIxDP1Pi1rIBKABb3tiIqCw0fzL38GmvKbuMUyOoMODmf9Ct8d3l3CsfpByR9Pu4KbXg5zhjxBUZlSp8yPPoF7NIhwWG5jb5/h16kbltBrShLw+K4SCvOVCYt2no7HslWg7e9iW5fWcxVNvIGmGVMRGYEoO4zmykLhsBx3heTk4VSgW+lENSObQ8n9POSOHUEi90L97dHOlQKtXg9FFSVwu+A+XLmbx5Tp2F1qhvr7d7Ezb+MhBPjD8tdbNA+SSGSgYwmUGpFwo7AczuYX/an/iEdM6B3qKqbZAbguIKJQEZEosYSLi3efzsKyVZxd3/V1Cc0FisQMGsMAUqkBXfXoqgXChjlgF/LAfCiLOXfuQ5G2tDRcY5CGaRhxO41R4qJlRJSaEZVrjOLbapY6Z9BASkJswn18Sw2CVqx/t5ghncoZElQsBTqm8u+X3A0UaRm48gcD8D/XZskfp8IFSwAAAABJRU5ErkJggg==) 3421 | } 3422 | 3423 | .msp-plugin .msp-plugin-content { 3424 | color: #ccd4e0 3425 | } 3426 | 3427 | .msp-plugin .msp-plugin-init-error { 3428 | white-space: pre; 3429 | margin: 10px 3430 | } 3431 | 3432 | .msp-plugin .msp-svg-text { 3433 | fill: #ccd4e0 3434 | } 3435 | 3436 | .msp-plugin { 3437 | background: #111318 3438 | } 3439 | 3440 | .msp-plugin ::-webkit-scrollbar-thumb { 3441 | background-color: rgba(128, 128, 128, .5019607843); 3442 | border-radius: 10px; 3443 | border: solid 1px rgba(0, 0, 0, 0); 3444 | background-clip: content-box 3445 | } 3446 | 3447 | .msp-plugin .msp-plugin-init-error { 3448 | color: gray 3449 | } 3450 | 3451 | .msp-plugin .msp-layout-static { 3452 | container-type: size 3453 | } 3454 | 3455 | .msp-plugin .msp-viewport-controls-panel .msp-viewport-controls-panel-controls { 3456 | max-height: calc(100cqh - 41px - 24px - 10px) 3457 | } 3458 | 3459 | .msp-plugin .msp-simple-help-section { 3460 | overflow: hidden; 3461 | text-wrap: nowrap; 3462 | text-overflow: ellipsis 3463 | } 3464 | 3465 | .msp-plugin .msp-control-group-header button { 3466 | overflow: hidden; 3467 | text-wrap: nowrap; 3468 | text-overflow: ellipsis 3469 | } 3470 | 3471 | .msp-plugin .pdbemolstar-custom-control-viewport-top-left { 3472 | float: left 3473 | } 3474 | 3475 | .msp-plugin .pdbemolstar-custom-control-viewport-top-left:not(:empty) { 3476 | margin-right: 10px 3477 | } 3478 | 3479 | .msp-plugin .pdbemolstar-viewport-top-center-controls { 3480 | width: 100%; 3481 | display: flex; 3482 | flex-direction: column; 3483 | align-items: center; 3484 | padding-inline: 74px; 3485 | pointer-events: none 3486 | } 3487 | 3488 | .msp-plugin .pdbemolstar-viewport-top-center-controls>* { 3489 | margin-top: 10px; 3490 | pointer-events: auto; 3491 | max-width: 100% 3492 | } 3493 | 3494 | .msp-plugin .pdbemolstar-overlay { 3495 | z-index: 1000; 3496 | position: absolute; 3497 | inset: 0px; 3498 | display: flex; 3499 | justify-content: center; 3500 | align-items: center; 3501 | pointer-events: none 3502 | } 3503 | 3504 | .msp-plugin .pdbemolstar-overlay .pdbemolstar-overlay-box { 3505 | width: 25%; 3506 | height: 25% 3507 | } 3508 | 3509 | .msp-plugin .pdbemolstar-overlay svg.pdbe-animated-logo { 3510 | background-color: rgba(0, 0, 0, 0); 3511 | width: 100%; 3512 | height: 100%; 3513 | opacity: 80% 3514 | } 3515 | 3516 | .msp-plugin .pdbemolstar-overlay svg.pdbe-animated-logo .path-fg { 3517 | stroke-dasharray: 1812 250; 3518 | stroke-dashoffset: -250; 3519 | animation: dash linear normal infinite; 3520 | animation-duration: 5s; 3521 | animation-delay: 1s 3522 | } 3523 | 3524 | @keyframes dash { 3525 | 0% { 3526 | stroke-dashoffset: 1812 3527 | } 3528 | 3529 | 80% { 3530 | stroke-dashoffset: -250 3531 | } 3532 | 3533 | 100% { 3534 | stroke-dashoffset: -250 3535 | } 3536 | } 3537 | 3538 | .msp-plugin .pdbemolstar-state-gallery-controls { 3539 | overflow-x: hidden; 3540 | word-wrap: break-word 3541 | } 3542 | 3543 | .msp-plugin .pdbemolstar-state-gallery-controls:focus-visible { 3544 | outline: none 3545 | } 3546 | 3547 | .msp-plugin .pdbemolstar-state-gallery-controls .pdbemolstar-state-gallery-state-button { 3548 | height: 24px; 3549 | line-height: 24px; 3550 | padding-inline: 5px; 3551 | text-align: left; 3552 | overflow: hidden; 3553 | text-overflow: ellipsis 3554 | } 3555 | 3556 | .msp-plugin .pdbemolstar-state-gallery-controls .pdbemolstar-state-gallery-state-button .msp-icon { 3557 | margin-right: 3px 3558 | } 3559 | 3560 | .msp-plugin .pdbemolstar-state-gallery-controls .pdbemolstar-state-gallery-legend { 3561 | margin-block: 8px 3562 | } 3563 | 3564 | .msp-plugin .pdbemolstar-state-gallery-controls .pdbemolstar-state-gallery-legend .image_legend_li { 3565 | margin-left: 16px 3566 | } 3567 | 3568 | .msp-plugin .pdbemolstar-state-gallery-controls .pdbemolstar-state-gallery-legend .highlight { 3569 | font-weight: bold 3570 | } 3571 | 3572 | .msp-plugin .pdbemolstar-state-gallery-title-box { 3573 | width: 500px; 3574 | max-width: 100%; 3575 | background-color: rgb(11.7134146341, 13.0914634146, 16.5365853659); 3576 | display: flex; 3577 | flex-direction: row; 3578 | justify-content: space-between; 3579 | align-items: stretch 3580 | } 3581 | 3582 | .msp-plugin .pdbemolstar-state-gallery-title-box .msp-btn-icon { 3583 | background-color: rgba(0, 0, 0, 0); 3584 | height: 100% 3585 | } 3586 | 3587 | .msp-plugin .pdbemolstar-state-gallery-title-box .pdbemolstar-state-gallery-title { 3588 | margin: 5px; 3589 | min-height: 2.9em; 3590 | display: flex; 3591 | flex-direction: row; 3592 | align-items: center; 3593 | text-align: center; 3594 | font-weight: bold 3595 | } 3596 | 3597 | .msp-plugin .pdbemolstar-state-gallery-title-box .pdbemolstar-state-gallery-title .pdbemolstar-state-gallery-title-icon { 3598 | width: 1.2em 3599 | } 3600 | 3601 | .msp-plugin .pdbemolstar-state-gallery-title-box .pdbemolstar-state-gallery-title .pdbemolstar-state-gallery-title-text { 3602 | margin-right: 1.2em; 3603 | padding-inline: 4px 3604 | } --------------------------------------------------------------------------------