Graduate Program KB

Module 6 - Typography and Images

Text Rendering

  • Given the exact same HTML and CSS viewed in two different browsers (such as Chrome and Safari), they'd look almost identical

    • Lookingly closely, letters shift very slightly between browsers
    • The reason for this is browsers implement different kerning algorithms
  • Kerning refers to the space between characters, nudging them left or right to make them "feel" right

    • No kerning occurs for monospaced fonts since those characters align neatly into columns
    • Kerning can be disabled with font-kerning: none
    • The property letter-spacing allows us to increase or decrease spacing between characters
      • It amplifies based off whatever kerning the browser decides on
  • Custom kerning can be achieved by:

    • Disable built-in kerning
    • Wrap each letter in a span
    • Shift letters using letter-spacing, picking custom values for each span
  • The browser's operating system affects how typography is rendered by using different rasterisation and anti-aliasing algorithms

  • In the early days of computing, fonts were basically a collection of images, one for each character at each font size (bitmap font)

    • Nowadays, it's more common for fonts to use vector formats such as ttf, otf, svg, woff and woff2
    • Vector fonts store a mathematical set of instructions for each character, it can be scaled to any size without it becoming pixelated/blurry
    • Rasterisation is the process of turning a vector font into characters on the screen, the browser has to figure out which colour to make each pixel
      • The simplest method is filling pixels that the vector path crosses over
    • This method of rasterisation produces sharp, pixelated text. The browser applies anti-aliasing to make the text appear smoother
  • The -webkit-font-smoothing property allows us to switch which aliasing algorithm a browser uses

    • It has the default value subpixel-antialiased with the other options of antialiased and none
    • Subpixel antialiasing uses RGB elements of a pixel to produce more precise anti-aliasing, essentially it's taking advantage of extra thickness to produce a smoother effect
    • Unfortunately, -webkit-font-smoothing only works on macOS
    • font-smooth also exists but it's not a standard feature yet
  • Modern day phones have a higher device pixel ratio, so Apple has disabled subpixel antialiasing by default because they do more harm than benefit on their retina displays

  • However, macOS browsers like Chrome and Safari don't inherit the system default, so those need to be explicitly set to antialiased

    *, *:before, *:after {
        box-sizing: border-box;
        -webkit-font-smoothing: antialiased;
    }
    

Text Overflow

  • The text placement algorithm the browser uses to decide when to line-break is relatively intuitive

  • The browser groups charaters into words which are a collection of characters that can't be broken up

    • Words are separated by "breaking characters" such as whitespace and hyphen, these are soft wrap opportunities
    • When content spills outside a container, the browser looks for the closest soft wrap opportunity and splits the remaining content on a new line
    • You can also add space without creating a soft wrap opportunity by using the special HTML entity   instead
    // $10 AUD won't split up now
    <p>That item costs about $10&nbsp;AUD or you can trade me an item.</p>
    
  • There are times when a word is too long to fit in a container with no soft wrapping opportunities present, such as a long URL link

    • In this case, we can use the overflow-wrap property to line-wrap long words
    p {
        overflow-wrap: break-word;
    }
    
  • Hyphenation is the convention of indicating a long word has been split by joining the segments with a hyphen

    • It's not part of the text placement algorithm but it can be added with the hyphens property
    p {
        overflow-wrap: break-word;
        hyphens: auto;
    }
    
  • Another option instead of deciding to line-wrap is to leave off the sentence with an ellipsis ...

    p {
        overflow: hidden;
        text-overflow: ellipsis;
    }
    
    • We can also prevent line-wrapping and add an ellipsis if the content can't fit on a single line
    • In order to truncate the content, line-wrapping needs to be disabled first
      • The browser decides where to put line-breaks first before concerning itself with overflow
    p {
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }
    
    • Multi-line ellipsis is also possible if we want to show a few lines and add the ellipsis afterwards
    • Use the -webkit-line-clamp property which is available on all major browsers
    • Though, you need to also set display: -webkit-box and -webkit-box-orient: vertical
    p {
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 3;
        overflow: hidden;
    }
    
  • This type of layout resembles print media like books and magazines

  • Column layout is one of the major layout modes

  • To create the print-style layout, it requires another layout mode called multi-column layout

    • It's a niche layout that automatically splits content across multiple columns that allows the container to grow and shrink dynamically
    .wrapper {
        columns: 2;
        column-gap: 16px;
        padding: 16px;
    }
    
    • We can specify that a particular child element isn't broken across columns with the following declaration
    p {
        break-inside: avoid;
    }
    
  • Float is another layout algorithm, where text or inline elements typically wrap around another element placed on the side

    • This layout didn't last long since Flexbox offered a more elegant solution to these problems
    • Only this layout allows text to wrap around an embedded element using the float property
      • Elements can float left or right
      • Either add left or right margin depending on where your element is floating to create some space from the wrapping text
    img {
        float: left;
        margin-right: 16px;
    }
    
  • Books generally use indentation to differentiate between paragraphs

    • The first line of each paragraph is inset, achieved by selecting the first letter with the :first-letter pseudo-element
    • A more explicit way is to use the text-indent property
    p::first-letter {
        margin-left: 2rem;
    }
    
    /* OR */
    
    p {
        text-indent: 2rem;
    }
    
        This is a paragraph that has been indented, using the first-letter pseudo-element. 
    Normally, we can't apply CSS directly to text, but this pseudo-element acts as an 
    invisible "span" that wraps around the first character.
    
  • It's common for text to be "justified", meaning that space between each word is slightly tweaked so each line fills the available horizontal space

    p {
        text-align: justify;
        padding: 16px;
    }
    
  • Another typographical effect are "drop caps" where the first letter is large, typically on the first paragraph in a page/chapter

    p:first-of-type::first-letter {
        initial-letter: 2;      /* Number of lines to span */
    }
    

