diff --git a/package-lock.json b/package-lock.json index a86bf92..844ec4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,12 @@ "version": "0.0.1", "dependencies": { "@sveltestrap/sveltestrap": "^7.1.0", - "bootstrap-icons": "^1.11.3", + "@types/leaflet": "^1.9.19", + "bootstrap-icons": "^1.13.1", "js-cookie": "^3.0.5", "leaflet": "^1.9.4", - "leaflet-velocity": "^2.1.4" + "leaflet-velocity": "^2.1.4", + "leaflet.heat": "^0.2.0" }, "devDependencies": { "@sveltejs/adapter-auto": "^4.0.0", @@ -866,6 +868,19 @@ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==" }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==" + }, + "node_modules/@types/leaflet": { + "version": "1.9.19", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.19.tgz", + "integrity": "sha512-pB+n2daHcZPF2FDaWa+6B0a0mSDf4dPU35y5iTXsx7x/PzzshiX5atYiS1jlBn43X7XvM8AP+AB26lnSk0J4GA==", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/acorn": { "version": "8.14.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", @@ -894,9 +909,9 @@ } }, "node_modules/bootstrap-icons": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz", - "integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.13.1.tgz", + "integrity": "sha512-ijombt4v6bv5CLeXvRWKy7CuM3TRTuPEuGaGKvTV5cz65rQSY8RQ2JcHt6b90cBBAC7s8fsf2EkQDldzCoXUjw==", "funding": [ { "type": "github", @@ -1098,6 +1113,11 @@ "resolved": "https://registry.npmjs.org/leaflet-velocity/-/leaflet-velocity-2.1.4.tgz", "integrity": "sha512-uTmSb2/Kn28S0itlmJBMy2ZRKsisWUr2wm9rtkKXjpq9Sai7tqKdTRHKfLgTOgEdWFf5Ctt2bQoB7kb50qC7eg==" }, + "node_modules/leaflet.heat": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/leaflet.heat/-/leaflet.heat-0.2.0.tgz", + "integrity": "sha512-Cd5PbAA/rX3X3XKxfDoUGi9qp78FyhWYurFg3nsfhntcM/MCNK08pRkf4iEenO1KNqwVPKCmkyktjW3UD+h9bQ==" + }, "node_modules/locate-character": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", diff --git a/package.json b/package.json index db0e69e..9d82df6 100644 --- a/package.json +++ b/package.json @@ -22,9 +22,11 @@ }, "dependencies": { "@sveltestrap/sveltestrap": "^7.1.0", - "bootstrap-icons": "^1.11.3", + "@types/leaflet": "^1.9.19", + "bootstrap-icons": "^1.13.1", "js-cookie": "^3.0.5", "leaflet": "^1.9.4", - "leaflet-velocity": "^2.1.4" + "leaflet-velocity": "^2.1.4", + "leaflet.heat": "^0.2.0" } } diff --git a/src/app.html b/src/app.html index a01612f..3ba5379 100644 --- a/src/app.html +++ b/src/app.html @@ -4,8 +4,15 @@ + + + + %sveltekit.head% diff --git a/src/lib/components/PanelContainer.svelte b/src/lib/components/PanelContainer.svelte new file mode 100644 index 0000000..3a9bb43 --- /dev/null +++ b/src/lib/components/PanelContainer.svelte @@ -0,0 +1,11 @@ + + +
+ +
diff --git a/src/lib/components/ScenarioPanel.svelte b/src/lib/components/ScenarioPanel.svelte new file mode 100644 index 0000000..4791ced --- /dev/null +++ b/src/lib/components/ScenarioPanel.svelte @@ -0,0 +1,115 @@ + + + + + + + + {#if !isCollapsed} + + + + + + + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ {/if} +
diff --git a/src/lib/components/TabComponent.svelte b/src/lib/components/TabComponent.svelte new file mode 100644 index 0000000..594f4d0 --- /dev/null +++ b/src/lib/components/TabComponent.svelte @@ -0,0 +1,50 @@ + + +
+ {#each tabs as tab (tab.id)} + + {/each} +
+ + \ No newline at end of file diff --git a/src/lib/components/TelemetryPanel.svelte b/src/lib/components/TelemetryPanel.svelte new file mode 100644 index 0000000..7a69ca4 --- /dev/null +++ b/src/lib/components/TelemetryPanel.svelte @@ -0,0 +1,97 @@ + + + + + Последние данные телеметрии + + + {#if !isCollapsed} + + + + + + + + + + + + + + + + + + + + + + + {/if} + + diff --git a/src/routes/Toast.svelte b/src/lib/components/Toast.svelte similarity index 100% rename from src/routes/Toast.svelte rename to src/lib/components/Toast.svelte diff --git a/src/routes/WindVisualisation.svelte b/src/lib/components/WindVisualisation.svelte similarity index 99% rename from src/routes/WindVisualisation.svelte rename to src/lib/components/WindVisualisation.svelte index b028916..386c503 100644 --- a/src/routes/WindVisualisation.svelte +++ b/src/lib/components/WindVisualisation.svelte @@ -96,7 +96,7 @@ displayValues: true, displayOptions: { velocityType: 'Wind Speed', - position: 'bottomleft', + position: 'bottomright', emptyString: 'No wind data', }, data: windData diff --git a/src/lib/ext/leaflet-ruler/leaflet-ruler.ts b/src/lib/ext/leaflet-ruler/leaflet-ruler.ts new file mode 100644 index 0000000..40ef2be --- /dev/null +++ b/src/lib/ext/leaflet-ruler/leaflet-ruler.ts @@ -0,0 +1,286 @@ +import * as L from "leaflet"; +import { distHaversine, bearingHaversine } from "$lib/mathutil"; + +// Define an interface for the control's options for type safety. +export interface RulerOptions extends L.ControlOptions { + events?: { + onToggle?: (isActive: boolean) => void; + }; + circleMarker?: L.CircleMarkerOptions; + lineStyle?: L.PolylineOptions; + lengthUnit?: { + display?: string; + decimal?: number; + factor?: number | null; + label?: string; + }; + angleUnit?: { + display?: string; + decimal?: number; + factor?: number | null; + label?: string; + }; +} + +// Define an interface for the measurement result. +interface MeasurementResult { + Bearing: number; + Distance: number; +} + +// Use a modern TypeScript class that extends L.Control. +export class Ruler extends L.Control { + // Override the default options with our custom ones. + public options: RulerOptions = { + position: "topright", + events: { + onToggle: () => {}, + }, + circleMarker: { + color: "red", + radius: 2, + }, + lineStyle: { + color: "red", + dashArray: "1,6", + }, + lengthUnit: { + display: "km", + decimal: 2, + factor: null, + label: "Distance:", + }, + angleUnit: { + display: "°", + decimal: 2, + factor: null, + label: "Bearing:", + }, + }; + + // Declare class properties with types. + private _lastClickTime = 0; + private _map?: L.Map; + private _container?: HTMLElement; + private _choice = false; + private _defaultCursor = ""; + private _allLayers: L.LayerGroup = L.layerGroup(); + private _clickedLatLong: L.LatLng | null = null; + private _clickedPoints: L.LatLng[] = []; + private _totalLength = 0; + private _clickCount = 0; + private _tempLine: L.FeatureGroup = L.featureGroup(); + private _tempPoint: L.FeatureGroup = L.featureGroup(); + private _pointLayer: L.FeatureGroup = L.featureGroup(); + private _polylineLayer: L.FeatureGroup = L.featureGroup(); + private _movingLatLong: L.LatLng | null = null; + private _result: MeasurementResult = { Bearing: 0, Distance: 0 }; + private _addedLength = 0; + + constructor(options?: RulerOptions) { + super(options); + L.Util.setOptions(this, options); + } + + public isActive(): boolean { + return this._choice; + } + + public onAdd(map: L.Map): HTMLElement { + this._map = map; + this._container = L.DomUtil.create("div", "leaflet-bar leaflet-ruler"); + L.DomEvent.disableClickPropagation(this._container); + L.DomEvent.on(this._container, "click", this._toggleMeasure, this); + this._defaultCursor = this._map.getContainer().style.cursor; + this._allLayers = L.layerGroup(); + return this._container; + } + + public onRemove(): void { + if (this._container) { + L.DomEvent.off(this._container, "click", this._toggleMeasure, this); + } + if (this._choice) { + this._toggleMeasure(); // Turn off measurements + } + } + + private _toggleMeasure(): void { + this._choice = !this._choice; + this.options.events?.onToggle?.(this._choice); + + this._clickedLatLong = null; + this._clickedPoints = []; + this._totalLength = 0; + + if (!this._map || !this._container) return; + + const mapContainer = this._map.getContainer(); + + if (this._choice) { + this._map.doubleClickZoom.disable(); + L.DomEvent.on(mapContainer, "keydown", this._escape, this); + L.DomEvent.on(mapContainer, "dblclick", this._closePath, this); + this._container.classList.add("leaflet-ruler-clicked"); + this._clickCount = 0; + this._tempLine = L.featureGroup().addTo(this._allLayers); + this._tempPoint = L.featureGroup().addTo(this._allLayers); + this._pointLayer = L.featureGroup().addTo(this._allLayers); + this._polylineLayer = L.featureGroup().addTo(this._allLayers); + this._allLayers.addTo(this._map); + mapContainer.style.cursor = "crosshair"; + this._map.on("click", this._clicked, this); + this._map.on("mousemove", this._moving, this); + } else { + this._map.doubleClickZoom.enable(); + L.DomEvent.off(mapContainer, "keydown", this._escape, this); + L.DomEvent.off(mapContainer, "dblclick", this._closePath, this); + this._container.classList.remove("leaflet-ruler-clicked"); + this._map.removeLayer(this._allLayers); + this._allLayers = L.layerGroup(); + mapContainer.style.cursor = this._defaultCursor; + this._map.off("click", this._clicked, this); + this._map.off("mousemove", this._moving, this); + } + } + + private _clicked(e: L.LeafletMouseEvent): void { + // hack to prevent adding the same point twice on double click + let clickTime = Date.now(); + if (clickTime - this._lastClickTime < 200) { + this._closePath(); + return; + } + + this._lastClickTime = clickTime; + + this._clickedLatLong = e.latlng; + this._clickedPoints.push(this._clickedLatLong); + L.circleMarker(this._clickedLatLong, this.options.circleMarker).addTo(this._pointLayer); + + if (this._clickCount > 0 && !e.latlng.equals(this._clickedPoints[this._clickedPoints.length - 2], 0.0001)) { + if (this._movingLatLong) { + L.polyline( + [this._clickedPoints[this._clickCount - 1], this._movingLatLong], + this.options.lineStyle + ).addTo(this._polylineLayer); + } + let text: string; + this._totalLength += this._result.Distance; + const angleUnit = this.options.angleUnit!; + const lengthUnit = this.options.lengthUnit!; + + if (this._clickCount > 1) { + text = `${angleUnit.label} ${this._result.Bearing.toFixed(angleUnit.decimal)} ${ + angleUnit.display + }
${lengthUnit.label} ${this._totalLength.toFixed(lengthUnit.decimal)} ${ + lengthUnit.display + }`; + } else { + text = `${angleUnit.label} ${this._result.Bearing.toFixed(angleUnit.decimal)} ${ + angleUnit.display + }
${lengthUnit.label} ${this._result.Distance.toFixed(lengthUnit.decimal)} ${ + lengthUnit.display + }`; + } + L.circleMarker(this._clickedLatLong, this.options.circleMarker) + .bindTooltip(text, { permanent: true, className: "result-tooltip" }) + .addTo(this._pointLayer) + .openTooltip(); + } + this._clickCount++; + } + + private _moving(e: L.LeafletMouseEvent): void { + if (this._clickedLatLong && this._map) { + this._movingLatLong = e.latlng; + + this._tempLine.clearLayers(); + this._tempPoint.clearLayers(); + + this._calculateBearingAndDistance(); + this._addedLength = this._result.Distance + this._totalLength; + + L.polyline([this._clickedLatLong, this._movingLatLong], this.options.lineStyle).addTo(this._tempLine); + + const angleUnit = this.options.angleUnit!; + const lengthUnit = this.options.lengthUnit!; + let text: string; + + if (this._clickCount > 1) { + text = `${angleUnit.label} ${this._result.Bearing.toFixed(angleUnit.decimal)} ${ + angleUnit.display + }
${lengthUnit.label} ${this._addedLength.toFixed(lengthUnit.decimal)} ${ + lengthUnit.display + }
(+${this._result.Distance.toFixed(lengthUnit.decimal)})
`; + } else { + text = `${angleUnit.label} ${this._result.Bearing.toFixed(angleUnit.decimal)} ${ + angleUnit.display + }
${lengthUnit.label} ${this._result.Distance.toFixed(lengthUnit.decimal)} ${ + lengthUnit.display + }`; + } + L.circleMarker(this._movingLatLong, this.options.circleMarker) + .bindTooltip(text, { sticky: true, offset: L.point(0, -40), className: "moving-tooltip" }) + .addTo(this._tempPoint) + .openTooltip(); + } + } + + private _escape(e: Event): void { + if ((e as KeyboardEvent).key === "Escape") { + if (this._clickCount > 0) { + this._closePath(); + } else { + this._toggleMeasure(); + } + } + } + + private _calculateBearingAndDistance(): void { + if (!this._clickedLatLong || !this._movingLatLong) return; + + const f1 = this._clickedLatLong.lat; + const l1 = this._clickedLatLong.lng; + const f2 = this._movingLatLong.lat; + const l2 = this._movingLatLong.lng; + + const angleUnit = this.options.angleUnit!; + const lengthUnit = this.options.lengthUnit!; + + const brng = bearingHaversine({ lat: f1, lng: l1 }, { lat: f2, lng: l2 }); + + const distance = distHaversine({ lat: f1, lng: l1 }, { lat: f2, lng: l2 }); + + if (angleUnit.factor) { + this._result.Bearing = brng * angleUnit.factor; + } else { + this._result.Bearing = brng; + } + + if (lengthUnit.factor) { + this._result.Distance = distance * lengthUnit.factor; + } else { + this._result.Distance = distance; + } + + this._result = { + Bearing: brng, + Distance: distance, + }; + } + + private _closePath(): void { + if (!this._map || !this._container) return; + + this._map.removeLayer(this._tempLine); + this._map.removeLayer(this._tempPoint); + this._choice = false; + this._toggleMeasure(); + } +} + +// Factory function for creating the control, maintaining the Leaflet convention. +export const ruler = (options?: RulerOptions) => { + return new Ruler(options); +}; diff --git a/src/lib/mathutil.ts b/src/lib/mathutil.ts index 75bcebe..eee4bac 100644 --- a/src/lib/mathutil.ts +++ b/src/lib/mathutil.ts @@ -2,7 +2,7 @@ export function distHaversine( p1: { lat: number; lng: number }, p2: { lat: number; lng: number }, precision?: number -): string { +): number { const R = 6371; // Earth's mean radius in km const rad = (x: number): number => (x * Math.PI) / 180; @@ -20,5 +20,20 @@ export function distHaversine( const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); const d = R * c; - return d.toFixed(precision ?? 3); + return precision ? parseFloat(d.toFixed(precision)) : d; +} + +export function bearingHaversine( + p1: { lat: number; lng: number }, + p2: { lat: number; lng: number } +): number { + const rad = (x: number): number => (x * Math.PI) / 180; + + const dLong = rad(p2.lng - p1.lng); + const y = Math.sin(dLong) * Math.cos(rad(p2.lat)); + const x = + Math.cos(rad(p1.lat)) * Math.sin(rad(p2.lat)) - + Math.sin(rad(p1.lat)) * Math.cos(rad(p2.lat)) * Math.cos(dLong); + + return (Math.atan2(y, x) * 180) / Math.PI; } \ No newline at end of file diff --git a/src/lib/types.ts b/src/lib/types.ts index 34029d5..388f8d0 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -23,6 +23,11 @@ export interface FlightParameters { version: number; } +export interface Point { + latlng: LatLngLiteral & { alt: number }; + datetime: Date; +} + export interface TelemetryPoint { altitude: number; datetime: string; @@ -42,11 +47,8 @@ export interface RawTelemetry { } export interface Telemetry { - flight_path: [number, number, number][]; - launch: { - latlng: LatLngExpression; - datetime: Date; - }; + flight_path: LatLngExpression[]; + launch: Point; datapoints: TelemetryPoint[]; } @@ -74,38 +76,10 @@ export interface RawPrediction { } export interface Prediction { - flight_path: [number, number, number][]; - launch: { - latlng: LatLngExpression; - datetime: Date; - }; - burst: { - latlng: LatLngExpression; - datetime: Date; - }; - landing: { - latlng: LatLngExpression; - datetime: Date; - }; + flight_path: LatLngExpression[]; + launch: Point; + burst: Point; + landing: Point; profile: string; flight_time: number; } - -export interface Point { - latlng: LatLngLiteral & { alt: number }; - datetime: Date; -} - -export interface PredictionData { - launch: Point; - landing: Point; - burst: Point; - flight_path: LatLngExpression[]; - flight_time: number; -} - -export interface TelemetryData { - launch: Point; - datapoints: TelemetryPoint[]; - flight_path: LatLngExpression[]; -} \ No newline at end of file diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index f961cbf..cbb97e2 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,5 +1,5 @@
diff --git a/src/routes/ControlPanel.svelte b/src/routes/ControlPanel.svelte deleted file mode 100644 index 5288255..0000000 --- a/src/routes/ControlPanel.svelte +++ /dev/null @@ -1,296 +0,0 @@ - - -
- - - - - - {#if !isCollapsed} - - - - - - {#each Object.keys(PROFILE_MAP) as profileName} - - {/each} - - - - - - - - - - - - - - - - - - - - - - / - - - - - - - - - - - - - - -
- - - - - - - - -
- -
- - - - - - - - -
- - - - - - -
- - - -
-
- {/if} -
-
diff --git a/src/routes/Navbar.svelte b/src/routes/Navbar.svelte deleted file mode 100644 index 3197208..0000000 --- a/src/routes/Navbar.svelte +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - - diff --git a/src/routes/TelemetryPanel.svelte b/src/routes/TelemetryPanel.svelte deleted file mode 100644 index 20e9b19..0000000 --- a/src/routes/TelemetryPanel.svelte +++ /dev/null @@ -1,74 +0,0 @@ - - -
-
-
Последние данные телеметрии
- -
- {#if !isCollapsed} -
-
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
-
- {/if} -
- - diff --git a/src/routes/predict/+page.svelte b/src/routes/predict/+page.svelte index 54e13d0..62fc819 100644 --- a/src/routes/predict/+page.svelte +++ b/src/routes/predict/+page.svelte @@ -1,17 +1,23 @@
diff --git a/static/css/custom.css b/static/css/custom.css index b9ce25f..e716523 100644 --- a/static/css/custom.css +++ b/static/css/custom.css @@ -2,26 +2,26 @@ height: var(--navbar-height); padding-top: 0rem; padding-bottom: 0rem; - z-index: 1000; + z-index: 1002; border: none; background-color: white !important; } -.nav-link { +.nav-full-height.nav-link { color: inherit; padding-left: 1rem !important; padding-right: 1rem !important; padding-top: 12px; - background-color: var(--bs-light); - margin-right: 1px; + background-color: white; + margin-right: -1px; } -.nav-link:hover { +.nav-full-height.nav-link:hover { color: white !important;; background-color: var(--bs-primary); } -.nav-link.active { +.nav-full-height.nav-link.active { color: white !important; background-color: var(--bs-primary); } @@ -39,7 +39,7 @@ margin-right: 1em; } .navbar { - z-index: 1001; + z-index: 1002; } .card { @@ -51,6 +51,8 @@ :root { --navbar-height: 44px; + --panel-left: 20px; + --panel-top: 20px; } .map-container { @@ -69,7 +71,6 @@ font-family: Arial, sans-serif; font-size: 14px; z-index: 1000; - box-shadow: 0 0 5px rgba(0, 0, 0, 0.2); border: 1px solid #ccc; width: auto; white-space: nowrap; @@ -77,7 +78,45 @@ .panel-container { position: absolute; - bottom: 20px; - left: 20px; - z-index: 1000; -} \ No newline at end of file + top: var(--panel-top); + left: var(--panel-left); + width: 23rem; + max-height: 90vh; + max-width: calc(100vw - var(--panel-left) - var(--panel-left)); + overflow-y: auto; + z-index: 1001; +} + +.leaflet-bar { + border: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important; + border-radius: var(--bs-border-radius) !important; +} + +.leaflet-tooltip-top::before { + border-top-color: var(--bs-border-color) !important; +} +.leaflet-tooltip-bottom::before { + border-bottom-color: var(--bs-border-color) !important; +} +.leaflet-tooltip-left::before { + border-left-color: var(--bs-border-color) !important; +} +.leaflet-tooltip-right::before { + border-right-color: var(--bs-border-color) !important; +} + +.leaflet-tooltip { + background-color: var(--bs-body-bg) !important; + border: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important; + border-radius: var(--bs-border-radius) !important; + color: var(--bs-body-color); + box-shadow: none !important; +} + + +@media (max-width: 767.98px) +{ + .coordinates-display { + display: none; + } +} diff --git a/static/ext/leaflet-ruler/icon.png b/static/ext/leaflet-ruler/icon.png new file mode 100644 index 0000000..028741e Binary files /dev/null and b/static/ext/leaflet-ruler/icon.png differ diff --git a/static/ext/leaflet-ruler/leaflet-ruler.css b/static/ext/leaflet-ruler/leaflet-ruler.css new file mode 100644 index 0000000..69927c3 --- /dev/null +++ b/static/ext/leaflet-ruler/leaflet-ruler.css @@ -0,0 +1,41 @@ +.leaflet-ruler{ + height: 35px; + width: 35px; + background-image: url("./icon.png"); /*
Icons made by Freepik from www.flaticon.com is licensed by CC 3.0 BY
*/ + background-repeat: no-repeat; + background-position: center; +} +.leaflet-ruler:hover{ + background-image: url("./icon.png"); /*
Icons made by Freepik from www.flaticon.com is licensed by CC 3.0 BY
*/ +} +.leaflet-ruler-clicked{ + height: 35px; + width: 35px; + background-repeat: no-repeat; + background-position: center; + background-image: url("./icon.png"); + border-color: chartreuse !important; +} +.leaflet-bar{ + background-color: #ffffff; +} +.leaflet-control { + cursor: pointer; +} +.result-tooltip{ + background-color: white; + border-width: medium; + border-color: #de0000; + font-size: smaller; +} +.moving-tooltip{ + background-color: rgba(255, 255, 255, .7); + background-clip: padding-box; + opacity: 0.5; + border: dotted; + border-color: red; + font-size: smaller; +} +.plus-length{ + padding-left: 45px; +} \ No newline at end of file