@vixoniccom/news-internal 0.4.20-dev.4 → 0.4.20-dev.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/build/index.html +1 -1
- package/build/main.js +1 -1
- package/build/test/downloads/custom-template/main.html +72 -61
- package/build.zip +0 -0
- package/package.json +1 -1
- package/src/components/App.tsx +25 -42
- package/src/components/FormattedHtmlText/index.tsx +4 -3
- package/src/components/FormattedText/index.tsx +27 -22
- package/src/components/GalleryItem/index.tsx +1 -1
- package/src/components/ImageContainer/index.tsx +10 -8
- package/src/index.html +13 -10
- package/src/main.ts +1 -1
- package/src/test/downloads/custom-template/main.html +72 -61
|
@@ -1,62 +1,73 @@
|
|
|
1
|
-
<!
|
|
1
|
+
<!DOCTYPE html>
|
|
2
2
|
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
3
|
+
<head>
|
|
4
|
+
<title></title>
|
|
5
|
+
<style type="text/css">
|
|
6
|
+
@font-face {
|
|
7
|
+
font-family: 'MainFont', sans-serif;
|
|
8
|
+
src: url('./assets/main-font.ttf'); /* IE9 Compat Modes */
|
|
9
|
+
}
|
|
10
|
+
.news-item,
|
|
11
|
+
.background {
|
|
12
|
+
position: absolute;
|
|
13
|
+
width: 800px;
|
|
14
|
+
height: 500px;
|
|
15
|
+
background-image: url('./assets/background.png');
|
|
16
|
+
}
|
|
17
|
+
.news-item__title {
|
|
18
|
+
display: flex;
|
|
19
|
+
position: absolute;
|
|
20
|
+
top: 35px;
|
|
21
|
+
left: 35px;
|
|
22
|
+
width: 725px;
|
|
23
|
+
height: 52px;
|
|
24
|
+
align-items: center;
|
|
25
|
+
font-size: 30px;
|
|
26
|
+
font-family: 'MainFont', sans-serif;
|
|
27
|
+
color: #112244;
|
|
28
|
+
}
|
|
29
|
+
.news-item__description {
|
|
30
|
+
display: flex;
|
|
31
|
+
position: absolute;
|
|
32
|
+
top: 155px;
|
|
33
|
+
left: 390px;
|
|
34
|
+
width: 370px;
|
|
35
|
+
height: 292px;
|
|
36
|
+
font-size: 25px;
|
|
37
|
+
font-family: 'MainFont', sans-serif;
|
|
38
|
+
color: #224466;
|
|
39
|
+
}
|
|
40
|
+
.news-item__image,
|
|
41
|
+
.news-item__image-loading,
|
|
42
|
+
.news-item__image-error {
|
|
43
|
+
border-radius: 10px;
|
|
44
|
+
position: absolute;
|
|
45
|
+
left: 32px;
|
|
46
|
+
top: 135px;
|
|
47
|
+
width: 335px;
|
|
48
|
+
height: 335px;
|
|
49
|
+
object-fit: cover;
|
|
50
|
+
}
|
|
51
|
+
</style>
|
|
52
|
+
</head>
|
|
53
|
+
<body>
|
|
54
|
+
<div class="background"></div>
|
|
55
|
+
<div class="news-item">
|
|
56
|
+
<div class="news-item__title">Título</div>
|
|
57
|
+
<div class="news-item__description">Descripción</div>
|
|
58
|
+
<div>
|
|
59
|
+
<img
|
|
60
|
+
class="news-item__image"
|
|
61
|
+
alt="title" />
|
|
62
|
+
<img
|
|
63
|
+
class="news-item__image-error"
|
|
64
|
+
src="./assets/image-error.png"
|
|
65
|
+
alt="error" />
|
|
66
|
+
<img
|
|
67
|
+
class="news-item__image-loading"
|
|
68
|
+
src="./assets/image-loading.gif"
|
|
69
|
+
alt="loading" />
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</body>
|
|
73
|
+
</html>
|
package/build.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/src/components/App.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React, { useState, useEffect } from 'react'
|
|
2
2
|
import { v4 as uuid } from 'uuid'
|
|
3
3
|
import { NewsContainer } from './NewsContainer'
|
|
4
4
|
import { FontLoader } from './FontLoader'
|
|
@@ -6,43 +6,12 @@ import dayjs from 'dayjs'
|
|
|
6
6
|
|
|
7
7
|
export type Props = {
|
|
8
8
|
data?: VixonicData
|
|
9
|
-
start: boolean
|
|
10
9
|
}
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
news: NewsItem[]
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export class App extends React.Component<Props, State> {
|
|
18
|
-
state: State = { currentIndex: 0, news: [] }
|
|
19
|
-
|
|
20
|
-
constructor(props: Props) {
|
|
21
|
-
super(props)
|
|
22
|
-
this.state = { currentIndex: 0, news: this.parseNews(props.data) }
|
|
23
|
-
this.updateCssVariables(props)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
componentWillReceiveProps(nextProps: Props) {
|
|
27
|
-
const currentNews = this?.props?.data?.parameters?.news
|
|
28
|
-
const nextNews = nextProps?.data?.parameters?.news
|
|
29
|
-
if ((JSON.stringify(currentNews) !== JSON.stringify(nextNews)) || (JSON.stringify(this.props.data) !== JSON.stringify(nextProps.data))) {
|
|
30
|
-
this.setState({ news: this.parseNews(nextProps.data) })
|
|
31
|
-
}
|
|
32
|
-
this.updateCssVariables(nextProps)
|
|
33
|
-
}
|
|
11
|
+
export const App: React.FunctionComponent<Props> = ({ data }) => {
|
|
12
|
+
const [news, setNews] = useState<NewsItem[]>([])
|
|
34
13
|
|
|
35
|
-
|
|
36
|
-
if (!props.data) return
|
|
37
|
-
const { animationSpeed, animationType, template } = props.data.parameters
|
|
38
|
-
const speed = 1750 / (animationSpeed || 1)
|
|
39
|
-
document?.documentElement?.style?.setProperty('--speed', `${speed}ms`)
|
|
40
|
-
document?.documentElement?.style?.setProperty('--enterDelay', `${animationType === 'crossFade' ? 0 : speed}ms`)
|
|
41
|
-
// If gallery make a cross fade with delay to prevent background.
|
|
42
|
-
document?.documentElement?.style?.setProperty('--exitDelay', `${(animationType === 'crossFade' && template === 'gallery') ? speed : 0}ms`)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
parseNews(data?: VixonicData): NewsItem[] {
|
|
14
|
+
const parseNews = (data?: VixonicData): NewsItem[] => {
|
|
46
15
|
if (!data) return []
|
|
47
16
|
const { news } = data.parameters
|
|
48
17
|
const parsedNews = news?.map((n) => {
|
|
@@ -61,10 +30,24 @@ export class App extends React.Component<Props, State> {
|
|
|
61
30
|
return parsedNews || []
|
|
62
31
|
}
|
|
63
32
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
33
|
+
const updateCssVariables = (data?: VixonicData) => {
|
|
34
|
+
if (!data) return
|
|
35
|
+
const { animationSpeed, animationType, template } = data.parameters
|
|
36
|
+
const speed = 1750 / (animationSpeed ?? 1)
|
|
37
|
+
document?.documentElement?.style?.setProperty('--speed', `${speed}ms`)
|
|
38
|
+
document?.documentElement?.style?.setProperty('--enterDelay', `${animationType === 'crossFade' ? 0 : speed}ms`)
|
|
39
|
+
document?.documentElement?.style?.setProperty('--exitDelay', `${(animationType === 'crossFade' && template === 'gallery') ? speed : 0}ms`)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
setNews(parseNews(data))
|
|
44
|
+
updateCssVariables(data)
|
|
45
|
+
}, [data])
|
|
46
|
+
|
|
47
|
+
if (!data) return null
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div style={{
|
|
68
51
|
position: 'absolute',
|
|
69
52
|
top: 0,
|
|
70
53
|
left: 0,
|
|
@@ -73,8 +56,8 @@ export class App extends React.Component<Props, State> {
|
|
|
73
56
|
overflow: 'hidden',
|
|
74
57
|
backgroundSize: '100% 100%',
|
|
75
58
|
}}>
|
|
76
|
-
<NewsContainer data={data} items={
|
|
59
|
+
<NewsContainer data={data} items={news} />
|
|
77
60
|
<FontLoader downloadPath={data.downloadsPath} parameters={data.parameters} />
|
|
78
|
-
</div
|
|
79
|
-
|
|
61
|
+
</div>
|
|
62
|
+
)
|
|
80
63
|
}
|
|
@@ -43,10 +43,11 @@ export const FormattedText: React.FunctionComponent<Props> = (props) => {
|
|
|
43
43
|
const alignment = format?.alignment
|
|
44
44
|
const fontColor = format?.fontColor
|
|
45
45
|
const fontSize = format?.fontSize
|
|
46
|
-
const containerStyle =
|
|
46
|
+
const containerStyle = {
|
|
47
47
|
display: 'inline-flex',
|
|
48
|
-
justifyContent: getHorizontalAlignment(alignment?.horizontal || defaults.alignment)
|
|
49
|
-
|
|
48
|
+
justifyContent: getHorizontalAlignment(alignment?.horizontal || defaults.alignment),
|
|
49
|
+
...props.style
|
|
50
|
+
}
|
|
50
51
|
|
|
51
52
|
return <div style={containerStyle}>
|
|
52
53
|
<div style={{
|
|
@@ -3,11 +3,11 @@ import React from 'react'
|
|
|
3
3
|
const alignments = {
|
|
4
4
|
center: 'center',
|
|
5
5
|
left: 'flex-start',
|
|
6
|
-
right: 'flex-end'
|
|
6
|
+
right: 'flex-end',
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
type Aligment = {
|
|
10
|
-
horizontal: keyof typeof alignments
|
|
10
|
+
horizontal: keyof typeof alignments
|
|
11
11
|
vertical: keyof typeof alignments
|
|
12
12
|
}
|
|
13
13
|
|
|
@@ -20,22 +20,24 @@ interface Props {
|
|
|
20
20
|
unit?: string
|
|
21
21
|
paddingBottom?: string
|
|
22
22
|
paddingTop?: string
|
|
23
|
-
multiline?: boolean
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
export const FormattedText: React.FunctionComponent<Props> = (
|
|
25
|
+
export const FormattedText: React.FunctionComponent<Props> = (props) => {
|
|
26
|
+
const { text, format, maxChar, lineHeight, style, unit, paddingBottom, paddingTop } = props
|
|
27
|
+
|
|
27
28
|
const trimText = (text: any, maxChar: any) => {
|
|
28
29
|
const isValid = maxChar && maxChar >= 3
|
|
29
|
-
if (isValid &&
|
|
30
|
+
if (isValid && text && text.length > maxChar) {
|
|
30
31
|
const returnText = text.substring(0, maxChar - 3)
|
|
32
|
+
returnText.substr(-1, 3)
|
|
31
33
|
return `${returnText.trim()}...`
|
|
32
34
|
}
|
|
33
35
|
return text
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
const checkNested = (obj: any, path: string): boolean => {
|
|
37
|
-
return path.split('.').every(segment => {
|
|
38
|
-
if (obj
|
|
39
|
+
return path.split('.').every((segment) => {
|
|
40
|
+
if (obj?.hasOwnProperty(segment)) {
|
|
39
41
|
obj = obj[segment]
|
|
40
42
|
return true
|
|
41
43
|
}
|
|
@@ -52,24 +54,27 @@ export const FormattedText: React.FunctionComponent<Props> = ({ text, format, ma
|
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
const renderText = maxChar ? trimText(text, maxChar) : text
|
|
55
|
-
const containerStyle =
|
|
57
|
+
const containerStyle = {
|
|
56
58
|
display: 'inline-flex',
|
|
57
|
-
justifyContent: getHorizontalAlignment(format.alignment)
|
|
58
|
-
|
|
59
|
+
justifyContent: getHorizontalAlignment(format.alignment),
|
|
60
|
+
...style,
|
|
61
|
+
}
|
|
59
62
|
|
|
60
63
|
return (
|
|
61
64
|
<div style={containerStyle}>
|
|
62
|
-
<span
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
65
|
+
<span
|
|
66
|
+
style={{
|
|
67
|
+
color: format?.fontColor,
|
|
68
|
+
fontFamily: checkNested(format, 'font.filename') ? `'${format?.font?.filename?.replace('.', '-')}'` : '',
|
|
69
|
+
fontSize: `${format?.fontSize}${unit}`,
|
|
70
|
+
textAlign: checkNested(format, 'alignment.horizontal') ? format?.alignment?.horizontal : 'left',
|
|
71
|
+
lineHeight: lineHeight,
|
|
72
|
+
paddingBottom: paddingBottom,
|
|
73
|
+
paddingTop: paddingTop,
|
|
74
|
+
display: 'inline-flex',
|
|
75
|
+
}}>
|
|
76
|
+
{renderText}
|
|
77
|
+
</span>
|
|
73
78
|
</div>
|
|
74
79
|
)
|
|
75
80
|
}
|
|
@@ -78,5 +83,5 @@ FormattedText.defaultProps = {
|
|
|
78
83
|
format: {},
|
|
79
84
|
lineHeight: 1,
|
|
80
85
|
unit: 'vh',
|
|
81
|
-
maxChar: undefined
|
|
86
|
+
maxChar: undefined,
|
|
82
87
|
}
|
|
@@ -35,7 +35,7 @@ const hexToRgbA = (hex: string, opacity?: number) => {
|
|
|
35
35
|
c = hex.substring(1).split('')
|
|
36
36
|
if (c.length === 3) c = [c[0], c[0], c[1], c[1], c[2], c[2]]
|
|
37
37
|
c = '0x' + c.join('')
|
|
38
|
-
return 'rgba(' + [(c >> 16) & 255, (c >> 8) & 255, c & 255].join(',') + ',' + (opacity
|
|
38
|
+
return 'rgba(' + [(c >> 16) & 255, (c >> 8) & 255, c & 255].join(',') + ',' + (opacity ?? 1) + ')'
|
|
39
39
|
}
|
|
40
40
|
throw new Error('Bad Hex')
|
|
41
41
|
}
|
|
@@ -21,23 +21,25 @@ const imageModes = {
|
|
|
21
21
|
export const ImageContainer: React.FunctionComponent<Props> = (props) => {
|
|
22
22
|
const { src, style, parameters } = props
|
|
23
23
|
const { imagePosition, imageMode, imageStyle, imageEnabled, imageSize } = parameters
|
|
24
|
-
if (imageEnabled
|
|
24
|
+
if (!imageEnabled) return null
|
|
25
25
|
|
|
26
26
|
const getSize = (): string => {
|
|
27
27
|
const { imageSize, imageCustomSize, template } = props.parameters
|
|
28
28
|
if (template === 'gallery') return '100%'
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
|
|
30
|
+
switch (imageSize) {
|
|
31
|
+
case 'custom': return `${imageCustomSize}%`
|
|
32
|
+
case 'large': return '100%'
|
|
33
|
+
case 'medium': return '70%'
|
|
34
|
+
default: return '50%'
|
|
33
35
|
}
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
const getImagePadding = (): string => {
|
|
37
39
|
const { template, imageSeparation, imagePosition } = props.parameters
|
|
38
40
|
if (template === 'gallery') return ''
|
|
39
|
-
const padding = `${
|
|
40
|
-
const imagePos = imagePosition
|
|
41
|
+
const padding = `${imageSeparation ?? 10}px`
|
|
42
|
+
const imagePos = imagePosition ?? 'left'
|
|
41
43
|
const getPadding = (position: string) => imagePos === position ? padding : '0px'
|
|
42
44
|
return `${getPadding('bottom')} ${getPadding('left')} ${getPadding('top')} ${getPadding('right')}`
|
|
43
45
|
}
|
|
@@ -47,7 +49,7 @@ export const ImageContainer: React.FunctionComponent<Props> = (props) => {
|
|
|
47
49
|
const size: string = getSize()
|
|
48
50
|
|
|
49
51
|
const shouldBeSquare = imageStyle !== undefined && imageStyle !== 'normal'
|
|
50
|
-
const preserveAspectRatio = imageMode && imageModes[imageMode] || imageModes
|
|
52
|
+
const preserveAspectRatio = imageMode && imageModes?.[imageMode] || imageModes?.contain
|
|
51
53
|
const radius = (imageStyle === 'rounded' || imageStyle === 'circle') ? imageRadius[imageStyle] : '0'
|
|
52
54
|
|
|
53
55
|
const commonImageProps = {
|
package/src/index.html
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
<
|
|
6
|
-
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
<html
|
|
3
|
+
lang="en"
|
|
4
|
+
style="position: absolute; height: 100%; width: 100%; overflow: hidden">
|
|
5
|
+
<head>
|
|
6
|
+
<title></title>
|
|
7
|
+
<meta charset="utf-8" />
|
|
8
|
+
</head>
|
|
9
|
+
<body style="margin: 0; overflow: hidden">
|
|
10
|
+
<div
|
|
11
|
+
id="root"
|
|
12
|
+
style="position: absolute; top: 0; right: 0; bottom: 0; left: 0; overflow: hidden"></div>
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|
package/src/main.ts
CHANGED
|
@@ -24,5 +24,5 @@ ipcRenderer.on('finish', (_event: any) => {
|
|
|
24
24
|
})
|
|
25
25
|
|
|
26
26
|
function render(data: VixonicData) {
|
|
27
|
-
ReactDOM.render(React.createElement<Props>(App, { data
|
|
27
|
+
ReactDOM.render(React.createElement<Props>(App, { data }), document.getElementById('root'))
|
|
28
28
|
}
|
|
@@ -1,62 +1,73 @@
|
|
|
1
|
-
<!
|
|
1
|
+
<!DOCTYPE html>
|
|
2
2
|
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
3
|
+
<head>
|
|
4
|
+
<title></title>
|
|
5
|
+
<style type="text/css">
|
|
6
|
+
@font-face {
|
|
7
|
+
font-family: 'MainFont', sans-serif;
|
|
8
|
+
src: url('./assets/main-font.ttf'); /* IE9 Compat Modes */
|
|
9
|
+
}
|
|
10
|
+
.news-item,
|
|
11
|
+
.background {
|
|
12
|
+
position: absolute;
|
|
13
|
+
width: 800px;
|
|
14
|
+
height: 500px;
|
|
15
|
+
background-image: url('./assets/background.png');
|
|
16
|
+
}
|
|
17
|
+
.news-item__title {
|
|
18
|
+
display: flex;
|
|
19
|
+
position: absolute;
|
|
20
|
+
top: 35px;
|
|
21
|
+
left: 35px;
|
|
22
|
+
width: 725px;
|
|
23
|
+
height: 52px;
|
|
24
|
+
align-items: center;
|
|
25
|
+
font-size: 30px;
|
|
26
|
+
font-family: 'MainFont', sans-serif;
|
|
27
|
+
color: #112244;
|
|
28
|
+
}
|
|
29
|
+
.news-item__description {
|
|
30
|
+
display: flex;
|
|
31
|
+
position: absolute;
|
|
32
|
+
top: 155px;
|
|
33
|
+
left: 390px;
|
|
34
|
+
width: 370px;
|
|
35
|
+
height: 292px;
|
|
36
|
+
font-size: 25px;
|
|
37
|
+
font-family: 'MainFont', sans-serif;
|
|
38
|
+
color: #224466;
|
|
39
|
+
}
|
|
40
|
+
.news-item__image,
|
|
41
|
+
.news-item__image-loading,
|
|
42
|
+
.news-item__image-error {
|
|
43
|
+
border-radius: 10px;
|
|
44
|
+
position: absolute;
|
|
45
|
+
left: 32px;
|
|
46
|
+
top: 135px;
|
|
47
|
+
width: 335px;
|
|
48
|
+
height: 335px;
|
|
49
|
+
object-fit: cover;
|
|
50
|
+
}
|
|
51
|
+
</style>
|
|
52
|
+
</head>
|
|
53
|
+
<body>
|
|
54
|
+
<div class="background"></div>
|
|
55
|
+
<div class="news-item">
|
|
56
|
+
<div class="news-item__title">Título</div>
|
|
57
|
+
<div class="news-item__description">Descripción</div>
|
|
58
|
+
<div>
|
|
59
|
+
<img
|
|
60
|
+
class="news-item__image"
|
|
61
|
+
alt="title" />
|
|
62
|
+
<img
|
|
63
|
+
class="news-item__image-error"
|
|
64
|
+
src="./assets/image-error.png"
|
|
65
|
+
alt="error" />
|
|
66
|
+
<img
|
|
67
|
+
class="news-item__image-loading"
|
|
68
|
+
src="./assets/image-loading.gif"
|
|
69
|
+
alt="loading" />
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</body>
|
|
73
|
+
</html>
|