├── .gitignore
├── images
├── gomtl-logo-512.png
└── sponsors
│ ├── la-gare.png
│ ├── intel-security.png
│ ├── google.svg
│ ├── intel-security.svg
│ └── shopify-logo.svg
├── event.yaml
├── sponsors.yaml
├── data.yaml
├── technology
├── README.txt
├── technology.css
└── technology.html
├── talks.yaml
├── models.go
├── README.md
├── main.go
└── template.html
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
--------------------------------------------------------------------------------
/images/gomtl-logo-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abourget/video-automation/HEAD/images/gomtl-logo-512.png
--------------------------------------------------------------------------------
/images/sponsors/la-gare.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abourget/video-automation/HEAD/images/sponsors/la-gare.png
--------------------------------------------------------------------------------
/images/sponsors/intel-security.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abourget/video-automation/HEAD/images/sponsors/intel-security.png
--------------------------------------------------------------------------------
/event.yaml:
--------------------------------------------------------------------------------
1 | event:
2 | tag: gomtl-03
3 | date: "Tuesday, August 30th 2016"
4 | venue: "La Gare"
5 | sponsors:
6 | - intel-security
7 | - la-gare
8 | title: "#gomtl-03 - August 30th"
9 |
--------------------------------------------------------------------------------
/sponsors.yaml:
--------------------------------------------------------------------------------
1 | google:
2 | image: google.svg
3 | name: Google
4 |
5 | intel-security:
6 | image: intel-security.svg
7 | name: Intel Security
8 | url: https://www.truekey.com/
9 |
10 | shopify:
11 | image: shopify-logo.svg
12 | name: Shopify
13 | url: https://www.shopify.ca
14 |
15 | la-gare:
16 | image: la-gare.png
17 | name: La Gare
18 | url: https://garemtl.com/
19 |
--------------------------------------------------------------------------------
/data.yaml:
--------------------------------------------------------------------------------
1 | - slug: 01-alexandre-bourget
2 | first: Alexandre
3 | last: Bourget
4 | twitter: bourgetalexndre
5 | tagline: Data Scientist, Intel Security
6 |
7 | meetup: "#gomtl-01"
8 | date: 22 février 2016
9 |
10 | - slug: 02-robert-de-niro
11 | first: Robert
12 | last: De Niro
13 | tagline: POS Dev, Redshift Inc
14 |
15 | meetup: "#gomtl-01"
16 | date: February 22nd 2016
17 |
--------------------------------------------------------------------------------
/technology/README.txt:
--------------------------------------------------------------------------------
1 |
2 | How to use this theme
3 | ---------------------
4 |
5 | Add the following line inside your
tag _AFTER_ the
6 | webcomponents-lite.min.js and other HTML imports:
7 |
8 |
9 |
10 |
11 |
12 | Usage within your own custom elements
13 | -------------------------------------
14 |
15 | To use this theme within your own custom elements, add the
16 | following line inside your tag in each of your
17 | custom elements:
18 |
19 |
20 |
--------------------------------------------------------------------------------
/talks.yaml:
--------------------------------------------------------------------------------
1 | talks:
2 | - title: "Tests in Go"
3 | presenter:
4 | name: "Dave MacFarlane"
5 | tagline: "Sr Software Dev. @ McGill Centre For Integrative Neuroscience"
6 | abstract: |
7 |
8 | Dave will be giving an overview of the testing tools that come built in with Go.
9 |
10 | - title: "Adventure with a race-y Set"
11 | presenter:
12 | name: Steve Venzerul
13 | twitter: ckm2k1
14 | tagline: "Software Engineer at Intel / JS-Montreal organizer"
15 | abstract: |
16 |
17 | Steve will show an example of some subtle bugs when writing concurrent
18 | code, found while implementing a Set data structure.
19 |
20 | - title: "Goa for microservices, or how to Generate All the Things"
21 | presenter:
22 | name: Alexandre Bourget
23 | twitter: bourgetalexndre
24 | tagline: "Data Scientist at Intel Security"
25 | abstract: |
26 |
27 | We'll be building a REST API, with Swagger API output, using the Goa code
28 | generator.
29 |
--------------------------------------------------------------------------------
/models.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "io/ioutil"
5 |
6 | yaml "gopkg.in/yaml.v1"
7 | )
8 |
9 | type Event struct {
10 | Event struct {
11 | Tag string
12 | Date string
13 | Venue string
14 | }
15 | Sponsors []string
16 | Title string
17 | }
18 |
19 | type Talks struct {
20 | Talks []struct {
21 | Title string
22 | Presenter struct {
23 | Name string
24 | Tagline string
25 | Twitter string
26 | }
27 | Abstract string
28 | }
29 | }
30 |
31 | type Sponsors map[string]struct {
32 | Image string
33 | Name string
34 | URL string
35 | }
36 |
37 | var event Event
38 | var talks Talks
39 | var sponsors Sponsors
40 |
41 | func loadTalks(filename string) error {
42 | cnt, err := ioutil.ReadFile(filename)
43 | if err != nil {
44 | return err
45 | }
46 |
47 | err = yaml.Unmarshal(cnt, &talks)
48 | if err != nil {
49 | return err
50 | }
51 |
52 | return nil
53 | }
54 |
55 | func loadEvent(filename string) error {
56 | cnt, err := ioutil.ReadFile(filename)
57 | if err != nil {
58 | return err
59 | }
60 |
61 | err = yaml.Unmarshal(cnt, &event)
62 | if err != nil {
63 | return err
64 | }
65 |
66 | return nil
67 | }
68 |
69 | func loadSponsors(filename string) error {
70 | cnt, err := ioutil.ReadFile(filename)
71 | if err != nil {
72 | return err
73 | }
74 |
75 | err = yaml.Unmarshal(cnt, &sponsors)
76 | if err != nil {
77 | return err
78 | }
79 |
80 | return nil
81 | }
82 |
--------------------------------------------------------------------------------
/images/sponsors/google.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Generate videos out of animated HTML pages
2 | ==========================================
3 |
4 | This little project does only one simple thing: it generates a series
5 | of videos based on some data in `.yaml` files and a `template.html`,
6 | rendering each as MP4 files (using ffmpeg) from the moment an element
7 | `.capture` is displayed, until it is hidden.
8 |
9 | My primary goal was to create dynamic videos to introduce speakers in
10 | the video recording of the sessions at Golang Montréal meetups
11 | (https://golangmontreal.org). I wanted to cut the post-production
12 | cost of doing videos recordings of the session, so I coded the
13 | repetitive stuff.
14 |
15 | Check `talks.yaml`, `sponsors.yaml` and `event.yaml` to see the
16 | format. This mostly refers to the structure we use in
17 | https://github.com/gomtl/golangmontreal.org `content` and `data` dirs.
18 |
19 | See the included `template.html` for an example template.
20 |
21 | It then uses ChromeDriver (using
22 | [the agouti WebDriver implementation](https://github.com/sclevine/agouti)
23 | ) to open the browser, resize it properly, load the template from its
24 | own internal web server. It waits until an HTML node with a `capture`
25 | class appears (on the body for example). It then starts `ffmpeg` to
26 | capture the screen at 25 fps, writing an .mp4 file in H.264 to disk for each
27 | entry in the `data.yaml`. When the `capture` class disappears, the
28 | video stops. It then proceeds to the next video.
29 |
30 | Pretty simple eh ?
31 |
32 |
33 | Dependencies
34 | ------------
35 |
36 | You need to have the binary for `ffmpeg` available in your PATH (with
37 | libx264 support). You also need
38 | [chromedriver](https://sites.google.com/a/chromium.org/chromedriver/downloads)
39 | to be in your PATH for agouti to pick up. You'll also need Google
40 | Chrome install.. I'll let you figure that one out.
41 |
42 |
43 | Limitations
44 | -----------
45 |
46 | This currently runs only on Linux (uses the x11grab ffmpeg
47 | module). The window-decorations dimensions are modeled around the
48 | Ubuntu Unity environment and are currently hard-coded.
49 |
50 | It could be adapted to run on Windows though.
51 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "html/template"
7 | "log"
8 | "net/http"
9 | "os"
10 | "os/exec"
11 | "time"
12 |
13 | "github.com/sclevine/agouti"
14 | )
15 |
16 | var data map[string]interface{}
17 | var datas []map[string]interface{}
18 |
19 | var videoSize = size{1280, 720}
20 |
21 | var talksFile = flag.String("talks", "talks.yaml", "The talks file in yaml format")
22 | var eventFile = flag.String("event", "event.yaml", "The event data file, in yaml format")
23 | var sponsorsFile = flag.String("sponsors", "sponsors.yaml", "Sponsors metadata, in yaml format")
24 | var eventName = "Golang Montréal"
25 |
26 | func main() {
27 | flag.Parse()
28 |
29 | err := loadTalks(*talksFile)
30 | if err != nil {
31 | log.Fatalln("Error reading talks file:", err)
32 | }
33 |
34 | err = loadEvent(*eventFile)
35 | if err != nil {
36 | log.Fatalln("Error reading event file:", err)
37 | }
38 |
39 | err = loadSponsors(*sponsorsFile)
40 | if err != nil {
41 | log.Fatalln("Error reading sponsors file:", err)
42 | }
43 |
44 | go serveTemplates()
45 |
46 | time.Sleep(100 * time.Millisecond)
47 |
48 | launchVideoRecording()
49 | }
50 |
51 | func serveTemplates() {
52 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
53 | if r.URL.Path != "/" {
54 | http.FileServer(http.Dir(".")).ServeHTTP(w, r)
55 | return
56 | }
57 |
58 | t, err := template.ParseFiles("template.html")
59 | if err != nil {
60 | log.Println("ERROR parsing template.html:", err)
61 | http.Error(w, "ERROR parsing template.html: "+err.Error(), 500)
62 | return
63 | }
64 |
65 | w.Header().Set("Content-Type", "text/html")
66 |
67 | err = t.Execute(w, map[string]interface{}{
68 | "talks": talks.Talks,
69 | "event": event,
70 | "eventName": eventName,
71 | "baseurl": "http://" + r.Host,
72 | "sponsors": sponsors,
73 | })
74 | if err != nil {
75 | fmt.Fprintf(w, "\n\nERROR executing template: "+err.Error())
76 | }
77 | })
78 | fmt.Println("Listening on 127.0.0.1:7777")
79 | err := http.ListenAndServe(":7777", nil)
80 | if err != nil {
81 | log.Fatalln("Error serving:", err)
82 | }
83 | }
84 |
85 | func launchVideoRecording() {
86 | dr := agouti.ChromeDriver()
87 | failOnError(dr.Start())
88 |
89 | page, err := dr.NewPage()
90 | if err != nil {
91 | log.Fatalln("couldn't get Page")
92 | }
93 |
94 | failOnError(page.Size(videoSize.W+chrome.Left+chrome.Right, videoSize.H+chrome.Top+chrome.Bottom))
95 |
96 | failOnError(page.Navigate("http://localhost:7777/"))
97 |
98 | var screenOffset point
99 | failOnError(page.RunScript(`
100 | window.loadVideoAutomation();
101 | return {x: window.screenX, y: window.screenY};
102 | `, nil, &screenOffset))
103 |
104 | for i := 0; i < 10; i++ {
105 |
106 | // Wait for .capture to appear
107 | for {
108 | found, err := page.All(".capture").Count()
109 | failOnError(err)
110 | if found == 1 {
111 | break
112 | }
113 | }
114 |
115 | doneCh := launchFFMPEG(screenOffset.X+chrome.Left, screenOffset.Y+chrome.Top, videoSize.W, videoSize.H, fmt.Sprintf("/tmp/video-automation-%02d.mp4", i+1))
116 |
117 | // Wait for .capture to disappear
118 | for {
119 | found, _ := page.All(".capture").Count()
120 | if found == 0 {
121 | break
122 | }
123 | }
124 |
125 | doneCh <- true
126 |
127 | time.Sleep(1 * time.Second)
128 |
129 | }
130 |
131 | dr.Stop()
132 | }
133 |
134 | type rect struct {
135 | X, Y, W, H int
136 | }
137 | type point struct {
138 | X, Y int
139 | }
140 | type size struct {
141 | W, H int
142 | }
143 | type dim struct {
144 | Top, Bottom, Left, Right int
145 | }
146 |
147 | //var chrome = dim{77, 5, 5, 5}
148 | var chrome = dim{114, 0, 13, 0}
149 |
150 | func launchFFMPEG(x, y, w, h int, filename string) (done chan bool) {
151 | done = make(chan bool)
152 |
153 | args := []string{"-video_size", fmt.Sprintf("%dx%d", w, h), "-framerate", "25", "-f", "x11grab", "-draw_mouse", "0", "-i", fmt.Sprintf(":0.0+%d,%d", x, y), "-vcodec", "libx264", "-preset", "veryfast", "-y", filename}
154 | fmt.Printf("\n\nCommand: ffmpeg %s\n\n", args)
155 | cmd := exec.Command("ffmpeg", args...)
156 | cmd.Stdout = os.Stdout
157 | cmd.Stderr = os.Stderr
158 | stdinPipe, err := cmd.StdinPipe()
159 | failOnError(err)
160 |
161 | go func() {
162 | failOnError(cmd.Start())
163 | <-done
164 | stdinPipe.Write([]byte("q"))
165 | }()
166 |
167 | return done
168 | }
169 |
170 | func failOnError(err error) {
171 | if err != nil {
172 | log.Fatalln("Failed", err)
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
61 |
62 |
63 |
110 |
111 | Video Automation
112 |
113 |
114 | {{ with .talks }}
115 | {{ range $index, $talk := . }}
116 |
117 |
118 |

119 |
120 |
121 |
122 | {{ $.eventName }} — #{{ $.event.Event.Tag }}
123 | {{ $.event.Event.Date }}
124 |
125 |
126 |
127 |
{{ $talk.Title }}
128 |
129 |
130 |
131 |
132 |
{{ $talk.Presenter.Name }}
133 | {{ with $talk.Presenter.Twitter }}
134 |
135 | {{ end }}
136 |
137 | {{ with $talk.Presenter.Tagline }}
138 |
{{ . }}
139 | {{ end }}
140 |
141 |
142 |
143 |
144 |
145 | {{ end }}
146 | {{ end }}
147 |
148 |
161 |
162 |
163 |
164 |
165 |
166 |
--------------------------------------------------------------------------------
/technology/technology.css:
--------------------------------------------------------------------------------
1 | /*
2 | * PolymerThemes v1.0.3
3 | * Homepage: https://polymerthemes.com
4 | * Copyright 2015 Polymer Themes
5 | * Licensed under BSD
6 | * Based on Polymer: http://www.polymer-project.org/
7 | * Compatible with Polymer 1.0
8 | */
9 |
10 | @import url("https://fonts.googleapis.com/css?family=Oxygen:700|Source+Sans+Pro");
11 | html { -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; font-size: 16px; }
12 |
13 | html, body { margin: 0px; padding: 0px; min-height: 100%; color: #444444; background-color: #ffffff; font-family: 'Source Sans Pro', sans-serif; font-weight: 400; }
14 |
15 | section { padding: 40px 0px; }
16 | section[hero] { background-color: #f2f7fa; }
17 |
18 | h1, h2, h3, h4, h5, h6 { font-weight: 400; margin: 0px; padding: 0px; line-height: 1em; }
19 |
20 | h1, h3, h5 { font-family: 'Oxygen', sans-serif; }
21 |
22 | h1 { font-size: 50px; margin: 30px 0px; }
23 |
24 | h2 { color: #444444; font-size: 35px; margin: 20px 0px; }
25 |
26 | h3 { font-size: 25px; margin: 15px 0px; }
27 |
28 | h4 { font-size: 20px; }
29 |
30 | a, a:active, a:visited, a:focus { color: #444444; text-decoration: none; }
31 |
32 | a:hover { color: #444444; text-decoration: underline; }
33 |
34 | :root { --default-primary-color: #373b50; --dark-primary-color: #373b50; --light-primary-color: #c5cae9; --text-primary-color: #ffffff; --accent-color: #ff4081; --primary-background-color: #ffffff; --primary-text-color: #444444; --secondary-text-color: #444444; --disabled-text-color: #313238; --divider-color: #e0e0e0; --paper-checkbox-checked-color: #373b50; --paper-checkbox-checked-ink-color: #373b50; --paper-checkbox-unchecked-color: #444444; --paper-checkbox-unchecked-ink-color: #444444; --paper-checkbox-label-color: #444444; --paper-fab-background: #373b50; --paper-fab-disabled-background: #afb1b9; --paper-fab-disabled-text: #313238; --paper-icon-button-disabled-text: #afb1b9; --paper-input-container-color: #919191; --paper-input-container-focus-color: #373b50; --paper-input-container-invalid-color: #ff6e40; --paper-input-container-input-color: #919191; --paper-menu-background-color: #f2f7fa; --paper-menu-color: #444444; --paper-menu-disabled-color: #afb1b9; --paper-progress-active-color: #373b50; --paper-progress-secondary-color: #90a4ae; --paper-radio-button-checked-color: #373b50; --paper-radio-button-checked-ink-color: #d6d8e3; --paper-radio-button-unchecked-color: #444444; --paper-radio-button-unchecked-ink-color: #444444; --paper-radio-button-label-color: #444444; --paper-slider-knob-color: #373b50; --paper-slider-active-color: #373b50; --paper-slider-pin-color: #373b50; --paper-spinner-layer-1-color: #373b50; --paper-spinner-layer-2-color: #373b50; --paper-spinner-layer-3-color: #373b50; --paper-spinner-layer-4-color: #373b50; --paper-tabs-selection-bar-color: #90a4ae; --paper-tab-ink: #90a4ae; --paper-toggle-button-checked-bar-color: #373b50; --paper-toggle-button-checked-button-color: #373b50; --paper-toggle-button-checked-ink-color: #373b50; --paper-toggle-button-unchecked-bar-color: #444444; --paper-toggle-button-unchecked-button-color: white; --paper-toggle-button-unchecked-ink-color: white; --paper-toolbar-background: #373b50; --paper-toolbar-color: #ffffff; }
35 |
36 | paper-toolbar a, paper-toolbar a:hover, paper-toolbar a:active, paper-toolbar a:visited, paper-toolbar a:focus { color: #ffffff; }
37 |
38 | paper-button.primary, paper-button.btn-primary, paper-button[primary] { color: #373b50; }
39 | paper-button.primary[raised], paper-button.btn-primary[raised], paper-button[primary][raised] { background-color: #373b50; color: #fff; }
40 | paper-button.secondary, paper-button.btn-secondary, paper-button[secondary] { color: #90a4ae; }
41 | paper-button.secondary[raised], paper-button.btn-secondary[raised], paper-button[secondary][raised] { background-color: #90a4ae; color: #2b3134; }
42 | paper-button.success, paper-button.btn-success, paper-button[success] { color: #9dc56e; }
43 | paper-button.success[raised], paper-button.btn-success[raised], paper-button[success][raised] { background-color: #9dc56e; color: #2f3b21; }
44 | paper-button.info, paper-button.btn-info, paper-button[info] { color: #ffb74d; }
45 | paper-button.info[raised], paper-button.btn-info[raised], paper-button[info][raised] { background-color: #ffb74d; color: #4d3717; }
46 | paper-button.warning, paper-button.btn-warning, paper-button[warning] { color: #fadd60; }
47 | paper-button.warning[raised], paper-button.btn-warning[raised], paper-button[warning][raised] { background-color: #fadd60; color: #4b421d; }
48 | paper-button.error, paper-button.btn-error, paper-button[error] { color: #ff6e40; }
49 | paper-button.error[raised], paper-button.btn-error[raised], paper-button[error][raised] { background-color: #ff6e40; color: #4d2113; }
50 | paper-button.link, paper-button.btn-link, paper-button[link] { text-decoration: underline; }
51 | paper-button[raised] { color: #444444; }
52 | paper-button[disabled] { color: #313238 !important; background: #afb1b9 !important; }
53 |
54 | paper-icon-button.primary, paper-icon-button.btn-primary, paper-icon-button[primary] { color: #373b50; }
55 | paper-icon-button.primary:hover, paper-icon-button.btn-primary:hover, paper-icon-button[primary]:hover { background-color: #ebebee; background-color: rgba(55, 59, 80, 0.2); border-radius: 50%; }
56 | paper-icon-button.secondary, paper-icon-button.btn-secondary, paper-icon-button[secondary] { color: #90a4ae; }
57 | paper-icon-button.secondary:hover, paper-icon-button.btn-secondary:hover, paper-icon-button[secondary]:hover { background-color: #f4f6f7; background-color: rgba(144, 164, 174, 0.2); border-radius: 50%; }
58 | paper-icon-button.success, paper-icon-button.btn-success, paper-icon-button[success] { color: #9dc56e; }
59 | paper-icon-button.success:hover, paper-icon-button.btn-success:hover, paper-icon-button[success]:hover { background-color: #f5f9f1; background-color: rgba(157, 197, 110, 0.2); border-radius: 50%; }
60 | paper-icon-button.info, paper-icon-button.btn-info, paper-icon-button[info] { color: #ffb74d; }
61 | paper-icon-button.info:hover, paper-icon-button.btn-info:hover, paper-icon-button[info]:hover { background-color: #fff8ed; background-color: rgba(255, 183, 77, 0.2); border-radius: 50%; }
62 | paper-icon-button.warning, paper-icon-button.btn-warning, paper-icon-button[warning] { color: #fadd60; }
63 | paper-icon-button.warning:hover, paper-icon-button.btn-warning:hover, paper-icon-button[warning]:hover { background-color: #fffcef; background-color: rgba(250, 221, 96, 0.2); border-radius: 50%; }
64 | paper-icon-button.error, paper-icon-button.btn-error, paper-icon-button[error] { color: #ff6e40; }
65 | paper-icon-button.error:hover, paper-icon-button.btn-error:hover, paper-icon-button[error]:hover { background-color: #fff1ec; background-color: rgba(255, 110, 64, 0.2); border-radius: 50%; }
66 |
67 | paper-checkbox #checkmark.paper-checkbox, paper-checkbox /deep/, paper-checkbox::shadow #checkmark.paper-checkbox { border-color: #fff !important; }
68 |
69 | paper-dialog { color: #444444; background-color: #ffffff; font-family: 'Source Sans Pro', sans-serif; font-weight: 400; }
70 | paper-dialog h1, paper-dialog h2, paper-dialog h3, paper-dialog h4, paper-dialog h5, paper-dialog h6 { overflow: visible; padding: 0 1em; text-align: center; }
71 |
72 | paper-fab { --text-primary-color: white; }
73 |
74 | paper-fab { background-color: #373b50; color: #fff; --paper-fab-keyboard-focus-background: #6c749a; }
75 | paper-fab.primary, paper-fab.btn-primary, paper-fab[primary] { background-color: #373b50; color: #fff; --paper-fab-keyboard-focus-background: #6c749a; }
76 | paper-fab.secondary, paper-fab.btn-secondary, paper-fab[secondary] { background-color: #90a4ae; color: #2b3134; --paper-fab-keyboard-focus-background: #50646e; }
77 | paper-fab.success, paper-fab.btn-success, paper-fab[success] { background-color: #9dc56e; color: #2f3b21; --paper-fab-keyboard-focus-background: #5d8033; }
78 | paper-fab.info, paper-fab.btn-info, paper-fab[info] { background-color: #ffb74d; color: #4d3717; --paper-fab-keyboard-focus-background: #cd7a00; }
79 | paper-fab.warning, paper-fab.btn-warning, paper-fab[warning] { background-color: #fadd60; color: #4b421d; --paper-fab-keyboard-focus-background: #d4ad07; }
80 | paper-fab.error, paper-fab.btn-error, paper-fab[error] { background-color: #ff6e40; color: #4d2113; --paper-fab-keyboard-focus-background: #c02e00; }
81 | paper-fab[disabled] { color: #313238 !important; background: #afb1b9 !important; }
82 |
83 | section.hero paper-menu, section[hero] paper-menu { --paper-menu-background-color: #f2f7fa; }
84 |
85 | paper-tabs { background-color: #373b50; color: #ffffff; }
86 |
--------------------------------------------------------------------------------
/technology/technology.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/images/sponsors/intel-security.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/images/sponsors/shopify-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
391 |
--------------------------------------------------------------------------------