Masonry Grid with Columns

  • A masonry layout is a way of stacking elements in an asymmetric grid, think of sites like Pinterest and Unsplash

    • Typically used with images because it creates a seamless grid of elements with varying sizes
    <style>
        ul {
            --gap: 16px;
            column-count: 3;
            column-gap: var(--gap);
            padding: var(--gap);
            list-style-type: none;
        }
    
        li {
            break-inside: avoid;
        }
    
        img {
            display: block;
            width: 100%;
            margin-bottom: var(--gap);
        }
    </style>
    
    <ul>
        <li>
            <a href="">
                <img src=""/>
            </a>
        </li>
        <li> 
            ... 
        </li>
        ...
    </ul>
    
  • One limitation is the items are laid out from the top to bottom

    • It's not intuitive because our eyes view content from side to side
    • It affects the tab order
    • If more items are dynamically added to the grid, they aren't added to the end as we'd expect
      • They're added to the final column, then everything is redistributed after
  • Currently, an implementation of masonry layouts is in development but it's still experimental

    .container {
        display: grid;
        grid-template-columns: repeat(4, 1fr);
        grid-template-rows: masonry;
    }
    

Text Styling

  • Pages like Wikipedia don't constrain the maximum width so it will grow as wide as possible
    • Makes text much harder to read
    • Ideally, sources agree that an acceptable line length is between 50 and 75 charaters
    • This guideline can be enforced in CSS through the built-in ch unit
      • 1ch is equal to the width of the 0 character at the current font size
      • Different font types can affect this measurement, just use your judgment to decide a good maximum character width
    p {
        max-width: 50ch;
    }
    

Font Stacks

  • The font-family property specifies how to change the font for an element and potentially its children

  • It takes in multiple values separated by commas, almost like a list of preferences

  • Sometimes, fonts may be unavailable for the following reasons:

    • The font isn't installed on the device
    • The font is a web font and it hasn't been downloaded
  • Every operating system has different fonts, but only a handful are universally available such as:

    • Arial
    • Courier New
    • Georgia
    • Helvetica
    • Tahoma
    • Times New Roman
  • The idea with a font stack is to provide a list of fonts that the browser can pick, making sure all users see an acceptable non-default font

  • The system font stack is a trend where a stack of fonts default to the nicest default option for each platform

    p {
        font-family:
            -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui,
            helvetica neue, helvetica, Ubuntu, roboto, noto, arial, sans-serif;
    }
    
    • CSS variables make working with the system font stack much easier
    html {
        --font-sans-serif:
            -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui,
            helvetica neue, helvetica, Ubuntu, roboto, noto, arial, sans-serif;
    
        --font-serif:
            Iowan Old Style, Apple Garamond, Baskerville, Times New Roman,
            Droid Serif, Times, Source Serif Pro, serif, Apple Color Emoji,
            Segoe UI Emoji, Segoe UI Symbol;
    
        /* Set a global default */
        font-family: var(--font-sans-serif);
    }
    
    /* Apply different fonts as needed */
    p {
        font-family: var(--font-serif);
    }
    

