Full Code of WoodNeck/css-camera for AI

master 94c82efbd7b4 cached
28 files
73.1 KB
22.1k tokens
50 symbols
1 requests
Download .txt
Repository: WoodNeck/css-camera
Branch: master
Commit: 94c82efbd7b4
Files: 28
Total size: 73.1 KB

Directory structure:
gitextract_yn11t5kk/

├── .editorconfig
├── .gitignore
├── LICENSE
├── README.md
├── demo/
│   ├── css/
│   │   ├── fpv.css
│   │   ├── index.css
│   │   └── ortho.css
│   ├── fpv.html
│   ├── index.html
│   ├── js/
│   │   ├── fpv.js
│   │   ├── index.js
│   │   └── ortho.js
│   └── ortho.html
├── jsdoc.json
├── package.json
├── rollup.config.js
├── src/
│   ├── CSSCamera.ts
│   ├── constants/
│   │   ├── default.ts
│   │   └── error.ts
│   ├── index.ts
│   ├── index.umd.ts
│   ├── types.ts
│   └── utils/
│       ├── helper.ts
│       └── math.ts
├── test/
│   └── manual/
│       ├── css/
│       │   └── common.css
│       └── test.html
├── tsconfig.json
└── tslint.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
root = true

[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 2


================================================
FILE: .gitignore
================================================

# Created by https://www.gitignore.io/api/node,visualstudiocode
# Edit at https://www.gitignore.io/?templates=node,visualstudiocode

### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# TypeScript v1 declaration files
typings/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env
.env.test

# parcel-bundler cache (https://parceljs.org/)
.cache

# next.js build output
.next

# nuxt.js build output
.nuxt

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

### VisualStudioCode Patch ###
# Ignore all local history of files
.history

# End of https://www.gitignore.io/api/node,visualstudiocode

## Custom ###
.DS_Store
lib/
docs/
demo/release
demo/_data/version.yml



================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2019 WoodNeck

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# 📷 CSS-CAMERA

<!-- BADGES -->
![npm](https://img.shields.io/npm/v/css-camera?style=for-the-badge)
![GitHub](https://img.shields.io/github/license/woodneck/css-camera?style=for-the-badge)

<p>
  <h4><a href="https://woodneck.github.io/css-camera">🎥 Demo</a> · <a href="https://woodneck.github.io/css-camera/release/latest/docs/index.html">📄 Document</a></h2>
</p>

<b>Add depth to your web page with CSS3 3D transform.</b>

> This project is mostly inspired by [Keith Clark's work](https://keithclark.co.uk/labs/css-fps/).

## ✨ Features
- Movable, and Rotatable camera for your scene.
- Can move to in front of any element in your scene, whether it has been rotated or translated.

## ⚙️ Installation
```sh
npm i css-camera
# or
yarn add css-camera
```

## 🏃 Quick Start
```js
// Prerequisite:
// Create your scene as you like
const card = document.querySelector("#card");
const cardButton = document.querySelector("#card-button");

// First, make camera
const camera = new CSSCamera("#space");

// Call its method, then update it!
cardButton.onclick = () => {
  camera.focus(card);
  camera.update(2000);
}
```
Check more methods on the <a href="https://woodneck.github.io/css-camera/release/latest/docs/index.html">📄API Documentation</a> page</h2>

## 📜 License
[MIT](https://github.com/WoodNeck/css-camera/blob/master/LICENSE)


================================================
FILE: demo/css/fpv.css
================================================
* {
  box-sizing: border-box;
  transform-style: preserve-3d;
  font-size: 0;
}
html, body {
  position: relative;
  width: 100vw;
  height: 100vh;
  margin: 0;
  padding: 0;
  overflow: hidden;
  display: flex;
  justify-content: center;
  align-items: center;
}
#space {
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
}
.zone {
  width: 100%;
  height: 100%;
  display: flex;
  position: absolute;
  justify-content: center;
  align-items: center;
}
.zone0 {
  left: 0;
}
.zone1 {
  left: 700px;
}
.zone2 {
  left: 1400px;
}
.long-hallway {
  width: 200px;
  height: 200px;
}
.is-left {
  left: 0%;
  transform-origin: left;
  transform: rotateY(90deg);
}
.is-right {
  right: 0%;
  transform-origin: right;
  transform: rotateY(-90deg);
}
.wall {
  height: 200px;
  position: absolute;
  border: 10px solid #333;
  background-color: white;
}
.is-1200 {
  width: 1200px;
}
.is-1000 {
  width: 1000px;
}
.is-800 {
  width: 800px;
}
.is-600 {
  width: 600px;
}
.is-400 {
  width: 400px;
}
.is-200 {
  width: 200px;
}
.arrow {
  transform: rotateX(90deg);
  width: 200px;
  font-size: 300px;
  text-align: center;
  position: absolute;
  bottom: 0;
  transform-origin: bottom;
}
#inst {
  position: fixed;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 72px;
}


================================================
FILE: demo/css/index.css
================================================
* {
  box-sizing: border-box;
}
html, body {
  position: relative;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  overflow: hidden;
  transform-style: preserve-3d;
  display: flex;
  justify-content: center;
}
.page-header-container {
  position: absolute;
  bottom: 2%;
  display: flex;
  justify-content: center;
  flex-flow: column;
}
.page-header-wrapper {
  display: flex;
  flex-direction: row;
  justify-content: center;
  align-items: center;
}
.page-header-icon {
  margin-top: auto;
  margin-bottom: auto;
  margin-right: 0.8rem;
}
.page-title {
  color: #4a4a4a;
}
.page-header-buttons {
  display: flex;
  justify-content: center;
  align-items: center;
  margin-top: 1%;
}
.page-header-buttons .button {
  margin: 0.3rem;
}
#hero .button {
  background-color: transparent;
}
#space {
  display: flex;
  width: 100%; height: 100%;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  margin: 0; padding: 0; border: 0;
  transform-style: preserve-3d;
  position: relative;
}
#card {
  transform-style: preserve-3d;
  transform: rotateX(-30deg) rotateY(45deg) translate3d(-180%, 80%, -200px);
}
.card-youtube {
  width: 100%; height: 100%;
  position: absolute;
  top: 0; bottom: 0; left: 0; right: 0;
}
.card-side {
  width: 10%; height: 100%;
  position: absolute; top: 0;
  background: #222;
  transform-style: preserve-3d;
  transform-origin: 0% 50%;
  transform: rotateY(90deg);
}
#code {
  max-width: 100%;
  transform-style: preserve-3d;
  transform: rotateZ(30deg) rotateY(-45deg) rotateX(70deg) translate3d(100%, -40%, 0px);
}
#code .title {
  text-align: center;
  font-family: 'Nanum Pen Script', cursive;
}
.camera-code {
  padding: 1rem;
}
#hero {
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  transform-style: preserve-3d;
  transform: translateZ(2400px);
  background-color: transparent;
}
#more {
  width: 100%; min-height: 100%;
  transform-style: preserve-3d;
  transform: rotateX(90deg) translate3d(0, 0, -1600px);
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}
#more h1 {
  margin-top: 1.5rem;
  position: absolute;
  top: 0%;
}
#more .arrow {
  position: absolute;
  z-index: 1;
}
#more .arrow.left {
  left: 1.5rem;
}
#more .arrow.right {
  right: 1.5rem;
}
#demo-flicking {
  width: 100%; height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}
.eg-flick-camera {
  display: flex;
  justify-content: center;
  align-items: center;
}
.demo-entry {
  width: 70%;
  position: absolute;
  z-index: 0;
  display: flex;
  justify-content: center;
  box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.5);
}
.demo-wrapper {
  width: 100%;
}
.demo-wrapper .caption-wrapper {
  width: 100%; height: 100%;
  display: flex;
  position: absolute;
  top: 0%;
  left: 0%;
  justify-content: center;
  align-items: center;
}
.demo-wrapper .demo-caption {
  font-size: 48px;
  color: gold;
  background-color: rgba(0, 0, 0, 0.5);
  font-weight: bold;
  padding: 30px;
  border-radius: 10px;
}
.demo-wrapper .demo-caption:hover {
  color: hotpink;
}


================================================
FILE: demo/css/ortho.css
================================================
* {
  box-sizing: border-box;
}
html, body {
  position: relative;
  width: 100vw;
  height: 100vh;
  margin: 0;
  padding: 0;
  overflow: hidden;
  transform-style: preserve-3d;
  display: flex;
  justify-content: center;
  align-items: center;
}
#info {
  position: fixed;
  top: 12px;
  color: gold;
  font-size: 24px;
}
#info2 {
  position: fixed;
  bottom: 12px;
  font-size: 24px;
}
#info2 a {
  color: hotpink;
  text-decoration: none;
}
#space {
  transform-style: preserve-3d;
  width: 640px;
  height: 100%;
  font-size: 0;
}
.tile {
  display: inline-block;
  background-size: cover;
  transform-style: preserve-3d;
  image-rendering: -moz-crisp-edges;         /* Firefox */
  image-rendering:   -o-crisp-edges;         /* Opera */
  image-rendering: -webkit-optimize-contrast;/* Webkit (non-standard naming) */
  image-rendering: pixelated;
  -ms-interpolation-mode: nearest-neighbor;  /* IE (non-standard property) */
  margin: 0; border: 0; padding: 0;
  box-sizing: border-box;
  width: 64px; height: 64px;
}
.tile::before {
  content: '';
  background-size: cover;
  transform-style: preserve-3d;
  position: absolute;
  width: 64px; height: 32px;
  transform: translateZ(-32px) scaleZ(-1) rotateZ(90deg) rotateX(-90deg);
  transform-origin: 0% 0%;
}
.tile::after {
  content: '';
  background-size: cover;
  transform-style: preserve-3d;
  position: absolute;
  width: 64px; height: 32px;
  bottom: 0;
  transform: rotateX(90deg);
  transform-origin: bottom;
}

.height-1 {
  transform: translateZ(32px);
}
.height-2 {
  transform: translateZ(64px);
}
.height-3 {
  transform: translateZ(96px);
}
.height-4 {
  transform: translateZ(128px);
}
.height-5 {
  transform: translateZ(160px);
}

.grass {
  background-color: #8AD44E;
}
.grass::before {
  background-color: #5FB535;
}
.grass::after {
  background-color: #3D9756;
}

.grass.patch {
  background-image: url(../asset/patch.png);
}

.grass.grassa {
  background-image: url(../asset/grassa.png);
}
.grass.grassb {
  background-image: url(../asset/grassb.png);
}
.grass.grassc {
  background-image: url(../asset/grassc.png);
}
.grass.grassd {
  background-image: url(../asset/grassd.png);
}
.grass.grasse {
  background-image: url(../asset/grasse.png);
}
.grass.grassf {
  background-image: url(../asset/grassf.png);
}
.grass.grassg {
  background-image: url(../asset/grassg.png);
}
.grass.grassh {
  background-image: url(../asset/grassh.png);
}
.grass.grassi {
  background-image: url(../asset/grassi.png);
}

