Merge branch 'master' into copyToClipboard

This commit is contained in:
Stefan Feješ
2018-03-06 08:59:32 +01:00
committed by GitHub
58 changed files with 1742 additions and 458 deletions

46
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,46 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at cc.glows@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

View File

@ -42,3 +42,5 @@ Brief description
If no link is provided, it defaults to 99+%. --> If no link is provided, it defaults to 99+%. -->
* https://caniuse.com/#feat=some-feature * https://caniuse.com/#feat=some-feature
<!-- tags: (separate each by a comma) -->

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

34
package-lock.json generated
View File

@ -236,7 +236,7 @@
"dev": true, "dev": true,
"requires": { "requires": {
"browserslist": "1.7.7", "browserslist": "1.7.7",
"caniuse-db": "1.0.30000810", "caniuse-db": "1.0.30000812",
"normalize-range": "0.1.2", "normalize-range": "0.1.2",
"num2fraction": "1.2.2", "num2fraction": "1.2.2",
"postcss": "5.2.18", "postcss": "5.2.18",
@ -261,7 +261,7 @@
"integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=",
"dev": true, "dev": true,
"requires": { "requires": {
"caniuse-db": "1.0.30000810", "caniuse-db": "1.0.30000812",
"electron-to-chromium": "1.3.34" "electron-to-chromium": "1.3.34"
} }
}, },
@ -1703,7 +1703,7 @@
"dev": true, "dev": true,
"requires": { "requires": {
"browserslist": "1.7.7", "browserslist": "1.7.7",
"caniuse-db": "1.0.30000810", "caniuse-db": "1.0.30000812",
"lodash.memoize": "4.1.2", "lodash.memoize": "4.1.2",
"lodash.uniq": "4.5.0" "lodash.uniq": "4.5.0"
}, },
@ -1714,16 +1714,16 @@
"integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=",
"dev": true, "dev": true,
"requires": { "requires": {
"caniuse-db": "1.0.30000810", "caniuse-db": "1.0.30000812",
"electron-to-chromium": "1.3.34" "electron-to-chromium": "1.3.34"
} }
} }
} }
}, },
"caniuse-db": { "caniuse-db": {
"version": "1.0.30000810", "version": "1.0.30000812",
"resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000810.tgz", "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000812.tgz",
"integrity": "sha1-vSWDDEHvq2Qzmi44H0lnc0PIRQk=", "integrity": "sha1-KcKN1pJ+5D6MKrZI5SNqyRbJfWk=",
"dev": true "dev": true
}, },
"caniuse-lite": { "caniuse-lite": {
@ -1934,6 +1934,11 @@
} }
} }
}, },
"classnames": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.5.tgz",
"integrity": "sha1-+zgB1FNGdknvNgPH1hoCvRKb3m0="
},
"cli-boxes": { "cli-boxes": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz",
@ -3107,6 +3112,14 @@
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
"dev": true "dev": true
}, },
"feather-icons": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/feather-icons/-/feather-icons-4.7.0.tgz",
"integrity": "sha512-/dKhEelpOazdN0MtcBZTgqo1tZHn35y40V0sGPi6FzzwNbQ5H6vzTqYWWMeRtKw5JePmog7RthR/PYPA1lYNFA==",
"requires": {
"classnames": "2.2.5"
}
},
"filename-regex": { "filename-regex": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz",
@ -3148,6 +3161,11 @@
"integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=", "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=",
"dev": true "dev": true
}, },
"focus-visible": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/focus-visible/-/focus-visible-4.1.0.tgz",
"integrity": "sha512-K+bqYCQOPs5qHFag28o36TyILpqx43Qlq8/YxWdXEOWy3LZmxk8P+dhI5oPzuC28nwwIcerRNwRNiT3Msk0MqA=="
},
"for-in": { "for-in": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
@ -7601,7 +7619,7 @@
"integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=",
"dev": true, "dev": true,
"requires": { "requires": {
"caniuse-db": "1.0.30000810", "caniuse-db": "1.0.30000812",
"electron-to-chromium": "1.3.34" "electron-to-chromium": "1.3.34"
} }
}, },

View File

@ -15,7 +15,7 @@
"babel-cli": "^6.26.0", "babel-cli": "^6.26.0",
"babel-preset-env": "^1.6.1", "babel-preset-env": "^1.6.1",
"babel-preset-stage-2": "^6.24.1", "babel-preset-stage-2": "^6.24.1",
"caniuse-db": "^1.0.30000810", "caniuse-db": "^1.0.30000812",
"jsdom": "^11.6.2", "jsdom": "^11.6.2",
"marked": "^0.3.16", "marked": "^0.3.16",
"node-sass": "^4.7.2", "node-sass": "^4.7.2",
@ -25,5 +25,9 @@
"prettier": "^1.10.2", "prettier": "^1.10.2",
"pretty": "^2.0.0", "pretty": "^2.0.0",
"prismjs": "^1.11.0" "prismjs": "^1.11.0"
},
"dependencies": {
"feather-icons": "^4.7.0",
"focus-visible": "^4.1.0"
} }
} }

View File

