markuapad 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.babelrc +3 -0
- data/.gitignore +30 -0
- data/.jshintrc +4 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +30 -0
- data/Rakefile +1 -0
- data/app/assets/javascripts/markuapad.js +72 -0
- data/app/assets/stylesheets/markuapad.css +1 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/bower.json +30 -0
- data/build/app.css +1 -0
- data/build/app.js +72 -0
- data/build/browser-polyfill.min.js +3 -0
- data/build/dist.css +1 -0
- data/build/dist.js +72 -0
- data/build/index.css +1 -0
- data/build/index.html +30 -0
- data/build/index.js +1 -0
- data/build/web.css +1 -0
- data/build/web.js +72 -0
- data/lib/markuapad.rb +5 -0
- data/lib/markuapad/engine.rb +4 -0
- data/lib/markuapad/version.rb +3 -0
- data/markuapad.gemspec +32 -0
- data/package.json +55 -0
- data/prepare_gem.sh +3 -0
- data/src/dist.js +7 -0
- data/src/file_accessor.js +77 -0
- data/src/index.js +180 -0
- data/src/jsx/editor.jsx +122 -0
- data/src/jsx/file_browser.jsx +252 -0
- data/src/jsx/file_browser_list_item.jsx +86 -0
- data/src/jsx/image_modal.jsx +49 -0
- data/src/jsx/live_preview.jsx +36 -0
- data/src/jsx/main.jsx +145 -0
- data/src/jsx/preview.jsx +32 -0
- data/src/jsx/toolbar.jsx +24 -0
- data/src/markuapad.js +39 -0
- data/src/styles/_editor.scss +27 -0
- data/src/styles/_extendables.scss +115 -0
- data/src/styles/_file_browser.scss +160 -0
- data/src/styles/_grid-settings.scss +13 -0
- data/src/styles/_layout.scss +18 -0
- data/src/styles/_modal.scss +145 -0
- data/src/styles/_preview.scss +65 -0
- data/src/styles/_toolbar.scss +99 -0
- data/src/styles/_variables.scss +25 -0
- data/src/styles/_workspace.scss +31 -0
- data/src/styles/app.scss +52 -0
- data/src/styles/index.scss +41 -0
- data/src/util.js +43 -0
- data/src/web.js +4 -0
- data/test/editor.es6 +6 -0
- data/webpack.config.js +48 -0
- metadata +129 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import FileAccessor from "../file_accessor"
|
|
3
|
+
|
|
4
|
+
class ImageModal extends React.Component {
|
|
5
|
+
|
|
6
|
+
componentDidMount() {
|
|
7
|
+
this.grabImageData();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
componentWillReceiveProps(nextProps) {
|
|
11
|
+
if (nextProps.file !== this.props.file) this.grabImageData();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
grabImageData() {
|
|
15
|
+
FileAccessor.get(this.props.file.filename, this.forceUpdate.bind(this), "image");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
getSelectedImageDataURI() {
|
|
19
|
+
let imageFile = FileAccessor.getSync(this.props.file.filename, "image")
|
|
20
|
+
|
|
21
|
+
if (!imageFile || !imageFile.data)
|
|
22
|
+
return null
|
|
23
|
+
|
|
24
|
+
return `data:${imageFile.mimetype.string};base64,${imageFile.data}`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
render() {
|
|
28
|
+
return (
|
|
29
|
+
<div className="modal">
|
|
30
|
+
<div className="modal-fade-screen">
|
|
31
|
+
<div className="modal-inner">
|
|
32
|
+
<div className="modal-close" onClick={ this.props.onClose }></div>
|
|
33
|
+
<h1>{ this.props.file.filename }</h1>
|
|
34
|
+
<div className="modal-content">
|
|
35
|
+
<img src={ this.getSelectedImageDataURI() } />
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
ImageModal.propTypes = {
|
|
45
|
+
file: React.PropTypes.object.isRequired,
|
|
46
|
+
onClose: React.PropTypes.func.isRequired
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default ImageModal;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { fixImagePaths } from "../util"
|
|
3
|
+
import { default as $ } from "jquery"
|
|
4
|
+
|
|
5
|
+
class LivePreview extends React.Component {
|
|
6
|
+
componentDidUpdate(lastProps, lastState) {
|
|
7
|
+
if (this.props.previewState !== lastProps.previewState) {
|
|
8
|
+
let cursor = $("[data-markua-cursor-position='__markuaCursorPosition__']").get(0)
|
|
9
|
+
if (cursor)
|
|
10
|
+
$(React.findDOMNode(this)).animate({
|
|
11
|
+
scrollTop: cursor.offsetTop - 100
|
|
12
|
+
}, 200);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
render() {
|
|
17
|
+
return (
|
|
18
|
+
<section className={`live-preview ${this.props.previewState}`}>
|
|
19
|
+
<section className="preloader"></section>
|
|
20
|
+
<section className="previewWarnigs"></section>
|
|
21
|
+
<section className="previewErrors">
|
|
22
|
+
{ this.props.previewErrors }
|
|
23
|
+
</section>
|
|
24
|
+
<div ref="content" dangerouslySetInnerHTML={{ __html: fixImagePaths(this.props.html, this.forceUpdate.bind(this)) }} className="container content" />
|
|
25
|
+
</section>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
LivePreview.propTypes = {
|
|
31
|
+
html: React.PropTypes.string.isRequired,
|
|
32
|
+
previewState: React.PropTypes.string.isRequired,
|
|
33
|
+
previewErrors: React.PropTypes.string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default LivePreview;
|
data/src/jsx/main.jsx
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import Editor from "./editor";
|
|
4
|
+
import Toolbar from "./toolbar";
|
|
5
|
+
import FileBrowser from "./file_browser";
|
|
6
|
+
import Preview from "./preview";
|
|
7
|
+
import LivePreview from "./live_preview";
|
|
8
|
+
import FileAccessor from "../file_accessor";
|
|
9
|
+
import ImageModal from "./image_modal";
|
|
10
|
+
import _ from "underscore";
|
|
11
|
+
|
|
12
|
+
import { getCached } from "../util"
|
|
13
|
+
|
|
14
|
+
class Main extends React.Component {
|
|
15
|
+
constructor(props) {
|
|
16
|
+
super(props);
|
|
17
|
+
|
|
18
|
+
// Setup some initial state
|
|
19
|
+
this.state = {
|
|
20
|
+
currentFile: null,
|
|
21
|
+
imageFile: null,
|
|
22
|
+
previewState: 'closed',
|
|
23
|
+
inLiveMode: true,
|
|
24
|
+
previewHtml: ""
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Autobind
|
|
28
|
+
this.onChangeFile = this.onChangeFile.bind(this);
|
|
29
|
+
this.onGeneratePreview = this.onGeneratePreview.bind(this);
|
|
30
|
+
this.onPreviewReady = this.onPreviewReady.bind(this);
|
|
31
|
+
this.onClosePreview = this.onClosePreview.bind(this);
|
|
32
|
+
this.toggleLiveMode = this.toggleLiveMode.bind(this);
|
|
33
|
+
this.onBookContentChanged = this.onBookContentChanged.bind(this);
|
|
34
|
+
this.getWorkspaceClass = this.getWorkspaceClass.bind(this);
|
|
35
|
+
this.onManuscriptChange = this.onManuscriptChange.bind(this);
|
|
36
|
+
this.onFileAdded = this.onFileAdded.bind(this);
|
|
37
|
+
this.onPreviewImage = this.onPreviewImage.bind(this);
|
|
38
|
+
|
|
39
|
+
// File access hooks
|
|
40
|
+
FileAccessor.onDelete(this.onManuscriptChange);
|
|
41
|
+
FileAccessor.onManuscriptChange(this.onManuscriptChange);
|
|
42
|
+
FileAccessor.onAdd(this.onFileAdded);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Lifecycle methods
|
|
46
|
+
componentDidMount() {
|
|
47
|
+
// Trigger a preview right away -- since we start in live mode
|
|
48
|
+
this.onGeneratePreview();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// File event operations
|
|
52
|
+
onFileAdded(file) {
|
|
53
|
+
this.setState({ currentFile: file });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
onManuscriptChange() {
|
|
57
|
+
// Re-preview
|
|
58
|
+
if (this.state.inLiveMode)
|
|
59
|
+
this.onGeneratePreview();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
onPreviewImage(file) {
|
|
63
|
+
this.setState({ imageFile: file });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
onChangeFile(file) {
|
|
67
|
+
this.setState({ currentFile: file });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Preview based methods
|
|
71
|
+
onPreviewReady(errors, html) {
|
|
72
|
+
// If someone has already stopped the preview then just bail
|
|
73
|
+
if (this.state.previewState === "closed")
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
this.setState({ previewHtml: html, previewState: "done", previewErrors: errors })
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
onGeneratePreview(e) {
|
|
80
|
+
// Call out to the markua processor
|
|
81
|
+
this.setState({ previewState: "previewing" });
|
|
82
|
+
this.props.markua.run(this.onPreviewReady, { cursor: getCached("markuapad_cursor") })
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
onClosePreview(e) {
|
|
86
|
+
this.setState({ previewState: "closed" })
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
toggleLiveMode() {
|
|
90
|
+
// Clear the preview state here as well, so we don't accidentally open a preview
|
|
91
|
+
this.setState({ inLiveMode: !this.state.inLiveMode, previewState: "closed" }, () => {
|
|
92
|
+
window.dispatchEvent(new Event('resize'));
|
|
93
|
+
|
|
94
|
+
// If we transition into live mode, then kick off an initial preview;
|
|
95
|
+
if (this.state.inLiveMode)
|
|
96
|
+
this.onGeneratePreview();
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
onBookContentChanged() {
|
|
101
|
+
if (this.state.inLiveMode)
|
|
102
|
+
this.onGeneratePreview();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
getWorkspaceClass() {
|
|
106
|
+
let workspaceClass = `workspace`;
|
|
107
|
+
workspaceClass += this.state.inLiveMode ? ' live' : '';
|
|
108
|
+
return workspaceClass;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
render() {
|
|
112
|
+
return (
|
|
113
|
+
<section className="markuapad">
|
|
114
|
+
<Toolbar
|
|
115
|
+
bookTitle={this.props.bookTitle}
|
|
116
|
+
onGeneratePreview={this.onGeneratePreview}
|
|
117
|
+
toggleLiveMode={this.toggleLiveMode}
|
|
118
|
+
inLiveMode={this.state.inLiveMode}
|
|
119
|
+
/>
|
|
120
|
+
<section className="main-view">
|
|
121
|
+
<section className={this.getWorkspaceClass()}>
|
|
122
|
+
<FileBrowser onPreviewImage={this.onPreviewImage} onChangeFile={this.onChangeFile} currentFile={this.state.currentFile} projectRoot={this.props.projectRoot }/>
|
|
123
|
+
<Editor
|
|
124
|
+
onBookContentChanged={this.onBookContentChanged}
|
|
125
|
+
inLiveMode={this.state.inLiveMode}
|
|
126
|
+
currentFile={this.state.currentFile}
|
|
127
|
+
/>
|
|
128
|
+
{ this.state.inLiveMode ? <LivePreview key='live-mode' ref="liveMode" html={this.state.previewHtml} previewState={this.state.previewState} previewErrors={this.state.previewErrors} /> : null }
|
|
129
|
+
</section>
|
|
130
|
+
</section>
|
|
131
|
+
{ this.state.inLiveMode ? <span /> : <Preview key='preview' onClosePreview={this.onClosePreview} html={this.state.previewHtml} previewState={this.state.previewState} previewErrors={this.state.previewErrors} /> }
|
|
132
|
+
{ this.state.imageFile ? <ImageModal file={this.state.imageFile} onClose={ _.partial(this.onPreviewImage, null) } /> : null }
|
|
133
|
+
</section>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
Main.propTypes = {
|
|
139
|
+
markua: React.PropTypes.object.isRequired,
|
|
140
|
+
bookTitle: React.PropTypes.string.isRequired,
|
|
141
|
+
projectRoot: React.PropTypes.string.isRequired,
|
|
142
|
+
options: React.PropTypes.object.isRequired
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export default Main;
|
data/src/jsx/preview.jsx
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { fixImagePaths } from "../util"
|
|
3
|
+
|
|
4
|
+
class Preview extends React.Component {
|
|
5
|
+
render() {
|
|
6
|
+
let clazz = `preview ${this.props.previewState}`
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<section className={clazz}>
|
|
10
|
+
<header className="container">
|
|
11
|
+
<button className="close" onClick={this.props.onClosePreview}><i className="fa fa-times fa-2x"></i></button>
|
|
12
|
+
</header>
|
|
13
|
+
<section className="preloader"></section>
|
|
14
|
+
<section className="previewWarnigs"></section>
|
|
15
|
+
<section className="previewErrors">
|
|
16
|
+
{ this.props.previewErrors }
|
|
17
|
+
</section>
|
|
18
|
+
<div dangerouslySetInnerHTML={{ __html: fixImagePaths(this.props.html, this.forceUpdate.bind(this)) }} className="container content" />
|
|
19
|
+
</section>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
Preview.propTypes = {
|
|
25
|
+
inLiveMode: React.PropTypes.bool.isRequired,
|
|
26
|
+
onClosePreview: React.PropTypes.func.isRequired,
|
|
27
|
+
html: React.PropTypes.string.isRequired,
|
|
28
|
+
previewState: React.PropTypes.string.isRequired,
|
|
29
|
+
previewErrors: React.PropTypes.string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default Preview;
|
data/src/jsx/toolbar.jsx
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
class Toolbar extends React.Component {
|
|
4
|
+
render() {
|
|
5
|
+
return (
|
|
6
|
+
<nav className="toolbar">
|
|
7
|
+
<h3 className="book-title">{ this.props.bookTitle}</h3>
|
|
8
|
+
<ul className="actions">
|
|
9
|
+
<li><a className={this.props.inLiveMode ? 'active' : '' } onClick={this.props.toggleLiveMode}><i className="fa fa-columns"></i> Live Mode</a></li>
|
|
10
|
+
<li><a className={this.props.inLiveMode ? 'disabled' : ''} onClick={this.props.onGeneratePreview}><i className="fa fa-play"></i> Preview</a></li>
|
|
11
|
+
</ul>
|
|
12
|
+
</nav>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
Toolbar.propTypes = {
|
|
18
|
+
inLiveMode: React.PropTypes.bool.isRequired,
|
|
19
|
+
toggleLiveMode: React.PropTypes.func.isRequired,
|
|
20
|
+
onGeneratePreview: React.PropTypes.func.isRequired,
|
|
21
|
+
bookTitle: React.PropTypes.string.isRequired
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default Toolbar;
|
data/src/markuapad.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Get the stylesheet
|
|
2
|
+
require("./styles/app.scss");
|
|
3
|
+
|
|
4
|
+
import _ from "underscore";
|
|
5
|
+
import _string from "underscore.string";
|
|
6
|
+
import Main from "./jsx/main";
|
|
7
|
+
import React from "react";
|
|
8
|
+
import Markua from "markua-js";
|
|
9
|
+
import FileAccessor from "./file_accessor";
|
|
10
|
+
|
|
11
|
+
_.string = _string;
|
|
12
|
+
|
|
13
|
+
let DEFAULT_OPTIONS = {}
|
|
14
|
+
|
|
15
|
+
class Markuapad {
|
|
16
|
+
constructor() {
|
|
17
|
+
this.options = DEFAULT_OPTIONS;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
create(elementId, options = {}) {
|
|
21
|
+
// apply options to defaults
|
|
22
|
+
this.options = _.extend(this.options, options);
|
|
23
|
+
|
|
24
|
+
// Project Root
|
|
25
|
+
let projectTitle = options.title || "My First Markuapad Book";
|
|
26
|
+
let projectRoot = options.slug || _.string.slugify(projectTitle);
|
|
27
|
+
|
|
28
|
+
// Instantiate a new markua processor instance
|
|
29
|
+
this.markua = new Markua(projectRoot, { fileAccessor: this.options.fileAccessor })
|
|
30
|
+
|
|
31
|
+
// Setup the markuapad file accessor
|
|
32
|
+
FileAccessor.setup(this.options.fileAccessor, projectRoot);
|
|
33
|
+
|
|
34
|
+
// Render the markuapad
|
|
35
|
+
React.render(React.createFactory(Main)({ bookTitle: projectTitle, options: options, markua: this.markua, projectRoot: projectRoot }), document.getElementById(elementId));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default new Markuapad();
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
.markuapad {
|
|
2
|
+
.editor {
|
|
3
|
+
@extend %box-shadow;
|
|
4
|
+
@include flex(1);
|
|
5
|
+
|
|
6
|
+
// Advanced font feature settings break the ace editor
|
|
7
|
+
font-feature-settings: initial;
|
|
8
|
+
-webkit-font-feature-settings: initial;
|
|
9
|
+
-ms-font-feature-settings: initial;
|
|
10
|
+
-moz-font-feature-settings: initial;
|
|
11
|
+
|
|
12
|
+
// No file selected styles
|
|
13
|
+
&.nofile {
|
|
14
|
+
text-align: center;
|
|
15
|
+
padding: 15% 0;
|
|
16
|
+
background: $editor-background;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Overrides from ace editor
|
|
20
|
+
&.emacs-mode .ace_cursor {
|
|
21
|
+
background-color: #222;
|
|
22
|
+
border: none !important;
|
|
23
|
+
border-left: 1px solid #222 !important;
|
|
24
|
+
width: 2px !important;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// TODO: move these intro individual files once they get to large.. e.g. stylesheets/common/extendables/lists.scss
|
|
2
|
+
// use this as the base file and include the extendables files from an extendables directory
|
|
3
|
+
|
|
4
|
+
// TODO: these shadows need some serious work.. consider using linear-gradient instead of box-shadow because we will have more control. Or at least ONLY use box shadow or ONLY use inset shadow... attching them to pseudo elements as linear gradients might be good
|
|
5
|
+
|
|
6
|
+
// Shadows
|
|
7
|
+
$shadow-color: rgba(0, 0, 0, 0.4);
|
|
8
|
+
$shadow-blur: 40px;
|
|
9
|
+
$shadow-spread: -$shadow-blur / 2;
|
|
10
|
+
|
|
11
|
+
// Inset
|
|
12
|
+
%inset-shadow-top { box-shadow: inset 0 7px $shadow-blur $shadow-spread $shadow-color; }
|
|
13
|
+
%inset-shadow-bottom { box-shadow: inset 0 -7px $shadow-blur $shadow-spread $shadow-color; }
|
|
14
|
+
|
|
15
|
+
%inset-shadow-top-bottom {
|
|
16
|
+
box-shadow: inset 0 7px $shadow-blur $shadow-spread $shadow-color,
|
|
17
|
+
inset 0 -7px $shadow-blur $shadow-spread $shadow-color;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Box shadows
|
|
21
|
+
%box-shadow-top { box-shadow: 0 -7px $shadow-blur $shadow-spread $shadow-color; }
|
|
22
|
+
%box-shadow-bottom { box-shadow: 0 7px $shadow-blur $shadow-spread $shadow-color; }
|
|
23
|
+
|
|
24
|
+
%box-shadow-top-bottom {
|
|
25
|
+
box-shadow: 0 7px $shadow-blur $shadow-spread $shadow-color,
|
|
26
|
+
0 -7px $shadow-blur $shadow-spread $shadow-color;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Box and Inset
|
|
30
|
+
%box-shadow-bottom-inset-top {
|
|
31
|
+
box-shadow: inset 0 7px $shadow-blur $shadow-spread $shadow-color,
|
|
32
|
+
0 7px $shadow-blur $shadow-spread $shadow-color;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
%book-drop-shadow {
|
|
36
|
+
position: relative;
|
|
37
|
+
box-shadow: 0 2px 3px rgba(0, 0, 0, .33);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
%box-shadow {
|
|
41
|
+
position: relative;
|
|
42
|
+
box-shadow: 0 0 15px rgba(0, 0, 0, 0.10);
|
|
43
|
+
}
|
|
44
|
+
%radial-drop-shadow {
|
|
45
|
+
position: relative;
|
|
46
|
+
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
|
|
47
|
+
&:after{
|
|
48
|
+
content: "";
|
|
49
|
+
position: absolute;
|
|
50
|
+
z-index: -1;
|
|
51
|
+
box-shadow: 0 0 40px rgba(0,0,0,0.8);
|
|
52
|
+
bottom: 0px;
|
|
53
|
+
left: 10%;
|
|
54
|
+
right: 10%;
|
|
55
|
+
width: 80%;
|
|
56
|
+
height: 50%;
|
|
57
|
+
border-radius: 100%;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Image replacement mixin http://nicolasgallagher.com/another-css-image-replacement-technique/
|
|
62
|
+
%replace-text {
|
|
63
|
+
border: 0;
|
|
64
|
+
font: 0/0 a;
|
|
65
|
+
text-shadow: none;
|
|
66
|
+
color: transparent;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Layout
|
|
70
|
+
%absolute-center {
|
|
71
|
+
position: absolute;
|
|
72
|
+
top: 0;
|
|
73
|
+
right: 0;
|
|
74
|
+
bottom: 0;
|
|
75
|
+
left: 0;
|
|
76
|
+
margin: auto;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Lists
|
|
80
|
+
%horizontal-list {
|
|
81
|
+
text-align: center;
|
|
82
|
+
li {
|
|
83
|
+
text-align: inherit;
|
|
84
|
+
display: inline-block;
|
|
85
|
+
list-style-type: none;
|
|
86
|
+
margin-right: 1em;
|
|
87
|
+
&:last-child { margin-right: 0; }
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Transitions and animations
|
|
92
|
+
%base-transition {
|
|
93
|
+
@include transition(all, $base-transition-duration, ease-in-out);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
%vanilla-button {
|
|
97
|
+
@include appearance(none);
|
|
98
|
+
background: initial;
|
|
99
|
+
border-radius: initial;
|
|
100
|
+
color: initial;
|
|
101
|
+
|
|
102
|
+
&:focus {
|
|
103
|
+
outline: none;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
%close-button {
|
|
108
|
+
@extend %vanilla-button;
|
|
109
|
+
|
|
110
|
+
position: absolute;
|
|
111
|
+
right: 5px;
|
|
112
|
+
top: 5px;
|
|
113
|
+
|
|
114
|
+
padding: 5px;
|
|
115
|
+
}
|