This commit is contained in:
decentral1se 2022-05-27 00:54:11 +02:00
commit fb462f8233
Signed by: decentral1se
GPG Key ID: 03789458B3D0C410
12 changed files with 905 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
mist-connections

34
.goreleaser.yaml Normal file
View File

@ -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

40
README.md Normal file
View File

@ -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)

1
assets/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
assets_gen.go

14
assets/assets.go Normal file
View File

@ -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",
}
}

165
assets/static/style.css Normal file
View File

@ -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%;
}
}

10
go.mod Normal file
View File

@ -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

11
go.sum Normal file
View File

@ -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=

24
makefile Normal file
View File

@ -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

343
mist-connections.go Normal file
View File

@ -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 = `
<html>
<head>
<style>
a { color: magenta; }
</style>
</head>
<body>`
var bottomHTML = `
<hr></hr>
<a href="../">Back</a>
<a href="http://localhost:5555">Home</a>
</body>
</html>`
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)
}
}

75
public/index.html Normal file
View File

@ -0,0 +1,75 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<title>Mist Connections</title>
<script src="https://unpkg.com/vue@3"></script>
<script src="https://unpkg.com/axios@0.27.2"></script>
<link rel="stylesheet" href="style.css">
<style>
ul li {
list-style: none;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 3.0s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
transform: translateX(30px);
}
</style>
</head>
<body>
<div id="app">
<!-- <img class="fogwrapper" src="background.jpg"> -->
<ul>
<transition-group name="fade" tag="li">
<li v-for="node in nodes" v-if="nodes.length">
<a :href="'http://' + node.addr + ':1312'">{{ node.mistyID }}</a>
</li>
</transition-group>
</ul>
</div>
<div class="fog-background">
<div id="foglayer_01" class="fog">
<div class="image01"></div>
<div class="image02"></div>
</div>
<div id="foglayer_02" class="fog">
<div class="image01"></div>
<div class="image02"></div>
</div>
<div id="foglayer_03" class="fog">
<div class="image01"></div>
<div class="image02"></div>
</div>
</div>
</body>
<script>
var app = Vue.createApp({
data() {
return {
nodes: []
}
},
methods: {
pollNodes() {
axios
.get('/api/nodes')
.then(response => (this.nodes = response.data))
}
},
mounted() {
this.pollNodes()
setInterval(() => { this.pollNodes(); }, 1000);
},
})
app.mount("#app")
</script>
</html>

187
public/style.css Normal file
View File

@ -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%;
}
}