/
45case
/
ptable
Обзор
Документация
Войти
/
45case
/
ptable
Код
Запросы
3
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
dev
src/entities/element/ui/ElementDetailPanel.vue
1 416 строк
41 KB
Olya
Страница Электроны + сайдбар с характеритстиками (Степень окисления, Конфигурация, Развёрнутая, Уровни энергии, ВЗМО)
24 июл 2026, 11:54
24 июл 2026, 11:54
3cf1d48
Код
Авторство
О чём код?
<script setup lang="ts"> import { computed, onBeforeUnmount, ref } from "vue"; import { getAbundanceValue, getAtomicRadiusPm, getConductivityValue, getDensityKgM3, getElementPropertiesByAtomicNumber, getHardnessValue, getHeatValue, getIonizationEnergyKJmol, getModulusGPa, } from "../api/getElementProperties"; import { formatElementMass } from "../lib/formatElementMass"; import { ABUNDANCE_KIND_OPTIONS, ATOMIC_RADIUS_KIND_OPTIONS, CONDUCTIVITY_KIND_OPTIONS, DENSITY_KIND_OPTIONS, formatAbundance, formatConductivity, formatDensity, formatDiscoveredYear, formatElectronsPerShellDisplay, formatElectronAffinity, formatHeat, formatKelvinTemperature, formatMolarEnergy, formatPropertyNumber, getConductivityUnitLabel, getHeatUnitLabel, HARDNESS_KIND_OPTIONS, HEAT_KIND_OPTIONS, IONIZATION_ORDERS, MODULUS_KIND_OPTIONS, } from "../lib/formatHomeProperties"; import type { PeriodicElement } from "../model/elements"; import { getElementCategoryLabel } from "../model/category"; import { getElementPhaseLabel } from "../model/phase"; import { homePropertyCatalog, type HomePropertyKey } from "../model/homeProperties"; import { getElementState, useTemperatureStore, type AbundanceKind, type AtomicRadiusKind, type ConductivityKind, type DensityKind, type DensityUnit, type HardnessKind, type HeatKind, type ModulusKind, type MolarEnergyUnit, type TemperatureUnit, } from "@/entities/temperature"; import { categoryLegendItems } from "@/entities/table/model/categoryLegend"; import ElementIconCard from "./ElementIconCard.vue"; const props = withDefaults( defineProps<{ element: PeriodicElement; /** When false, the shared page shell renders the icon. */ showIcon?: boolean; /** When true, render only the characteristics list (no aside chrome). */ embedded?: boolean; }>(), { showIcon: true, embedded: false, }, ); const emit = defineEmits<{ "select-property": [key: HomePropertyKey]; "open-article": []; }>(); const { currentTemperatureK, temperatureUnit, molarEnergyUnit, ionizationOrder, atomicRadiusKind, hardnessKind, modulusKind, densityKind, densityUnit, conductivityKind, heatKind, abundanceKind, temperatureInSelectedUnit, minTemperatureInSelectedUnit, maxTemperatureInSelectedUnit, setTemperatureFromSelectedUnit, setTemperatureUnit, setMolarEnergyUnit, setIonizationOrder, setAtomicRadiusKind, setHardnessKind, setModulusKind, setDensityKind, setDensityUnit, setConductivityKind, setHeatKind, setAbundanceKind, } = useTemperatureStore(); const conductivityUnitLabel = computed(() => getConductivityUnitLabel(conductivityKind.value)); const heatUnitLabel = computed(() => getHeatUnitLabel(heatKind.value, molarEnergyUnit.value)); const isHeatMolarEnergy = computed( () => heatKind.value === "vaporization" || heatKind.value === "fusion", ); const selectedProperty = ref<HomePropertyKey>("series"); const physical = computed(() => getElementPropertiesByAtomicNumber(props.element.atomicNumber), ); const currentState = computed(() => getElementState(props.element, currentTemperatureK.value)); const currentStateLabel = computed(() => getElementPhaseLabel(currentState.value)); const displayTemperature = computed(() => Math.round(temperatureInSelectedUnit.value)); const minDisplayTemperature = computed(() => Math.round(minTemperatureInSelectedUnit.value)); const maxDisplayTemperature = computed(() => Math.round(maxTemperatureInSelectedUnit.value)); const isTemperatureAtMin = computed( () => displayTemperature.value <= minDisplayTemperature.value, ); const isTemperatureAtMax = computed( () => displayTemperature.value >= maxDisplayTemperature.value, ); const seriesLabel = computed(() => { const match = categoryLegendItems.find( (item) => item.category === props.element.category || item.highlightCategories?.includes(props.element.category), ); return match?.label ?? getElementCategoryLabel(props.element.category); }); const propertyRows = computed(() => { const data = physical.value; const mass = props.element.atomicMass ?? data?.atomicMass ?? null; const values: Record<HomePropertyKey, string> = { series: seriesLabel.value, article: props.element.name, stateAt: currentStateLabel.value, atomicMass: formatElementMass(mass, { source: props.element.atomicMassWeight ?? data?.atomicMassWeight, full: true, unit: "u", }), energyLevels: formatElectronsPerShellDisplay( data?.electronsPerShell?.length ? data.electronsPerShell : null, ), electronegativity: formatPropertyNumber(data?.electronegativityPauling, { maxDigits: 2 }), meltingPoint: formatKelvinTemperature( data?.meltingPointKelvin ?? props.element.meltingPointKelvin, temperatureUnit.value, { includeUnit: false }, ), boilingPoint: formatKelvinTemperature( data?.boilingPointKelvin ?? props.element.boilingPointKelvin, temperatureUnit.value, { includeUnit: false }, ), electronAffinity: formatElectronAffinity(data?.electronAffinityKJmol, molarEnergyUnit.value, { includeUnit: false, }), ionizationEnergy: formatMolarEnergy( getIonizationEnergyKJmol(data, ionizationOrder.value), molarEnergyUnit.value, { includeUnit: false }, ), radius: formatPropertyNumber(getAtomicRadiusPm(data, atomicRadiusKind.value), { digits: 0, suffix: "pm", }), hardness: formatPropertyNumber(getHardnessValue(data, hardnessKind.value), { maxDigits: 2, suffix: "MPa", }), modulus: formatPropertyNumber(getModulusGPa(data, modulusKind.value), { maxDigits: 2, suffix: "GPa", }), density: formatDensity(getDensityKgM3(data, densityKind.value), densityUnit.value, { includeUnit: false, }), conductivity: formatConductivity( getConductivityValue(data, conductivityKind.value), conductivityKind.value, { includeUnit: false }, ), heat: formatHeat(getHeatValue(data, heatKind.value), heatKind.value, molarEnergyUnit.value, { includeUnit: false, }), abundance: formatAbundance(getAbundanceValue(data, abundanceKind.value), { includeUnit: false, }), discovered: formatDiscoveredYear(data?.discoveredYear), }; return homePropertyCatalog.map((item) => ({ ...item, value: values[item.key], })); }); function selectProperty(key: HomePropertyKey) { selectedProperty.value = key; emit("select-property", key); } function openArticle(event: Event) { event.stopPropagation(); selectProperty("article"); emit("open-article"); } /** Open article popup from the element icon without switching the active property. */ function openArticleFromIcon() { emit("open-article"); } function onTemperatureInput(event: Event) { const target = event.target as HTMLInputElement; if (target.value === "" || target.value === "-") { return; } const value = Number(target.value); if (Number.isFinite(value)) { setTemperatureFromSelectedUnit(value); } } function onTemperatureUnitChange(event: Event) { const target = event.target as HTMLSelectElement; const unit = target.value as TemperatureUnit; if (unit === "C" || unit === "F" || unit === "K") { setTemperatureUnit(unit); } } function onMolarEnergyUnitChange(event: Event) { const target = event.target as HTMLSelectElement; const unit = target.value as MolarEnergyUnit; if (unit === "kJ" || unit === "eV") { setMolarEnergyUnit(unit); } } function onIonizationOrderChange(event: Event) { const target = event.target as HTMLSelectElement; const order = Number(target.value); if (Number.isInteger(order)) { setIonizationOrder(order); } } function onAtomicRadiusKindChange(event: Event) { const target = event.target as HTMLSelectElement; setAtomicRadiusKind(target.value as AtomicRadiusKind); } function onHardnessKindChange(event: Event) { const target = event.target as HTMLSelectElement; setHardnessKind(target.value as HardnessKind); } function onModulusKindChange(event: Event) { const target = event.target as HTMLSelectElement; setModulusKind(target.value as ModulusKind); } function onDensityKindChange(event: Event) { const target = event.target as HTMLSelectElement; setDensityKind(target.value as DensityKind); } function onDensityUnitChange(event: Event) { const target = event.target as HTMLSelectElement; setDensityUnit(target.value as DensityUnit); } function onConductivityKindChange(event: Event) { const target = event.target as HTMLSelectElement; setConductivityKind(target.value as ConductivityKind); } function onHeatKindChange(event: Event) { const target = event.target as HTMLSelectElement; setHeatKind(target.value as HeatKind); } function onAbundanceKindChange(event: Event) { const target = event.target as HTMLSelectElement; setAbundanceKind(target.value as AbundanceKind); } function stepStateTemperature(delta: number) { selectProperty("stateAt"); const next = Math.min( maxDisplayTemperature.value, Math.max(minDisplayTemperature.value, displayTemperature.value + delta), ); setTemperatureFromSelectedUnit(next); } const HOLD_DELAY_MS = 320; const HOLD_INTERVAL_MS = 55; let holdDelayTimer: ReturnType<typeof setTimeout> | null = null; let holdIntervalTimer: ReturnType<typeof setInterval> | null = null; function stopStateTemperatureHold() { if (holdDelayTimer !== null) { clearTimeout(holdDelayTimer); holdDelayTimer = null; } if (holdIntervalTimer !== null) { clearInterval(holdIntervalTimer); holdIntervalTimer = null; } } function startStateTemperatureHold(delta: number, event: PointerEvent) { if (event.button !== 0) { return; } event.preventDefault(); event.stopPropagation(); stopStateTemperatureHold(); stepStateTemperature(delta); const target = event.currentTarget as HTMLElement | null; target?.setPointerCapture?.(event.pointerId); holdDelayTimer = setTimeout(() => { holdIntervalTimer = setInterval(() => { if ((delta > 0 && isTemperatureAtMax.value) || (delta < 0 && isTemperatureAtMin.value)) { stopStateTemperatureHold(); return; } const before = displayTemperature.value; stepStateTemperature(delta); if (before === displayTemperature.value) { stopStateTemperatureHold(); } }, HOLD_INTERVAL_MS); }, HOLD_DELAY_MS); } onBeforeUnmount(() => { stopStateTemperatureHold(); }); </script> <template> <component :is="embedded ? 'div' : 'aside'" :class="embedded ? 'home-aside__embed' : 'home-aside'" :aria-label="embedded ? undefined : 'Свойства элемента'" > <ElementIconCard v-if="showIcon" :element="element" @open-article="openArticleFromIcon" /> <div class="home-aside__data"> <ul class="home-aside__props" role="list"> <li v-for="row in propertyRows" :key="row.key"> <div v-if="row.key === 'article'" class="home-aside__row" :class="{ 'is-active': selectedProperty === row.key }" role="button" tabindex="0" :aria-pressed="selectedProperty === row.key" @click="selectProperty('article')" @keydown.enter="selectProperty('article')" @keydown.space.prevent="selectProperty('article')" > <span class="home-aside__label">{{ row.label }}</span> <button type="button" class="home-aside__value home-aside__value--link" @click="openArticle" > {{ row.value }} </button> </div> <div v-else-if="row.key === 'stateAt'" class="home-aside__row home-aside__row--state-at" :class="{ 'is-active': selectedProperty === row.key }" role="button" tabindex="0" :aria-pressed="selectedProperty === row.key" @click="selectProperty('stateAt')" @keydown.enter="selectProperty('stateAt')" @keydown.space.prevent="selectProperty('stateAt')" > <div class="home-aside__state-at"> <span class="home-aside__label">{{ row.label }}</span> <span class="home-aside__temp-field" :class="{ 'is-at-min': isTemperatureAtMin, 'is-at-max': isTemperatureAtMax, }" @click.stop="selectProperty('stateAt')" > <input id="home-state-temperature" class="home-aside__temp-input" :class="{ 'is-at-limit': isTemperatureAtMin || isTemperatureAtMax }" type="number" :min="minDisplayTemperature" :max="maxDisplayTemperature" :value="displayTemperature" :data-unit="temperatureUnit" aria-label="Температура для агрегатного состояния" @input="onTemperatureInput" @change="onTemperatureInput" @click.stop="selectProperty('stateAt')" /> <span class="home-aside__temp-spinners" aria-hidden="true"> <button type="button" class="home-aside__temp-spin" :disabled="isTemperatureAtMax" tabindex="-1" @pointerdown="startStateTemperatureHold(1, $event)" @pointerup="stopStateTemperatureHold" @pointerleave="stopStateTemperatureHold" @pointercancel="stopStateTemperatureHold" > ▴ </button> <button type="button" class="home-aside__temp-spin" :disabled="isTemperatureAtMin" tabindex="-1" @pointerdown="startStateTemperatureHold(-1, $event)" @pointerup="stopStateTemperatureHold" @pointerleave="stopStateTemperatureHold" @pointercancel="stopStateTemperatureHold" > ▾ </button> </span> </span> <select class="home-aside__temp-unit" aria-label="Единица температуры" :value="temperatureUnit" @change="onTemperatureUnitChange" @click.stop="selectProperty('stateAt')" > <option value="C">°C</option> <option value="F">°F</option> <option value="K">K</option> </select> </div> <output class="home-aside__value"> {{ currentStateLabel }} </output> </div> <div v-else-if="row.key === 'meltingPoint' || row.key === 'boilingPoint'" class="home-aside__row home-aside__row--melt" :class="{ 'is-active': selectedProperty === row.key }" role="button" tabindex="0" :aria-pressed="selectedProperty === row.key" @click="selectProperty(row.key)" @keydown.enter="selectProperty(row.key)" @keydown.space.prevent="selectProperty(row.key)" > <span class="home-aside__label">{{ row.label }}</span> <span class="home-aside__melt-value"> <span class="home-aside__value">{{ row.value }}</span> <select class="home-aside__temp-unit" :aria-label=" row.key === 'boilingPoint' ? 'Единица температуры кипения' : 'Единица температуры плавления' " :value="temperatureUnit" @change="onTemperatureUnitChange" @click.stop="selectProperty(row.key)" > <option value="C">°C</option> <option value="F">°F</option> <option value="K">K</option> </select> </span> </div> <div v-else-if="row.key === 'electronAffinity'" class="home-aside__row home-aside__row--affinity" :class="{ 'is-active': selectedProperty === row.key }" role="button" tabindex="0" :aria-pressed="selectedProperty === row.key" @click="selectProperty('electronAffinity')" @keydown.enter="selectProperty('electronAffinity')" @keydown.space.prevent="selectProperty('electronAffinity')" > <span class="home-aside__label">{{ row.label }}</span> <span class="home-aside__melt-value"> <span class="home-aside__value">{{ row.value }}</span> <select class="home-aside__temp-unit" aria-label="Единица энергии сродства к электрону" :value="molarEnergyUnit" @change="onMolarEnergyUnitChange" @click.stop="selectProperty('electronAffinity')" > <option value="kJ">kJ/mol</option> <option value="eV">eV</option> </select> </span> </div> <div v-else-if="row.key === 'ionizationEnergy'" class="home-aside__row home-aside__row--ionize" :class="{ 'is-active': selectedProperty === row.key }" role="button" tabindex="0" :aria-pressed="selectedProperty === row.key" @click="selectProperty('ionizationEnergy')" @keydown.enter="selectProperty('ionizationEnergy')" @keydown.space.prevent="selectProperty('ionizationEnergy')" > <span class="home-aside__label home-aside__label--ionize"> <span>{{ row.label }},</span> <select class="home-aside__ionize-order" aria-label="Порядок энергии ионизации" :value="ionizationOrder" @change="onIonizationOrderChange" @click.stop="selectProperty('ionizationEnergy')" > <option v-for="order in IONIZATION_ORDERS" :key="order" :value="order"> {{ order }} </option> </select> </span> <span class="home-aside__melt-value"> <span class="home-aside__value">{{ row.value }}</span> <select class="home-aside__temp-unit" aria-label="Единица энергии ионизации" :value="molarEnergyUnit" @change="onMolarEnergyUnitChange" @click.stop="selectProperty('ionizationEnergy')" > <option value="kJ">kJ/mol</option> <option value="eV">eV</option> </select> </span> </div> <div v-else-if="row.key === 'radius'" class="home-aside__row home-aside__row--ionize" :class="{ 'is-active': selectedProperty === row.key }" role="button" tabindex="0" :aria-pressed="selectedProperty === row.key" @click="selectProperty('radius')" @keydown.enter="selectProperty('radius')" @keydown.space.prevent="selectProperty('radius')" > <span class="home-aside__label home-aside__label--ionize"> <span>{{ row.label }},</span> <select class="home-aside__ionize-order home-aside__radius-kind" aria-label="Тип атомного радиуса" :value="atomicRadiusKind" @change="onAtomicRadiusKindChange" @click.stop="selectProperty('radius')" > <option v-for="option in ATOMIC_RADIUS_KIND_OPTIONS" :key="option.value" :value="option.value" > {{ option.label }} </option> </select> </span> <span class="home-aside__melt-value"> <span class="home-aside__value">{{ row.value }}</span> </span> </div> <div v-else-if="row.key === 'hardness'" class="home-aside__row home-aside__row--ionize" :class="{ 'is-active': selectedProperty === row.key }" role="button" tabindex="0" :aria-pressed="selectedProperty === row.key" @click="selectProperty('hardness')" @keydown.enter="selectProperty('hardness')" @keydown.space.prevent="selectProperty('hardness')" > <span class="home-aside__label home-aside__label--ionize"> <span>{{ row.label }},</span> <select class="home-aside__ionize-order home-aside__hardness-kind" aria-label="Шкала твёрдости" :value="hardnessKind" @change="onHardnessKindChange" @click.stop="selectProperty('hardness')" > <option v-for="option in HARDNESS_KIND_OPTIONS" :key="option.value" :value="option.value" > {{ option.label }} </option> </select> </span> <span class="home-aside__melt-value"> <span class="home-aside__value">{{ row.value }}</span> </span> </div> <div v-else-if="row.key === 'modulus'" class="home-aside__row home-aside__row--ionize" :class="{ 'is-active': selectedProperty === row.key }" role="button" tabindex="0" :aria-pressed="selectedProperty === row.key" @click="selectProperty('modulus')" @keydown.enter="selectProperty('modulus')" @keydown.space.prevent="selectProperty('modulus')" > <span class="home-aside__label home-aside__label--ionize"> <span>{{ row.label }},</span> <select class="home-aside__ionize-order home-aside__modulus-kind" aria-label="Тип модуля упругости" :value="modulusKind" @change="onModulusKindChange" @click.stop="selectProperty('modulus')" > <option v-for="option in MODULUS_KIND_OPTIONS" :key="option.value" :value="option.value" > {{ option.label }} </option> </select> </span> <span class="home-aside__melt-value"> <span class="home-aside__value">{{ row.value }}</span> </span> </div> <div v-else-if="row.key === 'density'" class="home-aside__row home-aside__row--ionize" :class="{ 'is-active': selectedProperty === row.key }" role="button" tabindex="0" :aria-pressed="selectedProperty === row.key" @click="selectProperty('density')" @keydown.enter="selectProperty('density')" @keydown.space.prevent="selectProperty('density')" > <span class="home-aside__label home-aside__label--ionize"> <span>{{ row.label }},</span> <select class="home-aside__ionize-order home-aside__density-kind" aria-label="Условия плотности" :value="densityKind" @change="onDensityKindChange" @click.stop="selectProperty('density')" > <option v-for="option in DENSITY_KIND_OPTIONS" :key="option.value" :value="option.value" > {{ option.label }} </option> </select> </span> <span class="home-aside__melt-value"> <span class="home-aside__value">{{ row.value }}</span> <select class="home-aside__temp-unit" aria-label="Единица плотности" :value="densityUnit" @change="onDensityUnitChange" @click.stop="selectProperty('density')" > <option value="kg">kg/m³</option> <option value="g">g/cm³</option> </select> </span> </div> <div v-else-if="row.key === 'conductivity'" class="home-aside__row home-aside__row--ionize" :class="{ 'is-active': selectedProperty === row.key }" role="button" tabindex="0" :aria-pressed="selectedProperty === row.key" @click="selectProperty('conductivity')" @keydown.enter="selectProperty('conductivity')" @keydown.space.prevent="selectProperty('conductivity')" > <span class="home-aside__label home-aside__label--ionize"> <span>{{ row.label }},</span> <select class="home-aside__ionize-order home-aside__conductivity-kind" aria-label="Тип проводимости" :value="conductivityKind" @change="onConductivityKindChange" @click.stop="selectProperty('conductivity')" > <option v-for="option in CONDUCTIVITY_KIND_OPTIONS" :key="option.value" :value="option.value" > {{ option.label }} </option> </select> </span> <span class="home-aside__melt-value"> <span class="home-aside__value">{{ row.value }}</span> <span class="home-aside__unit-label">{{ conductivityUnitLabel }}</span> </span> </div> <div v-else-if="row.key === 'heat'" class="home-aside__row home-aside__row--ionize" :class="{ 'is-active': selectedProperty === row.key }" role="button" tabindex="0" :aria-pressed="selectedProperty === row.key" @click="selectProperty('heat')" @keydown.enter="selectProperty('heat')" @keydown.space.prevent="selectProperty('heat')" > <span class="home-aside__label home-aside__label--ionize"> <span>{{ row.label }},</span> <select class="home-aside__ionize-order home-aside__heat-kind" aria-label="Тип тепловой характеристики" :value="heatKind" @change="onHeatKindChange" @click.stop="selectProperty('heat')" > <option v-for="option in HEAT_KIND_OPTIONS" :key="option.value" :value="option.value" > {{ option.label }} </option> </select> </span> <span class="home-aside__melt-value"> <span class="home-aside__value">{{ row.value }}</span> <select v-if="isHeatMolarEnergy" class="home-aside__temp-unit" aria-label="Единица теплоты" :value="molarEnergyUnit" @change="onMolarEnergyUnitChange" @click.stop="selectProperty('heat')" > <option value="kJ">kJ/mol</option> <option value="eV">eV</option> </select> <span v-else class="home-aside__unit-label" >{{ heatUnitLabel }}</span> </span> </div> <div v-else-if="row.key === 'abundance'" class="home-aside__row home-aside__row--ionize" :class="{ 'is-active': selectedProperty === row.key }" role="button" tabindex="0" :aria-pressed="selectedProperty === row.key" @click="selectProperty('abundance')" @keydown.enter="selectProperty('abundance')" @keydown.space.prevent="selectProperty('abundance')" > <span class="home-aside__label home-aside__label--ionize"> <span>{{ row.label }},</span> <select class="home-aside__ionize-order home-aside__abundance-kind" aria-label="Среда изобилия" :value="abundanceKind" @change="onAbundanceKindChange" @click.stop="selectProperty('abundance')" > <option v-for="option in ABUNDANCE_KIND_OPTIONS" :key="option.value" :value="option.value" > {{ option.label }} </option> </select> </span> <span class="home-aside__melt-value"> <span class="home-aside__value">{{ row.value }}</span> <span class="home-aside__unit-label">%</span> </span> </div> <button v-else type="button" class="home-aside__row" :class="{ 'is-active': selectedProperty === row.key }" :aria-pressed="selectedProperty === row.key" @click="selectProperty(row.key)" > <span class="home-aside__label">{{ row.label }}</span> <span class="home-aside__value">{{ row.value }}</span> </button> </li> </ul> </div> </component> </template> <style scoped> .home-aside { display: flex; flex-direction: column; flex: 0 0 16.5rem; align-self: stretch; box-sizing: border-box; width: 16.5rem; min-width: 16.5rem; max-width: 16.5rem; min-height: 0; height: 100%; overflow: hidden; border-right: 1px solid var(--color-border); background: color-mix(in oklab, var(--color-surface) 88%, var(--color-surface-elevated)); font-family: var(--table-font-ui); } .home-aside__embed { display: flex; flex: 1 1 auto; flex-direction: column; min-width: 0; min-height: 0; height: 100%; overflow: hidden; } .home-aside__data { position: relative; flex: 1 1 auto; min-height: 0; overflow-x: hidden; overflow-y: scroll; overscroll-behavior: contain; scrollbar-gutter: stable; scrollbar-width: thin; scrollbar-color: #9aa3ad #e8ebef; } .home-aside__data::-webkit-scrollbar { width: 10px; } .home-aside__data::-webkit-scrollbar-track { background: #e8ebef; border-left: 1px solid color-mix(in oklab, var(--color-border) 80%, transparent); } .home-aside__data::-webkit-scrollbar-thumb { border: 2px solid #e8ebef; border-radius: 8px; background: #9aa3ad; } .home-aside__data::-webkit-scrollbar-thumb:hover { background: #7d8792; } html[data-theme="dark"] .home-aside__data { scrollbar-color: #6b7280 #2a2f36; } html[data-theme="dark"] .home-aside__data::-webkit-scrollbar-track { background: #2a2f36; border-left-color: #3a4048; } html[data-theme="dark"] .home-aside__data::-webkit-scrollbar-thumb { border-color: #2a2f36; background: #6b7280; } html[data-theme="dark"] .home-aside__data::-webkit-scrollbar-thumb:hover { background: #8b939e; } .home-aside__props { display: flex; flex-direction: column; gap: 0.28rem; margin: 0; padding: 0.35rem 0.2rem 0.55rem 0.4rem; list-style: none; } .home-aside__props > li { margin: 0; overflow: hidden; border-radius: 0.2rem; background: var(--color-surface-elevated, #fff); box-shadow: 0 1px 3px color-mix(in oklab, var(--color-shadow, #000) 12%, transparent), 0 1px 2px color-mix(in oklab, var(--color-shadow, #000) 22%, transparent); transition: background-color 120ms ease; } .home-aside__props > li:hover { background: color-mix(in oklab, var(--color-surface) 55%, #f5f8fa); } .home-aside__props > li:has(.is-active) { background: color-mix(in oklab, var(--color-selected-surface, #ddd) 82%, #ddd); } .home-aside__row { display: flex; align-items: center; justify-content: flex-end; gap: 0.45rem; width: 100%; min-height: 2.35rem; margin: 0; padding: 0.28rem 0.55rem; border: 0; background: transparent; color: var(--color-text-primary); font: inherit; text-align: start; text-decoration: none; cursor: pointer; } .home-aside__row--state-at { gap: 0.35rem; } .home-aside__row:hover, .home-aside__row:focus-visible, .home-aside__row.is-active { background: transparent; outline: none; } .home-aside__label { flex: 1 1 auto; min-width: 0; font-size: 0.72rem; font-weight: 400; line-height: 1.2; letter-spacing: 0; color: var(--color-text-primary); } .home-aside__value { flex: 0 1 auto; max-width: 48%; overflow: hidden; font-size: 0.72rem; font-weight: 400; line-height: 1.2; text-align: end; text-overflow: ellipsis; white-space: nowrap; color: var(--color-text-primary); } .home-aside__row:hover .home-aside__value { white-space: normal; overflow: visible; text-overflow: unset; word-break: break-word; } .home-aside__value--link { margin: 0; padding: 0; border: 0; background: transparent; color: var(--color-text-primary); font-family: inherit; font-size: 0.72rem; font-weight: 400; line-height: 1.2; text-align: end; text-decoration: underline; text-underline-offset: 0.12em; cursor: pointer; } .home-aside__value--link:hover, .home-aside__value--link:focus-visible { color: var(--color-focus-ring, #0276fd); outline: none; } .home-aside__state-at { display: flex; flex: 1 1 auto; flex-wrap: nowrap; gap: 0.18rem; align-items: center; min-width: 0; } .home-aside__melt-value { display: flex; flex: 0 0 auto; flex-wrap: nowrap; gap: 0.22rem; align-items: center; justify-content: flex-end; max-width: none; min-width: 0; } .home-aside__row--melt { flex-wrap: nowrap; gap: 0.35rem; } .home-aside__row--melt .home-aside__label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .home-aside__row--melt .home-aside__value { flex: 0 0 auto; max-width: none; overflow: visible; text-overflow: unset; white-space: nowrap; } .home-aside__row--melt:hover .home-aside__value, .home-aside__row--melt:focus-visible .home-aside__value { overflow: visible; text-overflow: unset; white-space: nowrap; word-break: normal; } .home-aside__row--melt .home-aside__temp-unit { flex: 0 0 auto; } .home-aside__row--affinity { flex-wrap: nowrap; align-items: center; gap: 0.35rem; } .home-aside__row--affinity .home-aside__label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .home-aside__row--affinity .home-aside__melt-value { flex: 0 0 auto; } .home-aside__row--affinity .home-aside__value { flex: 0 0 auto; max-width: none; overflow: visible; text-overflow: unset; white-space: nowrap; } .home-aside__row--affinity .home-aside__temp-unit { flex: 0 0 auto; } /* Selected: show full name when space allows; shrink label left if number needs room. */ .home-aside__row--affinity.is-active { gap: 0.2rem; padding-inline-start: 0.32rem; } .home-aside__row--affinity.is-active .home-aside__label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .home-aside__row--affinity.is-active .home-aside__melt-value { flex: 0 0 auto; } .home-aside__row--affinity.is-active .home-aside__value { flex: 0 0 auto; overflow: visible; text-overflow: unset; } .home-aside__row--ionize { flex-wrap: nowrap; align-items: center; gap: 0.35rem; } .home-aside__row--ionize .home-aside__label--ionize { display: inline-flex; flex: 1 1 auto; flex-wrap: nowrap; gap: 0.18rem; align-items: center; min-width: 0; overflow: hidden; } .home-aside__row--ionize .home-aside__label--ionize > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .home-aside__ionize-order { flex: 0 0 auto; max-width: 4.2rem; padding: 0.05rem 0.15rem; border: none; border-radius: 0.2rem; background: color-mix(in oklab, var(--color-surface-elevated) 70%, transparent); color: inherit; font: inherit; font-size: 0.92em; cursor: pointer; } .home-aside__ionize-order:focus-visible { outline: 2px solid var(--color-focus-ring, #0276fd); outline-offset: 1px; } .home-aside__radius-kind { max-width: 9.2rem; } .home-aside__hardness-kind { max-width: 6.5rem; } .home-aside__modulus-kind { max-width: 6.8rem; } .home-aside__density-kind { max-width: 6.2rem; } .home-aside__conductivity-kind { max-width: 8.5rem; } .home-aside__heat-kind { max-width: 9.5rem; } .home-aside__abundance-kind { max-width: 7.5rem; } .home-aside__unit-label { flex: 0 0 auto; font-size: 0.72rem; line-height: 1.2; white-space: nowrap; color: var(--color-text-secondary); } .home-aside__row--ionize .home-aside__melt-value { flex: 0 0 auto; } .home-aside__row--ionize .home-aside__value { flex: 0 0 auto; max-width: none; overflow: visible; text-overflow: unset; white-space: nowrap; } /* Selected: show full name when space allows; shrink label left if number needs room. */ .home-aside__row--ionize.is-active { gap: 0.2rem; padding-inline-start: 0.32rem; } .home-aside__row--ionize.is-active .home-aside__label--ionize { flex: 1 1 auto; min-width: 0; overflow: hidden; } .home-aside__row--ionize.is-active .home-aside__label--ionize > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .home-aside__row--ionize.is-active .home-aside__melt-value { flex: 0 0 auto; } .home-aside__row--ionize.is-active .home-aside__value { flex: 0 0 auto; overflow: visible; text-overflow: unset; } .home-aside__state-at .home-aside__label { flex: 0 1 auto; white-space: nowrap; } .home-aside__temp-field { display: inline-grid; grid-template-columns: auto auto; flex-shrink: 0; align-items: center; gap: 0.06rem; padding-bottom: 0.05rem; border-bottom: 1px solid color-mix(in oklab, var(--color-text-secondary) 55%, transparent); transition: border-bottom-color 140ms ease, border-bottom-width 140ms ease; } .home-aside__temp-field.is-at-min, .home-aside__temp-field.is-at-max { border-bottom-color: color-mix(in oklab, var(--color-text-secondary) 72%, #888); border-bottom-width: 2px; } .home-aside__temp-input { box-sizing: border-box; width: 2.55rem; flex-shrink: 0; margin: 0; padding: 0.02rem 0.05rem; border: 0; background: transparent; color: var(--color-text-primary); font: inherit; font-size: 0.72rem; font-weight: 400; line-height: 1.2; text-align: end; appearance: textfield; } .home-aside__temp-input::-webkit-outer-spin-button, .home-aside__temp-input::-webkit-inner-spin-button { margin: 0; appearance: none; } .home-aside__temp-input:focus-visible { outline: none; color: var(--color-focus-ring, #0276fd); } .home-aside__temp-input.is-at-limit { color: color-mix(in oklab, var(--color-text-primary) 78%, #666); } .home-aside__temp-spinners { display: inline-flex; flex-direction: column; gap: 0.04rem; } .home-aside__temp-spin { display: inline-flex; align-items: center; justify-content: center; width: 0.95rem; height: 0.78rem; margin: 0; padding: 0; border: 0; border-radius: 0.18rem; background: color-mix(in oklab, var(--color-surface-elevated) 70%, transparent); color: var(--color-text-secondary); font-size: 0.58rem; line-height: 1; cursor: pointer; touch-action: none; user-select: none; transition: color 120ms ease, background-color 120ms ease, transform 80ms ease, opacity 120ms ease; } .home-aside__temp-spin:hover, .home-aside__temp-spin:focus-visible { color: var(--color-text-primary); background: color-mix(in oklab, var(--color-text-secondary) 22%, transparent); outline: none; } .home-aside__temp-spin:active { color: var(--color-text-primary); background: color-mix(in oklab, var(--color-text-secondary) 38%, transparent); transform: scale(0.92); } .home-aside__temp-spin:disabled { cursor: default; opacity: 0.35; transform: none; background: transparent; } .home-aside__temp-unit { flex-shrink: 0; margin: 0; padding: 0.04rem 0; border: 0; background: transparent; color: var(--color-text-secondary); font: inherit; font-size: 0.68rem; font-weight: 400; line-height: 1.2; cursor: pointer; } .home-aside__row--state-at .home-aside__value { flex: 0 0 auto; max-width: none; white-space: nowrap; } .home-aside__temp-unit:focus-visible { outline: none; color: var(--color-focus-ring, #0276fd); } html[data-theme="dark"] .home-aside__props > li:hover { background: color-mix(in oklab, var(--color-surface-elevated) 70%, #141d26); } html[data-theme="dark"] .home-aside__props > li:has(.is-active) { background: color-mix(in oklab, var(--color-selected-surface, #393939) 88%, #393939); } @media (max-width: 860px) { .home-aside { flex-basis: 14.25rem; width: 14.25rem; min-width: 14.25rem; max-width: 14.25rem; } } </style>