.grass.river-top {
  background-image: url(../asset/rivertop.gif);
}
.grass.river-top-left {
  background-image: url(../asset/rivertopleft.gif);
}
.grass.river-top-right {
  background-image: url(../asset/rivertopleft.gif);
  transform: scaleX(-1);
}
.grass.river-bottom {
  background-image: url(../asset/riverbottom.gif);
}
.grass.river-bottom-left {
  background-image: url(../asset/riverbottomleft.gif);
}
.grass.river-bottom-right {
  background-image: url(../asset/riverbottomleft.gif);
  transform: scaleX(-1);
}

.grass.road-top {
  background-image: url(../asset/roadtop.png);
}
.grass.road-vertical {
  background-image: url(../asset/roadmiddle.png);
}
.grass.road-bottom {
  background-image: url(../asset/roadbottom.png);
}
.grass.road-left {
  background-image: url(../asset/roadtop.png);
  transform: rotateZ(-90deg);
}
.grass.road-horizontal {
  background-image: url(../asset/roadmiddle.png);
  transform: rotateZ(-90deg);
}
.grass.road-right {
  background-image: url(../asset/roadbottom.png);
  transform: rotateZ(-90deg);
}

.grass div {
  position: absolute;
  transform-origin: bottom;
  background-size: cover;
}

.barrel {
  width: 64px; height: 64px;
  transform: translateZ(-16px) rotateX(-90deg);
  background-image: url(../asset/barrel.png);
  left: -25%; bottom: 50%;
}
.rock {
  width: 64px; height: 64px;
  transform: translateZ(-16px) rotateX(-90deg);
  background-image: url(../asset/rock.png);
  left: 0px; bottom: 50%;
}
.sign {
  width: 64px; height: 64px;
  transform: translateZ(-20px) rotateX(-90deg);
  background-image: url(../asset/sign.png);
  left: 0px; bottom: 50%;
}
.bush {
  width: 64px; height: 64px;
  transform: translateZ(-12px) rotateX(-90deg);
  background-image: url(../asset/bush.gif);
  left: 0px; bottom: 50%;
}
.fence {
  width: 64px; height: 64px;
  transform: translateZ(-18px) rotateX(-90deg);
  background-image: url(../asset/fence.png);
  left: 0px; bottom: 50%;
}
.tree {
  width: 192px; height: 192px;
  transform: rotateX(-90deg);
  background-image: url(../asset/tree.gif);
  left: -64px; bottom: 0px;
}
.flower {
  width: 64px; height: 64px;
  transform: translateZ(-18px) rotateX(-90deg);
  background-image: url(../asset/flower.gif);
  left: 0px; bottom: 50%;
}
.chopped {
  width: 64px; height: 64px;
  transform: translateZ(-2px) rotateX(-90deg);
  background-image: url(../asset/chopped.gif);
  left: 0px; bottom: 50%;
}


================================================
FILE: demo/fpv.html
================================================
<!DOCTYPE html>
<html>
  <head>
    <title>css-camera</title>
    <meta charsset="utf-8" />
    <meta name="viewport" content="width=device-width, user-scalable=no">
    <meta property="og:title" content="css-camera" />
    <meta property="og:type" content="website" />
    <meta property="og:url" content="https://woodneck.github.io/css-camera/" />
    <link type="text/css" rel="stylesheet" href="css/fpv.css">
    <script src="./release/latest/lib/css-camera.pkgd.js"></script>
  </head>
  <body>
    <div id="space">
      <div class="zone zone0">
        <div class="long-hallway">
          <div class="arrow">↑</div>
          <div class="wall is-200"></div>
          <div class="wall is-left is-1200" style="background: linear-gradient( to right, white 800px, cyan )"></div>
          <div class="wall is-right is-1000" style="background: linear-gradient( to left, white 800px, magenta )"></div>
          <div class="wall is-600" style="left: 0; transform: translateZ(-1200px); background: linear-gradient( to right, cyan, white, yellow );"></div>
          <div class="wall is-200" style="left: 200px; transform: translateZ(-1000px); background: magenta;"></div>
          <div class="wall is-right is-600" style="transform: translateZ(-600px) rotateY(-90deg) translateZ(-400px); background: linear-gradient( to left, magenta, white, yellow )"></div>
          <div class="wall is-right is-200" style="transform: translateZ(-800px) rotateY(-90deg) translateZ(-200px); background: linear-gradient( to right, magenta, white, cyan )"></div>
          <div class="wall" style="width: 398px; border-left: none; transform: translateX(202px) translateZ(-600px); background: linear-gradient( to left, magenta, white 180px)"></div>
        </div>
      </div>
      <div class="zone zone1">
        <div style="width: 200px; height: 200px; transform: translateZ(-600px);">
          <div class="wall is-600" style="left: 0; background: linear-gradient( to right, white 400px, magenta );"></div>
          <div class="wall is-left is-600" style="background: linear-gradient( to right, white, white, cyan );"></div>
          <div class="wall is-600" style="transform: translateZ(-600px); background: linear-gradient( to left, yellow, white, cyan );"></div>
          <div class="wall is-right is-600" style="transform: rotateY(-90deg) translateZ(-400px); background: linear-gradient( to right, yellow, white, magenta );"></div>
          <div class="wall is-200" style="transform: translateX(200px) translateZ(-400px); background: magenta;"></div>
          <div class="wall is-200" style="transform: translateX(200px) translateZ(-200px); background: cyan;"></div>
          <div class="wall is-left is-200" style="transform: translateZ(-200px) rotateY(90deg) translateZ(200px); background: linear-gradient( to left, magenta, white, cyan);"></div>
          <div class="wall is-right is-200" style="transform: translateZ(-200px) rotateY(-90deg) translateZ(-200px); background: linear-gradient( to right, magenta, white, cyan);"></div>
        </div>
      </div>
      <div class="zone zone2">
        <div style="width: 200px; height: 200px; transform: translateZ(-600px);">
          <div class="wall is-600" style="left: 0; background: linear-gradient( to right, white 400px, magenta );"></div>
          <div class="wall is-left is-1200" style="background: linear-gradient( to right, white, white, cyan 600px)"></div>
          <div class="wall" style="width: 398px; border-left: none; transform: translateX(202px) translateZ(-600px); background: linear-gradient( to left, yellow, white, cyan );"></div>
          <div class="wall is-right is-600" style="transform: rotateY(-90deg) translateZ(-400px); background: linear-gradient( to right, yellow, white, magenta );"></div>
          <div class="wall is-200" style="transform: translateZ(-1200px); background: url(./asset/winner.png); background-position: center; background-repeat: no-repeat; background-size: cover; padding: 10%; background-origin: content-box;"></div>
          <div class="wall is-200" style="transform: translateX(200px) translateZ(-200px); background: cyan;"></div>
          <div class="wall is-left is-1000" style="backface-visibility: hidden; transform: translateZ(-1200px) translateX(200px) rotateY(-90deg); background: cyan;"></div>
          <div class="wall is-right is-200" style="transform: translateZ(-200px) rotateY(-90deg) translateZ(-200px); background: linear-gradient( to right, magenta, white, cyan);"></div>
        </div>
      </div>
    </div>
    <div id="inst">Click the screen to control</div>
    <script src="js/fpv.js"></script>
  </body>
</html>


================================================
FILE: demo/index.html
================================================
<!DOCTYPE html>
<html>
  <head>
    <title>css-camera</title>
    <meta charsset="utf-8" />
    <meta name="viewport" content="width=device-width, user-scalable=no">
    <meta property="og:title" content="css-camera" />
    <meta property="og:type" content="website" />
    <meta property="og:url" content="https://woodneck.github.io/css-camera/" />
    <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
    <link href="https://fonts.googleapis.com/css?family=Nanum+Pen+Script&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.css" integrity="sha256-ujE/ZUB6CMZmyJSgQjXGCF4sRRneOimQplBVLu8OU5w=" crossorigin="anonymous" />
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.15.9/build/styles/an-old-hope.min.css">
    <link type="text/css" rel="stylesheet" href="css/index.css">
    <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.15.9/build/highlight.min.js"></script>
    <script src="https://naver.github.io/egjs-flicking/release/latest/dist/flicking.pkgd.min.js"></script>
    <script src="./release/latest/lib/css-camera.pkgd.js"></script>
    <script>hljs.initHighlightingOnLoad();</script>
  </head>
  <body>
    <div id="space">
      <section id="hero" class="hero is-white is-fullheight">
        <div class="hero-body">
          <div class="container has-text-centered">
            <h1 class="title"><i class="fas fa-camera-retro"></i> CSS-CAMERA</h1>
            <h2 class="subtitle">Add depth to your web page with CSS3 3D transform.</h2>
            <a class="button is-white" href="./release/latest/docs/index.html">
              <span class="icon"><i class="fas fa-book"></i></span>
              <span>Documents</span>
            </a>
            <a class="button is-white" href="https://github.com/WoodNeck/css-camera">
              <span class="icon"><i class="fab fa-github"></i></span>
              <span>Github</span>
            </a>
          </div>
        </div>
        <div class="container is-fluid">
          <div class="container">
            <h1 class="title"><i class="fas fa-cog"></i> Install</h1>
            <div class="notification">$ npm i css-camera</div>
          </div>
        </div>
      </section>
      <div id="card" class="container">
        <div class="card-wrapper">
          <div class="card">
            <div class="card-image">
              <figure class="image is-4by3">
                <iframe class="card-youtube" src="https://www.youtube.com/embed/AckybSTFSXo" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
              </figure>
            </div>
            <div class="card-content">
              <div class="media">
                <div class="media-left">
                  <figure class="image is-48x48">
                    <img src="https://avatars0.githubusercontent.com/u/26213435?s=460&v=4" alt="Placeholder image">
                  </figure>
                </div>
                <div class="media-content">
                  <p class="title is-4">WoodNeck</p>
                  <p class="subtitle is-6">@woodneck</p>
                </div>
              </div>

              <div class="content">
                You can put any HTML element you like, then just transform it.
                <a href="https://github.com/WoodNeck/css-camera">@css-camera</a> will do the rest for you.
              </div>
            </div>
          </div>
        </div>
      </div>
      <div id="code" class="container">
        <h1 class="title">It's super simple!</h1>
        <pre><code class="camera-code js">// Prerequisite:
// Create your scene as you like
const card = document.querySelector("#card");
const cardButton = document.querySelector("#card-button");

// First, make camera
const camera = new CSSCamera("#space");

