Last active
June 30, 2018 20:10
-
-
Save enten/cf9455a4a256ca8db03f945ac01f155f to your computer and use it in GitHub Desktop.
atomik: responsive design in an atomic way
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// atomik: responsive design in an atomic way. | |
// | |
// When responsive design must be or becomes an atomic job: | |
// let's atomik do the dirty work with a pretty-sugar api. | |
// | |
// | |
// 0. CORE | |
// | |
@mixin css-props($props) { | |
@each $name, $value in $props { | |
#{$name}: $value; | |
} | |
@content; | |
} | |
// TODO remove it by hardcoded $A_BASE values. | |
@function rem($multiplier) { | |
$font-size: 10px; | |
@return $multiplier * $font-size; | |
} | |
// | |
// 1. CONSTANTS | |
// | |
// | |
// Default first screen. | |
// | |
// whe `mobile`? because we live in mobile-first world! | |
// | |
$A_FIRST_SCREEN: mobile !default; | |
// | |
// Default first level. | |
// | |
// why `4`? because levels 1-2-3 are for highlighting | |
// and levels 4-5-6 for lowering (and level 7 is for moron _\*laught\*_) | |
// | |
$A_FIRST_LEVEL: 4 !default; | |
// | |
// Map of screen media-query features. | |
// | |
$A_BREAKING: ( | |
mobile: ( max-width: 899px ), | |
desktop: ( min-width: 900px ) | |
) !default; | |
// | |
// Map of sizing used to compute value for each screen resolution. | |
// | |
$A_SIZING: ( | |
border-radius: ( | |
mobile: ( 0, rem(2), rem(1), rem(0.5), rem(0.25), rem(0.125), rem(0.1), 0 ), | |
desktop: ( 0, rem(4), rem(2), rem(1), rem(0.5), rem(0.25), rem(0.125), 0 ) | |
), | |
border-width: ( | |
mobile: ( 0, rem(2), rem(1), rem(0.5), rem(0.25), rem(0.125), rem(0.1), 0 ), | |
desktop: ( 0, rem(4), rem(2), rem(1), rem(0.5), rem(0.25), rem(0.125), 0 ) | |
), | |
font-size: ( | |
mobile: ( 0, rem(3), rem(2.5), rem(2), rem(1.5), rem(1.25), rem(1), rem(0.75) ), | |
desktop: ( 0, rem(6), rem(5), rem(4), rem(3), rem(1.5), rem(1.25), rem(1) ) | |
), | |
font-weight: ( | |
mobile: ( 100, bold, 800, 700, 500, 300, 200, 100 ), | |
desktop: ( 100, bold, 900, 800, 700, 500, 300, 200 ) | |
), | |
letter-spacing: ( | |
mobile: ( 0, rem(2.5), rem(2), rem(1.5), rem(1), rem(0.75), rem(0.5), 0 ), | |
desktop: ( 0, rem(3), rem(2.5), rem(2), rem(1.5), rem(1), rem(0.75), rem(0.5) ) | |
), | |
line-height: ( | |
mobile: ( 0, rem(3), rem(2.5), rem(2), rem(1.5), rem(1.25), rem(1), rem(0.75) ), | |
desktop: ( 0, rem(6), rem(5), rem(4), rem(3), rem(1.5), rem(1.25), rem(1) ) | |
), | |
// spacing: ( | |
// mobile: ( none, 32px, 16px, 8px, 4px, 2px, 1px, 0 ), | |
// desktop: ( none, 128px, 64px, 32px, 16px, 8px, 4px, 2px ) | |
// ), | |
margin: ( | |
mobile: ( none, 32px, 16px, 8px, 4px, 2px, 1px, 0 ), | |
desktop: ( none, 128px, 64px, 32px, 16px, 8px, 4px, 2px ) | |
), | |
padding: ( | |
mobile: ( none, 32px, 16px, 8px, 4px, 2px, 1px, 0 ), | |
desktop: ( none, 128px, 64px, 32px, 16px, 8px, 4px, 2px ) | |
), | |
height: ( | |
mobile: ( none, rem(48), rem(32), rem(16), rem(8), rem(4), rem(2), 0 ), | |
desktop: ( none, rem(96), rem(48), rem(32), rem(16), rem(8), rem(4), rem(2) ) | |
), | |
width: ( | |
mobile: ( none, rem(48), rem(32), rem(16), rem(8), rem(4), rem(2), 0 ), | |
desktop: ( none, rem(96), rem(48), rem(32), rem(16), rem(8), rem(4), rem(2) ) | |
) | |
) !default; | |
// | |
// Map of styling used to resolve sizing. | |
// | |
// Use String to reference a sizing. | |
// Use List to derive/compose or alias/shorthand styling. | |
// | |
$A_STYLING: ( | |
// border styling | |
border: border, | |
border-top: border, | |
border-right: border, | |
border-bottom: border, | |
border-left: border, | |
border-top-left: ( border-top, border-left ), | |
border-top-right: ( border-top, border-right ), | |
border-bottom-left: ( border-bottom, border-left ), | |
border-bottom-right: ( border-bottom, border-right ), | |
border-vertical: ( border-top, border-bottom ), | |
border-horizontal: ( border-top, border-bottom ), | |
// border-width styling | |
border-width: border-width, | |
border-width-top: border-width, | |
border-width-right: border-width, | |
border-width-bottom: border-width, | |
border-width-left: border-width, | |
border-width-top-left: ( border-width-top, border-width-left ), | |
border-width-top-right: ( border-width-top, border-width-right ), | |
border-width-bottom-left: ( border-width-bottom, border-width-left ), | |
border-width-bottom-right: ( border-width-bottom, border-width-right ), | |
border-width-vertical: ( border-width-top, border-width-bottom ), | |
border-width-horizontal: ( border-width-top, border-width-bottom ), | |
// border-style styling | |
border-style: border-style, | |
border-style-top: border-style, | |
border-style-right: border-style, | |
border-style-bottom: border-style, | |
border-style-left: border-style, | |
border-style-top-left: ( border-style-top, border-style-left ), | |
border-style-top-right: ( border-style-top, border-style-right ), | |
border-style-bottom-left: ( border-style-bottom, border-style-left ), | |
border-style-bottom-right: ( border-style-bottom, border-style-right ), | |
border-style-vertical: ( border-style-top, border-style-bottom ), | |
border-style-horizontal: ( border-style-top, border-style-bottom ), | |
// border-color styling | |
border-color: border-color, | |
border-color-top: border-color, | |
border-color-right: border-color, | |
border-color-bottom: border-color, | |
border-color-left: border-color, | |
border-color-top-left: ( border-color-top, border-color-left ), | |
border-color-top-right: ( border-color-top, border-color-right ), | |
border-color-bottom-left: ( border-color-bottom, border-color-left ), | |
border-color-bottom-right: ( border-color-bottom, border-color-right ), | |
border-color-vertical: ( border-color-top, border-color-bottom ), | |
border-color-horizontal: ( border-color-top, border-color-bottom ), | |
// border-radius styling | |
border-radius: border-radius, | |
border-top-left-radius: border-radius, | |
border-top-right-radius: border-radius, | |
border-bottom-left-radius: border-radius, | |
border-bottom-right-radius: border-radius, | |
border-top-radius: ( border-top-left-radius, border-top-right-radius ), | |
border-bottom-radius: ( border-bottom-left-radius, border-bottom-right-radius ), | |
// font styling | |
font-size: font-size, | |
font-weight: font-weight, | |
letter-spacing: letter-spacing, | |
line-height: line-height, | |
// margin styling | |
margin: margin, | |
margin-top: margin, | |
margin-right: margin, | |
margin-bottom: margin, | |
margin-left: margin, | |
margin-top-left: ( margin-top, margin-left ), | |
margin-top-right: ( margin-top, margin-right ), | |
margin-bottom-left: ( margin-bottom, margin-left ), | |
margin-bottom-right: ( margin-bottom, margin-right ), | |
margin-vertical: ( margin-top, margin-bottom ), | |
margin-horizontal: ( margin-top, margin-bottom ), | |
// padding styling | |
padding: padding, | |
padding-top: padding, | |
padding-right: padding, | |
padding-bottom: padding, | |
padding-left: padding, | |
padding-top-left: ( padding-top, padding-left ), | |
padding-top-right: ( padding-top, padding-right ), | |
padding-bottom-left: ( padding-bottom, padding-left ), | |
padding-bottom-right: ( padding-bottom, padding-right ), | |
padding-vertical: ( padding-top, padding-bottom ), | |
padding-horizontal: ( padding-left, padding-right ), | |
// width styling | |
width: width, | |
max-width: width, | |
min-width: width, | |
// height styling | |
height: height, | |
max-height: height, | |
min-height: height, | |
// font shorthand-styling | |
fs: ( font-size, ), | |
fw: ( font-weight, ), | |
ls: ( letter-spacing, ), | |
lh: ( line-height, ), | |
// margin shorthand-styling | |
ma: ( margin, ), | |
mt: ( margin-top, ), | |
mr: ( margin-right, ), | |
mb: ( margin-bottom, ), | |
ml: ( margin-left, ), | |
mtl: ( margin-top-left, ), | |
mtr: ( margin-top-right, ), | |
mbl: ( margin-bottom-left, ), | |
mbr: ( margin-bottom-right, ), | |
mv: ( margin-vertical, ), | |
mh: ( margin-horizontal, ), | |
// padding shorthand-styling | |
pa: ( padding, ), | |
pt: ( padding-top, ), | |
pr: ( padding-right, ), | |
pb: ( padding-bottom, ), | |
pl: ( padding-left, ), | |
ptl: ( padding-top-left, ), | |
ptr: ( padding-top-right, ), | |
pbl: ( padding-bottom-left, ), | |
pbr: ( padding-bottom-right, ), | |
pv: ( padding-vertical, ), | |
ph: ( padding-horizontal, ), | |
// width shorthand-styling | |
w: ( width, ), | |
max-w: ( max-width, ), | |
min-w: ( min-width, ), | |
// height shorthand-styling | |
h: ( height, ), | |
max-h: ( max-height, ), | |
min-h: ( min-height, ) | |
) !default; | |
// | |
// Default atomik [BluePrint]. | |
// | |
$A_BASE: ( | |
first-screen: $A_FIRST_SCREEN, | |
first-level: $A_FIRST_LEVEL, | |
breaking: $A_BREAKING, | |
sizing: $A_SIZING, | |
styling: $A_STYLING | |
) !default; | |
// | |
// 2. FUNCTIONS | |
// | |
// | |
// Creates new blueprint based on $A_BASE. | |
// | |
// If args are given, every arg must be a [BluePrint] | |
// which will be merge into the base blueprint. | |
// | |
// A blueprint instance is the only required variable | |
// to work with the entire API functions and mixins. | |
// | |
// @param $user-a [Array<BluePrint>] | |
// @returns [BluePrint] A new blueprint. | |
// | |
@function a-core($user-a...) { | |
$a: map-merge((), $A_BASE); | |
@if length($user-a) < 1 { | |
@return $a; | |
} | |
@for $index from 1 through length($user-a) { | |
$b: nth($user-a, $index); | |
$a: a-core-merge($a, $b); | |
} | |
@return $a; | |
} | |
// | |
// Merges two blueprints into a new [BluePrint]. | |
// Values in `$b` will take precedence over values in `$a`. | |
// | |
// This is the best way to merging blueprints. | |
// | |
// Only [BluePrint] propertes are merged. | |
// | |
// @param $a [BluePrint] | |
// @param $b [BluePrint] | |
// @returns [BluePrint] | |
// | |
@function a-core-merge($a, $b) { | |
// support user first-level | |
@if map-has-key($b, first-level) { | |
$a: map-merge($a, ( first-level: map-get($b, first-level) )); | |
} | |
// support user first-screen | |
@if map-has-key($b, first-screen) { | |
$a: map-merge($a, ( first-screen: map-get($b, first-screen) )); | |
} | |
// support user breaking | |
// | |
// devnote: breaking is an advanced option | |
// it can't be merged (it doesn't make sense) | |
@if map-has-key($b, breaking) { | |
$a: map-merge($a, ( breaking: map-get($b, breaking) )); | |
} | |
// support user styling | |
// | |
// devnote: merging map-merge($a.styling, $b.styling) | |
@if map-has-key($b, styling) { | |
$a: map-merge($a, ( styling: map-merge(map-get($a, styling), map-get($b, styling)) )); | |
} | |
// support user sizing | |
// | |
// devnote: dvance merge because sizing is the | |
// more dfficult option to extend | |
@if map-has-key($b, sizing) { | |
$screens-keys: map-keys(map-get($a, breaking)); | |
$screens-len: length($screens-keys); | |
$a-sizing: map-get($a, sizing); | |
$b-sizing: map-get($b, sizing); | |
// loop on each user sizing | |
@each $name, $b-screens in $b-sizing { | |
$a-screens: map-get($a-sizing, $name); | |
@if not $a-screens { | |
$a-screens: (); | |
} | |
$prev-screen: null; | |
// loop on each $a.breaking key | |
@for $index from 1 through $screens-len { | |
$screen: nth($screens-keys, $index); | |
// @warn('cp' $a-sizing); | |
$a-screen: map-get($a-screens, $screen); | |
$b-screen: map-get($b-screens, $screen); | |
$a-screen-len: 0; | |
$b-screen-len: 0; | |
@if $a-screen { | |
$a-screen-len: length($a-screen); | |
} | |
@if $b-screen { | |
$b-screen-len: length($b-screen); | |
} | |
$sizes: (); | |
$sizes-len: $a-screen-len; | |
@if $b-screen-len > $sizes-len { | |
$sizes-len: $b-screen-len; | |
} | |
// loop to deep level | |
@for $level from 1 through $sizes-len { | |
$size: null; | |
@if $level <= $a-screen-len { | |
$size: nth($a-screen, $level); | |
} | |
@if $level <= $b-screen-len { | |
$size: nth($b-screen, $level); | |
} | |
// support fallback placeholder with previous screen values | |
@if $size == _ { | |
$size: null; | |
@if $prev-screen { | |
$size: nth($prev-screen, $level); | |
} | |
} | |
$sizes: append($sizes, $size); | |
} | |
$prev-screen: $sizes; | |
// override screen sizes for $screen resolution | |
$a-screens: map-merge($a-screens, ( $screen: $sizes )); | |
} | |
// override sizing styling $name | |
$a-sizing: map-merge($a-sizing, ( $name: $a-screens )); | |
} | |
// finaly, set merged sizing | |
$a: map-merge($a, ( sizing: $a-sizing )); | |
} | |
// breaking cohercion (only if needed) | |
// | |
// devnote: required to avoid blinking with | |
// new narrow-screens (with min/max like tablet) | |
@if map-has-key($b, breaking) { | |
$screens-keys: map-keys(map-get($a, breaking)); | |
$screens-len: length($screens-keys); | |
$a-sizing: map-get($a, sizing); | |
$b-sizing: map-get($b, sizing); | |
// loop on each names | |
@each $name, $a-screens in $a-sizing { | |
// filter name contains in $b-sizing | |
@if not map-has-key($b-sizing, $name) { | |
$prev-screen: null; | |
// loop on each screen | |
@for $index from 1 through $screens-len { | |
$screen: nth($screens-keys, $index); | |
$a-screen: map-get($a-screens, $screen); | |
// fallback with previous screen | |
@if not $a-screen and $prev-screen { | |
$a-screen: $prev-screen | |
} | |
// TODO inspect and comment | |
@if $a-screen { | |
$prev-screen: $a-screen; | |
$a-screens: map-merge($a-screens, ( $screen: $a-screen )); | |
} @else { | |
$a-screens: map-merge($a-screens, ( $screen: () )); | |
} | |
} | |
$a-sizing: map-merge($a-sizing, ( $name: $a-screens )); | |
} | |
} | |
// finaly, set coherced sizing | |
$a: map-merge($a, ( sizing: $a-sizing )); | |
} | |
@return $a; | |
} | |
// | |
// Returns a map of `( css-prop: sizing-key )`for a given styling. | |
// | |
// @param $a [BluePrint] | |
// @param $name [String] | |
// @returns [Map<String, String>] | |
// | |
@function a-styling-sizing-map($a, $name) { | |
$map: (); | |
// support of $name as list of styling names | |
@if type-of($name) == "list" { | |
@for $index from 1 through length($name) { | |
$map: map-merge($map, a-styling-sizing-map($a, nth($name, $index))); | |
} | |
@return $map; | |
} | |
$styling: map-get(map-get($a, styling), $name); | |
// support of $styling as list of styling names | |
// (allow of derive/compose/alias/shorthand styling) | |
@if type-of($styling) == "list" { | |
@for $index from 1 through length($styling) { | |
$map: map-merge($map, a-styling-sizing-map($a, nth($styling, $index))); | |
} | |
@return $map; | |
} | |
$sizing: map-get(map-get($a, sizing) , $styling); | |
@if $styling and $sizing { | |
$map: map-merge($map, ( $name: $styling )); | |
} @else if not $styling { | |
@warn("[atomik] StylingNotFoundWarn: #{$name}"); | |
} @else if not $sizing { | |
@warn("[atomik] SizingNotFoundWarn: #{$styling}"); | |
} | |
@return $map; | |
} | |
// | |
// Returns a map of styling map sized by screens. | |
// | |
// @param $a [BluePrint] | |
// @param $name [String] | |
// @param $level? [Number] | |
// @param $screens? [String|List<String>] | |
// @returns [Map<String, Map<String, Object>>] | |
// | |
@function a-styling-map($a, $name, $level: null, $screens: null) { | |
@if $level and type-of($level) != "number" { | |
$screens: $level; | |
$level: null; | |
} | |
@if not $level { | |
$level: map-get($a, first-level); | |
} | |
@if not $screens or $screens == all { | |
$screens: map-keys(map-get($a, breaking)) | |
} @else if type-of($screens == "string") { | |
$screens: ( $screens, ); | |
} | |
$styling-map: (); | |
$styling-sizing-map: a-styling-sizing-map($a, $name); | |
// about level incrementation line below: | |
// * list first index is 1 in SCSS (_\*private joke\*_) ; | |
// * remember that first index is reserved for `none` value ; | |
// * the first index 1 is the level 0. | |
$level: $level + 1; | |
// compute $styling-map for each screen | |
@for $index from 1 through length($screens) { | |
$screen: nth($screens, $index); | |
$styling: (); | |
// compute $styling for each style only if a sizing value exists | |
@each $styling-key, $sizing-key in $styling-sizing-map { | |
$sizing: map-get(map-get($a, sizing), $sizing-key); | |
@if $sizing and map-has-key($sizing, $screen) { | |
// avoid index out of bounds exception | |
// when $sizing doesn't contains the except level | |
// for the current $screen | |
@if $level <= length(map-get($sizing, $screen)) { | |
$value: nth(map-get($sizing, $screen), $level); | |
// avoid merge bad $value into $styling | |
@if $value { | |
$styling: map-merge($styling, ( $styling-key: $value )); | |
} | |
} | |
} | |
} | |
$styling-map: map-merge($styling-map, ( $screen: $styling )); | |
} | |
@return $styling-map; | |
} | |
// | |
// 3. MIXINS | |
// | |
// | |
// Mixin style inside a screen media-query. | |
// | |
// @param $a [BluePrint] | |
// @param $screen [String] | |
// @param $user-media-query? [Number] Additional media query features | |
// | |
@mixin a-screen($a, $screen, $user-media-query: null) { | |
$media-query: map-get(map-get($a, breaking), $screen); | |
@if $media-query { | |
@if $user-media-query { | |
@media only screen and ($media-query) and ($user-media-query) { | |
/* #{$screen} */ | |
@content; | |
} | |
} @else { | |
@media only screen and ($media-query) { | |
/* #{$screen} */ | |
@content; | |
} | |
} | |
} | |
} | |
// | |
// Mixin a style for a level. | |
// | |
// It will be responsive by default (when not $screens). | |
// | |
// @param $a [BluePrint] | |
// @param $name [String] | |
// @param $level? [Number] | |
// @param $screens? [String|List<String>] | |
// | |
@mixin a-style($a, $name, $level: null, $screens: null) { | |
@if $level and type-of($level) != "number" { | |
$screens: $level; | |
$level: null; | |
} | |
$first-screen: map-get($a, first-screen); | |
$styling-map: a-styling-map($a, $name, $level, $screens); | |
@each $screen, $props in $styling-map { | |
// when not $screens: make first-screen outside media-query block | |
// (mixin first-screen by default) | |
@if not $screens and $screen == $first-screen { | |
@include css-props(map-get($styling-map, $first-screen)) { | |
@content; | |
} | |
} @else { | |
@include a-screen($a, $screen) { | |
@include css-props(map-get($styling-map, $screen)) { | |
@content; | |
} | |
} | |
} | |
} | |
} | |
// | |
// Mixin a style for a level as like a screen resolution. | |
// | |
// @param $a [BluePrint] | |
// @param $name [String] | |
// @param $level? [Number] | |
// @param $screen [String] | |
// | |
@mixin a-style-like($a, $name, $level: null, $screen: null) { | |
@if $level and type-of($level) != "number" { | |
$screen: $level; | |
$level: null; | |
} | |
@if not $level { | |
$level: map-get($a, first-level); | |
} | |
@if not $screen { | |
@error("InvalidArgumentException: missing $screen argument"); | |
} | |
@if type-of($screen) != "string" { | |
@error("InvalidArgumentException: invalid $screen argument `#{$screen}`"); | |
} | |
$styling-map: a-styling-map($a, $name, $level, $screen); | |
@include css-props(map-get($styling-map, $screen)) { | |
@content; | |
} | |
} | |
// | |
// 4. Documentation | |
// | |
// | |
// Getting start guide | |
// | |
// | |
// 1. create blueprint | |
// | |
// A blueprint is a huge nested map. | |
// | |
// At its root level we have keys below. | |
// | |
// * first-level: number which indicates the first-level (`4` by default) | |
// * first-screen: string which is the key of the main breaking point (`mobile` as default) | |
// * breaking: map of screens and its breaking point features | |
// * sizing: all sizes by screen and level (he first level 0 is reserved for `none` value) | |
// * styling: like graphql resolver but here we resolved sizing | |
// | |
// @import '~atomik'; | |
// 1.1 basic setup: new default-based blueprint | |
$a: a-core(); | |
// 1.2 advanced setup: new blueprint with some overrides | |
$a: a-core(( | |
first-level: 4, // default level value | |
first-screen: mobile, // default screen value | |
// breaking points screen | |
breaking: ( | |
mobile: ( max-width: 899px ), | |
desktop: ( min-width: 900px ) | |
), | |
// sizing groupe by name, screen and level | |
// the first index of screen list values is the level 0 | |
sizing: ( | |
font-size: ( | |
mobile: ( 0, 30px, 25px, 20px, 15px, 12px, 10px, 8px ), | |
desktop: ( 0, 60px, 50px, 40px, 30px, 15px, 12px, 10px ) | |
), | |
spacing: ( | |
mobile: ( none, 32px, 16px, 8px, 4px, 2px, 1px, 0 ), | |
desktop: ( none, 128px, 64px, 32px, 16px, 8px, 4px, 2px ) | |
) | |
), | |
// styling resolution | |
// when value is a string: it's a reference to a sizing key | |
// when value is a list: it's a derived/composed styling | |
styling: ( | |
margin: spacing, | |
padding: spacing, | |
box: ( margin, padding ) | |
) | |
)); | |
// | |
// 2. Done | |
// | |
// Now, the setup is done! The only required thing | |
// to use every functions API is a blueprint instance. | |
// | |
// Now, we can do awesome responsive things without efforts. | |
// | |
// Three main mixins are declare by atomik scss import: | |
// | |
// * a-screen: mixin style inside a screen media-query | |
// * a-style: mixin a style for a level (responsive atomic way) | |
// * a-style-like: mixin a style for a level as like a screen resolution | |
// | |
// Let's use them! | |
// | |
.my-header { | |
@include a-style($a, ( box, font-size ), 3); | |
.title { | |
@include a-style($a, font-size, 2); | |
} | |
@include a-screen($a, desktop) { | |
@include a-style-like($a, min-height, 2, desktop); | |
} | |
} | |
// | |
// That will output: | |
// | |
// ```css | |
// .my-header { | |
// margin: 8px; | |
// padding: 8px; | |
// font-size: 20px; } | |
// @media only screen and ((min-width: 900px)) { | |
// .my-header { | |
// /* desktop */ | |
// margin: 32px; | |
// padding: 32px; | |
// font-size: 40px; } } | |
// .my-header .title { | |
// font-size: 25px; } | |
// @media only screen and ((min-width: 900px)) { | |
// .my-header .title { | |
// /* desktop */ | |
// font-size: 50px; } } | |
// @media only screen and ((min-width: 900px)) { | |
// .my-header { | |
// /* desktop */ | |
// min-height: 480px; } } | |
// ``` | |
// |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment