From fb462f823338d9cbfa17a14ff103dcd9e70cfcd7 Mon Sep 17 00:00:00 2001 From: decentral1se Date: Fri, 27 May 2022 00:54:11 +0200 Subject: [PATCH] init --- .gitignore | 1 + .goreleaser.yaml | 34 ++++ README.md | 40 +++++ assets/.gitignore | 1 + assets/assets.go | 14 ++ assets/static/style.css | 165 +++++++++++++++++++ go.mod | 10 ++ go.sum | 11 ++ makefile | 24 +++ mist-connections.go | 343 ++++++++++++++++++++++++++++++++++++++++ public/index.html | 75 +++++++++ public/style.css | 187 ++++++++++++++++++++++ 12 files changed, 905 insertions(+) create mode 100644 .gitignore create mode 100644 .goreleaser.yaml create mode 100644 README.md create mode 100644 assets/.gitignore create mode 100644 assets/assets.go create mode 100644 assets/static/style.css create mode 100644 go.mod create mode 100644 go.sum create mode 100644 makefile create mode 100644 mist-connections.go create mode 100644 public/index.html create mode 100644 public/style.css diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..94b2488 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +mist-connections diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..04e0cb8 --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,34 @@ +--- + +project_name: mist-connections + +before: + hooks: + - go mod tidy + - go generate ./... + +builds: + - env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin + goarch: + - 386 + - amd64 + - arm + - arm64 + goarm: + - 5 + - 6 + - 7 + +archives: + - replacements: + darwin: Darwin + linux: Linux + windows: Windows + 386: i386 + amd64: x86_64 + format: binary diff --git a/README.md b/README.md new file mode 100644 index 0000000..c7d60ad --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# mist-connections + +> ["Missed Connections"](https://en.m.wikipedia.org/wiki/Missed_connection) + +[Moar](https://git.vvvvvvaria.org/decentral1se/unnamed-project) post-calibre prototypin' :tada: + +## demo + +[`msc.mp4`](https://vvvvvvaria.org/~decentral1se/msc/msc.mp4) + +## install + +Download the binary which matches your OS :point_right: [linux](https://git.vvvvvvaria.org/decentral1se/mist-connections/raw/branch/main/dist/mist-connections_linux_amd64/mist-connections) | [osx](https://git.vvvvvvaria.org/decentral1se/mist-connections/raw/branch/main/dist/mist-connections_darwin_amd64/mist-connections) | [windoze](https://git.vvvvvvaria.org/decentral1se/mist-connections/raw/branch/main/dist/mist-connections_windows_amd64/mist-connections.exe) | ["other"](./dist) + +Then :point_right: `chmod +x mist-connections && ./mist-connections` + +(PS. No Idea what you do on Windoze :shrug:) + +## hack + +`go install github.com/go-bindata/go-bindata/...` + +`make` + +Or, if you want to access the binary directly without make: + +`go build .` + +Then you have a freshly built `./mist-connections` ready. + +## release + +`go install github.com/goreleaser/goreleaser@latest` + +`make release` + +## ack + +- [`@simoon`](https://git.vvvvvvaria.org/simoon) +- [`unnamed project`](https://git.vvvvvvaria.org/decentral1se/unnamed-project) diff --git a/assets/.gitignore b/assets/.gitignore new file mode 100644 index 0000000..f42b841 --- /dev/null +++ b/assets/.gitignore @@ -0,0 +1 @@ +assets_gen.go diff --git a/assets/assets.go b/assets/assets.go new file mode 100644 index 0000000..4c0f5f1 --- /dev/null +++ b/assets/assets.go @@ -0,0 +1,14 @@ +package assets + +import ( + assetfs "github.com/elazarl/go-bindata-assetfs" +) + +func AssetFS() *assetfs.AssetFS { + return &assetfs.AssetFS{ + Asset: Asset, + AssetDir: AssetDir, + AssetInfo: AssetInfo, + Prefix: "public", + } +} diff --git a/assets/static/style.css b/assets/static/style.css new file mode 100644 index 0000000..a14b4a3 --- /dev/null +++ b/assets/static/style.css @@ -0,0 +1,165 @@ +/* ---------- Fog ---------- */ +.fogwrapper { + height: 100%; + position: absolute; + top: 0; + width: 100%; + -webkit-filter: blur(1px) grayscale(0.2) saturate(1.2) sepia(0.2); + filter: blur(1px) grayscale(0.2) saturate(1.2) sepia(0.2); +} +#foglayer_01, #foglayer_02, #foglayer_03 { + height: 100%; + position: absolute; + width: 200%; +} +#foglayer_01 .image01, #foglayer_01 .image02, +#foglayer_02 .image01, #foglayer_02 .image02, +#foglayer_03 .image01, #foglayer_03 .image02 { + float: left; + height: 100%; + width: 50%; +} +#foglayer_01 { + -webkit-animation: foglayer_01_opacity 10s linear infinite, foglayer_moveme 15s linear infinite; + -moz-animation: foglayer_01_opacity 10s linear infinite, foglayer_moveme 15s linear infinite; + animation: foglayer_01_opacity 10s linear infinite, foglayer_moveme 15s linear infinite; +} +#foglayer_02, #foglayer_03 { + -webkit-animation: foglayer_02_opacity 21s linear infinite, foglayer_moveme 13s linear infinite; + -moz-animation: foglayer_02_opacity 21s linear infinite, foglayer_moveme 13s linear infinite; + animation: foglayer_02_opacity 21s linear infinite, foglayer_moveme 13s linear infinite; +} + +/* ---------- Moving Fog ---------- */ +/* + 'size: cover' || 'size: 100%'; results remain the same + 'attachment: scroll' can be added or removed; results remain the same + 'attachment: fixed' causing unexpected results in Chrome + 'repeat-x' || 'no-repeat'; results remain the same +*/ +#foglayer_01 .image01, #foglayer_01 .image02 { + background: url("https://raw.githubusercontent.com/danielstuart14/CSS_FOG_ANIMATION/master/fog1.png") center center/cover no-repeat transparent; +} +#foglayer_02 .image01, #foglayer_02 .image02, +#foglayer_03 .image01, #foglayer_03 .image02{ + background: url("https://raw.githubusercontent.com/danielstuart14/CSS_FOG_ANIMATION/master/fog2.png") center center/cover no-repeat transparent; +} + +/* ---------- Keyframe Layer 1 ---------- */ +@-webkit-keyframes foglayer_01_opacity { + 0% { opacity: .1; } + 22% { opacity: .5; } + 40% { opacity: .28; } + 58% { opacity: .4; } + 80% { opacity: .16; } + 100% { opacity: .1; } +} +@-moz-keyframes foglayer_01_opacity { + 0% { opacity: .1; } + 22% { opacity: .5; } + 40% { opacity: .28; } + 58% { opacity: .4; } + 80% { opacity: .16; } + 100% { opacity: .1; } +} +@-o-keyframes foglayer_01_opacity { + 0% { opacity: .1; } + 22% { opacity: .5; } + 40% { opacity: .28; } + 58% { opacity: .4; } + 80% { opacity: .16; } + 100% { opacity: .1; } +} +@keyframes foglayer_01_opacity { + 0% { opacity: .1; } + 22% { opacity: .5; } + 40% { opacity: .28; } + 58% { opacity: .4; } + 80% { opacity: .16; } + 100% { opacity: .1; } +} +/* ---------- Keyframe Layer 2 ---------- */ +@-webkit-keyframes foglayer_02_opacity { + 0% { opacity: .5; } + 25% { opacity: .2; } + 50% { opacity: .1; } + 80% { opacity: .3; } + 100% { opacity: .5; } +} +@-moz-keyframes foglayer_02_opacity { + 0% { opacity: .5; } + 25% { opacity: .2; } + 50% { opacity: .1; } + 80% { opacity: .3; } + 100% { opacity: .5; } +} +@-o-keyframes foglayer_02_opacity { + 0% { opacity: .5; } + 25% { opacity: .2; } + 50% { opacity: .1; } + 80% { opacity: .3; } + 100% { opacity: .5; } +} +@keyframes foglayer_02_opacity { + 0% { opacity: .5; } + 25% { opacity: .2; } + 50% { opacity: .1; } + 80% { opacity: .3; } + 100% { opacity: .5; } +} +/* ---------- Keyframe Layer 3 ---------- */ +@-webkit-keyframes foglayer_03_opacity { + 0% { opacity: .8 } + 27% { opacity: .2; } + 52% { opacity: .6; } + 68% { opacity: .3; } + 100% { opacity: .8; } +} +@-moz-keyframes foglayer_03_opacity { + 0% { opacity: .8 } + 27% { opacity: .2; } + 52% { opacity: .6; } + 68% { opacity: .3; } + 100% { opacity: .8; } +} +@-o-keyframes foglayer_03_opacity { + 0% { opacity: .8 } + 27% { opacity: .2; } + 52% { opacity: .6; } + 68% { opacity: .3; } + 100% { opacity: .8; } +} +@keyframes foglayer_03_opacity { + 0% { opacity: .8; } + 27% { opacity: .2; } + 52% { opacity: .6; } + 68% { opacity: .3; } + 100% { opacity: .8; } +} +/* ---------- Keyframe moveMe ---------- */ +@-webkit-keyframes foglayer_moveme { + 0% { left: 0; } + 100% { left: -100%; } +} +@-moz-keyframes foglayer_moveme { + 0% { left: 0; } + 100% { left: -100%; } +} +@-o-keyframes foglayer_moveme { + 0% { left: 0; } + 100% { left: -100%; } +} +@keyframes foglayer_moveme { + 0% { left: 0; } + 100% { left: -100%; } +} + +@media only screen + and (min-width: 280px) + and (max-width: 767px) { + #foglayer_01 .image01, #foglayer_01 .image02, + #foglayer_02 .image01, #foglayer_02 .image02, + #foglayer_03 .image01, #foglayer_03 .image02 { + width: 100%; + } + } \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..55794c2 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module varia.zone/mist-connections + +go 1.18 + +require ( + github.com/elazarl/go-bindata-assetfs v1.0.1 + github.com/gabriel-vasile/mimetype v1.4.0 +) + +require golang.org/x/net v0.0.0-20210505024714-0287a6fb4125 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a559eba --- /dev/null +++ b/go.sum @@ -0,0 +1,11 @@ +github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw= +github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= +github.com/gabriel-vasile/mimetype v1.4.0 h1:Cn9dkdYsMIu56tGho+fqzh7XmvY2YyGU0FnbhiOsEro= +github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125 h1:Ugb8sMTWuWRC3+sz5WeN/4kejDx9BvIwnPUiJBjJE+8= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/makefile b/makefile new file mode 100644 index 0000000..dc04417 --- /dev/null +++ b/makefile @@ -0,0 +1,24 @@ +default: run + +MAIN := mist-connections.go +ASSETS := assets/assets_gen.go + +generate: + @rm -rf $(ASSETS) && go generate $(MAIN) + +run: generate + @go run $(MAIN) + +bin: generate + @go build . + +release: + @goreleaser release --snapshot --rm-dist + +install: + @go install . + +build: + @go build . + +.PHONY: generate run bin release install build diff --git a/mist-connections.go b/mist-connections.go new file mode 100644 index 0000000..b7e198e --- /dev/null +++ b/mist-connections.go @@ -0,0 +1,343 @@ +package main + +//go:generate go-bindata -o assets/assets_gen.go -pkg assets public/... + +import ( + "container/list" + "encoding/json" + "flag" + "fmt" + "io/ioutil" + "log" + "net" + "net/http" + "os" + "os/user" + "path/filepath" + "strings" + "sync" + "time" + + "github.com/gabriel-vasile/mimetype" + "varia.zone/mist-connections/assets" +) + +func init() { + // silence underlying http logger which complains about the awful hacks we + // use to override the request/response handler. this keeps the command-line + // free from endless spurious logger warnings + log.SetOutput(ioutil.Discard) +} + +const help = `mist connections + +A missed connection is a type of personal advertisement which arises after two +people meet but are too shy or otherwise unable to exchange contact details. + +Options: + -n misty identifier (default: your system username) + -p path to serve (default: current working directory) + -h output help +` + +const indexPagePortFlag = 1312 +const homePagePortFlag = 5555 + +var helpFlag bool +var mistyID string +var sharePath string + +func main() { + systemUser, err := user.Current() + if err != nil { + log.Fatal(err) + } + + handleCliFlags(systemUser.Name) + + sharePathAbs, err := filepath.Abs(sharePath) + if err != nil { + log.Fatal(err) + } + + if helpFlag { + fmt.Printf(help) + os.Exit(0) + } + + conf := &config{ + MistyID: mistyID, + IndexPagePort: indexPagePortFlag, + HomePagePort: homePagePortFlag, + SharePath: sharePathAbs, + } + + serve(conf) +} + +func handleCliFlags(defaultUsername string) { + flag.StringVar(&mistyID, "n", defaultUsername, "misty identifier") + flag.StringVar(&sharePath, "p", ".", "path to serve") + flag.BoolVar(&helpFlag, "h", false, "output help") + flag.Parse() +} + +type config struct { + MistyID string `json:"mistyID"` + IndexPagePort int `json:"webPort"` + HomePagePort int `json:"filePort"` + SharePath string `json:"sharePath"` +} + +type announcer struct { + conf *config +} + +type nodeInfo struct { + MistyID string `json:"mistyID"` + Addr string `json:"addr"` + IndexPagePort int `json:"webPort"` + LastMulticast int64 `json:"lastMulticast"` +} + +var ( + nodeMutex sync.Mutex +) + +func newAnnouncePacket(n *nodeInfo) (string, error) { + jsonMessage, err := json.Marshal(n) + if err != nil { + return "", err + } + + message := fmt.Sprintf("%s%s%s", header, nodeAnnounceCommand, jsonMessage) + + return message, nil +} + +func announceNode(nodeInfo *nodeInfo) { + address, err := net.ResolveUDPAddr("udp", multicastAddress) + if err != nil { + return + } + + conn, err := net.DialUDP("udp", nil, address) + if err != nil { + return + } + + for { + message, err := newAnnouncePacket(nodeInfo) + if err != nil { + log.Fatal(err) + } + + conn.Write([]byte(message)) + time.Sleep(announceIntervalSec * time.Second) + } +} + +const ( + multicastAddress = "239.6.6.6:1337" + multicastBufferSize = 4096 + nodeAnnounceCommand = "\x01" + header = "\x60\x0D\xF0\x0D" + minPackageSize = 6 + expireTimeoutSec = 15 + announceIntervalSec = 10 +) + +func announcedNodeHandler(ninfo *nodeInfo, nodeList *list.List) { + nodeMutex.Lock() + updateNodeList(ninfo, nodeList) + nodeMutex.Unlock() +} + +func updateNodeList(ninfo *nodeInfo, nodeList *list.List) { + nodeExists := false + for el := nodeList.Front(); el != nil; el = el.Next() { + tmp := el.Value.(*nodeInfo) + + if tmp.MistyID == ninfo.MistyID { + tmp.LastMulticast = time.Now().Unix() + nodeExists = true + break + } + } + + for el := nodeList.Front(); el != nil; el = el.Next() { + tmp := el.Value.(*nodeInfo) + if isNodeExpired(tmp, expireTimeoutSec) { + nodeList.Remove(el) + } + } + + if !nodeExists { + ninfo.LastMulticast = time.Now().Unix() + nodeList.PushBack(ninfo) + } +} + +func isNodeExpired(nodeInfo *nodeInfo, timeout int) bool { + diff := time.Now().Unix() - nodeInfo.LastMulticast + return diff > int64(timeout) +} + +func parseAnnouncePacket(size int, addr *net.UDPAddr, packet []byte) (*nodeInfo, error) { + if size <= minPackageSize { + return nil, fmt.Errorf("Invalid packet size") + } + + if strings.Compare(string(packet[0:len(header)]), header) != 0 { + return nil, fmt.Errorf("Invalid packet header") + } + + if string(packet[len(header):len(header)+1]) != nodeAnnounceCommand[0:] { + return nil, fmt.Errorf("Command different than nodeAnnounceCommand") + } + + payload := string(packet[len(header)+1:]) + payload = strings.Trim(payload, "\x00") + + nodeInfo := &nodeInfo{} + + err := json.Unmarshal([]byte(payload), nodeInfo) + nodeInfo.Addr = addr.IP.String() + nodeInfo.MistyID = fmt.Sprintf("%s", nodeInfo.MistyID) + if err != nil { + return nil, err + } + + return nodeInfo, nil +} + +func listenForNodes(nodeList *list.List) { + address, err := net.ResolveUDPAddr("udp", multicastAddress) + if err != nil { + return + } + + conn, err := net.ListenMulticastUDP("udp", nil, address) + if err != nil { + return + } + + conn.SetReadBuffer(multicastBufferSize) + + for { + packet := make([]byte, multicastBufferSize) + size, udpAddr, err := conn.ReadFromUDP(packet) + if err != nil { + log.Fatal(err) + } + + nodeInfo, err := parseAnnouncePacket(size, udpAddr, packet) + if err != nil { + log.Fatal(err) + } + + go announcedNodeHandler(nodeInfo, nodeList) + } +} + +func (a *announcer) Start(nodeList *list.List) { + nodeInfo := &nodeInfo{ + MistyID: a.conf.MistyID, + Addr: "", + IndexPagePort: a.conf.IndexPagePort, + LastMulticast: 0, + } + + go announceNode(nodeInfo) + go listenForNodes(nodeList) +} + +func startAnnouncer(conf *config, nodeList *list.List) { + announcer := &announcer{conf: conf} + announcer.Start(nodeList) +} + +func serve(conf *config) { + nodeList := list.New() + + go startAnnouncer(conf, nodeList) + go fileServe(conf) + go dashboardServe(conf, nodeList) + + fmt.Printf("getting misty @ http://localhost:%v :: %s\n", conf.HomePagePort, conf.SharePath) + + select {} // hack to hang here forever until ctrl-c +} + +var topHTML = ` + + + + +` + +var bottomHTML = ` +
+Back +Home + +` + +func fileServe(conf *config) { + fs := http.FileServer(http.Dir(conf.SharePath)) + + var handler http.HandlerFunc + handler = func(w http.ResponseWriter, r *http.Request) { + r.Header.Del("If-Modified-Since") // squash cache + + fpath := filepath.Join(conf.SharePath, r.RequestURI) + mtype, _ := mimetype.DetectFile(fpath) + renderHTML := mtype.String() == "application/octet-stream" + + if renderHTML { + w.Write([]byte(topHTML)) + } + + fs.ServeHTTP(w, r) + + if renderHTML { + w.Write([]byte(bottomHTML)) + } + } + + address := fmt.Sprintf("0.0.0.0:%v", conf.IndexPagePort) + http.ListenAndServe(address, handler) +} + +func nodesHandler(nodeList *list.List) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + nodes := make([]*nodeInfo, 0) + for el := nodeList.Front(); el != nil; el = el.Next() { + tmp := el.Value.(*nodeInfo) + nodes = append(nodes, tmp) + } + + data, err := json.Marshal(nodes) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.Header().Add("Content-Type", "application/json") + w.Write(data) + } +} + +func dashboardServe(conf *config, nodeList *list.List) { + mux := http.NewServeMux() + mux.Handle("/", http.FileServer(assets.AssetFS())) + mux.HandleFunc("/api/nodes", nodesHandler(nodeList)) + + address := fmt.Sprintf("%s:%v", "0.0.0.0", conf.HomePagePort) + err := http.ListenAndServe(address, mux) + if err != nil { + log.Fatal(err) + } +} diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..1173fab --- /dev/null +++ b/public/index.html @@ -0,0 +1,75 @@ + + + + + + + Mist Connections + + + + + + +
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + diff --git a/public/style.css b/public/style.css new file mode 100644 index 0000000..429ca51 --- /dev/null +++ b/public/style.css @@ -0,0 +1,187 @@ +html, body { + margin: 0; + padding: 0; +} +body { + background: #000; + background: rgba(200, 200, 200, 200); +/* background: rgba(0, 0, 0, 1); */ + overflow-x: hidden; +} +div#app { + z-index: 100; + position: fixed; +} + +/* ---------- Fog ---------- */ + +.fog-background { +/* z-index: -1000; + position: sticky;*/ +} + +.fogwrapper { + height: 100%; + position: absolute; + top: 0; + width: 100%; + -webkit-filter: blur(1px) grayscale(0.2) saturate(1.2) sepia(0.2); + filter: blur(1px) grayscale(0.2) saturate(1.2) sepia(0.2); +} +#foglayer_01, #foglayer_02, #foglayer_03 { + height: 100%; + position: absolute; + width: 200%; +} +#foglayer_01 .image01, #foglayer_01 .image02, +#foglayer_02 .image01, #foglayer_02 .image02, +#foglayer_03 .image01, #foglayer_03 .image02 { + float: left; + height: 100%; + width: 50%; + color: #0f0d2d8a; +} +#foglayer_01 { + -webkit-animation: foglayer_01_opacity 10s linear infinite, foglayer_moveme 15s linear infinite; + -moz-animation: foglayer_01_opacity 10s linear infinite, foglayer_moveme 15s linear infinite; + animation: foglayer_01_opacity 10s linear infinite, foglayer_moveme 15s linear infinite; +} +#foglayer_02, #foglayer_03 { + -webkit-animation: foglayer_02_opacity 21s linear infinite, foglayer_moveme 13s linear infinite; + -moz-animation: foglayer_02_opacity 21s linear infinite, foglayer_moveme 13s linear infinite; + animation: foglayer_02_opacity 21s linear infinite, foglayer_moveme 13s linear infinite; +} + +/* ---------- Moving Fog ---------- */ +/* + 'size: cover' || 'size: 100%'; results remain the same + 'attachment: scroll' can be added or removed; results remain the same + 'attachment: fixed' causing unexpected results in Chrome + 'repeat-x' || 'no-repeat'; results remain the same +*/ +#foglayer_01 .image01, #foglayer_01 .image02 { + background: url("https://raw.githubusercontent.com/danielstuart14/CSS_FOG_ANIMATION/master/fog1.png") center center/cover no-repeat transparent; +} +#foglayer_02 .image01, #foglayer_02 .image02, +#foglayer_03 .image01, #foglayer_03 .image02{ + background: url("https://raw.githubusercontent.com/danielstuart14/CSS_FOG_ANIMATION/master/fog2.png") center center/cover no-repeat transparent; +} + +/* ---------- Keyframe Layer 1 ---------- */ +@-webkit-keyframes foglayer_01_opacity { + 0% { opacity: .1; } + 22% { opacity: .5; } + 40% { opacity: .28; } + 58% { opacity: .4; } + 80% { opacity: .16; } + 100% { opacity: .1; } +} +@-moz-keyframes foglayer_01_opacity { + 0% { opacity: .1; } + 22% { opacity: .5; } + 40% { opacity: .28; } + 58% { opacity: .4; } + 80% { opacity: .16; } + 100% { opacity: .1; } +} +@-o-keyframes foglayer_01_opacity { + 0% { opacity: .1; } + 22% { opacity: .5; } + 40% { opacity: .28; } + 58% { opacity: .4; } + 80% { opacity: .16; } + 100% { opacity: .1; } +} +@keyframes foglayer_01_opacity { + 0% { opacity: .1; } + 22% { opacity: .5; } + 40% { opacity: .28; } + 58% { opacity: .4; } + 80% { opacity: .16; } + 100% { opacity: .1; } +} +/* ---------- Keyframe Layer 2 ---------- */ +@-webkit-keyframes foglayer_02_opacity { + 0% { opacity: .5; } + 25% { opacity: .2; } + 50% { opacity: .1; } + 80% { opacity: .3; } + 100% { opacity: .5; } +} +@-moz-keyframes foglayer_02_opacity { + 0% { opacity: .5; } + 25% { opacity: .2; } + 50% { opacity: .1; } + 80% { opacity: .3; } + 100% { opacity: .5; } +} +@-o-keyframes foglayer_02_opacity { + 0% { opacity: .5; } + 25% { opacity: .2; } + 50% { opacity: .1; } + 80% { opacity: .3; } + 100% { opacity: .5; } +} +@keyframes foglayer_02_opacity { + 0% { opacity: .5; } + 25% { opacity: .2; } + 50% { opacity: .1; } + 80% { opacity: .3; } + 100% { opacity: .5; } +} +/* ---------- Keyframe Layer 3 ---------- */ +@-webkit-keyframes foglayer_03_opacity { + 0% { opacity: .8 } + 27% { opacity: .2; } + 52% { opacity: .6; } + 68% { opacity: .3; } + 100% { opacity: .8; } +} +@-moz-keyframes foglayer_03_opacity { + 0% { opacity: .8 } + 27% { opacity: .2; } + 52% { opacity: .6; } + 68% { opacity: .3; } + 100% { opacity: .8; } +} +@-o-keyframes foglayer_03_opacity { + 0% { opacity: .8 } + 27% { opacity: .2; } + 52% { opacity: .6; } + 68% { opacity: .3; } + 100% { opacity: .8; } +} +@keyframes foglayer_03_opacity { + 0% { opacity: .8; } + 27% { opacity: .2; } + 52% { opacity: .6; } + 68% { opacity: .3; } + 100% { opacity: .8; } +} +/* ---------- Keyframe moveMe ---------- */ +@-webkit-keyframes foglayer_moveme { + 0% { left: 0; } + 100% { left: -100%; } +} +@-moz-keyframes foglayer_moveme { + 0% { left: 0; } + 100% { left: -100%; } +} +@-o-keyframes foglayer_moveme { + 0% { left: 0; } + 100% { left: -100%; } +} +@keyframes foglayer_moveme { + 0% { left: 0; } + 100% { left: -100%; } +} + +@media only screen + and (min-width: 280px) + and (max-width: 767px) { + #foglayer_01 .image01, #foglayer_01 .image02, + #foglayer_02 .image01, #foglayer_02 .image02, + #foglayer_03 .image01, #foglayer_03 .image02 { + width: 100%; + } + } \ No newline at end of file