// Call its method, then update it!
cardButton.onclick = () => {
  // Move camera in front of "card"
  camera.focus(card);
  camera.update(2000);
}</code></pre>
        <h1 class="title is-size-5-mobile">See the <a href="https://github.com/WoodNeck/css-camera/blob/master/demo/js/index.js">source</a> of this page.</h1>
      </div>

      <div id="more" class="container">
        <h1 class="title has-text-centered">More demos?</h1>
        <div id="demo-flicking" class="container">
          <div class="demo-entry">
            <div class="demo-wrapper">
              <figure class="image is-4by3">
                <img class="demo-thumb" src="./asset/ortho-thumb.png" />
              </figure>
              <div class="caption-wrapper">
                <a class="demo-caption is-size-5-mobile" href="./ortho.html">
                  <span>Orthographic projection</span>
                </a>
              </div>
            </div>
          </div>
          <div class="demo-entry" style="left: calc(85% + 50px)">
            <div class="demo-wrapper">
              <figure class="image is-4by3">
                <img class="demo-thumb" src="./asset/fpv-thumb.png" />
              </figure>
              <div class="caption-wrapper">
                <a class="demo-caption is-size-5-mobile" href="./fpv.html">
                  <span>First person view demo</span>
                </a>
              </div>
            </div>
          </div>
        </div>
        <div class="arrow left"><i class="fas fa-arrow-left"></i></div>
        <div class="arrow right"><i class="fas fa-arrow-right"></i></div>
      </div>
    </div>
    <header class="page-header-container">
      <div class="page-header-buttons">
        <span>🎥</span>
        <a id="hero-button" class="button is-link">Main</a>
        <a id="card-button" class="button is-link is-outlined">Card</a>
        <a id="code-button" class="button is-link is-outlined">Code</a>
        <a id="more-button" class="button is-link is-outlined">More</a>
      </div>
    </header>
    <script src="js/index.js"></script>
  </body>
</html>


================================================
FILE: demo/js/fpv.js
================================================
const windowHeight = window.innerHeight;
const camera = new CSSCamera("#space", {
  position: [0, 0, -10],
  perspective: windowHeight / 2,
});

let zone = 0;
let rotate = 0;
const inst = document.querySelector("#inst");

document.documentElement.onclick = function() {
  document.documentElement.requestPointerLock();
}
document.addEventListener('pointerlockchange', onLockChange, false);
document.addEventListener('mozpointerlockchange', onLockChange, false);

function onLockChange() {
  if (document.pointerLockElement === document.documentElement ||
      document.mozPointerLockElement === document.documentElement) {
    inst.style.display = "none";
    document.addEventListener("mousemove", updateMouse, false);
  } else {
    inst.style.display = "flex";
    document.removeEventListener("mousemove", updateMouse, false);
  }
}

let up = false;
let right = false;
let down = false;
let left = false;

document.addEventListener('keydown', press);
function press(e){
  if (e.keyCode === 38 /* up */ || e.keyCode === 87 /* w */){
    up = true;
  }
  if (e.keyCode === 39 /* right */ || e.keyCode === 68 /* d */){
    right = true;
  }
  if (e.keyCode === 40 /* down */ || e.keyCode === 83 /* s */){
    down = true;
  }
  if (e.keyCode === 37 /* left */ || e.keyCode === 65 /* a */){
    left = true;
  }
}

document.addEventListener('keyup', release);
function release(e){
  if (e.keyCode === 38 /* up */ || e.keyCode === 87 /* w */){
    up = false
  }
  if (e.keyCode === 39 /* right */ || e.keyCode === 68 /* d */){
    right = false
  }
  if (e.keyCode === 40 /* down */ || e.keyCode === 83 /* s */){
    down = false
  }
  if (e.keyCode === 37 /* left */ || e.keyCode === 65 /* a */){
    left = false
  }
}

const prevMouseLocation = {
  x: NaN, y: NaN,
}

const clamp = function(val, min, max) {
  return Math.min(Math.max(val, min), max);
}
const degToRad = function(deg) {
  return Math.PI * deg / 180;
}

const clampPosition0 = function(prev, position) {
  // Long Corridor
  if (position[0] <= 90 || (prev[0] <= 90 && prev[2] > -1010)) {
    position[0] = clamp(position[0], -90, 90);
    position[2] = clamp(position[2], -1190, -10);
  }
  // Top
  else if (prev[2] <= -1010) {
    position[0] = clamp(position[0], -90, 490);
    if (prev[0] > 90 && prev[0] < 310) {
      position[2] = clamp(position[2], -1190, -1010);
    } else {
      position[2] = clamp(position[2], -1190, -10);
    }
  }
  // Right
  else if (position[0] >= 310 || (prev[0] >= 310 && prev[2] > -1010)) {
    position[0] = clamp(position[0], 310, 490);
    position[2] = clamp(position[2], -1190, -610);
  }

  return position;
}

const clampPosition1 = function(prev, position) {
  position[0] = clamp(position[0], 610, 1190);
  position[2] = clamp(position[2], -1190, -610);

  if (prev[0] <= 790 && (prev[2] <= -790 && prev[2] > -1010)) {
    position[0] = clamp(position[0], 610, 790);
  }
  else if (prev[2] <= -1010) {
    if (prev[0] > 790 && prev[0] < 1010) {
      position[2] = clamp(position[2], -1190, -1010);
    } else {
      position[2] = clamp(position[2], -1190, -610);
    }
  }
  else if (prev[0] >= 1010  && (prev[2] <= -790 && prev[2] > -1010)) {
    position[0] = clamp(position[0], 1010, 1190);
  }
  else {
    if (prev[0] > 790 && prev[0] < 1010) {
      position[2] = clamp(position[2], -790, -610);
    } else {
      position[2] = clamp(position[2], -1190, -610);
    }
  }

  return position;
}

const clampPosition2 = function(prev, position) {
  position[0] = clamp(position[0], 1310, 1890);
  position[2] = clamp(position[2], -1790, -610);

  // Long Corridor
  if (position[0] <= 1490 || (prev[0] <= 1490 && prev[2] < -790)) {
    position[0] = clamp(position[0], 1310, 1490);
    position[2] = clamp(position[2], -1790, -610);
  }
  // Bottom
  else if (prev[2] >= -790) {
    if (prev[0] > 1490 && prev[0] < 1710) {
      position[2] = clamp(position[2], -790, -610);
    } else {
      position[2] = clamp(position[2], -1790, -610);
    }
  }
  // Right
  else if (position[0] >= 310 || (prev[0] >= 310 && prev[2] > -1010)) {
    position[0] = clamp(position[0], 310, 490);
    position[2] = clamp(position[2], -1190, -610);
  }

  return position;
}

const checkZone = function(prevPos) {
  const newPos = camera.position;
  // Zone 0 to 1
  if (zone === 0 && newPos[0] > 310 && prevPos[2] <= -980 && newPos[2] > -980) {
    camera.translate(700, 0, 0);
    zone = 1;
    rotate = 0;
  } else if (zone === 1 && newPos[0] > 1010) {
    if (prevPos[2] <= -980 && newPos[2] > -980) {
      rotate += 1;
    } else if (prevPos[2] > -980 && newPos[2] <= -980) {
      rotate -= 1;
    }
    if (rotate < 0) {
      camera.translate(-700, 0, 0);
      zone = 0;
    }
  } else if (zone === 1 && newPos[2] >= -790) {
    if (prevPos[0] > 980 && newPos[0] <= 980) {
      if (rotate > 5) {
        camera.translate(700, 0, 0);
        zone = 2;
      }
    }
  } else if (zone === 2 && newPos[2] >= -790) {
    if (prevPos[0] < 1680 && newPos[0] >= 1680) {
      camera.translate(-700, 0, 0);
      zone = 1;
    }
  }
}

const updateMouse = function(e) {
  const diffX = e.movementX;
  const diffY = e.movementY;

  camera.rotate(-diffY / 5, diffX / 5);
  camera.rotation = [clamp(camera.rotation[0], -85, 85), camera.rotation[1], camera.rotation[2]];
  camera.update(0);

  prevMouseLocation.x = e.screenX;
  prevMouseLocation.y = e.screenY;
}
const speed = 5;
const keyLoop = function() {
  const prevPos = camera.position.concat();
  const speedVal = speed / Math.cos(degToRad(camera.rotation[0]));

  if (up){
    camera.translateLocal(0, 0, -speedVal);
  }
  if (right){
    camera.translateLocal(speed, 0, 0);
  }
  if (down){
    camera.translateLocal(0, 0, speedVal);
  }
  if (left){
    camera.translateLocal(-speed, 0, 0);
  }
  var newPos = [camera.position[0], 0, camera.position[2]];
  camera.position = zone === 0
    ? clampPosition0(prevPos, newPos)
    : zone === 1
      ? clampPosition1(prevPos, newPos)
      : clampPosition2(prevPos, newPos);
  checkZone(prevPos);
  camera.update(0);
  requestAnimationFrame(keyLoop);
}
keyLoop();


================================================
FILE: demo/js/index.js
================================================
const windowWidth = document.body.offsetWidth;

const cardButton = document.querySelector("#card-button");
const codeButton = document.querySelector("#code-button");
const heroButton = document.querySelector("#hero-button");
const moreButton = document.querySelector("#more-button");
const buttons = [
  cardButton, codeButton, heroButton, moreButton,
];

const card = document.querySelector("#card");
const code = document.querySelector("#code");
const hero = document.querySelector("#hero");
const more = document.querySelector("#more");

const restoreButtons = () => {
  buttons.forEach(button => button.classList.add("is-outlined"));
}

const flicking = new eg.Flicking("#demo-flicking", {
  collectStatistics: false,
  adaptive: true,
  zIndex: "",
  gap: 50,
  overflow: true
});

const camera = new CSSCamera("#space", {
  perspective: windowWidth / 2,
});

camera.focus(hero);
camera.update(2000);

cardButton.onclick = () => {
  camera.focus(card);
  camera.update(2000);
  restoreButtons();
  cardButton.classList.remove("is-outlined");
}
codeButton.onclick = () => {
  camera.focus(code);
  camera.update(2000);
  restoreButtons();
  codeButton.classList.remove("is-outlined");
}
heroButton.onclick = () => {
  camera.focus(hero);
  camera.update(2000);
  restoreButtons();
  heroButton.classList.remove("is-outlined");
}
moreButton.onclick = () => {
  camera.focus(more);
  camera.update(2000);
  restoreButtons();
  moreButton.classList.remove("is-outlined");
}


================================================
FILE: demo/js/ortho.js
================================================
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
const center = document.querySelector("#center");

const camera = new CSSCamera("#space");
camera.viewportEl.style.backgroundColor = "white";
camera.focus(center);
camera.update(0).then(async () => {
  await camera.update(1000);

  camera.rotation = [55, 0, 0];

  await camera.update(1000);

  camera.rotation = [55, 0, -45];
  camera.scale = [2, 2, 2];
  camera.viewportEl.style.backgroundColor = "black";
  await camera.update(2000, {
    property: "transform, background-color",
    timingFunction: "ease-out, linear",
    delay: "0ms, 0ms"
  });

  // Controls after init
  const Axes = eg.Axes;
  const PanInput = Axes.PanInput;
  const axes = new Axes({
    x: { range: [-400, 400] },
    y: { range: [-150, 250] }
  }, { deceleration: 0.004 }, { x: 0, y: 0 });
  const panInput = new PanInput(".cc-viewport", {
    scale: [0.3, 0.3],
  });

  axes.on({
    "change": evt => {
      camera.translateLocal(-evt.delta.x, -evt.delta.y);
      camera.update(0);
    }
  });

  axes.connect(["x", "y"], panInput);
});


