AMP is a way to build web pages that render with reliable and fast performance. AMP includes many responsive and interactive components, and resources such as quick-start templates to help developers create stunning AMP pages.
In this codelab, you're going to build a fully-responsive, interactive and beautiful AMP page that incorporates many of AMP's features and extended components such as:
|
We will use Node.js to run a local HTTP server to serve our AMP page. Check the Node.js website on how to install it on your Operating System.
Our tool of choice to serve content locally will be serve, a Node.js based static content server. To install it, run:
npm install -g serve
AMP Templates is a repository of quick-start AMP templates and components to help create modern, responsive AMP pages quickly.
Visit AMP Templates and download the code for the "Simple Article" for "Years Best Animal Photos" Template.
Extract the contents of the ZIP file.
Run the command serve
inside the article
folder to serve the files locally.
Visit http://localhost:5000/templates/article.amp.html in your browser! (Port might be 3000 or a different number depending on the version of the `serve` tool, please check the console for the exact address)
While we are at it, let's open the Chrome Devtools and toggle the Device mode as well!
At this point we have a mostly functioning AMP page scaffolded, but the purpose of this codelab is for you to learn and practice, so:
Delete everything inside the <body></body>
!
Now we are left with an empty page which only includes some boilerplate code.
Throughout this codelab, you will add many components to this empty page, partially recreating the template with even more functionality.
An AMP page is an HTML page with some restrictions for reliable performance.
Though most tags in an AMP page are regular HTML tags, some HTML tags are replaced with AMP-specific tags. These custom elements, called AMP HTML components, make common patterns easy to implement in a performant way.
The simplest AMP HTML file looks like this (sometimes referred to as AMP boilerplate)
<!doctype html>
<html ⚡>
<head>
<meta charset="utf-8">
<link rel="canonical" href="hello-world.html">
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
<script async src="https://cdn.ampproject.org/v0.js"></script>
</head>
<body>Hello World!</body>
</html>
Take a look at the code of the empty page you created during setup, you will notice it includes this boilerplate and has a few extra additions, importantly, a <style amp-custom>
tag that includes lots of minified CSS.
By default, AMP is not opinionated about design and does not enforce a particular set of styles. Most AMP components have very basic styling and it is left to the page authors to provide their custom CSS. That's where <style amp-custom>
comes into play.
AMP Templates however provides its own opinionated CSS styles that are beautifully-designed, cross-browser and responsive to help developers build elegant AMP pages quickly. The template code you have downloaded includes these opinionated CSS styles in <style amp-custom>.
We will start by adding back some of the components we have removed from the template to create a shell for our page, including navigation menu and page header image and title.
We will get help from AMP Start's UI Components page to do so, but we won't dig deep into their implementation details. Later steps in the codelab will provide plenty of opportunities to do so.
Head to https://ampstart.com/components#navigation and copy/paste the HTML code provided for RESPONSIVE MENUBAR into the body
of your page.
The code provided by AMP Start includes the necessary HTML and CSS class structure to implement a responsive navigation bar for your page.
Try it out! Resize your window to see how it responds to different screen sizes.
This code uses CSS media queries and couple of AMP components, namely amp-sidebar and amp-accordion.
You guessed it! AMP Start provides ready-to-use snippets for beautiful, responsive hero image and title as well.
Head to https://ampstart.com/components#media and copy/paste the HTML code provided for Fullpage Hero into your code, right after the <!-- End Navbar --> comment
in body.
Let's update the image and the title now.
As you may have noticed, there are two different amp-img
tags in the code snippet. One is used for smaller widths and should point to a lower-resolution image and the other one for larger displays. They are toggled automatically based on the media
attribute which AMP supports on all AMP elements.
Update the src
, width
and height
to different images and the title to "Most Beautiful Hikes in the Pacific Northwest" by replacing the existing <figure>...</figure>
with:
<figure class="ampstart-image-fullpage-hero m0 relative mb4">
<amp-img width="600" height="900" layout="responsive" src="https://unsplash.it/600/900?image=1003" media="(max-width: 415px)"></amp-img>
<amp-img height="1800" layout="fixed-height" src="https://unsplash.it/1200/1800?image=1003" media="(min-width: 416px)"></amp-img>
<figcaption class="absolute top-0 right-0 bottom-0 left-0">
<header class="p3">
<h1 class="ampstart-fullpage-hero-heading mb3">
<span class="ampstart-fullpage-hero-heading-text">
Most Beautiful Hikes in the Pacific Northwest
</span>
</h1>
<span class="ampstart-image-credit h4">
By <a href="#" role="author" class="text-decoration-none">D.F. Roy</a>,<br> January 14, 2017
</span>
</header>
<footer class="absolute left-0 right-0 bottom-0">
<a class="ampstart-readmore py3 caps line-height-2 text-decoration-none center block h5" href="#content">Read more</a>
</footer>
</figcaption>
</figure>
Let's take a look at the page now:
The completed code for this section can be found here: http://codepen.io/aghassemi/pen/RpRdzV
In this section we will add responsive images, videos and embeds in addition to some text to our page.
Let's add a main
element that will host the content of the page. We'll add it to the end of body:
<main id="content">
</main>
Add the following inside main
<h2 class="px3 pt2 mb2">Photo Gallery</h2>
<p class="ampstart-dropcap mb4 px3">Vivamus viverra augue libero, vitae dapibus lectus accumsan eget. Pellentesque eget ipsum purus. Maecenas leo odio, ornare nec ex id, suscipit porta ipsum. Ut fringilla semper cursus.</p>
Since AMP is just HTML, there is nothing special about this code except for those CSS class names! What are px3, mb2, ampstart-dropcap, etc..?
Where are they coming from?
These classes are not part of AMP HTML but provided by AMP Start boilerplate that we are using. Do you remember that minified CSS inside <style amp-custom>
? The rules for these classes are defined there.
AMP Start uses Basscss to provide a low level CSS toolkit and adds additional AMP Start specific classes on top of it.
In this snippet, px3, mb2, etc...
are defined by Basscss and translate to padding-left-right and margin-bottom respectively. ampstart-dropcap
is provided by AMP Start and makes the first letter of a paragraph larger.
You can find documentation for these pre-defined CSS classes on http://basscss.com/ and https://ampstart.com/components.
Let's see how the page looks now:
Making responsive pages is easy in AMP! In most cases as simple as adding layout="responsive"
attribute to the AMP elements such as amp-img.
Similar to HTML img
tag, amp-img
also supports srcset
to specify different images for varying viewport widths and pixel densities.
Add an amp-img
to main
:
<amp-img
layout="responsive" width="1080" height="720"
srcset="https://unsplash.it/1080/720?image=1043 1080w, https://unsplash.it/720/480?image=1043 720w"
alt="Photo of mountains and trees landscape">
</amp-img>
With this code, we are creating a responsive image by specifying layout="responsive"
and providing width
and height.
Why do I have to specify width and height when using responsive layout?
Two reasons:
Let's take a look at the page now:
AMP supports many third-party video players such as YouTube and Vimeo and has its own version of HTML5 video
element under amp-video
extended component. Some of these video players, including amp-video
and amp-youtube
support muted autoplay on mobile as well.
Similar to amp-img
, amp-video
can become responsive by just adding layout="responsive"
Let's add an autoplaying video to our page.
Add another paragraph and the following amp-video
element to main:
<p class="my2 px3">Vivamus viverra augue libero, vitae dapibus lectus accumsan eget. Pellentesque eget ipsum purus. Maecenas leo odio, ornare nec ex id, suscipit porta ipsum. Ut fringilla semper cursus.</p>
<amp-video
layout="responsive" width="1280" height="720"
autoplay controls loop
src="https://storage.googleapis.com/ampconf-76504.appspot.com/Bullfinch%20-%202797.mp4">
</amp-video>
Let's take a look:
AMP has extended components for many third-party embeds such as Twitter and Instagram, and for the ones we don't have a component for, there is always amp-iframe!
Now let's add an instagram embed to our page.
Unlike amp-img
and amp-video
, amp-instagram
is not a built-in component and the import script tag for it must be included explicitly in head
the AMP page before the component can be used.
The AMP Start boilerplate that we are using already has included several import script tags, look for them at the beginning of the head
tag and ensure the following import script line is included:
<script custom-element="amp-instagram" src="https://cdn.ampproject.org/v0/amp-instagram-0.1.js" async></script>
Add another paragraph and the following amp-instagram
element to main:
<p class="my2 px3">Vivamus viverra augue libero, vitae dapibus lectus accumsan eget. Pellentesque eget ipsum purus. Maecenas leo odio, ornare nec ex id, suscipit porta ipsum. Ut fringilla semper cursus.</p>
<amp-instagram
layout="responsive" width="566" height="708"
data-shortcode="BJ_sPxzAGyg">
</amp-instagram>
Let's take a look:
That's probably enough content for now!
The completed code for this section can be found here: http://codepen.io/aghassemi/pen/OpXGoa
So far we have only created static content for our page, in this section we will create an interactive photo gallery using components such as carousel, lightbox and AMP actions.
Although AMP does not support custom JavaScript, it still exposes several building blocks to receive and handle user actions.
Having every image for our photo-focused AMP page visible on the page will not create a great user experience. Fortunately we can use amp-carousel to create a horizontally swipeable slides of photos.
First, let's make sure the script tag for amp-carousel
is included in head
:
<script custom-element="amp-carousel" src="https://cdn.ampproject.org/v0/amp-carousel-0.1.js" async></script>
Now let's add a responsive amp-carousel
of type slides with several images to main
<p class="my2 px3">Vivamus viverra augue libero, vitae dapibus lectus accumsan eget. Pellentesque eget ipsum purus. Maecenas leo odio, ornare nec ex id, suscipit porta ipsum. Ut fringilla semper cursus.</p>
<amp-carousel
layout="responsive" width="1080" height="720"
type="slides">
<amp-img src="https://unsplash.it/1080/720?image=1037" layout="fill"></amp-img>
<amp-img src="https://unsplash.it/1080/720?image=1038" layout="fill"></amp-img>
<amp-img src="https://unsplash.it/1080/720?image=1039" layout="fill"></amp-img>
<amp-img src="https://unsplash.it/1080/720?image=1040" layout="fill"></amp-img>
<amp-img src="https://unsplash.it/1080/720?image=1041" layout="fill"></amp-img>
<amp-img src="https://unsplash.it/1080/720?image=1042" layout="fill"></amp-img>
<amp-img src="https://unsplash.it/1080/720?image=1043" layout="fill"></amp-img>
<amp-img src="https://unsplash.it/1080/720?image=1044" layout="fill"></amp-img>
</amp-carousel>
type="slides"
ensures only one image is visible at a time and allows users to swipe through them.
For the images inside the carousel, we use layout="fill"
since slide carousel always fills its size with the child element, so there is no need to specify a different layout that requires width and height.
Let's try it out and see how it looks:
Now let's add a horizontally scrollable container to host the thumbnails for these images. We will use <amp-carousel>
again but without type="slides"
and with a fixed-height layout.
Add the following right after the previous amp-carousel
element.
<amp-carousel layout="fixed-height" height="78" class="mt1">
<amp-img src="https://unsplash.it/108/72?image=1037" layout="fixed" width="108" height="72"></amp-img>
<amp-img src="https://unsplash.it/108/72?image=1038" layout="fixed" width="108" height="72"></amp-img>
<amp-img src="https://unsplash.it/108/72?image=1039" layout="fixed" width="108" height="72"></amp-img>
<amp-img src="https://unsplash.it/108/72?image=1040" layout="fixed" width="108" height="72"></amp-img>
<amp-img src="https://unsplash.it/108/72?image=1041" layout="fixed" width="108" height="72"></amp-img>
<amp-img src="https://unsplash.it/108/72?image=1042" layout="fixed" width="108" height="72"></amp-img>
<amp-img src="https://unsplash.it/108/72?image=1043" layout="fixed" width="108" height="72"></amp-img>
<amp-img src="https://unsplash.it/108/72?image=1044" layout="fixed" width="108" height="72"></amp-img>
</amp-carousel>
Note that for the thumbnail images we simply used layout="fixed"
and low-resolution versions of the same photos.
Let's take a look:
To do this, we need to tie events such as "tap" to actions such as "change the slide to n".
event: We can use the on
attribute to install event handlers on an element and the tap
event is supported on all elements.
action: amp-carousel
exposes a goToSlide(index=INTEGER)
action that we can call from the tap event handler of each thumbnail image.
Now that we know about event and action, let's tie them together.
First we need to give the slides carousel an id
so we can reference it from the tap event handler on the thumbnails.
Modify your existing code to add an id
attribute to the slides carousel (first one):
<amp-carousel
id="imageSlides"
type="slides"
....
Now let's install the event handler (on="tap:imageSlides.goToSlide(index=<slideNumber>)")
" on each thumbnail image:
<amp-img on="tap:imageSlides.goToSlide(index=0)" role="button" tabindex="1" layout="fixed" ...
<amp-img on="tap:imageSlides.goToSlide(index=1)" role="button" tabindex="1" layout="fixed" ...
<amp-img on="tap:imageSlides.goToSlide(index=2)" role="button" tabindex="1" layout="fixed" ...
...
Note that we must also give it a tabindex
and set the ARIA role
for accessibility.
That's it. Now tapping each thumbnail image shows the corresponding image inside the slides carousel!
Can we do this? There doesn't seem to be any actions to change CSS classes for an element to call from the tap event handlers. So how can we highlight the selected thumbnail?
<amp-selector>
to the rescue!
amp-selector is different from components we have used so far. It is not a presentation component as it does not affect the layout of the page, but rather is a building block that allows the AMP page to know what option user has selected.
What amp-selector does is fairly simple yet powerful. In short:
option=<value>
attribute.selected
attribute to that element (and removes it from other option elements in single-selection mode).selected
attribute via CSS attribute selector.Let's see how this helps us accomplish the task in hand.
Add the script tag for amp-selector
to head
:
<script custom-element="amp-selector" src="https://cdn.ampproject.org/v0/amp-selector-0.1.js" async></script>
amp-selector
option=<value>
attribute. selected
attribute as well.<amp-selector>
<amp-carousel layout="fixed-height" height="78" class="mt1">
<amp-img option=0 selected on="tap:imageSlides.goToSlide(index=0)" ...
<amp-img option=1 on="tap:imageSlides.goToSlide(index=1)" ...
...
</amp-carousel>
</amp-selector>
Now we need to add styling to highlight the selected thumbnail.
Add the following custom CSS in <style amp-custom>
right after the minified CSS boilerplate from AMP Start
<style amp-custom>
...
/* low opacity for non-selected thumbnails */
amp-selector amp-img[option] {
opacity: 0.4;
}
/* normal opacity for the selected thumbnail */
amp-selector amp-img[option][selected] {
opacity: 1;
}
</style>
Let's take a look:
Looks good, but did you notice a bug?
If user swipes the slides carousel, the selected thumbnail does not update to reflect that! How can we bind the current slide in the carousel with the selected thumbnail?
In the next section, we will learn how!
amp-selector
and how it can be used as a building block to implement interesting use-cases.The completed code for this section can be found here: http://codepen.io/aghassemi/pen/gmMJMy
In this section we will use amp-bind to improve the interactivity of the image gallery from the previous section.
It is a new extension that supports custom interactivity via data binding and expressions.
It has three key parts:
State is just a shared variable (can be as simple as a single value or a whole data structure). All components can read and write to this shared variable.
Binding is an expression that ties the state to an HTML attribute or text of an element (For both AMP and HTML elements).
Mutation is the action of changing the value of the state as result of some user action or event.
The magic of amp-bind starts when a mutation happens: All components that have a binding to that state will be notified and will update themselves automatically to reflect the new state!
Let's see it in action!
Previously we used AMP actions (e.g. goToSlide()
) to tie the full-image slides carousel with tap
event on the thumbnail images and used amp-selector
to highlight the selected thumbnail.
Let's see how we can completely reimplement this code using amp-bind's approach to data binding.
But before we start coding, let's design our approach:
(1) What is our state?
Fairly simple in our case, the only value we care about is what the current slide number is. So, selectedSlide
is our state.
(2) What are our bindings?
Let's put it this way, "What needs to change when selectedSlide changes?"
slide
of the full-image carousel: <amp-carousel [slide]="selectedSlide" ...
selected
item in amp-selector
needs to change too. This will fix the bug we ran into in the previous section! <amp-selector [selected]="selectedSlide" ...
(3) What are our mutations?
Let's put it this way, "When does selectedSlide need to change? "
<amp-carousel on="slideChange:AMP.setState({selectedSlide:event.index})" ...
<amp-selector on="select:AMP.setState({selectedSlide:event.targetOption})" ...
Let's use AMP.setState
to trigger a mutation, which means that we no longer need all the on="tap:imageSlides.goToSlide(index=n)"
code we had on thumbnail anymore!
Let's put it all together:
1) Add the script tag for amp-bind
to head
:
<script custom-element="amp-bind" src="https://cdn.ampproject.org/v0/amp-bind-0.1.js" async></script>
2) Replace the existing gallery code with the new approach:
<amp-carousel [slide]="selectedSlide" on="slideChange:AMP.setState({selectedSlide:event.index})" type="slides" id="imageSlides" layout="responsive" width="1080" height="720">
<amp-img src="https://unsplash.it/1080/720?image=1037" layout="fill"></amp-img>
<amp-img src="https://unsplash.it/1080/720?image=1038" layout="fill"></amp-img>
<amp-img src="https://unsplash.it/1080/720?image=1039" layout="fill"></amp-img>
<amp-img src="https://unsplash.it/1080/720?image=1040" layout="fill"></amp-img>
<amp-img src="https://unsplash.it/1080/720?image=1041" layout="fill"></amp-img>
<amp-img src="https://unsplash.it/1080/720?image=1042" layout="fill"></amp-img>
<amp-img src="https://unsplash.it/1080/720?image=1043" layout="fill"></amp-img>
<amp-img src="https://unsplash.it/1080/720?image=1044" layout="fill"></amp-img>
</amp-carousel>
<amp-selector [selected]="selectedSlide" on="select:AMP.setState({selectedSlide:event.targetOption})">
<amp-carousel layout="fixed-height" height="78" class="mt1">
<amp-img option=0 selected src="https://unsplash.it/108/72?image=1037" layout="fixed" width="108" height="72"></amp-img>
<amp-img option=1 src="https://unsplash.it/108/72?image=1038" layout="fixed" width="108" height="72"></amp-img>
<amp-img option=2 src="https://unsplash.it/108/72?image=1039" layout="fixed" width="108" height="72"></amp-img>
<amp-img option=3 src="https://unsplash.it/108/72?image=1040" layout="fixed" width="108" height="72"></amp-img>
<amp-img option=4 src="https://unsplash.it/108/72?image=1041" layout="fixed" width="108" height="72"></amp-img>
<amp-img option=5 src="https://unsplash.it/108/72?image=1042" layout="fixed" width="108" height="72"></amp-img>
<amp-img option=6 src="https://unsplash.it/108/72?image=1043" layout="fixed" width="108" height="72"></amp-img>
<amp-img option=7 src="https://unsplash.it/108/72?image=1044" layout="fixed" width="108" height="72"></amp-img>
</amp-carousel>
</amp-selector>
Let's test it out! Tap a thumbnail and image slides will change, swipe the image slides and the highlighted thumbnail will change!
We have already done the heavy work to define and mutate a state for our current slide. Now we can easily provide additional bindings to update other pieces of information based on the current slides number.
Let's add "Image x/of y" text to our gallery:
Add the following code above the slides carousel element:
<div class="px3">Image <span [text]="1*selectedSlide + 1">1</span> of 8</div>
This time we are binding to the inner text of an element using [text]=
instead of binding to an HTML attribute.
Let's try it out:
The completed code for this section can be found here: http://codepen.io/aghassemi/pen/MpeMdL
In this section we will use two new features to add animation to our page.
amp-fx-collection is an extension that provides a collection of preset visual effects such as parallax that can be easily enabled on any element via attributes.
With parallax effect, as the user scrolls the page, the element scrolls faster or slower depending on the value assigned to the attribute.
The parallax effect can be enabled by adding the amp-fx="parallax" data-parallax-factor="<a decimal factor>"
attribute to any HTML or AMP element.
Let's add parallax with a factor of 1.5 to our page title and see how it looks!
Add the script tag for amp-fx-collection
to head
:
<script custom-element="amp-fx-collection" src="https://cdn.ampproject.org/v0/amp-fx-collection-0.1.js" async></script>
Now, find the existing header title element in the code and add the amp-fx="parallax" and data-parallax-factor="1.5"
attribute to it:
<header amp-fx="parallax" data-parallax-factor="1.5" class="p3">
<h1 class="ampstart-fullpage-hero-heading mb3">
<span class="ampstart-fullpage-hero-heading-text">
Most Beautiful Hikes in the Pacific Northwest
</span>
</h1>
<span class="ampstart-image-credit h4">
By <a href="#" role="author" class="text-decoration-none">D.F. Roy</a>,<br> January 14, 2017
</span>
</header>
Let's take a look at the result:
Title is now scrolling faster than the user, creating a slide-out-of-sight effect. Cool!
amp-animation is an feature that brings the Web Animations API to AMP pages.
In this task we will use amp-animation to create a subtle zoom-in effect for the cover image.
Add the script tag for amp-animation to head
:
<script custom-element="amp-animation" src="https://cdn.ampproject.org/v0/amp-animation-0.1.js" async></script>
Now we need to define our animation and the target element it applies to.
Animations are defined as JSON inside a top-level amp-animation
tag.
Insert the following code directly below the opening body
tag in your page.
<amp-animation trigger="visibility" layout="nodisplay">
<script type="application/json">
{
"target": "heroimage",
"duration": 30000,
"delay": 0,
"fill": "forwards",
"easing": "ease-out",
"keyframes": {"transform": "scale(1.3)"}
}
</script>
</amp-animation>
Here are we are defining an animation that runs for 30 seconds without delay and scales the image to be 30% larger.
We define a forwards fill
to allow the image to stay zoomed in after the animation ends. target
is the HTML id
of the element that animation applies to.
Let's add an id
to the hero image element in our page so amp-animation
can act on it.
layout="fixed-height"
) in your code and add id="heroimage"
to the amp-img
tag.media="(min-width: 416px)"
and also remove the other low-resolution amp-img
so we don't have to deal with multiple animations and media queries in amp-animation for now.<figure class="ampstart-image-fullpage-hero m0 relative mb4">
<amp-img id="heroimage" height="1800" layout="fixed-height" src="https://unsplash.it/1200/1800?image=1003"></amp-img>
<figcaption class="absolute top-0 right-0 bottom-0 left-0">
...
As you may have noticed, scaling the image will make it overflow its parent, so we need to fix that by hiding the overflow.
Add the following CSS rule to the end of the existing <style amp-custom>
.ampstart-image-fullpage-hero {
overflow: hidden;
}
Let's try it out and see how it looks:
Subtle!
But I could have done that with CSS anyway, what's the point of amp-animation?
Although true in this case, amp-animation brings extra functionality not possible with CSS. For instance animation can be triggered based on visibility (and pause based on visibility as well) or they can be triggered via an AMP action. amp-animation is also based on Web Animations API which itself has more features than CSS animations, especially around composability.
The completed code for this section can be found here: http://codepen.io/aghassemi/pen/OpXKzo
You've just finished creating a beautiful, interactive and canonical AMP page.
Let's celebrate by taking another look at what you have accomplished today.
Here is a link to the finished page: http://s.codepen.io/aghassemi/debug/OpXKzo and the final code: http://codepen.io/aghassemi/pen/OpXKzo
The collection of Codepens for this codelab can be found here: https://codepen.io/collection/XzKmNB/
This codelab only scratches the surface of what's possible in AMP. There are many resources and codelabs available to help you create amazing AMP pages:
If you have questions, or run into issues, please find us on AMP Slack Channel or create discussion/bug/feature issues on GitHub.
We forgot to check how our page looks on other form factors like tablet in landscape mode.
Let's see:
Phew!
Have a beautiful day.