Web Fonts

  • We can download and use a custom font if we don't want to use one pre-installed on the device

  • Google Fonts is an online repository with hundreds of popular and free, open-source web fonts

    • Add this snippet in the <head> of the HTML file
      • If there's no index.html file in the project, and you're using a framework like Next.js, use the next/font module
      • Though, some frameworks generate this file automatically so they can include the JS bundles and perform other optimisations
    <link rel="preconnect" href="https://fonts.gstatic.com">
    <link
        href="https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@1,400;1,600&display=swap"
        rel="stylesheet"
    >
    
    • Keep in mind the display=swap query parameter at the end of the URL for later
    • It will download a stylesheet which downloads a font, that font is now accessible in our CSS
    p {
        font-family: "Open Sans", sans-serif;
    }
    
    • The downsides of Google Fonts is that lots of amazing fonts aren't available, and self-hosted web fonts can perform better
  • Vercel released a free resource called Fontsource which provides an easy method to install and use self-hosted web fonts

    • It's designed for modern JS applications, fonts can be installed with npm and they support all Google Fonts as well as free open-source fonts
  • The last way is to add a web font from scratch

  • Fonts that run on our computers typically come in .otf or .ttf formats

    • Here's a tool that converts our font into a web-friendly format
  • The @font-face at-rule allows us to inform the browser that we want to use a web font

    • Here, we have multiple statements because each font weight and style has its own file
    • There are three different font weights and an italic style fpr the regular weight
    • The font file contains no metadata about its weight/style so they need to be linked up
      • In the @font-face statement, the src for the set of characters is specified
      • Then, the associated metadata with that set is declared (font-family, font-weight and font-style)
    • The fonts should be loaded ASAP, so put the @font-face statements in the index.html file
      • When the statements are parsed by the browser, the font file declared will be fetched and loaded
      • Afer that, the font can be used like any other font in the CSS
    @font-face {
        font-family: "Wotfard";
        src: url("/fonts/wotfard-regular.woff2") format("woff2");
        font-weight: 400;
        font-style: normal;
    }
    
    @font-face {
        font-family: "Wotfard";
        src: url("/fonts/wotfard-medium.woff2") format("woff2");
        font-weight: 500;
        font-style: normal;
    }
    
    @font-face {
        font-family: "Wotfard";
        src: url("/fonts/wotfard-bold.woff2") format("woff2");
        font-weight: 700;
        font-style: normal;
    }
    
    @font-face {
        font-family: "Wotfard";
        src: url("/fonts/wotfard-regular-italic.woff2") format("woff2");
        font-weight: 400;
        font-style: italic;
    }
    
  • The @font-face statement lets us connect a specific font weight to a character set

    • Typically, whenever font-weight: bold or font-weight: 700 are used, the browser uses heavier characters
    • If there is no bold font file, then the browser creates a "faux" bold text by expanding the thickness of every line in every font
      • This imitation font tends to create muddy, indistinguishable letters
    • Similarly, for italic fonts, the browser starts slanting characters whereas real italics use alternate characters