================================================
FILE: demo/ortho.html
================================================
<!DOCTYPE html>
<html>
  <head>
    <title>css-camera</title>
    <meta charsset="utf-8" />
    <meta name="viewport" content="width=device-width, user-scalable=no">
    <meta property="og:title" content="css-camera" />
    <meta property="og:type" content="website" />
    <meta property="og:url" content="https://woodneck.github.io/css-camera/" />
    <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
    <link type="text/css" rel="stylesheet" href="css/ortho.css">
    <script src="./release/latest/lib/css-camera.pkgd.js"></script>
    <script src="https://naver.github.io/egjs-axes/release/latest/dist/axes.pkgd.js"></script>
  </head>
  <body>
    <div id="space">
      <div class="tile grass road-vertical"></div>
      <div class="tile grass"><div class="fence"></div></div>
      <div class="tile grass"></div>
      <div class="tile grass grassi height-1"></div>
      <div class="tile grass grassh height-2"></div>
      <div class="tile grass height-3"><div class="bush"></div></div>
      <div class="tile grass grassg height-3"></div>
      <div class="tile grass height-4"></div>
      <div class="tile grass grassh height-4"></div>
      <div class="tile grass height-5"><div class="flower"></div></div>

      <div class="tile grass road-vertical"></div>
      <div class="tile grass grassc"></div>
      <div class="tile grass"><div class="rock"></div></div>
      <div class="tile grass grassa"></div>
      <div class="tile grass grassb height-1"></div>
      <div class="tile grass grassh height-2"></div>
      <div class="tile grass grassd height-2"></div>
      <div class="tile grass patch height-3"></div>
      <div class="tile grass grasse height-4"></div>
      <div class="tile grass height-5"></div>

      <div class="tile grass road-vertical"></div>
      <div class="tile grass"><div class="barrel"></div></div>
      <div class="tile grass grassb"></div>
      <div class="tile grass patch"></div>
      <div class="tile grass"></div>
      <div class="tile grass height-1"></div>
      <div class="tile grass height-1"></div>
      <div class="tile grass height-2"></div>
      <div class="tile grass height-3"><div class="rock"></div></div>
      <div class="tile grass grassg height-4"></div>

      <div class="tile grass road-bottom"></div>
      <div class="tile grass"><div class="sign"></div></div>
      <div class="tile grass"><div class="flower"></div></div>
      <div class="tile grass grassi"></div>
      <div class="tile grass"><div class="tree"></div></div>
      <div class="tile grass" id="center"></div>
      <div class="tile grass grassc height-1"></div>
      <div class="tile grass grassi height-1"></div>
      <div class="tile grass grassd height-2"></div>
      <div class="tile grass height-3"></div>

      <div class="tile grass"></div>
      <div class="tile grass river-top-left"></div>
      <div class="tile grass river-top"></div>
      <div class="tile grass river-top"></div>
      <div class="tile grass river-top-right"></div>
      <div class="tile grass"><div class="fence"></div></div>
      <div class="tile grass"></div>
      <div class="tile grass grassh"></div>
      <div class="tile grass height-1"></div>
      <div class="tile grass grassh height-2"></div>

      <div class="tile grass grassh"></div>
      <div class="tile grass river-bottom-left"></div>
      <div class="tile grass river-bottom"></div>
      <div class="tile grass river-bottom"></div>
      <div class="tile grass river-bottom-right"></div>
      <div class="tile grass grassf"></div>
      <div class="tile grass grassg"></div>
      <div class="tile grass"></div>
      <div class="tile grass"><div class="bush"></div></div>
      <div class="tile grass height-1"></div>

      <div class="tile grass"></div>
      <div class="tile grass grassb"></div>
      <div class="tile grass"><div class="chopped"></div></div>
      <div class="tile grass grassc"></div>
      <div class="tile grass"></div>
      <div class="tile grass grassa"></div>
      <div class="tile grass"></div>
      <div class="tile grass road-top"></div>
      <div class="tile grass grassg"></div>
      <div class="tile grass"><div class="flower"></div></div>
    </div>
    <div id="info">All pixelart assets are from <a href="https://guttykreum.itch.io/field-of-green">https://guttykreum.itch.io/field-of-green</a></div>
    <div id="info2"><a href="https://github.com/WoodNeck/css-camera/blob/master/demo/js/ortho.js"><i class="fab fa-github"></i> Source</a></div>
    <script src="js/ortho.js"></script>
  </body>
</html>


================================================
FILE: jsdoc.json
================================================
{
  "tags": {
    "allowUnknownTags" : false,
    "dictionaries": ["jsdoc", "closure"]
  },
  "source": {
      "include": ["src", "README.md"],
      "includePattern": ".+\\.(j|t)s(doc|x)?$",
      "excludePattern": "(^|\\/|\\\\)_"
  },
  "opts": {
      "template": "node_modules/docdash",
      "destination": "./docs/",
      "ignores": [],
      "expendsItemMembers": true,
      "recurse": true
  },
  "plugins": [
    "plugins/markdown"
  ],
  "docdash": {
    "static": true,
    "cleverLinks": true,
    "monospaceLinks": true,
    "typedefs": true,
    "private": true,
    "search": true,
    "openGraph": {
      "title": "css-camera",
      "type": "website",
      "site_name": "css-camera",
      "image": "",
      "url": "https://woodneck.github.io/css-camera/"
    },
    "meta": {
      "title": "css-camera",
      "description": "Add a depth to your web page with CSS3 3D transform.",
      "keyword": "css3, camera, graphics, 3d"
    },
    "menu":{
      "Github":{
          "href":"https://github.com/WoodNeck/css-camera",
          "target":"_blank"
      }
    }
  },
  "markdown": {
    "parser": "gfm",
    "hardwrap": true
  }
}


================================================
FILE: package.json
================================================
{
  "name": "css-camera",
  "version": "1.0.1-snapshot",
  "description": "Add a depth to your web page with CSS3 3D transform.",
  "main": "lib/css-camera.js",
  "module": "lib/css-camera.esm.js",
  "types": "lib/declaration/index.d.ts",
  "scripts": {
    "build": "rm -rf ./lib && rollup -c && npm run declaration",
    "build:windows": "rd /s /q ./lib || rollup -c && npm run declaration:windows",
    "declaration": "rm -rf ./lib/declaration && tsc -p tsconfig.json",
    "declaration:windows": "rd /s /q ./lib || tsc -p tsconfig.json",
    "demo:build": "npm run build && cpx 'lib/**/*' demo/release/latest/lib --clean",
    "demo:prebuild-version": "cpx 'lib/**/*' demo/release/$npm_package_version/lib --clean && cpx 'docs/**/*' demo/release/$npm_package_version/docs --clean",
    "demo:prebuild-latest": "cpx 'lib/**/*' demo/release/latest/lib --clean && cpx 'docs/**/*' demo/release/latest/docs --clean",
    "demo:deploy": "npm run build && npm run doc && npm run demo:prebuild-version && npm run demo:prebuild-latest && gh-pages -d demo/",
    "doc": "rm -rf ./docs && jsdoc -c jsdoc.json"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/WoodNeck/css-camera.git"
  },
  "author": "WoodNeck",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/WoodNeck/css-camera/issues"
  },
  "homepage": "https://github.com/WoodNeck/css-camera#readme",
  "devDependencies": {
    "@daybrush/jsdoc": "^0.3.7",
    "@egjs/build-helper": "0.0.5",
    "@types/gl-matrix": "^2.4.5",
    "cpx": "^1.5.0",
    "docdash": "^1.1.1",
    "gh-pages": "2.0.1",
    "rollup": "^1.10.1",
    "rollup-plugin-node-resolve": "^4.2.3",
    "rollup-plugin-prototype-minify": "^1.0.5",
    "rollup-plugin-replace": "^2.2.0",
    "rollup-plugin-typescript": "^1.0.1",
    "rollup-plugin-uglify": "^6.0.2",
    "tslint": "^5.15.0",
    "tslint-consistent-codestyle": "^1.15.1",
    "tslint-eslint-rules": "^5.4.0",
    "typescript": "^3.4.3"
  },
  "dependencies": {
    "gl-matrix": "^3.0.0"
  }
}


================================================
FILE: rollup.config.js
================================================
const buildHelper = require("@egjs/build-helper");

const name = "CSSCamera";
const external = {
	"gl-matrix": "gl-matrix",
}
export default buildHelper([
  {
    name,
    input: "./src/index.umd.ts",
    output: "./lib/css-camera.js",
    format: "umd",
    external,
  },
  {
    name,
    input: "./src/index.umd.ts",
    output: "./lib/css-camera.min.js",
    format: "umd",
    uglify: true,
    external,
  },
  {
    name,
    input: "./src/index.umd.ts",
    output: "./lib/css-camera.pkgd.js",
    format: "umd",
    resolve: true,
  },
  {
    name,
    input: "./src/index.umd.ts",
    output: "./lib/css-camera.pkgd.min.js",
    format: "umd",
    resolve: true,
    uglify: true,
  },
  {
    input: "./src/index.ts",
    output: "./lib/css-camera.esm.js",
    format: "esm",
    external,
    exports: "named",
  },
]);


================================================
FILE: src/CSSCamera.ts
================================================
import { mat4, vec3, quat } from 'gl-matrix';
import { getElement, applyCSS, getTransformMatrix, findIndex, getOffsetFromParent, getRotateOffset, assign } from './utils/helper';
import { quatToEuler } from './utils/math';
import * as DEFAULT from './constants/default';
import { Offset, UpdateOption, ValueOf, Options } from './types';

class CSSCamera {
  private _element: HTMLElement;
  private _viewportEl: HTMLElement;
  private _cameraEl: HTMLElement;
  private _worldEl: HTMLElement;

  private _position: vec3;
  private _scale: vec3;
  private _rotation: vec3;
  private _perspective: number;
  private _rotateOffset: number;
  private _updateTimer: number;

  /**
   * Current version of CSSCamera.
   * @example
   * console.log(CSSCamera.VERSION); // ex) 1.0.0
   * @type {string}
   */
  static get VERSION() { return '#__VERSION__#'; }

  /**
   * The element provided in the constructor.
   * @example
   * const camera = new CSSCamera(el);
   * console.log(camera.element === el); // true
   * @type {HTMLElement}
   */
  public get element() { return this._element; }

  /**
   * The reference of viewport DOM element.
   * @type {HTMLElement}
   */
  public get viewportEl() { return this._viewportEl; }

  /**
   * The reference of camera DOM element.
   * @type {HTMLElement}
   */
  public get cameraEl() { return this._cameraEl; }

  /**
   * The reference of world DOM element.
   * @type {HTMLElement}
   */
  public get worldEl() { return this._worldEl; }

  /**
   * The current position as number array([x, y, z]).
   * @example
   * const camera = new CSSCamera(el);
   * console.log(camera.position); // [0, 0, 0];
   * camera.position = [0, 0, 300];
   * console.log(camera.position); // [0, 0, 300];
   * @type {number[]}
   */
  public get position() { return [...this._position]; }

  /**
   * The current scale as number array([x, y, z]).
   * @example
   * const camera = new CSSCamera(el);
   * console.log(camera.scale); // [1, 1, 1];
   * camera.scale = [5, 1, 1];
   * console.log(camera.scale); // [5, 1, 1];
   * @type {number[]}
   */
  public get scale() { return [...this._scale]; }

