clean up video config forms and styling
This commit is contained in:
@@ -44,15 +44,11 @@ export default function CPUUsageSelector({ defaultValue, onChange }: Props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="config-video-segements-conatiner">
|
<div className="config-video-cpu-container">
|
||||||
<Title level={3} className="section-title">
|
<Title level={3}>CPU Usage</Title>
|
||||||
CPU Usage
|
|
||||||
</Title>
|
|
||||||
<p className="description">
|
<p className="description">
|
||||||
There are trade-offs when considering CPU usage blah blah more wording here.
|
There are trade-offs when considering CPU usage blah blah more wording here.
|
||||||
</p>
|
</p>
|
||||||
<br />
|
|
||||||
|
|
||||||
<div className="segment-slider-container">
|
<div className="segment-slider-container">
|
||||||
<Slider
|
<Slider
|
||||||
tipFormatter={value => TOOLTIPS[value]}
|
tipFormatter={value => TOOLTIPS[value]}
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ export default function VideoLatency() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="config-video-segements-conatiner">
|
<div className="config-video-latency-container">
|
||||||
<Title level={3} className="section-title">
|
<Title level={3} className="section-title">
|
||||||
Latency Buffer
|
Latency Buffer
|
||||||
</Title>
|
</Title>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
// This content populates the video variant modal, which is spawned from the variants table.
|
// This content populates the video variant modal, which is spawned from the variants table.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Slider, Switch, Collapse, Typography } from 'antd';
|
import { Row, Col, Slider, Collapse, Typography } from 'antd';
|
||||||
import { FieldUpdaterFunc, VideoVariant, UpdateArgs } from '../../types/config-section';
|
import { FieldUpdaterFunc, VideoVariant, UpdateArgs } from '../../types/config-section';
|
||||||
import TextField from './form-textfield';
|
import TextField from './form-textfield';
|
||||||
import { DEFAULT_VARIANT_STATE } from '../../utils/config-constants';
|
import { DEFAULT_VARIANT_STATE } from '../../utils/config-constants';
|
||||||
import InfoTip from '../info-tip';
|
|
||||||
import CPUUsageSelector from './cpu-usage';
|
import CPUUsageSelector from './cpu-usage';
|
||||||
|
import ToggleSwitch from './form-toggleswitch';
|
||||||
|
|
||||||
const { Panel } = Collapse;
|
const { Panel } = Collapse;
|
||||||
|
|
||||||
@@ -146,52 +146,45 @@ export default function VideoVariantForm({
|
|||||||
}
|
}
|
||||||
return note;
|
return note;
|
||||||
};
|
};
|
||||||
const selectedPresetNote = '';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="config-variant-form">
|
<div className="config-variant-form">
|
||||||
<p className="description">
|
<p className="description">
|
||||||
Say a thing here about how this all works. Read more{' '}
|
Say a thing here about how this all works. Read more{' '}
|
||||||
<a href="https://owncast.online/docs/configuration/">here</a>.
|
<a href="https://owncast.online/docs/configuration/">here</a>. Click the OK button below to
|
||||||
|
save your information.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="row">
|
<Row gutter={16}>
|
||||||
<div>
|
<Col xs={12} xl={12}>
|
||||||
{/* ENCODER PRESET FIELD */}
|
{/* ENCODER PRESET FIELD */}
|
||||||
<div className="form-module cpu-usage-container">
|
<div className="form-module cpu-usage-container">
|
||||||
<CPUUsageSelector
|
<CPUUsageSelector
|
||||||
defaultValue={dataState.cpuUsageLevel}
|
defaultValue={dataState.cpuUsageLevel}
|
||||||
onChange={handleVideoCpuUsageLevelChange}
|
onChange={handleVideoCpuUsageLevelChange}
|
||||||
/>
|
/>
|
||||||
{selectedPresetNote && (
|
|
||||||
<span className="selected-value-note">{selectedPresetNote}</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* VIDEO PASSTHROUGH FIELD */}
|
{/* VIDEO PASSTHROUGH FIELD - currently disabled */}
|
||||||
<div style={{ display: 'none' }} className="form-module">
|
<div style={{ display: 'none' }} className="form-module">
|
||||||
<p className="label">
|
<ToggleSwitch
|
||||||
<InfoTip tip={VIDEO_VARIANT_DEFAULTS.videoPassthrough.tip} />
|
label="Use Video Passthrough?"
|
||||||
Use Video Passthrough?
|
fieldName="video-passthrough"
|
||||||
</p>
|
tip={VIDEO_VARIANT_DEFAULTS.videoPassthrough.tip}
|
||||||
<div className="form-component">
|
|
||||||
{/* todo: change to ToggleSwitch for layout */}
|
|
||||||
<Switch
|
|
||||||
defaultChecked={dataState.videoPassthrough}
|
|
||||||
checked={dataState.videoPassthrough}
|
checked={dataState.videoPassthrough}
|
||||||
onChange={handleVideoPassChange}
|
onChange={handleVideoPassChange}
|
||||||
// label="Use Video Passthrough"
|
|
||||||
checkedChildren="Yes"
|
|
||||||
unCheckedChildren="No"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Col>
|
||||||
|
|
||||||
|
<Col xs={12} xl={12}>
|
||||||
{/* VIDEO BITRATE FIELD */}
|
{/* VIDEO BITRATE FIELD */}
|
||||||
<div className={`form-module ${dataState.videoPassthrough ? 'disabled' : ''}`}>
|
<div
|
||||||
<Typography.Title level={3} className="section-title">
|
className={`form-module bitrate-container ${
|
||||||
Video Bitrate
|
dataState.videoPassthrough ? 'disabled' : ''
|
||||||
</Typography.Title>
|
}`}
|
||||||
|
>
|
||||||
|
<Typography.Title level={3}>Video Bitrate</Typography.Title>
|
||||||
<p className="description">{VIDEO_VARIANT_DEFAULTS.videoBitrate.tip}</p>
|
<p className="description">{VIDEO_VARIANT_DEFAULTS.videoBitrate.tip}</p>
|
||||||
<div className="segment-slider-container">
|
<div className="segment-slider-container">
|
||||||
<Slider
|
<Slider
|
||||||
@@ -205,41 +198,37 @@ export default function VideoVariantForm({
|
|||||||
max={videoBRMax}
|
max={videoBRMax}
|
||||||
marks={videoBRMarks}
|
marks={videoBRMarks}
|
||||||
/>
|
/>
|
||||||
<span className="selected-value-note">{selectedVideoBRnote()}</span>
|
<p className="selected-value-note">{selectedVideoBRnote()}</p>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Collapse className="advanced-settings">
|
<Collapse className="advanced-settings">
|
||||||
<Panel header="Advanced Settings" key="1">
|
<Panel header="Advanced Settings" key="1">
|
||||||
<div className="section-intro">
|
<p className="description">
|
||||||
Resizing your content will take additional resources on your server. If you wish to
|
Resizing your content will take additional resources on your server. If you wish to
|
||||||
optionally resize your output for this stream variant then you should either set the
|
optionally resize your output for this stream variant then you should either set the
|
||||||
width <strong>or</strong> the height to keep your aspect ratio.
|
width <strong>or</strong> the height to keep your aspect ratio.
|
||||||
</div>
|
</p>
|
||||||
<div className="field">
|
|
||||||
<TextField
|
<TextField
|
||||||
type="number"
|
type="number"
|
||||||
{...VIDEO_VARIANT_DEFAULTS.scaledWidth}
|
{...VIDEO_VARIANT_DEFAULTS.scaledWidth}
|
||||||
value={dataState.scaledWidth}
|
value={dataState.scaledWidth}
|
||||||
onChange={handleScaledWidthChanged}
|
onChange={handleScaledWidthChanged}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div className="field">
|
|
||||||
<TextField
|
<TextField
|
||||||
type="number"
|
type="number"
|
||||||
{...VIDEO_VARIANT_DEFAULTS.scaledHeight}
|
{...VIDEO_VARIANT_DEFAULTS.scaledHeight}
|
||||||
value={dataState.scaledHeight}
|
value={dataState.scaledHeight}
|
||||||
onChange={handleScaledHeightChanged}
|
onChange={handleScaledHeightChanged}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* FRAME RATE FIELD */}
|
{/* FRAME RATE FIELD */}
|
||||||
<div className="field">
|
<div className="form-module frame-rate">
|
||||||
<p className="label">
|
<Typography.Title level={3}>Frame rate</Typography.Title>
|
||||||
<InfoTip tip={VIDEO_VARIANT_DEFAULTS.framerate.tip} />
|
<p className="description">{VIDEO_VARIANT_DEFAULTS.framerate.tip}</p>
|
||||||
Frame rate:
|
<div className="segment-slider-container">
|
||||||
</p>
|
|
||||||
<div className="segment-slider-container form-component">
|
|
||||||
<Slider
|
<Slider
|
||||||
// tooltipVisible
|
// tooltipVisible
|
||||||
tipFormatter={value => `${value} ${framerateUnit}`}
|
tipFormatter={value => `${value} ${framerateUnit}`}
|
||||||
@@ -251,12 +240,11 @@ export default function VideoVariantForm({
|
|||||||
max={framerateMax}
|
max={framerateMax}
|
||||||
marks={framerateMarks}
|
marks={framerateMarks}
|
||||||
/>
|
/>
|
||||||
<span className="selected-value-note">{selectedFramerateNote()}</span>
|
<p className="selected-value-note">{selectedFramerateNote()}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Panel>
|
</Panel>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Typography } from 'antd';
|
import { Typography, Row, Col } from 'antd';
|
||||||
|
|
||||||
import VideoVariantsTable from '../components/config/video-variants-table';
|
import VideoVariantsTable from '../components/config/video-variants-table';
|
||||||
import VideoLatency from '../components/config/video-latency';
|
import VideoLatency from '../components/config/video-latency';
|
||||||
@@ -16,15 +16,18 @@ export default function ConfigVideoSettings() {
|
|||||||
how it impacts your stream performance.
|
how it impacts your stream performance.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="row">
|
<Row gutter={16}>
|
||||||
|
<Col lg={12}>
|
||||||
<div className="form-module variants-table-module">
|
<div className="form-module variants-table-module">
|
||||||
<VideoVariantsTable />
|
<VideoVariantsTable />
|
||||||
</div>
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col lg={12}>
|
||||||
<div className="form-module latency-module">
|
<div className="form-module latency-module">
|
||||||
<VideoLatency />
|
<VideoLatency />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Col>
|
||||||
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,9 @@ h5.ant-typography,
|
|||||||
h1.ant-typography {
|
h1.ant-typography {
|
||||||
font-size: 1.75em;
|
font-size: 1.75em;
|
||||||
color: var(--pink);
|
color: var(--pink);
|
||||||
|
&:first-of-type {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.ant-typography h2,
|
.ant-typography h2,
|
||||||
h2.ant-typography {
|
h2.ant-typography {
|
||||||
@@ -253,13 +256,13 @@ textarea.ant-input {
|
|||||||
|
|
||||||
// ANT BUTTON
|
// ANT BUTTON
|
||||||
.ant-btn {
|
.ant-btn {
|
||||||
background-color: var(--purple-dark);
|
background-color: var(--owncast-purple-25);
|
||||||
border-color: var(--white-25);
|
border-color: var(--owncast-purple-25);
|
||||||
color: var(--white-75);
|
color: var(--white-75);
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus {
|
&:focus {
|
||||||
background-color: var(--owncast-purple);
|
background-color: var(--button-focused);
|
||||||
border-color: var(--owncast-purple);
|
border-color: var(--button-focused);
|
||||||
color: var(--white);
|
color: var(--white);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -308,12 +311,13 @@ textarea.ant-input {
|
|||||||
}
|
}
|
||||||
.ant-table-tbody > tr > td {
|
.ant-table-tbody > tr > td {
|
||||||
transition-duration: var(--ant-transition-duration);
|
transition-duration: var(--ant-transition-duration);
|
||||||
background-color: #141417;
|
background-color: #222325;
|
||||||
color: var(--white-75);
|
color: var(--white-75);
|
||||||
}
|
}
|
||||||
.ant-table-tbody > tr:nth-child(odd) > td {
|
.ant-table-tbody > tr.ant-table-row:hover > td {
|
||||||
background-color: #222325;
|
background-color: var(--gray-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-empty {
|
.ant-empty {
|
||||||
color: var(--white-75);
|
color: var(--white-75);
|
||||||
}
|
}
|
||||||
@@ -335,6 +339,10 @@ textarea.ant-input {
|
|||||||
|
|
||||||
|
|
||||||
// MODAL
|
// MODAL
|
||||||
|
.ant-modal,
|
||||||
|
.ant-modal-body {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
.ant-modal-content {
|
.ant-modal-content {
|
||||||
border-radius: var(--container-border-radius);
|
border-radius: var(--container-border-radius);
|
||||||
border: 1px solid var(--owncast-purple);
|
border: 1px solid var(--owncast-purple);
|
||||||
@@ -362,7 +370,7 @@ textarea.ant-input {
|
|||||||
.ant-modal-content,
|
.ant-modal-content,
|
||||||
.ant-modal-header,
|
.ant-modal-header,
|
||||||
.ant-modal-footer {
|
.ant-modal-footer {
|
||||||
border-color: var(--gray);
|
border-color: var(--white-50);
|
||||||
}
|
}
|
||||||
|
|
||||||
// SELECT
|
// SELECT
|
||||||
@@ -378,6 +386,23 @@ textarea.ant-input {
|
|||||||
.ant-slider-mark-text {
|
.ant-slider-mark-text {
|
||||||
font-size: .85em;
|
font-size: .85em;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
color: var(--white);
|
||||||
|
opacity: .5;
|
||||||
|
}
|
||||||
|
.ant-slider-handle {
|
||||||
|
border-color: var(--blue);
|
||||||
|
}
|
||||||
|
.ant-slider:hover .ant-slider-track {
|
||||||
|
background-color: var(--blue);
|
||||||
|
}
|
||||||
|
.ant-slider-rail {
|
||||||
|
background-color: var(--black);
|
||||||
|
}
|
||||||
|
.ant-slider-track {
|
||||||
|
background-color: var(--nav-text);
|
||||||
|
}
|
||||||
|
.ant-slider-mark-text-active {
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ANT SWITCH
|
// ANT SWITCH
|
||||||
@@ -394,6 +419,7 @@ textarea.ant-input {
|
|||||||
|
|
||||||
// ANT COLLAPSE
|
// ANT COLLAPSE
|
||||||
.ant-collapse {
|
.ant-collapse {
|
||||||
|
font-size: 1em;
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
&> .ant-collapse-item,
|
&> .ant-collapse-item,
|
||||||
.ant-collapse-content {
|
.ant-collapse-content {
|
||||||
@@ -410,13 +436,16 @@ textarea.ant-input {
|
|||||||
.ant-collapse-content {
|
.ant-collapse-content {
|
||||||
background-color: var(--black-35); //#181231;
|
background-color: var(--black-35); //#181231;
|
||||||
}
|
}
|
||||||
|
.ant-collapse > .ant-collapse-item:last-child, .ant-collapse > .ant-collapse-item:last-child > .ant-collapse-header {
|
||||||
|
border-radius: var(--container-border-radius) var(--container-border-radius) 0 0;
|
||||||
|
}
|
||||||
|
.ant-collapse-item:last-child > .ant-collapse-content {
|
||||||
|
border-radius: 0 0 var(--container-border-radius) var(--container-border-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ANT POPOVER
|
// ANT POPOVER
|
||||||
.ant-popover {
|
|
||||||
|
|
||||||
}
|
|
||||||
.ant-popover-inner {
|
.ant-popover-inner {
|
||||||
background-color: var(--black);
|
background-color: var(--black);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.variants-table-module {
|
.variants-table-module {
|
||||||
min-width: 48%;
|
min-width: 400px;
|
||||||
max-width: 600px;
|
|
||||||
margin-right: 1em
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -20,80 +18,13 @@
|
|||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cpu-usage-container,
|
||||||
|
.bitrate-container {
|
||||||
|
height: 20em;
|
||||||
|
}
|
||||||
|
|
||||||
.advanced-settings {
|
.advanced-settings {
|
||||||
width: 48%;
|
margin-top: 1em;
|
||||||
margin-left: 2em;
|
|
||||||
}
|
|
||||||
.blurb {
|
|
||||||
margin: 1em;
|
|
||||||
opacity: .75;
|
|
||||||
}
|
|
||||||
.note {
|
|
||||||
display: inline-block;
|
|
||||||
margin-left: 1em;
|
|
||||||
font-size: .75em;
|
|
||||||
opacity: .5;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// .field {
|
|
||||||
// margin-bottom: 2em;
|
|
||||||
// display: flex;
|
|
||||||
// flex-direction: row;
|
|
||||||
// justify-content: center;
|
|
||||||
// align-items: flex-start;
|
|
||||||
// transform: opacity .15s;
|
|
||||||
// &.disabled {
|
|
||||||
// opacity: .25;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// .label {
|
|
||||||
// width: 40%;
|
|
||||||
// text-align: right;
|
|
||||||
// padding-right: 2em;
|
|
||||||
// font-weight: bold;
|
|
||||||
// color: var(--owncast-purple);
|
|
||||||
// }
|
|
||||||
// .info-tip {
|
|
||||||
// margin-right: 1em;
|
|
||||||
// }
|
|
||||||
// .form-component {
|
|
||||||
// width: 60%;
|
|
||||||
|
|
||||||
// .selected-value-note {
|
|
||||||
// font-size: .85em;
|
|
||||||
// display: inline-block;
|
|
||||||
// text-align: center;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// .ant-collapse {
|
|
||||||
// border: none;
|
|
||||||
// border-radius: 6px;
|
|
||||||
// }
|
|
||||||
// .ant-collapse > .ant-collapse-item:last-child,
|
|
||||||
// .ant-collapse > .ant-collapse-item:last-child > .ant-collapse-header {
|
|
||||||
// border: none;
|
|
||||||
// background-color: rgba(0,0,0,.25);
|
|
||||||
// border-radius: 6px;
|
|
||||||
// }
|
|
||||||
// .ant-collapse-content {
|
|
||||||
// background-color: rgba(0,0,0,.1);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.config-video-segements-conatiner {
|
|
||||||
// display: flex;
|
|
||||||
// flex-direction: row;
|
|
||||||
// justify-content: center;
|
|
||||||
// align-items: flex-start;
|
|
||||||
|
|
||||||
.status-message {
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +40,3 @@
|
|||||||
opacity: .8;
|
opacity: .8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.advanced-settings {
|
|
||||||
margin-top: 2em;
|
|
||||||
}
|
|
||||||
@@ -42,32 +42,31 @@ Ideal for wrapping each Textfield on a page with many text fields in a row. This
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* SEGMENT SLIDER */
|
/* SEGMENT SLIDER GROUP WITH SELECTED NOTE, OR STATUS */
|
||||||
.segment-slider-container {
|
.segment-slider-container {
|
||||||
width: 90%;
|
width: 100%;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
padding: 1em 2em .75em;
|
padding: 1em 2em .75em;
|
||||||
background-color: var(--owncast-purple-25);
|
background-color: var(--owncast-purple-25);
|
||||||
border-radius: 1em;
|
border-radius: var(--container-border-radius);
|
||||||
.ant-slider-rail {
|
|
||||||
background-color: var(--black);
|
|
||||||
}
|
|
||||||
.ant-slider-track {
|
|
||||||
background-color: var(--nav-text);
|
|
||||||
}
|
|
||||||
.ant-slider-mark-text,
|
|
||||||
.ant-slider-mark-text-active {
|
|
||||||
color: var(--white);
|
|
||||||
opacity: .5;
|
|
||||||
}
|
|
||||||
.ant-slider-mark-text-active {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
.status-container {
|
.status-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: .5em auto;
|
margin: .5em auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.selected-value-note {
|
||||||
|
width: 100%;
|
||||||
|
margin: 3em auto 0;
|
||||||
|
text-align: center;
|
||||||
|
font-size: .75em;
|
||||||
|
line-height: normal;
|
||||||
|
color: var(--white);
|
||||||
|
padding: 1em;
|
||||||
|
border-radius: var(--container-border-radius);
|
||||||
|
background-color: var(--black-35);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -72,6 +72,12 @@ code {
|
|||||||
background-color: var(--container-bg-color);
|
background-color: var(--container-bg-color);
|
||||||
padding: 2em;
|
padding: 2em;
|
||||||
border-radius: var(--container-border-radius);
|
border-radius: var(--container-border-radius);
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
&:first-of-type {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
|
|||||||
Reference in New Issue
Block a user