Font Loading UX

  • When a person visits a site, they need to download the web font files being used

  • There are two options but both introduce problems:

    • Wait until the web font has been downloaded before showing any text

      • Depriving user of text they care about
      • Flash Of Invisible Text (FOIT)
    • Render the text in a "fallback" font, one that's locally installed on the user's device

      • Experience can be different, changing fonts can cause layout shifts
      • Flash Of Unstyled Text (FOUT)
  • To control how a font should be rendered before it's available, use the font-display property

    • The value swap is set by the query parameter included in the URL when we added the Google Font in the other example
    • The moment an HTML element tries to render text in our web font, a timer starts and goes through 3 periods:
      • The block period when text will be painted with invisible ink, so text won't be visible. It will render the web font if it becomes available
      • The swap period when a fallback font is rendered (first available font in the font stack). It will render the web font if it becomes available
      • The failure period when the web font isn't loaded during the previous two periods, it stops trying. It will keep showing the fallback font no matter what happens with the web font
    • The length of the periods depends on the font-display property, it's a way to control the length of each window
      • font-display: block prioritises thhe availability of the font over everything else, it has a relatively long block period and infinite swap period
      • There's no explicit duration mentioned in the specification but it shouldn't take more than 3 seconds
      • With font-display: swap, there's little to no block period, and has an infinitely long swap period. The goal is to render text as quickly as possible
      • swap is the value Google Font uses
      • font-display: fallback is a perfect balance between the two, it has a very short block period (about 100ms) and a moderate swap period (about 3s)
      • It prioritises a smooth user experience
      • font-display: optional is good when the web font provides a subtle improvement but is not important. It has a short block period (less than 100ms) and no swap period
      • If the web font doesn't load immediately, it won't be used at all. Prevents flashes of unstyled text and layout shifts caused by a slow-loading font
      • Generally, users will see the fallback font for their first page but subsequent pages will apply the web font
    @font-face {
        font-family: "Great Vibes";
        src: url("/fonts/great-vibes.woff2") format("woff2");
        font-weight: 400;
        font-style: normal;
        font-display: swap;
    }
    
  • Browsers have begun adding font descriptors to make the transition between the fallback font and web font less jarring

    • size-adjust, ascent-override and descent-override all control the vertical size or position of the text
    • size-adjust is similar to font-size
    • ascent-override and descent-override are similar to line-height
    @font-face {
        font-family: "Great Vibes";
        src: url("/fonts/great-vibes.woff2") format("woff2");
        font-weight: 400;
        font-style: normal;
        font-display: swap;
    }
    
    @font-face {
        font-family: "Fallback";
        size-adjust: 95%;
        ascent-override: 90%;
        descent-override: 20%;
        src: local("Arial");
    }
    
    body {
        font-family: "Great Vibes", "Fallback", sans-serif;
    }
    

Variable Fonts

  • When using web fonts, each weight and style is its own file

    • Consider using three weights (regular, medium and bold) and two styles (regular and italic), that's six different font files
  • We can use variable fonts to pass parameters that can be manipulated to control the rendered output

    • With standard fonts, we might need to select two or three weights
    • With variable fonts, there are hundreds of valid values
  • Google Fonts has support for variable fonts, this is a list of supported fonts

    • Use it the same way by adding the snippet code in Web Fonts
  • With variable fonts created from scratch, the @font-face statement needs to be updated

    • font-weight now has a range which specifies that this @font-face statement should apply for any font weight between this inclusive range
    @font-face {
        font-family: "Recursive";
        src:
            url("/fonts/recursive-variable.woff2") format("woff2 supports variations"),
            url("/fonts/recursive-variable.woff2") format("woff2-variations");
        font-weight: 300 1000;
        font-display: fallback;
    }
    
    • To specify custom variations like "cursive" or "casual", the font-variation-settings property can be used\
    • It accepts a comma-separated list of settings, each setting tweaks an axis (four-letter string) and a value (number)
    • It can also be used to set "built-in" font properties such as font-weight and font-style but it's not as readable, so just declare them separately
    h1 {
        color: blue;
        font-variation-settings: "CRSV" 1, "CASL" 0.9;
    }
    

Icons

  • Icons are commonly used in pretty much every modern application

  • There are two common ways of implementing them:

    • Use an "icon font", where each character resolves to an icon instead of a letter
    • Use an SVG (Scalable Vector Graphic), where each icon is an inline SVG DOM node
      • SVGs store mathematical instructions for how to draw the shapes within an image
      • A typical image format such as JPEG and PNG store binary information about colours of specific pixels
  • The second option tends to be preferred

    • SVGs look better
    • Easier to position and use (width makes more sense than font-size)
    • More accessible
    • Can be multi-colour
    • Can apply more styling and animation
  • There are a wide range of pre-exisitng icons set that can be sourced online

  • If you're using React, SVG files need to be converted to JSX first

    • Here's a tool to assist with that
    • The creator of Feather has created an NPM package react-feather which can import their icons and render them
    • Similar packages also exist for Vue, Angular and Svelte
  • There's one common issue that SVGs bring, they have a default value of display: inline

    • Inline elements have magic space
    • If the following snippet was rendered, there'd be a noticeable amount of empty space at the bottom of the button
    function Header() {
        return (
            <Button>
                <SVGIcon />
            </Button>
        );
    }
    
    const Button = styled.button`
        padding: 0;
        border: 2px solid black;
    `
    
    • This can be solved in CSS using a descendant selector, setting the SVG to display as a block
    const Button = styled.button`
        padding: 0;
        border: 2px solid black;
    
        & > svg {
            display: block;
        }
    `;
    