  /**
   * The current Euler rotation angles in degree as number array([x, y, z]).
   * @example
   * const camera = new CSSCamera(el);
   * console.log(camera.rotation); // [0, 0, 0];
   * camera.rotation = [90, 0, 0];
   * console.log(camera.rotation); // [90, 0, 0];
   * @type {number[]}
   */
  public get rotation() { return [...this._rotation]; }

  /**
   * The current quaternion rotation as number array([x, y, z, w]).
   * @example
   * const camera = new CSSCamera(el);
   * console.log(camera.quaternion); // [0, 0, 0, 1];
   * camera.rotation = [90, 0, 0];
   * console.log(camera.quaternion); // [0.7071067690849304, 0, 0, 0.7071067690849304];
   * camera.quaternion = [0, 0, 0, 1];
   * console.log(camera.rotation); // [0, -0, 0];
   * @type {number[]}
   */
  public get quaternion() {
    const r = this._rotation;
    const quaternion = quat.fromEuler(quat.create(), r[0], r[1], r[2]);

    return [...quaternion];
  }

  /**
   * The current perspective value that will be applied to viewport element.
   * @example
   * const camera = new CSSCamera(el);
   * camera.perspective = 300;
   * console.log(camera.perspective); // 300
   * @type {number}
   */
  public get perspective() { return this._perspective; }

  /**
   * The current rotate offset value that will be applied to camera element.
   * The camera will be as far away from the focal point as this value.
   * |![rot0](https://woodneck.github.io/css-camera/asset/rot0.gif)|![rot150](https://woodneck.github.io/css-camera/asset/rot150.gif)|
   * |:---:|:---:|
   * @example
   * const camera = new CSSCamera(el);
   * camera.perspective = 300;
   * console.log(camera.cameraCSS); // scale3d(1, 1, 1) translateZ(300px) rotateX(0deg) rotateY(0deg) rotateZ(0deg);
   * camera.rotateOffset = 100;
   * console.log(camera.cameraCSS); // scale3d(1, 1, 1) translateZ(400px) rotateX(0deg) rotateY(0deg) rotateZ(0deg);
   * @type {number}
   */
  public get rotateOffset() { return this._rotateOffset; }

  /**
   * CSS string can be applied to camera element based on current transform.
   * @example
   * const camera = new CSSCamera(el);
   * camera.perspective = 300;
   * console.log(camera.cameraCSS); // scale3d(1, 1, 1) translateZ(300px) rotateX(0deg) rotateY(0deg) rotateZ(0deg);
   * @type {string}
   */
  public get cameraCSS() {
    const perspective = this._perspective;
    const rotateOffset = this._rotateOffset;
    const rotation = this._rotation;
    const scale = this._scale;

    // Rotate in order of Z - Y - X
    // tslint:disable-next-line: max-line-length
    return `scale3d(${scale[0]}, ${scale[1]}, ${scale[2]}) translateZ(${perspective - rotateOffset}px) rotateX(${rotation[0]}deg) rotateY(${rotation[1]}deg) rotateZ(${rotation[2]}deg)`;
  }

  /**
   * CSS string can be applied to world element based on current transform.
   * ```
   * const camera = new CSSCamera(el);
   * console.log(camera.worldCSS); // "translate3d(0px, 0px, 0px)";
   * camera.translate(0, 0, 300);
   * console.log(camera.worldCSS); // "translate3d(0px, 0px, -300px)";
   * ```
   * @type {string}
   */
  public get worldCSS() {
    const position = this._position;

    return `translate3d(${-position[0]}px, ${-position[1]}px, ${-position[2]}px)`;
  }

  public set position(val: number[]) { this._position = vec3.fromValues(val[0], val[1], val[2]); }
  public set scale(val: number[]) { this._scale = vec3.fromValues(val[0], val[1], val[2]); }
  public set rotation(val: number[]) { this._rotation = vec3.fromValues(val[0], val[1], val[2]); }
  public set quaternion(val: number[]) { this._rotation = quatToEuler(quat.fromValues(val[0], val[1], val[2], val[3])); }
  public set perspective(val: number) { this._perspective = val; }
  public set rotateOffset(val: number) { this._rotateOffset = val; }

  /**
   * Create new CSSCamera with given element / selector.
   * @param - The element to apply camera. Can be HTMLElement or CSS selector.
   * @param {Partial<Options>} [options] Camera options
   * @param {number[]} [options.position=[0, 0, 0]] Initial position of the camera.
   * @param {number[]} [options.scale=[1, 1, 1]] Initial scale of the camera.
   * @param {number[]} [options.rotation=[0, 0, 0]] Initial Euler rotation angles(x, y, z) of the camera in degree.
   * @param {number} [options.perspective=0] Initial perspective of the camera.
   * @param {number} [options.rotateOffset=0] Initial rotate offset of the camera.
   * @example
   * const camera = new CSSCamera("#el", {
   *   position: [0, 0, 150], // Initial pos(x, y, z)
   *   rotation: [90, 0, 0],  // Initial rotation(x, y, z, in degree)
   *   perspective: 300       // CSS "perspective" value to apply
   * });
   */
  constructor(el: string | HTMLElement, options: Partial<Options> = {}) {
    this._element = getElement(el);

    const op = assign(assign({}, DEFAULT.OPTIONS), options) as Options;

    this._position = vec3.fromValues(op.position[0], op.position[1], op.position[2]);
    this._scale = vec3.fromValues(op.scale[0], op.scale[1], op.scale[2]);
    this._rotation = vec3.fromValues(op.rotation[0], op.rotation[1], op.rotation[2]);
    this._perspective = op.perspective;
    this._rotateOffset = op.rotateOffset;
    this._updateTimer = -1;

    const element = this._element;
    const viewport = document.createElement('div');
    const camera = viewport.cloneNode() as HTMLElement;
    const world = viewport.cloneNode() as HTMLElement;

    viewport.className = DEFAULT.CLASS.VIEWPORT;
    camera.className = DEFAULT.CLASS.CAMERA;
    world.className = DEFAULT.CLASS.WORLD;

    applyCSS(viewport, DEFAULT.STYLE.VIEWPORT);
    applyCSS(camera, DEFAULT.STYLE.CAMERA);
    applyCSS(world, DEFAULT.STYLE.WORLD);

    camera.appendChild(world);
    viewport.appendChild(camera);

    this._viewportEl = viewport;
    this._cameraEl = camera;
    this._worldEl = world;

    // EL's PARENT -> VIEWPORT -> CAMERA -> WORLD -> EL
    element.parentElement!.insertBefore(viewport, element);
    world.appendChild(element);

    this.update(0);
  }

  /**
   * Focus a camera to given element.
   * After focus, element will be in front of camera with no rotation applied.
   * Also, it will have original width / height if neither [scale](#scale) nor [perspectiveOffset](#perspectiveOffset) is applied.
   * This method won't work if any of element's parent except camera element has scale applied.
   * @param - The element to focus. Can be HTMLElement or CSS selector.
   * @return {CSSCamera} The instance itself
   */
  public focus(el: string | HTMLElement): this {
    const element = getElement(el);
    const focusMatrix = this._getFocusMatrix(element);

    const rotation = quat.create();
    const translation = vec3.create();
    mat4.getRotation(rotation, focusMatrix);
    mat4.getTranslation(translation, focusMatrix);

    const eulerAngle = quatToEuler(rotation);

    vec3.negate(eulerAngle, eulerAngle);

    this._rotation = eulerAngle;
    this._position = translation;
    return this;
  }

  /**
   * Translate a camera in its local coordinate space.
   * For example, `camera.translateLocal(0, 0, -300)` will always move camera to direction where it's seeing.
   * @param - Amount of horizontal translation, in px.
   * @param - Amount of vertical translation, in px.
   * @param - Amount of translation in view direction, in px.
   * @return {CSSCamera} The instance itself
   */
  public translateLocal(x: number = 0, y: number = 0, z: number = 0): this {
    const position = this._position;
    const rotation = this._rotation;

    const transVec = vec3.fromValues(x, y, z);
    const rotQuat = quat.create();
    quat.fromEuler(rotQuat, -rotation[0], -rotation[1], -rotation[2]);
    vec3.transformQuat(transVec, transVec, rotQuat);

    vec3.add(position, position, transVec);
    return this;
  }

  /**
   * Translate a camera in world(absolute) coordinate space.
   * @param - Amount of translation in x axis, in px.
   * @param - Amount of translation in y axis, in px.
   * @param - Amount of translation in z axis, in px.
   * @return {CSSCamera} The instance itself
   */
  public translate(x: number = 0, y: number = 0, z: number = 0): this {
    vec3.add(this._position, this._position, vec3.fromValues(x, y, z));

    return this;
  }

  /**
   * Rotate a camera in world(absolute) coordinate space.
   * @param - Amount of rotation in x axis, in degree.
   * @param - Amount of rotation in y axis, in degree.
   * @param - Amount of rotation in z axis, in degree.
   * @return {CSSCamera} The instance itself
   */
  public rotate(x: number = 0, y: number = 0, z: number = 0): this {
    vec3.add(this._rotation, this._rotation, vec3.fromValues(x, y, z));

    return this;
  }

  /**
   * Updates a camera CSS with given duration.
   * Every other camera transforming properties / methods will be batched until this method is called.
   * @example
   * const camera = new CSSCamera(el);
   * console.log(camera.cameraEl.style.transform); // ''
   *
   * camera.perspective = 300;
   * camera.translate(0, 0, 300);
   * camera.rotate(0, 90, 0);
   * console.log(camera.cameraEl.style.transform); // '', Not changed!
   *
   * await camera.update(1000); // Camera style is updated.
   * console.log(camera.cameraEl.style.transform); // scale3d(1, 1, 1) translateZ(300px) rotateX(0deg) rotateY(90deg) rotateZ(0deg)
   *
   * // When if you want to apply multiple properties
   * camera.update(1000, {
   *   property: "transform, background-color",
   *   timingFunction: "ease-out, ease-out", // As same with CSS, you should assign values to each property
   *   delay: "0ms, 100ms"
   * });
   * @param - Transition duration in ms.
   * @param {Partial<UpdateOption>} [options] Transition options.
   * @param {string} [options.property="transform"] CSS [transition-property](https://developer.mozilla.org/en-US/docs/Web/CSS/transition-property) to apply.
   * @param {string} [options.timingFunction="ease-out"] CSS [transition-timing-function](https://developer.mozilla.org/en-US/docs/Web/CSS/transition-timing-function) to apply.
   * @param {string} [options.delay="0ms"] CSS [transition-delay](https://developer.mozilla.org/en-US/docs/Web/CSS/transition-delay) to apply.
   * @return {Promise<CSSCamera>} A promise resolving instance itself
   */
  public async update(duration: number = 0, options: Partial<UpdateOption> = {}): Promise<this> {
    applyCSS(this._viewportEl, { perspective: `${this.perspective}px` });
    applyCSS(this._cameraEl, { transform: this.cameraCSS });
    applyCSS(this._worldEl, { transform: this.worldCSS });

    const updateOptions = assign(assign({}, DEFAULT.UPDATE_OPTIONS), options) as UpdateOption;

    if (duration > 0) {
      if (this._updateTimer > 0) {
        window.clearTimeout(this._updateTimer);
      }

      const transitionDuration = `${duration}ms`;
      const updateOption = Object.keys(updateOptions).reduce((option: {[key: string]: ValueOf<UpdateOption>}, key) => {
        option[`transition${key.charAt(0).toUpperCase() + key.slice(1)}`] = updateOptions[key as keyof UpdateOption]!;
        return option;
      }, {});

      const finalOption = {
        transitionDuration,
        ...updateOption,
      };

      [this._viewportEl, this._cameraEl, this._worldEl].forEach(el => {
        applyCSS(el, finalOption);
      });
    }

    return new Promise(resolve => {
      // Make sure to use requestAnimationFrame even if duration is 0
      // To make sure DOM is updated, for successive update() calls.
      if (duration > 0) {
        this._updateTimer = window.setTimeout(() => {
          // Reset transition values
          [this._viewportEl, this._cameraEl, this._worldEl].forEach(el => {
            applyCSS(el, { transition: '' });
          });
          this._updateTimer = -1;
          resolve();
        }, duration);
      } else {
        requestAnimationFrame(() => {
          resolve();
        });
      }
    });
  }