@ -2,71 +2,134 @@ const fs = require('fs')
const path = require('path') const path = require('path')
const marked = require('marked') const marked = require('marked')
const pretty = require('pretty') const pretty = require('pretty')
const { JSDOM } = require('jsdom')
const caniuseDb = require('caniuse-db/data.json') const caniuseDb = require('caniuse-db/data.json')
const { toKebabCase, emptyHTML } = require('../utils/utils.js') const { toKebabCase, createElement, template, dom } = require('../utils/utils.js')
const renderer = new marked.Renderer()
renderer.heading = (text, level) =>
level === 3
? `<h${level} id="${toKebabCase(text)}">${text}</h${level}>`
: `<h${level} data-type="${text}">${text}</h${level}>`
renderer.link = (url, _, text) => `<a href="${url}" target="_blank">${text || url}</a>`
const SNIPPETS_PATH = './snippets' const SNIPPETS_PATH = './snippets'
const SNIPPET_CONTAINER_SELECTOR = '.main > .container' const TAGS = [
{
name: 'all',
icon: 'check'
},
{
name: 'layout',
icon: 'layout'
},
{
name: 'visual',
icon: 'eye'
},
{
name: 'animation',
icon: 'loader'
},
{
name: 'interactivity',
icon: 'edit-2'
}
]
const createElement = str => { const renderer = new marked.Renderer()
const el = document.createElement('div') renderer.heading = (text, level) => {
el.innerHTML = str if (level === 3) {
return el.firstElementChild return `<h${level} id="${toKebabCase(text)}"><span>${text}</span></h${level}>`
} else {
return ['HTML', 'CSS', 'JavaScript'].includes(text)
? `<h${level} data-type="${text}">${text}</h${level}>`
: `<h${level}>${text}</h${level}>`
}
}
renderer.link = (url, _, text) => `<a href="${url}" target="_blank">${text || url}</a>`
const document = dom('./src/html/index.html')
const components = {
backToTopButton: dom('./src/html/components/back-to-top-button.html'),
sidebar: dom('./src/html/components/sidebar.html'),
header: dom('./src/html/components/header.html'),
main: dom('./src/html/components/main.html'),
tags: dom('./src/html/components/tags.html')
} }
const template = markdown => ` const snippetContainer = components.main.querySelector('.container')
<div class="snippet"> const sidebarLinkContainer = components.sidebar.querySelector('.sidebar__links')
${markdown} TAGS.slice(1).forEach(tag => {
</div> sidebarLinkContainer.append(
` createElement(`
<section data-type="${tag.name}" class="sidebar__section">
const document = new JSDOM(fs.readFileSync('./index.html', 'utf8')).window.document <h4 class="sidebar__section-heading">${tag.name}</h4>
const snippetContainer = document.querySelector('.main > .container') </section>
const sidebarLinks = document.querySelector('.sidebar__links') `)
emptyHTML(snippetContainer, sidebarLinks) )
})
for (const snippetFile of fs.readdirSync(SNIPPETS_PATH)) { for (const snippetFile of fs.readdirSync(SNIPPETS_PATH)) {
const snippetPath = path.join(SNIPPETS_PATH, snippetFile) const snippetPath = path.join(SNIPPETS_PATH, snippetFile)
const snippetData = fs.readFileSync(snippetPath, 'utf8') const snippetData = fs.readFileSync(snippetPath, 'utf8')
const markdown = marked(snippetData, { renderer }) const markdown = marked(snippetData, { renderer })
const snippetTemplate = template(markdown) const snippetEl = createElement(`<div class="snippet">${markdown}</div>`)
const el = createElement(snippetTemplate) snippetContainer.append(snippetEl)
snippetContainer.append(el)
sidebarLinks.append(
createElement(
`<a class="sidebar__link" href="#${snippetFile.replace('.md', '')}">${
el.querySelector('h3').innerHTML
}</a>`
)
)
// browser support usage
const featUsageShares = (snippetData.match(/https?:\/\/caniuse\.com\/#feat=.*/g) || []).map( const featUsageShares = (snippetData.match(/https?:\/\/caniuse\.com\/#feat=.*/g) || []).map(
feat => { feat => {
const featData = caniuseDb.data[feat.match(/#feat=(.*)/)[1]] const featData = caniuseDb.data[feat.match(/#feat=(.*)/)[1]]
return featData ? Number(featData.usage_perc_y + featData.usage_perc_a) : 100 return featData ? Number(featData.usage_perc_y + featData.usage_perc_a) : 100
} }
) )
const browserSupportHeading = snippetEl.querySelector('h4:last-of-type')
el.querySelector('h4:last-of-type').after( browserSupportHeading.after(
createElement(` createElement(`
<div> <div>
<div class="snippet__browser-support"> <div class="snippet__browser-support">
${featUsageShares.length ? Math.min(...featUsageShares).toPrecision(3) : '99+'}% ${featUsageShares.length ? Math.min(...featUsageShares).toPrecision(3) : '99+'}%
</div> </div>
</div> </div>
`) `)
) )
// sidebar link
const link = createElement(
`<a class="sidebar__link" href="#${snippetFile.replace('.md', '')}">${
snippetEl.querySelector('h3').innerHTML
}</a>`
)
// tags
const tags = (snippetData.match(/<!--\s*tags:\s*(.+?)-->/) || [, ''])[1]
.split(/,\s*/)
.forEach(tag => {
tag = tag.trim().toLowerCase()
snippetEl
.querySelector('h3')
.append(
createElement(
`<span class="tags__tag snippet__tag" data-type="${tag}"><i data-feather="${
TAGS.find(t => t.name === tag).icon
}"></i>${tag}</span>`
)
)
sidebarLinkContainer.querySelector(`section[data-type="${tag}"]`).append(link)
})
} }
// build dom
TAGS.forEach(tag =>
components.tags.append(
createElement(
`<button class="tags__tag is-large ${tag.name === 'all' ? 'is-active' : ''}" data-type="${
tag.name
}"><i data-feather="${tag.icon}"></i>${tag.name}</button>`
)
)
)
const content = document.querySelector('.content-wrapper')
content.before(components.backToTopButton)
content.before(components.sidebar)
content.append(components.header)
content.append(components.main)
components.main.querySelector('.container').prepend(components.tags)
// doctype declaration gets stripped, add it back in // doctype declaration gets stripped, add it back in
const html = `<!DOCTYPE html> const html = `<!DOCTYPE html>
${pretty(document.documentElement.outerHTML, { ocd: true })} ${pretty(document.documentElement.outerHTML, { ocd: true })}

113
snippets/bouncing-loader.md Normal file
View File

@ -0,0 +1,113 @@
### Bouncing loader
Creates a bouncing loader animation.
#### HTML
```html
<div class="bouncing-loader">
<div></div>
<div></div>
<div></div>
</div>
```
#### CSS
```css
@keyframes bouncing-loader {
from {
opacity: 1;
transform: translateY(0);
}
to {
opacity: 0.1;
transform: translateY(-1rem);
}
}
.bouncing-loader {
display: flex;
justify-content: center;
}
.bouncing-loader > div {
width: 1rem;
height: 1rem;
margin: 3rem 0.2rem;
background: #8385aa;
border-radius: 50%;
animation: bouncing-loader 0.6s infinite alternate;
}
.bouncing-loader > div:nth-of-type(2) {
animation-delay: 0.2s;
}
.bouncing-loader > div:nth-of-type(3) {
animation-delay: 0.4s;
}
```
#### Demo
<div class="snippet-demo">
<div class="snippet-demo__bouncing-loader">
<div></div>
<div></div>
<div></div>
</div>
</div>
<style>
@keyframes bouncing-loader {
from {
opacity: 1;
transform: translateY(0);
}
to {
opacity: 0.1;
transform: translateY(-1rem);
}
}
.snippet-demo__bouncing-loader {
display: flex;
justify-content: center;
}
.snippet-demo__bouncing-loader > div {
width: 1rem;
height: 1rem;
margin: 3rem 0.2rem;
background: #8385aa;
border-radius: 50%;
animation: bouncing-loader 0.6s infinite alternate;
}
.snippet-demo__bouncing-loader > div:nth-child(2) {
animation-delay: 0.2s;
}
.snippet-demo__bouncing-loader > div:nth-child(3) {
animation-delay: 0.4s;
}
</style>
#### Explanation
Note: `1rem` is usually `16px`.
1. `@keyframes` defines an animation that has two states, where the element changes `opacity`, and is translated up on the 2D plane using `transform: translateY()`.
2. `.bouncing-loader` is the parent container of the bouncing circles and uses `display: flex`
and `justify-content: center` to position them in the in the center.
3. Using `.bouncing-loader > div`, we target the three child `div`s of the parent to be styled. The `div`s are given a width and height of `1rem`, using `border-radius: 50%` to turn them from squares to circles.
4. `margin: 3rem 0.2rem` specifies that each circle has a top/bottom margin of `3rem` and left/right margin
of `0.2rem` so that they do not directly touch each other, giving them some breathing room.
5. `animation` is a shorthand property for the various animation properties: `animation-name`, `animation-duration`, `animation-iteration-count`, `animation-direction` are used.
6. `animation-delay` is used on the second and third `div` respectively, so that each element does not start the animation at the same time.
#### Browser support
<span class="snippet__support-note">✅ No caveats.</span>
* https://caniuse.com/#feat=css-animation
<!-- tags: animation -->

View File

@ -44,3 +44,5 @@ html {
<span class="snippet__support-note">✅ No caveats.</span> <span class="snippet__support-note">✅ No caveats.</span>
* https://caniuse.com/#feat=css3-boxsizing * https://caniuse.com/#feat=css3-boxsizing
<!-- tags: layout -->

50
snippets/circle.md Normal file
View File

@ -0,0 +1,50 @@
### Circle
Creates a circle shape with pure CSS.
#### HTML
```html
<div class="circle"></div>
```
#### CSS
```css
.circle {
border-radius: 50%;
width: 2rem;
height: 2rem;
background: #333;
}
```
#### Demo
<div class="snippet-demo">
<div class="snippet-demo__circle"></div>
</div>
<style>
.snippet-demo__circle {
border-radius: 50%;
width: 2rem;
height: 2rem;
background: #333;
}
</style>
#### Explanation
`border-radius: 50%` curves the borders of an element to create a circle.
Since a circle has the same radius at any given point, the `width` and `height` must be the same. Differing
values will create an ellipse.
#### Browser support
<span class="snippet__support-note">✅ No caveats.</span>
* https://caniuse.com/#feat=border-radius
<!-- tags: visual -->

View File

@ -18,7 +18,7 @@ Ensures that an element self-clears its children.
```css ```css
.clearfix::after { .clearfix::after {
content: ""; content: '';
display: block; display: block;
clear: both; clear: both;
} }
@ -59,4 +59,6 @@ Ensures that an element self-clears its children.
#### Browser support #### Browser support
<span class="snippet__support-note">✅ No caveats.</span> <span class="snippet__support-note">⚠️ For this snippet to work properly you need to ensure that there are no non-floating children in the container and that there are no tall floats before the clearfixed container but in the same formatting context (e.g. floated columns).</span>
<!-- tags: layout -->

View File

@ -43,3 +43,5 @@ causes an element's height to become a percentage of its parent's width, i.e. `5
#### Browser support #### Browser support
<span class="snippet__support-note">⚠️ `padding-top` pushes any content within the element to the bottom.</span> <span class="snippet__support-note">⚠️ `padding-top` pushes any content within the element to the bottom.</span>
<!-- tags: layout -->

View File

@ -19,13 +19,13 @@ Customizes the scrollbar style for the document and elements with scrollable ove
} }
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
box-shadow: inset 0 0 6px rgba(0,0,0,0.3); box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
border-radius: 10px; border-radius: 10px;
} }
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
border-radius: 10px; border-radius: 10px;
box-shadow: inset 0 0 6px rgba(0,0,0,0.5); box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.5);
} }
/* Scrollable element */ /* Scrollable element */
@ -80,3 +80,5 @@ There are many other pseudo-elements that you can use to style scrollbars. For m
<span class="snippet__support-note">⚠️ Scrollbar styling doesn't appear to be on any standards track.</span> <span class="snippet__support-note">⚠️ Scrollbar styling doesn't appear to be on any standards track.</span>
* https://caniuse.com/#feat=css-scrollbar * https://caniuse.com/#feat=css-scrollbar
<!-- tags: visual -->

View File

@ -48,3 +48,5 @@ Changes the styling of text selection.
in any specification.</span> in any specification.</span>
* https://caniuse.com/#feat=css-selection * https://caniuse.com/#feat=css-selection
<!-- tags: visual -->

View File

@ -37,5 +37,8 @@ Makes the content unselectable.
#### Browser support #### Browser support
<span class="snippet__support-note">⚠️ Requires prefixes for full support.</span> <span class="snippet__support-note">⚠️ Requires prefixes for full support.</span>
<span class="snippet__support-note">⚠️ This is not a secure method to prevent users from copying content.</span>
* https://caniuse.com/#feat=user-select-none * https://caniuse.com/#feat=user-select-none
<!-- tags: interactivity -->

View File

@ -12,8 +12,12 @@ Creates a donut spinner that can be used to indicate the loading of content.
```css ```css
@keyframes donut-spin { @keyframes donut-spin {
0% { transform: rotate(0deg); } 0% {
100% { transform: rotate(360deg); } transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
} }
.donut { .donut {
display: inline-block; display: inline-block;
@ -59,3 +63,5 @@ serve as the loading indicator for the donut. Use `animation` to rotate the elem
* https://caniuse.com/#feat=css-animation * https://caniuse.com/#feat=css-animation
* https://caniuse.com/#feat=transforms2d * https://caniuse.com/#feat=transforms2d
<!-- tags: animation -->

View File

@ -104,3 +104,5 @@ The variables are defined globally within the `:root` CSS pseudo-class which mat
<span class="snippet__support-note">✅ No caveats.</span> <span class="snippet__support-note">✅ No caveats.</span>
* https://caniuse.com/#feat=css-variables * https://caniuse.com/#feat=css-variables
<!-- tags: animation -->

View File

@ -49,3 +49,5 @@ of the background.
<span class="snippet__support-note">✅ No caveats.</span> <span class="snippet__support-note">✅ No caveats.</span>
* https://caniuse.com/#feat=css-textshadow * https://caniuse.com/#feat=css-textshadow
<!-- tags: visual -->

View File

@ -51,3 +51,5 @@ Alternatively, use `justify-content: space-around` to distribute the children wi
<span class="snippet__support-note">⚠️ Needs prefixes for full support.</span> <span class="snippet__support-note">⚠️ Needs prefixes for full support.</span>
* https://caniuse.com/#feat=flexbox * https://caniuse.com/#feat=flexbox
<!-- tags: layout -->

View File

@ -1,19 +1,19 @@
### Horizontal and vertical centering ### Flexbox centering
Horizontally and vertically centers a child element within a parent element. Horizontally and vertically centers a child element within a parent element using `flexbox`.
#### HTML #### HTML
```html ```html
<div class="horizontal-and-vertical-centering"> <div class="flexbox-centering">
<div class="child"></div> <div class="child">Centered content.</div>
</div> </div>
``` ```
#### CSS #### CSS
```css ```css
.horizontal-and-vertical-centering { .flexbox-centering {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@ -23,13 +23,13 @@ Horizontally and vertically centers a child element within a parent element.
#### Demo #### Demo
<div class="snippet-demo"> <div class="snippet-demo">
<div class="snippet-demo__horizontal-and-vertical-centering"> <div class="snippet-demo__flexbox-centering">
<p class="snippet-demo__horizontal-and-vertical-centering__child">Centered content.</p> <p class="snippet-demo__flexbox-centering__child">Centered content.</p>
</div> </div>
</div> </div>
<style> <style>
.snippet-demo__horizontal-and-vertical-centering { .snippet-demo__flexbox-centering {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@ -48,3 +48,5 @@ Horizontally and vertically centers a child element within a parent element.
<span class="snippet__support-note">⚠️ Needs prefixes for full support.</span> <span class="snippet__support-note">⚠️ Needs prefixes for full support.</span>
* https://caniuse.com/#feat=flexbox * https://caniuse.com/#feat=flexbox
<!-- tags: layout -->

View File

@ -49,3 +49,5 @@ Gives text a gradient color.
<span class="snippet__support-note">⚠️ Uses non-standard properties.</span> <span class="snippet__support-note">⚠️ Uses non-standard properties.</span>
* https://caniuse.com/#feat=text-stroke * https://caniuse.com/#feat=text-stroke
<!-- tags: visual -->

View File

@ -0,0 +1,52 @@
### Grid centering
Horizontally and vertically centers a child element within a parent element using `grid`.
#### HTML
```html
<div class="grid-centering">
<div class="child">Centered content.</div>
</div>
```
#### CSS
```css
.grid-centering {
display: grid;
justify-content: center;
align-items: center;
}
```
#### Demo
<div class="snippet-demo">
<div class="snippet-demo__grid-centering">
<p class="snippet-demo__grid-centering__child">Centered content.</p>
</div>
</div>
<style>
.snippet-demo__grid-centering {
display: grid;
justify-content: center;
align-items: center;
height: 200px;
}
</style>
#### Explanation
1. `display: grid` enables grid.
2. `justify-content: center` centers the child horizontally.
3. `align-items: center` centers the child vertically.
#### Browser support
<span class="snippet__support-note">✅ No caveats.</span>
* https://caniuse.com/#feat=css-grid
<!-- tags: layout -->

View File

@ -81,3 +81,5 @@ very sharp and crisp.
<hr> <hr>
\*Chrome does not support subpixel values on `border`. Safari does not support subpixel values on `box-shadow`. Firefox supports subpixel values on both. \*Chrome does not support subpixel values on `border`. Safari does not support subpixel values on `box-shadow`. Firefox supports subpixel values on both.
<!-- tags: visual -->

View File

@ -69,7 +69,7 @@ Creates an animated underline effect when the text is hovered over.
#### Explanation #### Explanation
1. `display: inline-block` makes the block `p` an `inline-block` to prevent the underline from 1. `display: inline-block` makes the block `p` an `inline-block` to prevent the underline from
spanning the entire parent width rather than just the content (text). spanning the entire parent width rather than just the content (text).
2. `position: relative` on the element establishes a Cartesian positioning context for pseudo-elements. 2. `position: relative` on the element establishes a Cartesian positioning context for pseudo-elements.
3. `::after` defines a pseudo-element. 3. `::after` defines a pseudo-element.
4. `position: absolute` takes the pseudo element out of the flow of the document and positions it in relation to the parent. 4. `position: absolute` takes the pseudo element out of the flow of the document and positions it in relation to the parent.
@ -77,11 +77,11 @@ spanning the entire parent width rather than just the content (text).
6. `transform: scaleX(0)` initially scales the pseudo element to 0 so it has no width and is not visible. 6. `transform: scaleX(0)` initially scales the pseudo element to 0 so it has no width and is not visible.
7. `bottom: 0` and `left: 0` position it to the bottom left of the block. 7. `bottom: 0` and `left: 0` position it to the bottom left of the block.
8. `transition: transform 0.25s ease-out` means changes to `transform` will be transitioned over 0.25 seconds 8. `transition: transform 0.25s ease-out` means changes to `transform` will be transitioned over 0.25 seconds
with an `ease-out` timing function. with an `ease-out` timing function.
9. `transform-origin: bottom right` means the transform anchor point is positioned at the bottom right of the block. 9. `transform-origin: bottom right` means the transform anchor point is positioned at the bottom right of the block.
10. `:hover::after` then uses `scaleX(1)` to transition the width to 100%, then changes the `transform-origin` 10. `:hover::after` then uses `scaleX(1)` to transition the width to 100%, then changes the `transform-origin`
to `bottom left` so that the anchor point is reversed, allowing it transition out in the other direction when to `bottom left` so that the anchor point is reversed, allowing it transition out in the other direction when
hovered off. hovered off.
#### Browser support #### Browser support
@ -89,3 +89,5 @@ hovered off.
* https://caniuse.com/#feat=transforms2d * https://caniuse.com/#feat=transforms2d
* https://caniuse.com/#feat=css-transitions * https://caniuse.com/#feat=css-transitions
<!-- tags: animation -->

View File

@ -41,7 +41,7 @@ A hover effect where the gradient follows the mouse cursor.
height: var(--size); height: var(--size);
background: radial-gradient(circle closest-side, pink, transparent); background: radial-gradient(circle closest-side, pink, transparent);
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
transition: width .2s ease, height .2s ease; transition: width 0.2s ease, height 0.2s ease;
} }
.mouse-cursor-gradient-tracking:hover::before { .mouse-cursor-gradient-tracking:hover::before {
@ -53,7 +53,7 @@ A hover effect where the gradient follows the mouse cursor.
```js ```js
var btn = document.querySelector('.mouse-cursor-gradient-tracking') var btn = document.querySelector('.mouse-cursor-gradient-tracking')
btn.onmousemove = function (e) { btn.onmousemove = function(e) {
var x = e.pageX - btn.offsetLeft var x = e.pageX - btn.offsetLeft
var y = e.pageY - btn.offsetTop var y = e.pageY - btn.offsetTop
btn.style.setProperty('--x', x + 'px') btn.style.setProperty('--x', x + 'px')
@ -131,7 +131,10 @@ var y = e.pageY - btn.offsetTop - btn.offsetParent.offsetTop
``` ```
#### Browser support #### Browser support
<div class="snippet__requires-javascript">Requires JavaScript</div> <div class="snippet__requires-javascript">Requires JavaScript</div>
<span class="snippet__support-note">⚠️ Requires JavaScript.</span> <span class="snippet__support-note">⚠️ Requires JavaScript.</span>
* https://caniuse.com/#feat=css-variables * https://caniuse.com/#feat=css-variables
<!-- tags: visual, interactivity -->

View File

@ -22,16 +22,19 @@ Adds a fading gradient to an overflowing element to better indicate there is mor
content: ''; content: '';
position: absolute; position: absolute;
bottom: 0; bottom: 0;
width: 300px; width: 240px;
height: 25px; height: 25px;
background: linear-gradient(rgba(255, 255, 255, 0.001), white); /* transparent keyword is broken in Safari */ background: linear-gradient(
rgba(255, 255, 255, 0.001),
white
); /* transparent keyword is broken in Safari */
pointer-events: none; pointer-events: none;
} }
.overflow-scroll-gradient__scroller { .overflow-scroll-gradient__scroller {
overflow-y: scroll; overflow-y: scroll;
background: white; background: white;
width: 300px; width: 240px;
height: 250px; height: 200px;
padding: 15px 0; padding: 15px 0;
line-height: 1.2; line-height: 1.2;
text-align: center; text-align: center;
@ -56,7 +59,7 @@ Adds a fading gradient to an overflowing element to better indicate there is mor
content: ''; content: '';
background: linear-gradient(rgba(255, 255, 255, 0.001), white); background: linear-gradient(rgba(255, 255, 255, 0.001), white);
position: absolute; position: absolute;
width: 300px; width: 240px;
height: 25px; height: 25px;
bottom: 0; bottom: 0;
pointer-events: none; pointer-events: none;
@ -64,8 +67,8 @@ Adds a fading gradient to an overflowing element to better indicate there is mor
.snippet-demo__overflow-scroll-gradient__scroller { .snippet-demo__overflow-scroll-gradient__scroller {
overflow-y: scroll; overflow-y: scroll;
background: white; background: white;
width: 300px; width: 240px;
height: 250px; height: 200px;
padding: 15px 0; padding: 15px 0;
line-height: 1.2; line-height: 1.2;
text-align: center; text-align: center;
@ -73,7 +76,7 @@ Adds a fading gradient to an overflowing element to better indicate there is mor
</style> </style>
<script> <script>
document.querySelector('.snippet-demo__overflow-scroll-gradient__scroller').innerHTML = 'content '.repeat(200) document.querySelector('.snippet-demo__overflow-scroll-gradient__scroller').innerHTML = 'content '.repeat(100)
</script> </script>
#### Explanation #### Explanation
@ -81,10 +84,10 @@ document.querySelector('.snippet-demo__overflow-scroll-gradient__scroller').inne
1. `position: relative` on the parent establishes a Cartesian positioning context for pseudo-elements. 1. `position: relative` on the parent establishes a Cartesian positioning context for pseudo-elements.
2. `::after` defines a pseudo element. 2. `::after` defines a pseudo element.
3. `background-image: linear-gradient(...)` adds a linear gradient that fades from transparent to white 3. `background-image: linear-gradient(...)` adds a linear gradient that fades from transparent to white
(top to bottom). (top to bottom).
4. `position: absolute` takes the pseudo element out of the flow of the document and positions it in relation to the parent. 4. `position: absolute` takes the pseudo element out of the flow of the document and positions it in relation to the parent.
5. `width: 300px` matches the size of the scrolling element (which is a child of the parent that has 5. `width: 240px` matches the size of the scrolling element (which is a child of the parent that has
the pseudo element). the pseudo element).
6. `height: 25px` is the height of the fading gradient pseudo-element, which should be kept relatively small. 6. `height: 25px` is the height of the fading gradient pseudo-element, which should be kept relatively small.
7. `bottom: 0` positions the pseudo-element at the bottom of the parent. 7. `bottom: 0` positions the pseudo-element at the bottom of the parent.
8. `pointer-events: none` specifies that the pseudo-element cannot be a target of mouse events, allowing text behind it to still be selectable/interactive. 8. `pointer-events: none` specifies that the pseudo-element cannot be a target of mouse events, allowing text behind it to still be selectable/interactive.
@ -94,3 +97,5 @@ document.querySelector('.snippet-demo__overflow-scroll-gradient__scroller').inne
<span class="snippet__support-note">✅ No caveats.</span> <span class="snippet__support-note">✅ No caveats.</span>
* https://caniuse.com/#feat=css-gradients * https://caniuse.com/#feat=css-gradients
<!-- tags: visual -->

View File

@ -75,3 +75,5 @@ Reveals an interactive popout menu on hover.
#### Browser support #### Browser support
<span class="snippet__support-note">✅ No caveats.</span> <span class="snippet__support-note">✅ No caveats.</span>
<!-- tags: interactivity -->

View File

@ -16,10 +16,7 @@ Natively implemented as `text-decoration-skip-ink: auto` but it has less control
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
display: inline; display: inline;
font-size: 18px; font-size: 18px;
text-shadow: 1px 1px 0 #f5f6f9, text-shadow: 1px 1px 0 #f5f6f9, -1px 1px 0 #f5f6f9, -1px -1px 0 #f5f6f9, 1px -1px 0 #f5f6f9;
-1px 1px 0 #f5f6f9,
-1px -1px 0 #f5f6f9,
1px -1px 0 #f5f6f9;
background-image: linear-gradient(90deg, currentColor 100%, transparent 100%); background-image: linear-gradient(90deg, currentColor 100%, transparent 100%);
background-position: 0 0.98em; background-position: 0 0.98em;
background-repeat: repeat-x; background-repeat: repeat-x;
@ -70,13 +67,13 @@ Natively implemented as `text-decoration-skip-ink: auto` but it has less control
#### Explanation #### Explanation
1. `text-shadow: ...` has 4 values with offsets that cover a 4x4 px area to ensure the underline 1. `text-shadow: ...` has 4 values with offsets that cover a 4x4 px area to ensure the underline
has a "thick" shadow that covers the line where descenders clip it. Use a color has a "thick" shadow that covers the line where descenders clip it. Use a color
that matches the background. For a larger font, use a larger `px` size. that matches the background. For a larger font, use a larger `px` size.
2. `background-image: linear-gradient(...)` creates a 90deg gradient with the current 2. `background-image: linear-gradient(...)` creates a 90deg gradient with the current
text color (`currentColor`). text color (`currentColor`).
3. The `background-*` properties size the gradient as 1x1px at the bottom and repeats it along the x-axis. 3. The `background-*` properties size the gradient as 1x1px at the bottom and repeats it along the x-axis.
4. The `::selection` pseudo selector ensures the text shadow does not interfere with text 4. The `::selection` pseudo selector ensures the text shadow does not interfere with text
selection. selection.
#### Browser support #### Browser support
@ -84,3 +81,5 @@ Natively implemented as `text-decoration-skip-ink: auto` but it has less control
* https://caniuse.com/#feat=css-textshadow * https://caniuse.com/#feat=css-textshadow
* https://caniuse.com/#feat=css-gradients * https://caniuse.com/#feat=css-gradients
<!-- tags: visual -->

View File

@ -0,0 +1,47 @@
### Reset all styles
Resets all styles to default values with one property. This will not affect `direction` and `unicode-bidi` properties.
#### HTML
```html
<div class="reset-all-styles">
<h4>Title</h4>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Iure id exercitationem nulla qui repellat laborum vitae, molestias tempora velit natus. Quas, assumenda nisi. Quisquam enim qui iure, consequatur velit sit?</p>
</div>
```
#### CSS
```css
.reset-all-styles {
all: initial;
}
```
#### Demo
<div class="snippet-demo">
<div class="snippet-demo__reset-all-styles">
<h3>Title</h3>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Iure id exercitationem nulla qui repellat laborum vitae, molestias tempora velit natus. Quas, assumenda nisi. Quisquam enim qui iure, consequatur velit sit?</p>
</div>
</div>
<style>
.snippet-demo__reset-all-styles {
all: initial;
}
</style>
#### Explanation
The `all` property allows you to reset all styles (inherited or not) to default values.
#### Browser support
<span class="snippet__support-note">⚠️ MS Edge status is under consideration.</span>
* https://caniuse.com/#feat=css-all
<!-- tags: visual -->

View File

@ -14,6 +14,7 @@ Uses an SVG shape to separate two different blocks to create more a interesting
.shape-separator { .shape-separator {
position: relative; position: relative;
height: 48px; height: 48px;
background: #333;
} }
.shape-separator::after { .shape-separator::after {
content: ''; content: '';
@ -64,3 +65,5 @@ Uses an SVG shape to separate two different blocks to create more a interesting
<span class="snippet__support-note">✅ No caveats.</span> <span class="snippet__support-note">✅ No caveats.</span>
* https://caniuse.com/#feat=svg * https://caniuse.com/#feat=svg
<!-- tags: visual -->

74
snippets/sibling-fade.md Normal file
View File

@ -0,0 +1,74 @@
### Sibling fade
Fades out the siblings of a hovered item.
#### HTML
```html
<div class="sibling-fade">
<span>Item 1</span>
<span>Item 2</span>
<span>Item 3</span>
<span>Item 4</span>
<span>Item 5</span>
<span>Item 6</span>
</div>
```
#### CSS
```css
span {
padding: 0 1rem;
transition: opacity 0.2s;
}
.sibling-fade:hover span:not(:hover) {
opacity: 0.5;
}
```
#### Demo
<div class="snippet-demo">
<nav class="snippet-demo__sibling-fade">
<span>Item 1</span>
<span>Item 2</span>
<span>Item 3</span>
<span>Item 4</span>
<span>Item 5</span>
<span>Item 6</span>
</nav>
</div>
<style>
.snippet-demo__sibling-fade {
cursor: default;
line-height: 2;
vertical-align: middle;
}
.snippet-demo__sibling-fade span {
padding: 1rem;
transition: opacity 0.2s;
}
.snippet-demo__sibling-fade:hover span:not(:hover) {
opacity: 0.5;
}
</style>
#### Explanation
1. `transition: opacity 0.2s` specifies that changes to opacity will be transitioned over 0.2 seconds.
2. `.sibling-fade:hover span:not(:hover)` specifies that when the parent is hovered, select any `span` children
that are not currently being hovered and change their opacity to `0.5`.
#### Browser support
<span class="snippet__support-note">✅ No caveats.</span>
* https://caniuse.com/#feat=css-sel3
* https://caniuse.com/#feat=css-transitions
<!-- tags: interactivity -->

View File

@ -12,7 +12,8 @@ Uses the native font of the operating system to get close to a native app feel.
```css ```css
.system-font-stack { .system-font-stack {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", Helvetica, Arial, sans-serif; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu,
Cantarell, 'Helvetica Neue', Helvetica, Arial, sans-serif;
} }
``` ```
@ -46,3 +47,5 @@ falls back to the next if it cannot find the font (on the system or defined in C
#### Browser support #### Browser support
<span class="snippet__support-note">✅ No caveats.</span> <span class="snippet__support-note">✅ No caveats.</span>
<!-- tags: visual -->

View File

@ -89,3 +89,5 @@ Experiment with the `px` values to change the proportion of the triangle.
#### Browser support #### Browser support
<span class="snippet__support-note">✅ No caveats.</span> <span class="snippet__support-note">✅ No caveats.</span>
<!-- tags: visual -->

View File

@ -51,3 +51,5 @@ If the text is longer than one line, it will be truncated and end with an ellips
<span class="snippet__support-note">⚠️ Only works for single line elements.</span> <span class="snippet__support-note">⚠️ Only works for single line elements.</span>
* https://caniuse.com/#feat=text-overflow * https://caniuse.com/#feat=text-overflow
<!-- tags: layout -->

View File

@ -1,5 +1,12 @@
html { html {
font-size: 0.95rem; font-size: 0.95rem;
box-sizing: border-box;
}
*,
*::after,
*::before {
box-sizing: inherit;
} }
body { body {
@ -32,7 +39,7 @@ ol {
} }
.container { .container {
max-width: 1000px; max-width: 64rem;
padding: 0 2%; padding: 0 2%;
margin: 0 auto; margin: 0 auto;
} }

View File

@ -0,0 +1,76 @@
.btn {
display: inline-block;
position: relative;
top: -1px;
font-weight: bold;
font-size: 0.75rem;
text-transform: uppercase;
color: #8385aa;
white-space: nowrap;
border: 1px solid #c8cbf2;
border-radius: 2px;
vertical-align: middle;
line-height: 2;
padding: 0 0.5rem;
margin-right: 0.5rem;
transition: all 0.1s ease-out;
outline: 0;
&.is-large {
font-size: 0.95rem;
border-radius: 0.2rem;
.feather {
top: -2px;
width: 18px;
height: 18px;
}
}
.feather {
vertical-align: middle;
margin-right: 0.25rem;
position: relative;
top: -1px;
width: 14px;
height: 14px;
}
}
button.btn {
user-select: none;
cursor: pointer;
margin-bottom: 1rem;
margin-right: 1rem;
background: white;
&:hover {
background: #8385aa;
border-color: #8385aa;
color: white;
}
&.focus-visible:focus {
box-shadow: 0 0 0 0.25rem transparentize(#8385aa, 0.5);
}
&:active {
box-shadow: inset 0 0.1rem 0.1rem 0.1rem rgba(0, 0, 0, 0.2);
background: darken(#8385aa, 10);
border-color: darken(#8385aa, 10);
}
&.is-active {
background: #7983ff;
border-color: #7983ff;
color: white;
&.focus-visible:focus {
box-shadow: 0 0 0 0.25rem transparentize(#7983ff, 0.5);
}
}
&.codepen-btn {
margin-top: 0.5rem;
}
}

View File

@ -1,7 +1,8 @@
.header { .header {
position: relative; position: relative;
padding: 5rem 1rem 4rem; padding: 5rem 1rem 4rem;
background: linear-gradient(#5cd2ff, #5b67ff, #681ae4); background: #5b67ff;
background: linear-gradient(45deg, #5cd2ff, #5b67ff, #681ae4);
color: white; color: white;
margin-bottom: 2rem; margin-bottom: 2rem;
text-align: center; text-align: center;

View File

@ -35,6 +35,7 @@
max-height: 378px; max-height: 378px;
margin-top: 44px; margin-top: 44px;
box-shadow: 0 0.25rem 0.5rem -0.1rem rgba(0, 32, 128, 0.2); box-shadow: 0 0.25rem 0.5rem -0.1rem rgba(0, 32, 128, 0.2);
padding-bottom: 1rem;
&.is-active { &.is-active {
transform: rotateX(0); transform: rotateX(0);
@ -59,6 +60,16 @@
border-color: pink; border-color: pink;
} }
} }
&__section {
padding: 0 0.75rem;
}
&__section-heading {
text-transform: capitalize;
color: #e3f5ff;
margin-bottom: 0.5rem;
}
} }
@media (min-width: 992px) { @media (min-width: 992px) {

View File

@ -14,6 +14,10 @@
margin-bottom: 1.25rem; margin-bottom: 1.25rem;
margin-top: 0; margin-top: 0;
line-height: 1.3; line-height: 1.3;
span:not(.snippet__tag) {
margin-right: 0.75rem;
}
} }
code:not([class*='lang']) { code:not([class*='lang']) {
@ -42,16 +46,20 @@
h4 { h4 {
display: inline-block; display: inline-block;
margin: 1rem 0 0.5rem; margin: 1rem 0 0.5rem;
font-size: 1.1rem;
line-height: 2; line-height: 2;
padding: 0 0.5rem;
border-radius: 3px; &[data-type] {
font-size: 0.9rem; background: #333;
text-transform: uppercase; padding: 0 0.5rem;
background: #333; border-radius: 3px;
border: 1px solid #c6d6ea; font-size: 0.9rem;
border-bottom-color: darken(#c6d6ea, 5); text-transform: uppercase;
background: white; border: 1px solid #c6d6ea;
box-shadow: 0 0.25rem 0.5rem -0.1rem rgba(0, 32, 64, 0.15); border-bottom-color: darken(#c6d6ea, 5);
background: white;
box-shadow: 0 0.25rem 0.5rem -0.1rem rgba(0, 32, 64, 0.15);
}
&[data-type='HTML'] { &[data-type='HTML'] {
color: white; color: white;
@ -107,6 +115,9 @@
top: 1rem; top: 1rem;
right: 0; right: 0;
} }
&__tag {
}
} }
.snippet-demo { .snippet-demo {

View File

@ -0,0 +1,84 @@
.tags {
position: relative;
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
margin-bottom: 1rem;
&__tag {
display: inline-block;
position: relative;
top: -1px;
font-weight: bold;
font-size: 0.75rem;
text-transform: uppercase;
color: #8385aa;
white-space: nowrap;
border: 1px solid #c8cbf2;
border-radius: 2px;
vertical-align: middle;
line-height: 2;
padding: 0 0.5rem;
margin-right: 0.5rem;
transition: all 0.1s ease-out;
outline: 0;
&.is-large {
font-size: 0.95rem;
border-radius: 0.2rem;
.feather {
top: -2px;
width: 18px;
height: 18px;
}
}
.feather {
vertical-align: middle;
margin-right: 0.25rem;
position: relative;
top: -1px;
width: 14px;
height: 14px;
}
}
button.tags__tag {
user-select: none;
cursor: pointer;
margin-bottom: 1rem;
margin-right: 1rem;
background: white;
&:hover {
background: #8385aa;
border-color: #8385aa;
color: white;
}
&.focus-visible:focus {
box-shadow: 0 0 0 0.25rem transparentize(#8385aa, 0.5);
}
&:active {
box-shadow: inset 0 0.1rem 0.1rem 0.1rem rgba(0, 0, 0, 0.2);
background: darken(#8385aa, 10);
border-color: darken(#8385aa, 10);
}
&.is-active {
background: #7983ff;
border-color: #7983ff;
color: white;
&.focus-visible:focus {
box-shadow: 0 0 0 0.25rem transparentize(#7983ff, 0.5);
}
}
}
}
@media (min-width: 579px) {
}

View File

@ -4,3 +4,5 @@
@import './components/header.scss'; @import './components/header.scss';
@import './components/snippet.scss'; @import './components/snippet.scss';
@import './components/back-to-top-button.scss'; @import './components/back-to-top-button.scss';
@import './components/tags.scss';
@import './components/buttons.scss';

View File

@ -0,0 +1 @@
<button class="back-to-top-button" aria-label="back to top"></button>

View File

@ -0,0 +1 @@
<footer class="footer"></footer>

View File

@ -0,0 +1,10 @@
<header class="header">
<div class="container">
<img class="header__logo" draggable="false" src="./src/img/logo.png">
<h1 class="header__heading">30 Seconds of <strong class="header__css">CSS</strong></h1>
<p class="header__description">
A curated collection of useful CSS snippets you can understand in 30 seconds or less.
</p>
<a class="github-button header__github-button" href="https://github.com/atomiks/30-seconds-of-css" data-icon="octicon-star" data-size="large" data-show-count="true" aria-label="Star atomiks/30-seconds-of-css on GitHub">Star</a>
</div>
</header>

View File

@ -0,0 +1,4 @@
<main class="main" id="main">
<div class="container">
</div>
</main>

View File

@ -0,0 +1,9 @@
<nav class="sidebar" aria-label="Table of Contents">
<button class="hamburger hamburger--spin sidebar__menu" type="button" aria-label="Menu" aria-expanded="false">
<span class="hamburger-box">
<span class="hamburger-inner"></span>
</span>
</button>
<div class="sidebar__links">
</div>
</nav>

View File

@ -0,0 +1 @@
<nav class="tags" aria-label="Filter by tags"></nav>

15
src/html/index.html Normal file
View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>30 Seconds of CSS</title>
<meta name="description" content="A curated collection of useful CSS snippets you can understand in 30 seconds or less. From foundational elements such as clearfix to gradient text color and gradient cursor tracking to CSS easing and far beyond.">
<link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32.png">
<script src="./src/js/index.js" defer=""></script>
<script async="" defer="" src="https://buttons.github.io/buttons.js"></script>
</head>
<body>
<div class="content-wrapper"></div>
</body>
</html>

View File

@ -0,0 +1,28 @@
import { selectAll } from '../deps/utils'
const snippets = selectAll('.snippet')
snippets.forEach(snippet => {
var codepenForm = document.createElement('form')
codepenForm.action = 'https://codepen.io/pen/define'
codepenForm.method = 'POST'
codepenForm.target = '_blank'
var codepenInput = document.createElement('input')
codepenInput.type = 'hidden'
codepenInput.name = 'data'
var codepenButton = document.createElement('button')
codepenButton.classList = 'btn is-large codepen-btn'
codepenButton.innerHTML = '<i data-feather="edit-2"></i>Edit on Codepen'
var css = snippet.querySelector('pre code.lang-css')
var html = snippet.querySelector('pre code.lang-html')
var js = snippet.querySelector('pre code.lang-js')
var data = {
css: css.textContent,
title: snippet.querySelector('h3 > span').textContent,
html: html ? html.textContent : '',
js: js ? js.textContent : ''
}
codepenInput.value = JSON.stringify(data)
codepenForm.appendChild(codepenInput)
codepenForm.appendChild(codepenButton)
snippet.insertBefore(codepenForm, snippet.querySelector('.snippet-demo').nextSibling)
})

View File

@ -1,38 +0,0 @@
import jump from '../deps/jump'
import { select, selectAll, easeOutQuint } from '../deps/utils'
const menu = select('.hamburger')
const links = select('.sidebar__links')
const ACTIVE_CLASS = 'is-active'
const toggle = () => {
const els = [menu, links]
els.forEach(el => el.classList.toggle(ACTIVE_CLASS))
menu.setAttribute('aria-expanded', menu.classList.contains(ACTIVE_CLASS) ? 'true' : 'false')
}
menu.addEventListener('click', toggle)
links.addEventListener('click', e => {
setTimeout(toggle, 40)
if (e.target.classList.contains('sidebar__link')) {
e.preventDefault()
jump(e.target.getAttribute('href'), {
duration: 750,
offset: window.innerWidth <= 768 ? -64 : -32,
easing: easeOutQuint
})
}
})
document.addEventListener('click', e => {
if (
!e.target.closest('.sidebar__links') &&
!e.target.closest('.hamburger') &&
links.classList.contains(ACTIVE_CLASS)
) {
toggle()
}
})
export default { toggle }

View File

@ -0,0 +1,50 @@
import jump from '../deps/jump'
import { select, selectAll, easeOutQuint } from '../deps/utils'
const menu = select('.hamburger')
const links = select('.sidebar__links')
const sections = selectAll('.sidebar__section')
const ACTIVE_CLASS = 'is-active'
const toggle = () => {
if (window.innerWidth <= 991) {
const els = [menu, links]
els.forEach(el => el.classList.toggle(ACTIVE_CLASS))
menu.setAttribute('aria-expanded', menu.classList.contains(ACTIVE_CLASS) ? 'true' : 'false')
}
}
menu.addEventListener('click', toggle)
links.addEventListener('click', e => {
const link = e.target.closest('.sidebar__link')
if (link) {
setTimeout(toggle, 50)
jump(link.getAttribute('href'), {
duration: 500,
easing: easeOutQuint,
offset: window.innerWidth <= 991 ? -64 : -32
})
}
})
document.addEventListener('click', e => {
if (
!e.target.closest('.sidebar__links') &&
!e.target.closest('.hamburger') &&
links.classList.contains(ACTIVE_CLASS)
) {
toggle()
}
})
EventHub.on('Tag.click', data => {
sections.forEach(section => {
section.style.display = 'block'
if (section.dataset.type !== data.type && data.type !== 'all') {
section.style.display = 'none'
}
})
})
export default { toggle }

View File

@ -0,0 +1,13 @@
import { selectAll } from '../deps/utils'
const snippets = selectAll('.snippet')
EventHub.on('Tag.click', data => {
snippets.forEach(snippet => {
snippet.style.display = 'block'
if (data.type === 'all') return
const tags = selectAll('.tags__tag', snippet)
if (!tags.some(el => el.dataset.type === data.type)) {
snippet.style.display = 'none'
}
})
})

14
src/js/components/Tag.js Normal file
View File

@ -0,0 +1,14 @@
import { select, selectAll, on } from '../deps/utils'
const tagButtons = selectAll('button.tags__tag')
const onClick = function() {
tagButtons.forEach(button => button.classList.remove('is-active'))
this.classList.add('is-active')
EventHub.emit('Tag.click', {
type: this.dataset.type
})
}
tagButtons.forEach(button => on(button, 'click', onClick))

View File

@ -1,8 +1,34 @@
export const select = s => document.querySelector(s) export const select = (s, parent = document) => parent.querySelector(s)
export const selectAll = s => [].slice.call(document.querySelectorAll(s))
export const selectAll = (s, parent = document) => [].slice.call(parent.querySelectorAll(s))
export const scrollY = () => window.scrollY || window.pageYOffset export const scrollY = () => window.scrollY || window.pageYOffset
export const easeOutQuint = (t, b, c, d) => c * ((t = t / d - 1) * t ** 4 + 1) + b export const easeOutQuint = (t, b, c, d) => c * ((t = t / d - 1) * t ** 4 + 1) + b
export const on = (el, evt, fn, opts = {}) => {
const delegatorFn = e => e.target.matches(opts.target) && fn.call(e.target, e)
el.addEventListener(evt, opts.target ? delegatorFn : fn, opts.options || false)
if (opts.target) return delegatorFn
}
export const createEventHub = () => ({
hub: Object.create(null),
emit(event, data) {
;(this.hub[event] || []).forEach(handler => handler(data))
},
on(event, handler) {
if (!this.hub[event]) this.hub[event] = []
this.hub[event].push(handler)
},
off(event, handler) {
const i = (this.hub[event] || []).findIndex(h => h === handler)
if (i > -1) this.hub[event].splice(i, 1)
}
})
window.EventHub = createEventHub()
/* /*
* Make iOS behave normally. * Make iOS behave normally.
*/ */

View File

@ -1,6 +1,9 @@
// Deps // Deps
import 'focus-visible'
import 'normalize.css' import 'normalize.css'
import 'prismjs' import 'prismjs'
import feather from 'feather-icons'
feather.replace()
// CSS // CSS
import '../css/deps/prism.css' import '../css/deps/prism.css'
@ -10,5 +13,8 @@ import '../css/index.scss'
import './deps/polyfills' import './deps/polyfills'
// Components // Components
import Menu from './components/Menu' import Sidebar from './components/Sidebar'
import BackToTopButton from './components/BackToTopButton' import BackToTopButton from './components/BackToTopButton'
import Tag from './components/Tag'
import Snippet from './components/Snippet'
import CodepenCopy from './components/CodepenCopy'

View File

@ -1,8 +1,20 @@
const fs = require('fs')
const { JSDOM } = require('jsdom')
exports.toKebabCase = str => exports.toKebabCase = str =>
str && str &&
str str
.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g) .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
.map(x => x.toLowerCase()) .map(x => x.toLowerCase())
.join('-'); .join('-')
exports.emptyHTML = (...els) => els.forEach(el => (el.innerHTML = '')) exports.dom = path => {
const doc = new JSDOM(fs.readFileSync(path, 'utf8')).window.document
return path.includes('component') ? doc.body.firstElementChild : doc
}
exports.createElement = str => {
const el = new JSDOM().window.document.createElement('div')
el.innerHTML = str
return el.firstElementChild
}