Images

  • The <img> tag has two required attributes:

    • alt is a property to specify alternative text, serving as a description
      • It provides critical context to people who can't see images, whether it's visual disability or faulty network connection
      • Not all images require alternative text, in this case just specify alt=""
    • src specifies the source of the image, could be a URL or a path to the image file for instance
  • Good alternate texts should consider the context that the image is used in, such as what the image is displaying and where it could redirect the user

    • This means the same image could have different alternative texts
    • The following example informs the user the image is a logo and it leads to the homepage
    <img
        alt="Company Logo - Home"
        src"/logo.png"
    />
    
  • There's also the background-image property in CSS that allows images to function as wallpapers, they should be hung behind the content for aesthetic purposes

    • The <img> tag should be used for semantically-meaningful images that require an alternate text

Fit and Position

  • Remember that <img> is a replaced element, unlike other DOM nodes, the browser replaces the <img> tag with a foreign entity, the image itself

    • Images don't always follow the rules, for instance, they can be given a height even though inline elemenets generally wouldn't be able to have one
    • They come with their own intrinsic size based on the dimension of the image file
    • They also have their own intrinsic aspect ratio, meaning if one dimension is modified, the other one scales up or down to preserve the aspect ratio
    • If both sides are provided a length that doesn't match the natural aspect ratio, the image will distort
  • An alternative to distorting the images is to crop it, trimming off any excess that can't fit in the container

    • The property object-fit takes a few values to control how the image is presented
    • fill is exactly like it sounds, the image expands or shrinks to fill the available space in the container
    • contain scales an image proportionately until it fits in the container while preserving its aspect ratio
    • cover preserves the aspect ratio and fills the container but we won't be able to see the whole image
    • none scales the image to be its natural size but only displays whatever can fit in the container DOM node
    • This property also works on <video> tags
  • The property object-position allows us to specify how an image should be translated or cropped within its available space

    • It takes two numbers, one for the horizontal offset and another for the vertical offset
    • It's useful when using the cover value for object-fit so we can select which portion of the cropped image to display
    img {
        object-position: left top;      /* Same as 0% 0% */
    }
    

Images and Flexbox

  • Consider the following snippet when rendered

    • The images appear vertically stretched
    • flex: 1 is set which sets flex-basis to 0, but it's still overflowing the container on smaller window sizes
    • Furthermore, there are two images and one is set to consume twice as much space as the other by using flex: 2, but it doesn't behave as expected
    <style>
        body {
            margin: 0;
            padding: 0;
        }
    
        main {
            display: flex;
            gap: 8px;
        }
    
        img {
            flex: 1;
        }
    
        .twice {
            flex: 2;
        }
    </style>
    
    <main>
        <img
            alt=""
            src="URL"
        />
        <img
            alt=""
            class="twice"
            src="URL2"
        />
    </main>
    
    • Issues:

      • The flexbox layout algorithm displays the images in a row, the primary-axis is horizontal and cross-axis is vertical

        • Whenever, there's a cross-axis, its default value is stretched
        • A workable solution to this would be setting align-items: flex-start to the <main> tag
      • The images don't scale until a larger window size, this is due to the images having a minimum width set to their intrinsic width

        • A workable solution to this would be setting min-width: 0 to the <img> tag
      • Images should be treated as content, not a direct child of a flex container

        • Put images in a parent container, this positions them according to flow layout rather than flex layout
        • The images fill the vertical space and rescales as the container changes shape
    • Here's the CSS to fix all the issues presented

    <style>
        body {
            margin: 0;
            padding: 0;
        }
    
        main {
            display: flex;
            gap: 8px;
        }
    
        .wrapper {
            flex: 1;
        }
    
        .twice {
            flex: 2;
        }
    
        img {
            width: 100%
        }
    </style>
    
    <main>
        <div class="wrapper">
            <img
                alt=""
                src="URL"
            />
        </div>
        <div class="wrapper twice">
            <img
                alt=""
                src="URL2"
            />
        </div>
    </main>
    