  private _getFocusMatrix(element: HTMLElement): mat4 {
    const elements: HTMLElement[] = [];
    while (element) {
      elements.push(element);
      if (element === this._element) break;
      element = element.parentElement!;
    }

    // Order by shallow to deep
    elements.reverse();

    const elStyles = elements.map(el => window.getComputedStyle(el));

    // Find first element that transform-style is not preserve-3d
    // As all childs of that element is affected by its matrix
    const firstFlatIndex = findIndex(elStyles, style => style.transformStyle !== 'preserve-3d');
    if (firstFlatIndex > 0) { // el doesn't have to be preserve-3d'ed
      elStyles.splice(firstFlatIndex + 1);
    }

    let parentOffset: Offset = {
      left: 0,
      top: 0,
      width: this.viewportEl.offsetWidth,
      height: this.viewportEl.offsetHeight,
    };

    // Accumulated rotation
    const accRotation = quat.identity(quat.create());
    // Assume center of screen as (0, 0, 0)
    const centerPos = vec3.fromValues(0, 0, 0);

    elStyles.forEach((style, idx) => {
      const el = elements[idx];
      const currentOffset = {
        left: el.offsetLeft,
        top: el.offsetTop,
        width: el.offsetWidth,
        height: el.offsetHeight,
      };
      const transformMat = getTransformMatrix(style);
      const offsetFromParent = getOffsetFromParent(currentOffset, parentOffset);
      vec3.transformQuat(offsetFromParent, offsetFromParent, accRotation);

      vec3.add(centerPos, centerPos, offsetFromParent);

      const rotateOffset = getRotateOffset(style, currentOffset);
      vec3.transformQuat(rotateOffset, rotateOffset, accRotation);

      const transformOrigin = vec3.clone(centerPos);
      vec3.add(transformOrigin, transformOrigin, rotateOffset);

      const centerFromOrigin = vec3.create();
      vec3.sub(centerFromOrigin, centerPos, transformOrigin);

      const invAccRotation = quat.invert(quat.create(), accRotation);
      vec3.transformQuat(centerFromOrigin, centerFromOrigin, invAccRotation);
      vec3.transformMat4(centerFromOrigin, centerFromOrigin, transformMat);
      vec3.transformQuat(centerFromOrigin, centerFromOrigin, accRotation);

      const newCenterPos = vec3.add(vec3.create(), transformOrigin, centerFromOrigin);
      const rotation = mat4.getRotation(quat.create(), transformMat);

      vec3.copy(centerPos, newCenterPos);
      quat.mul(accRotation, accRotation, rotation);
      parentOffset = currentOffset;
    });

    const perspective = vec3.fromValues(0, 0, this.perspective);
    vec3.transformQuat(perspective, perspective, accRotation);
    vec3.add(centerPos, centerPos, perspective);

    const matrix = mat4.create();
    mat4.fromRotationTranslation(matrix, accRotation, centerPos);

    return matrix;
  }
}

export default CSSCamera;


================================================
FILE: src/constants/default.ts
================================================
export const STYLE = {
  VIEWPORT: {
    width: '100%',
    height: '100%',
    'transform-style': 'preserve-3d',
    overflow: 'hidden',
  },
  CAMERA: {
    width: '100%',
    height: '100%',
    'transform-style': 'preserve-3d',
    'will-change': 'transform',
  },
  WORLD: {
    width: '100%',
    height: '100%',
    'transform-style': 'preserve-3d',
    'will-change': 'transform',
  },
};

export const CLASS = {
  VIEWPORT: 'cc-viewport',
  CAMERA: 'cc-camera',
  WORLD: 'cc-world',
};

export const OPTIONS = {
  position: [0, 0, 0],
  scale: [1, 1, 1],
  rotation: [0, 0, 0],
  perspective: 0,
  rotateOffset: 0,
};

export const UPDATE_OPTIONS = {
  property: 'transform',
  timingFunction: 'ease-out',
  delay: '0ms',
};


================================================
FILE: src/constants/error.ts
================================================
export const ELEMENT_NOT_EXIST = (selector: string) => `Element with selector "${selector}" doesn't exist.`;
export const MUST_STRING_OR_ELEMENT = (received: any) => `Element should be provided in string or HTMLElement. Received: ${received}`;


================================================
FILE: src/index.ts
================================================
import CSSCamera from './CSSCamera';

export * from './types';
export default CSSCamera;


================================================
FILE: src/index.umd.ts
================================================
import CSSCamera from './CSSCamera';

export default CSSCamera;


================================================
FILE: src/types.ts
================================================
export type ValueOf<T> = T[keyof T];

export type Matrix4x4 = [
  number, number, number, number,
  number, number, number, number,
  number, number, number, number,
  number, number, number, number,
];

/**
 * @typedef
 * @property - Initial position of the camera.
 * @property - Initial scale of the camera.
 * @property - Initial Euler rotation angles(x, y, z) of the camera in degree.
 * @property - Initial perspective of the camera.
 * @property - Initial rotate offset of the camera.
 */
export interface Options {
  position: number[];
  scale: number[];
  rotation: number[];
  perspective: number;
  rotateOffset: number;
}

export interface Offset {
  left: number;
  top: number;
  width: number;
  height: number;
}

/**
 * @typedef
 * @property - CSS [transition-property](https://developer.mozilla.org/en-US/docs/Web/CSS/transition-property) to apply.
 * @property - CSS [transition-timing-function](https://developer.mozilla.org/en-US/docs/Web/CSS/transition-timing-function) to apply.
 * @property - CSS [transition-delay](https://developer.mozilla.org/en-US/docs/Web/CSS/transition-delay) to apply.
 */
export interface UpdateOption {
  property: string;
  timingFunction: string;
  delay: string;
}


================================================
FILE: src/utils/helper.ts
================================================
import { mat4, vec3 } from 'gl-matrix';
import { ELEMENT_NOT_EXIST, MUST_STRING_OR_ELEMENT } from '../constants/error';
import { Matrix4x4, Offset } from '../types';

export const getElement = (el: string | HTMLElement, baseElement?: HTMLElement): HTMLElement => {
    if (typeof el === 'string') {
        const queryResult = baseElement
          ? baseElement.querySelector(el)
          : document.querySelector(el);
        if (!queryResult) {
            throw new Error(ELEMENT_NOT_EXIST(el));
        }
        return queryResult as HTMLElement;
    } else if (el.nodeName && el.nodeType === 1) {
        return el;
    } else {
        throw new Error(MUST_STRING_OR_ELEMENT(el));
    }
};

export function applyCSS(element: HTMLElement, cssObj: { [keys: string]: string }): void {
  Object.keys(cssObj).forEach(property => {
    (element.style as any)[property] = cssObj[property];
  });
}

export function getTransformMatrix(elStyle: CSSStyleDeclaration): mat4 {
  const trVal = elStyle.getPropertyValue('transform');
  const transformStr = /\(((\s|\S)+)\)/.exec(trVal);
  const matrixVal = transformStr
    ? transformStr[1].split(',').map(val => parseFloat(val)) as Matrix4x4
    : [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] as Matrix4x4;
  if (matrixVal.length === 16 ) {
    return mat4.fromValues(...matrixVal);
  } else {
    // Convert 2d matrix(length 6) to 3d
    const matrix = mat4.create();
    mat4.identity(matrix);

    matrix[0] = matrixVal[0];
    matrix[1] = matrixVal[1];
    matrix[4] = matrixVal[2];
    matrix[5] = matrixVal[3];
    matrix[12] = matrixVal[4];
    matrix[13] = matrixVal[5];

    return matrix;
  }
}

export function getOffsetFromParent(currentOffset: Offset, parentOffset: Offset): vec3 {
  const offsetLeft = currentOffset.left + (currentOffset.width - parentOffset.width) / 2;
  const offsetTop = currentOffset.top + (currentOffset.height - parentOffset.height) / 2;

  return vec3.fromValues(offsetLeft, offsetTop, 0);
}

export function getRotateOffset(elStyle: CSSStyleDeclaration, currentOffset: Offset): vec3 {
  const axis = (elStyle.transformOrigin as string)
    .split(' ')
    .map(str => parseFloat(str.substring(0, str.length - 2)));
  const ax = axis[0] - currentOffset.width / 2;
  const ay = axis[1] - currentOffset.height / 2;

  return vec3.fromValues(ax, ay, 0);
}

export function findIndex<T>(iterable: T[], callback: (el: T) => boolean): number {
  for (let i = 0; i < iterable.length; i += 1) {
    const element = iterable[i];
    if (element && callback(element)) {
      return i;
    }
  }

  return -1;
}

// return [0, 1, ...., max - 1]
export function range(max: number): number[] {
  const counterArray: number[] = [];
  for (let i = 0; i < max; i += 1) {
    counterArray[i] = i;
  }
  return counterArray;
}

export function clamp(val: number, min: number, max: number): number {
  return Math.max(Math.min(val, max), min);
}

export function assign(target: object, ...srcs: object[]): object {
  srcs.forEach(source => {
    Object.keys(source).forEach(key => {
      const value = (source as any)[key];
      (target as any)[key] = value;
    });
  });

  return target;
}


================================================
FILE: src/utils/math.ts
================================================
import { mat4, quat, vec3 } from 'gl-matrix';
import { clamp } from './helper';

export function degToRad(deg: number): number {
  return Math.PI * deg / 180;
}

export function radToDeg(rad: number): number {
  return 180 * rad / Math.PI;
}

// From Three.js https://github.com/mrdoob/three.js/blob/dev/src/math/Euler.js
export function quatToEuler(q: quat): vec3 {
  const rotM = mat4.create();
  mat4.fromQuat(rotM, q);

  const m11 = rotM[0];
  const m12 = rotM[4];
  // const m13 = rotM[8];
  const m21 = rotM[1];
  const m22 = rotM[5];
  // const m23 = rotM[9];
  const m31 = rotM[2];
  const m32 = rotM[6];
  const m33 = rotM[10];

  const euler = vec3.create();

  // ZYX
  euler[1] = Math.asin(-clamp(m31, -1, 1));
  if (Math.abs(m31) < 0.99999) {
    euler[0] = Math.atan2(m32, m33);
    euler[2] = Math.atan2(m21, m11);
  } else {
    euler[0] = 0;
    euler[2] = Math.atan2(-m12, m22);
  }

  return euler.map(val => radToDeg(val)) as vec3;
}


================================================
FILE: test/manual/css/common.css
================================================
html {
  height: 100%;
}
body {
  width: 100%;
  height: 100%;
  min-height: 100%;
  margin: 0;
}

/* .cc-camera {
  transition: transform 1s;
}

.cc-world {
  transition: transform 1s;
} */
#space {
  transform-style: preserve-3d;
  position: relative;
  width: 100%;
  height: 100%;
}
.cube {
  display: inline-block;
  width: 300px;
  height: 300px;
  position: absolute;
  left: calc(50% - 150px);
  top: calc(50% - 150px);
  transform-style: preserve-3d;
  transition: transform 3s;
}
#cube2 {
  transform: translate3d(150px, 0, -150px) rotateY(-90deg);
}
#cube3 {
  transform: translate3d(300px, 0, 0px) rotateY(180deg);
}
.cube-side {
  width: 100%;
  height: 100%;
  background: rgb(114, 55, 55);
  position: absolute;
  left: 0; top: 0;
}
.cube-side.up {
  background: #222;
  transform-origin: top;
  transform: rotateX(-90deg);
}
.cube-side.down {
  background: #444;
  transform-origin: bottom;
  transform: rotateX(90deg);
}
.cube-side.left {
  background: #666;
  transform-origin: left;
  transform: rotateY(90deg);
}
.cube-side.right {
  background: #888;
  transform-origin: right;
  transform: rotateY(-90deg);
}
#cube1 .cube-side.right,
#cube2 .cube-side.right {
  background: transparent;
}
.cube-side.front {
  background: url("../assets/cover.png");
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
}
.cube-side.back {
  background: url(https://thumbs.gfycat.com/IdealisticOilyFlicker-size_restricted.gif);
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
  transform: translateZ(-300px);
  transform-style: preserve-3d;
}
#rotated {
  transform-style: preserve-3d;
  transform: rotateZ(-45deg) translate3d(200px, -500px, 300px) rotateX(90deg);
  /* transform-origin: 50px 30%; */
  width: 50vw;
  height: 50vh;
  transform-origin: 0% 0%;
  background: aquamarine;
  backface-visibility: hidden;
}
#rotated2 {
  transform-style: preserve-3d;
  transform: rotateY(15deg) translate3d(200px, -500px, 300px) rotateZ(10deg);
  transform-origin: 0% 50px;
  width: 50vw;
  height: 50vh;
  background: chartreuse;
  backface-visibility: hidden;
}
#rotated3 {
  transform-style: preserve-3d;
  transform: translate3d(200px, -500px, 300px) rotateX(30deg) rotateY(-30deg);
  transform-origin: 0% 120px;
  width: 50vw;
  height: 50vh;
  background: tomato;
  backface-visibility: hidden;
}


================================================
FILE: test/manual/test.html
================================================
<!DOCTYPE html>
<html>
  <title>CSS-Camera Demo Page</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link type="text/css" rel="stylesheet" href="css/common.css">
</html>
<body>
  <div id="space">
    <div id="rotated">
      TEXT
      <div id="rotated2">
        TEXT
        <div id="rotated3">
          <input type="text" />
          TEXT
        </div>
      </div>
    </div>˜
    <div class="cube" id="cube1">
      <div class="cube-side front"></div>
      <div class="cube-side back"></div>
      <div class="cube-side up"></div>
      <div class="cube-side down"></div>
      <div class="cube-side left"></div>
      <div class="cube-side right"></div>
    </div>
    <div class="cube" id="cube2">
      <div class="cube-side front"></div>
      <div class="cube-side back"></div>
      <div class="cube-side up"></div>
      <div class="cube-side down"></div>
      <div class="cube-side left"></div>
      <div class="cube-side right"></div>
    </div>
    <div class="cube" id="cube3">
      <div class="cube-side front"></div>
      <div class="cube-side back"></div>
      <div class="cube-side up"></div>
      <div class="cube-side down"></div>
      <div class="cube-side left"></div>
      <div class="cube-side right"></div>
    </div>
  </div>
  <script type="text/javascript" src="../../lib/css-camera.pkgd.js"></script>
  <script type="text/javascript">
    var camera = new CSSCamera("#space", {
      perspective: 300,
      rotateOffset: 0,
      position: [0, 0, -150],
    });
    camera.update(0).then(async () => {
      camera.rotate(0, 90, 0);
      await camera.update(2000, {
        timingFunction: 'linear'
      });
      camera.rotate(0, 90, 0);
      await camera.update(2000, {
        timingFunction: 'linear'
      });
      camera.rotate(0, 90, 0);
      await camera.update(2000, {
        timingFunction: 'linear'
      });
      camera.rotate(0, 90, 0);
      await camera.update(2000, {
        timingFunction: 'linear'
      });
    });
    // camera.update(0).then(async () => {
    //   camera.focus(rotated3)
    //   await camera.update(3000);
    // });


    const front = document.querySelector(".front");
    const back = document.querySelector(".back");
    const cubeLeft = document.querySelector(".left");
    const cubeRight = document.querySelector(".right");

    // camera.focus(rotated3);
    // camera.update(2000);

    // var up = false,
    // right = false,
    // down = false,
    // left = false,
    // space = false,
    // x = window.innerWidth/2,
    // y = window.innerHeight/2;

    // document.addEventListener('keydown', press);
    // function press(e){
    //   if (e.keyCode === 38 /* up */ || e.keyCode === 87 /* w */){
    //     up = true;
    //   }
    //   if (e.keyCode === 39 /* right */ || e.keyCode === 68 /* d */){
    //     right = true;
    //   }
    //   if (e.keyCode === 40 /* down */ || e.keyCode === 83 /* s */){
    //     down = true;
    //   }
    //   if (e.keyCode === 37 /* left */ || e.keyCode === 65 /* a */){
    //     left = true;
    //   }
    //   if (e.keyCode === 32 /* space */) {
    //     space = true;
    //   }
    // }

    // document.addEventListener('keyup', release);
    // function release(e){
    //   if (e.keyCode === 38 /* up */ || e.keyCode === 87 /* w */){
    //     up = false
    //   }
    //   if (e.keyCode === 39 /* right */ || e.keyCode === 68 /* d */){
    //     right = false
    //   }
    //   if (e.keyCode === 40 /* down */ || e.keyCode === 83 /* s */){
    //     down = false
    //   }
    //   if (e.keyCode === 37 /* left */ || e.keyCode === 65 /* a */){
    //     left = false
    //   }
    //   if (e.keyCode === 32 /* space */) {
    //     space = false;
    //   }
    // }

    // const prevMouseLocation = {
    //   x, y,
    // }
    // window.onmousemove = e => {
    //   const diffX = e.screenX - prevMouseLocation.x;
    //   const diffY = e.screenY - prevMouseLocation.y;

    //   camera.rotate(-diffY / 5, diffX / 5);
    //   camera.update(0);

    //   prevMouseLocation.x = e.screenX;
    //   prevMouseLocation.y = e.screenY;
    // }
    // const keyLoop = () => {
    //   if (up){
    //     camera.translateLocal(0, 0, -5);
    //   }
    //   if (right){
    //     camera.translateLocal(5, 0, 0);
    //   }
    //   if (down){
    //     camera.translateLocal(0, 0, 5);
    //   }
    //   if (left){
    //     camera.translateLocal(-5, 0, 0);
    //   }
    //   if (space) {
    //     camera.translate(0, -5, 0);
    //   }
    //   camera.position = [camera.position[0], 0, camera.position[2]];
    //   camera.update(0);
    //   requestAnimationFrame(keyLoop);
    // }
    // keyLoop();

    // const loop = async () => {
    //   await camera.update(1000);
    //   camera.translateLocal(0, 0, -450);
    //   await camera.update(1000);
    //   camera.rotate(0, 90, 0);
    //   await camera.update(1000);
    //   camera.translateLocal(0, 0, -300);
    //   await camera.update(1000);
    //   camera.rotate(0, 90, 0);
    //   await camera.update(1000);
    //   camera.translateLocal(0, 0, -300);
    //   await camera.update(1000);
    //   camera.rotate(0, 180, 0);
    //   await camera.update(1000);
    //   camera.translateLocal(0, 0, -300);
    //   await camera.update(1000);
    //   camera.rotate(0, -90, 0);
    //   await camera.update(1000);
    //   camera.translateLocal(0, 0, -300);
    //   await camera.update(1000);
    //   camera.rotate(0, -90, 0);
    //   await camera.update(1000);
    //   camera.translateLocal(0, 0, -450);
    //   await camera.update(1000);
    //   camera.rotate(0, 180, 0);
    //   await camera.update(1000);
    //   loop();
    // }
    // loop();
  </script>
</body>


================================================
FILE: tsconfig.json
================================================
{
    "compilerOptions": {
        "target": "es5",

        // Module
        "module": "es2015",
        "moduleResolution": "node",
        "allowSyntheticDefaultImports": true,
        "esModuleInterop": true,

        // Path
        "rootDir": "./src",
        "outDir": "./lib/",

        // Delcaration
        "declaration": true,
        "declarationDir": "./lib/declaration",

        // Log
        "pretty": true,

        // Lint
        "strict": true,
        "allowUnreachableCode": false,
        "allowUnusedLabels": false,
        "noFallthroughCasesInSwitch": true,
        "noImplicitReturns": true,
        "noUnusedLocals": true,
        "noUnusedParameters": true,
        "newLine": "lf",

        // Outfile
        "charset": "utf8",
        "removeComments": true,
        "noEmitOnError": true,

        // SourceMap
        "sourceMap": true,

        // etc
        "importHelpers": true,
        "downlevelIteration": true
    },
    "include": [
        "./src/**/*.ts"
    ]
  }


================================================
FILE: tslint.json
================================================
{
    "defaultSeverity": "error",
    "extends": [
      "tslint:recommended"
    ],
    "rulesDirectory": [
      "tslint-consistent-codestyle",
      "tslint-eslint-rules"
    ],
    "rules": {
      "quotemark": [true, "single"],
      "variable-name": false,
      "arrow-parens": false,
      "object-literal-key-quotes": false,
      "object-literal-sort-keys": false,
      "object-literal-shorthand": false,
      "ordered-imports": false,
      "no-console": false,
      "curly": false,
      "adjacent-overload-signatures": false,
      "no-bitwise": false,
      "interface-name": false,
      "max-line-length": false
    },
    "linterOptions": {
      "exclude": [
        "node_modules/**/*"
      ]
    }
  }
Download .txt
gitextract_yn11t5kk/

├── .editorconfig
├── .gitignore
├── LICENSE
├── README.md
├── demo/
│   ├── css/
│   │   ├── fpv.css
│   │   ├── index.css
│   │   └── ortho.css
│   ├── fpv.html
│   ├── index.html
│   ├── js/
│   │   ├── fpv.js
│   │   ├── index.js
│   │   └── ortho.js
│   └── ortho.html
├── jsdoc.json
├── package.json
├── rollup.config.js
├── src/
│   ├── CSSCamera.ts
│   ├── constants/
│   │   ├── default.ts
│   │   └── error.ts
│   ├── index.ts
│   ├── index.umd.ts
│   ├── types.ts
│   └── utils/
│       ├── helper.ts
│       └── math.ts
├── test/
│   └── manual/
│       ├── css/
│       │   └── common.css
│       └── test.html
├── tsconfig.json
└── tslint.json
Download .txt
SYMBOL INDEX (50 symbols across 6 files)