Aspect Ratio

  • Consider three images given the same height of 200px, but they render with different widths due to differing natural aspect ratios

  • If you wanted all the images to have the same aspect ratio, then you could set the width, height and object-fit properties to avoid stretching

    img {
        width: 200px;
        height: 200px;
        object-fit: cover;
    }
    
  • To scale the images proportionately at a prescribed aspect ratio, use the property aspect-ratio

    • It takes two slash-separated numbers, such as 4 / 3, 7 / 11, etc.
    • This represents the ratio of width to height
    • This property is not exclusive to images, it can be used on any element
    img {
        width: 30%;
        aspect-ratio: 1 / 1;
        object-fit: cover;
    }
    
    section {
        display: flex;
        gap: 8px;
    }
    
    .wrapper {
        flex: 1;
    }
    
    img {
        display: block;
        width: 100%:
        aspect-ratio: 3 / 2;
        object-fit: cover;
    }
    

Responsive Images

  • Different screens have different hardware to software pixel ratios, known as high-DPI displays

  • Rendering images at their native size will appear fuzzy on high-DPI displays

    • We need to serve different images depending on the screen's pixel ratio
  • It's common to export two or three versions of an image with the ratio included in the filename

    • cat.jpg (300 x 300)
    • cat@2x.jpg (600 x 600)
    • cat@3x.jpg (900 x 900)
  • Use the srcset attribute on the <img> tag

    • srcset is essentially a plural version of src, the browser scans through the list and applies a matching image
    • The redundant src property is set here to support older browsers
    <img
        alt=""
        src="cat.png"
        srcset="
            cat.png 1x,
            cat@2x.png 2x,
            cat@3x.png 3x
        "
     />
    
  • Another way to solve this problem is by using the <picture> tag

    • The benefit here is that we can specify multiple sources, allowing us to supply different file formats
    • This allows us to use modern image formats in a safe way, providing fallbacks for other browsers
    <picture>
        <source
            type="image/avif"
            srcset="
                cat.avif 1x,
                cat@2x.avif 2x,
                cat@3x.avif 3x
            "
        />
        <source
            type="image/webp"
            srcset="
                cat.webp 1x,
                cat@2x.webp 2x,
                cat@3x.webp 3x
            "
        />
        <img
            alt=""
            src="cat.png"
        />
    </picture>
    
    • <source> tags should be ignored from a styling perspective, it's just metadata
    • Additional properties like alternate text should be added to the <img> tag
    • The <picture> element behaves like a <span>, an inline wrapper that wraps around the <img> tag
    • <picture> elements can be children of flex containers which help display and scale <img> elements easily

Background Images

  • Images can't be tiled, this means they can't form a repeating pattern unless we use a CSS background image instead

    • A background image will render at its native size by default, then tiled across the element
    • -webkit-min-device-pixel-ratio is used to support Safari
    body {
        background-image: url("cat.png");
    }
    
  • However, most monitors are "high DPI". An image rendered at natural size will look blurry

    • We need to provide different images for different devices, scaling based on pixel ratio
    • Can use media queries and the min-resolution property
    body {
        background-image: url("cat.png");
        background-size: 450px;
    }
    
    @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 2dppx) {
        body {
            background-image: url("cat@2x.png");
        }
    }
    
    @media (-webkit-min-device-pixel-ratio: 3), (min-resolution: 3dppx) {
        body {
            background-image: url("cat@3x.png");
        }
    }
    
  • The background-size property also accepts values similar to object-fit

    body {
        background-image: url("cat.png");
        background-size: cover;
    }
    
    • There's also background-position, which works just like object-position
  • By default, the background image will be tiled side-by-side, top-to-bottom

    • The pattern will be truncated at the bottom and on the right
    • The background-repeat property allows us to manipulate this algorithm, repeat is the default value
      • The other two options are round and space
      • round scales the image up and down to avoid having the image cut off at the bottom or right, it preserves the aspect ratio
      • space won't tweak the image size, instead it leaves gaps between the images