FILE: demo/js/fpv.js
  function onLockChange (line 17) | function onLockChange() {
  function press (line 34) | function press(e){
  function release (line 50) | function release(e){

FILE: src/CSSCamera.ts
  class CSSCamera (line 7) | class CSSCamera {
    method VERSION (line 26) | static get VERSION() { return '#__VERSION__#'; }
    method element (line 35) | public get element() { return this._element; }
    method viewportEl (line 41) | public get viewportEl() { return this._viewportEl; }
    method cameraEl (line 47) | public get cameraEl() { return this._cameraEl; }
    method worldEl (line 53) | public get worldEl() { return this._worldEl; }
    method position (line 64) | public get position() { return [...this._position]; }
    method scale (line 75) | public get scale() { return [...this._scale]; }
    method rotation (line 86) | public get rotation() { return [...this._rotation]; }
    method quaternion (line 99) | public get quaternion() {
    method perspective (line 114) | public get perspective() { return this._perspective; }
    method rotateOffset (line 129) | public get rotateOffset() { return this._rotateOffset; }
    method cameraCSS (line 139) | public get cameraCSS() {
    method worldCSS (line 160) | public get worldCSS() {
    method position (line 166) | public set position(val: number[]) { this._position = vec3.fromValues(...
    method scale (line 167) | public set scale(val: number[]) { this._scale = vec3.fromValues(val[0]...
    method rotation (line 168) | public set rotation(val: number[]) { this._rotation = vec3.fromValues(...
    method quaternion (line 169) | public set quaternion(val: number[]) { this._rotation = quatToEuler(qu...
    method perspective (line 170) | public set perspective(val: number) { this._perspective = val; }
    method rotateOffset (line 171) | public set rotateOffset(val: number) { this._rotateOffset = val; }
    method constructor (line 189) | constructor(el: string | HTMLElement, options: Partial<Options> = {}) {
    method focus (line 236) | public focus(el: string | HTMLElement): this {
    method translateLocal (line 262) | public translateLocal(x: number = 0, y: number = 0, z: number = 0): th...
    method translate (line 282) | public translate(x: number = 0, y: number = 0, z: number = 0): this {
    method rotate (line 295) | public rotate(x: number = 0, y: number = 0, z: number = 0): this {
    method update (line 329) | public async update(duration: number = 0, options: Partial<UpdateOptio...
    method _getFocusMatrix (line 377) | private _getFocusMatrix(element: HTMLElement): mat4 {

FILE: src/constants/default.ts
  constant STYLE (line 1) | const STYLE = {
  constant CLASS (line 22) | const CLASS = {
  constant OPTIONS (line 28) | const OPTIONS = {
  constant UPDATE_OPTIONS (line 36) | const UPDATE_OPTIONS = {

FILE: src/types.ts
  type ValueOf (line 1) | type ValueOf<T> = T[keyof T];
  type Matrix4x4 (line 3) | type Matrix4x4 = [
  type Options (line 18) | interface Options {
  type Offset (line 26) | interface Offset {
  type UpdateOption (line 39) | interface UpdateOption {

FILE: src/utils/helper.ts
  function applyCSS (line 21) | function applyCSS(element: HTMLElement, cssObj: { [keys: string]: string...
  function getTransformMatrix (line 27) | function getTransformMatrix(elStyle: CSSStyleDeclaration): mat4 {
  function getOffsetFromParent (line 51) | function getOffsetFromParent(currentOffset: Offset, parentOffset: Offset...
  function getRotateOffset (line 58) | function getRotateOffset(elStyle: CSSStyleDeclaration, currentOffset: Of...
  function findIndex (line 68) | function findIndex<T>(iterable: T[], callback: (el: T) => boolean): numb...
  function range (line 80) | function range(max: number): number[] {
  function clamp (line 88) | function clamp(val: number, min: number, max: number): number {
  function assign (line 92) | function assign(target: object, ...srcs: object[]): object {

FILE: src/utils/math.ts
  function degToRad (line 4) | function degToRad(deg: number): number {
  function radToDeg (line 8) | function radToDeg(rad: number): number {
  function quatToEuler (line 13) | function quatToEuler(q: quat): vec3 {
Condensed preview — 28 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (80K chars).
[
  {
    "path": ".editorconfig",
    "chars": 147,
    "preview": "root = true\n\n[*]\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\nindent_sty"
  },
  {
    "path": ".gitignore",
    "chars": 1750,
    "preview": "\n# Created by https://www.gitignore.io/api/node,visualstudiocode\n# Edit at https://www.gitignore.io/?templates=node,visu"
  },
  {
    "path": "LICENSE",
    "chars": 1065,
    "preview": "MIT License\n\nCopyright (c) 2019 WoodNeck\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\no"
  },
  {
    "path": "README.md",
    "chars": 1332,
    "preview": "# 📷 CSS-CAMERA\n\n<!-- BADGES -->\n![npm](https://img.shields.io/npm/v/css-camera?style=for-the-badge)\n![GitHub](https://im"
  },
  {
    "path": "demo/css/fpv.css",
    "chars": 1321,
    "preview": "* {\n  box-sizing: border-box;\n  transform-style: preserve-3d;\n  font-size: 0;\n}\nhtml, body {\n  position: relative;\n  wid"
  },
  {
    "path": "demo/css/index.css",
    "chars": 3088,
    "preview": "* {\n  box-sizing: border-box;\n}\nhtml, body {\n  position: relative;\n  width: 100%;\n  height: 100%;\n  margin: 0;\n  padding"
  },
  {
    "path": "demo/css/ortho.css",
    "chars": 4877,
    "preview": "* {\n  box-sizing: border-box;\n}\nhtml, body {\n  position: relative;\n  width: 100vw;\n  height: 100vh;\n  margin: 0;\n  paddi"
  },
  {
    "path": "demo/fpv.html",
    "chars": 4652,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>css-camera</title>\n    <meta charsset=\"utf-8\" />\n    <meta name=\"viewport\" co"
  },
  {
    "path": "demo/index.html",
    "chars": 6055,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>css-camera</title>\n    <meta charsset=\"utf-8\" />\n    <meta name=\"viewport\" co"
  },
  {
    "path": "demo/js/fpv.js",
    "chars": 6080,
    "preview": "const windowHeight = window.innerHeight;\nconst camera = new CSSCamera(\"#space\", {\n  position: [0, 0, -10],\n  perspective"
  },
  {
    "path": "demo/js/index.js",
    "chars": 1475,
    "preview": "const windowWidth = document.body.offsetWidth;\n\nconst cardButton = document.querySelector(\"#card-button\");\nconst codeBut"
  },
  {
    "path": "demo/js/ortho.js",
    "chars": 1100,
    "preview": "const windowWidth = window.innerWidth;\nconst windowHeight = window.innerHeight;\nconst center = document.querySelector(\"#"
  },
  {
    "path": "demo/ortho.html",
    "chars": 4594,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>css-camera</title>\n    <meta charsset=\"utf-8\" />\n    <meta name=\"viewport\" co"
  },
  {
    "path": "jsdoc.json",
    "chars": 1159,
    "preview": "{\n  \"tags\": {\n    \"allowUnknownTags\" : false,\n    \"dictionaries\": [\"jsdoc\", \"closure\"]\n  },\n  \"source\": {\n      \"include"
  },
  {
    "path": "package.json",
    "chars": 2017,
    "preview": "{\n  \"name\": \"css-camera\",\n  \"version\": \"1.0.1-snapshot\",\n  \"description\": \"Add a depth to your web page with CSS3 3D tra"
  },
  {
    "path": "rollup.config.js",
    "chars": 835,
    "preview": "const buildHelper = require(\"@egjs/build-helper\");\n\nconst name = \"CSSCamera\";\nconst external = {\n\t\"gl-matrix\": \"gl-matri"
  },
  {
    "path": "src/CSSCamera.ts",
    "chars": 16898,
    "preview": "import { mat4, vec3, quat } from 'gl-matrix';\nimport { getElement, applyCSS, getTransformMatrix, findIndex, getOffsetFro"
  },
  {
    "path": "src/constants/default.ts",
    "chars": 734,
    "preview": "export const STYLE = {\n  VIEWPORT: {\n    width: '100%',\n    height: '100%',\n    'transform-style': 'preserve-3d',\n    ov"
  },
  {
    "path": "src/constants/error.ts",
    "chars": 244,
    "preview": "export const ELEMENT_NOT_EXIST = (selector: string) => `Element with selector \"${selector}\" doesn't exist.`;\nexport cons"
  },
  {
    "path": "src/index.ts",
    "chars": 89,
    "preview": "import CSSCamera from './CSSCamera';\n\nexport * from './types';\nexport default CSSCamera;\n"
  },
  {
    "path": "src/index.umd.ts",
    "chars": 64,
    "preview": "import CSSCamera from './CSSCamera';\n\nexport default CSSCamera;\n"
  },
  {
    "path": "src/types.ts",
    "chars": 1219,
    "preview": "export type ValueOf<T> = T[keyof T];\n\nexport type Matrix4x4 = [\n  number, number, number, number,\n  number, number, numb"
  },
  {
    "path": "src/utils/helper.ts",
    "chars": 3166,
    "preview": "import { mat4, vec3 } from 'gl-matrix';\nimport { ELEMENT_NOT_EXIST, MUST_STRING_OR_ELEMENT } from '../constants/error';\n"
  },
  {
    "path": "src/utils/math.ts",
    "chars": 955,
    "preview": "import { mat4, quat, vec3 } from 'gl-matrix';\nimport { clamp } from './helper';\n\nexport function degToRad(deg: number): "
  },
  {
    "path": "test/manual/css/common.css",
    "chars": 2380,
    "preview": "html {\n  height: 100%;\n}\nbody {\n  width: 100%;\n  height: 100%;\n  min-height: 100%;\n  margin: 0;\n}\n\n/* .cc-camera {\n  tra"
  },
  {
    "path": "test/manual/test.html",
    "chars": 5782,
    "preview": "<!DOCTYPE html>\n<html>\n  <title>CSS-Camera Demo Page</title>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"w"
  },
  {
    "path": "tsconfig.json",
    "chars": 1014,
    "preview": "{\n    \"compilerOptions\": {\n        \"target\": \"es5\",\n\n        // Module\n        \"module\": \"es2015\",\n        \"moduleResolu"
  },
  {
    "path": "tslint.json",
    "chars": 726,
    "preview": "{\n    \"defaultSeverity\": \"error\",\n    \"extends\": [\n      \"tslint:recommended\"\n    ],\n    \"rulesDirectory\": [\n      \"tsli"
  }
]

About this extraction

This page contains the full source code of the WoodNeck/css-camera GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 28 files (73.1 KB), approximately 22.1k tokens, and a symbol index with 50 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!