website: remove source code (#12806)
|
@ -596,50 +596,6 @@ jobs:
|
|||
NOMAD_VERSION: main
|
||||
steps: *NOMAD_INTEGRATION_TEST_STEPS
|
||||
|
||||
build-website-docker-image:
|
||||
docker:
|
||||
- image: docker.mirror.hashicorp.services/circleci/buildpack-deps
|
||||
shell: /usr/bin/env bash -euo pipefail -c
|
||||
steps:
|
||||
- checkout
|
||||
- setup_remote_docker
|
||||
- run:
|
||||
name: Build Docker Image if Necessary
|
||||
command: |
|
||||
# Ignore job if running an enterprise build
|
||||
IMAGE_TAG=$(cat website/Dockerfile website/package-lock.json | sha256sum | awk '{print $1;}')
|
||||
echo "Using $IMAGE_TAG"
|
||||
if [ "$CIRCLE_REPOSITORY_URL" != "git@github.com:hashicorp/consul.git" ]; then
|
||||
echo "Not Consul OSS Repo, not building website docker image"
|
||||
elif curl https://hub.docker.com/v2/repositories/hashicorp/consul-website/tags/$IMAGE_TAG -fsL > /dev/null; then
|
||||
echo "Dependencies have not changed, not building a new website docker image."
|
||||
else
|
||||
cd website/
|
||||
docker build -t hashicorp/consul-website:$IMAGE_TAG .
|
||||
docker tag hashicorp/consul-website:$IMAGE_TAG hashicorp/consul-website:latest
|
||||
docker login -u $WEBSITE_DOCKER_USER -p $WEBSITE_DOCKER_PASS
|
||||
docker push hashicorp/consul-website
|
||||
fi
|
||||
- run: *notify-slack-failure
|
||||
|
||||
algolia-index:
|
||||
docker:
|
||||
- image: docker.mirror.hashicorp.services/node:14
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Push content to Algolia Index
|
||||
command: |
|
||||
if [ "$CIRCLE_REPOSITORY_URL" != "git@github.com:hashicorp/consul.git" ]; then
|
||||
echo "Not Consul OSS Repo, not indexing Algolia"
|
||||
exit 0
|
||||
fi
|
||||
cd website/
|
||||
npm install -g npm@latest
|
||||
npm install
|
||||
node scripts/index_search_content.js
|
||||
- run: *notify-slack-failure
|
||||
|
||||
# build frontend yarn cache
|
||||
frontend-cache:
|
||||
docker:
|
||||
|
@ -1155,20 +1111,6 @@ workflows:
|
|||
requires:
|
||||
- dev-build
|
||||
|
||||
website:
|
||||
unless: << pipeline.parameters.trigger-load-test >>
|
||||
jobs:
|
||||
- build-website-docker-image:
|
||||
context: website-docker-image
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- main
|
||||
- algolia-index:
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- stable-website
|
||||
frontend:
|
||||
unless: << pipeline.parameters.trigger-load-test >>
|
||||
jobs:
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M20 12l-6 6M4 12h16H4zm16 0l-6-6 6 6z" stroke="var(--gray-3, #727274)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
Before Width: | Height: | Size: 249 B |
|
@ -1,56 +0,0 @@
|
|||
import Button from '@hashicorp/react-button'
|
||||
|
||||
export default function BasicHero({
|
||||
heading,
|
||||
content,
|
||||
links,
|
||||
brand,
|
||||
backgroundImage,
|
||||
}) {
|
||||
return (
|
||||
<div className={`g-basic-hero ${backgroundImage ? 'has-background' : ''}`}>
|
||||
<div className="g-grid-container">
|
||||
<h1 className="g-type-display-1">{heading}</h1>
|
||||
{content && <p className="g-type-body-large">{content}</p>}
|
||||
{links && links.length > 0 && (
|
||||
<>
|
||||
<div className="links">
|
||||
{links.slice(0, 2).map((link, stableIdx) => {
|
||||
const buttonVariant = stableIdx === 0 ? 'primary' : 'secondary'
|
||||
return (
|
||||
<Button
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
key={stableIdx}
|
||||
linkType={link.type}
|
||||
theme={{
|
||||
variant: buttonVariant,
|
||||
brand,
|
||||
background: 'light',
|
||||
}}
|
||||
title={link.text}
|
||||
url={link.url}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{links[2] && (
|
||||
<div className="third-link">
|
||||
<Button
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
linkType={links[2].type}
|
||||
theme={{
|
||||
variant: 'tertiary-neutral',
|
||||
brand,
|
||||
background: 'light',
|
||||
}}
|
||||
title={links[2].text}
|
||||
url={links[2].url}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
.g-basic-hero {
|
||||
padding: 88px 0;
|
||||
|
||||
& .g-type-display-1 {
|
||||
color: var(--gray-1);
|
||||
text-align: center;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 0;
|
||||
max-width: 14em;
|
||||
}
|
||||
|
||||
& .g-type-body-large {
|
||||
color: var(--gray-2);
|
||||
margin: 0 auto 0 auto;
|
||||
text-align: center;
|
||||
max-width: 40em;
|
||||
}
|
||||
|
||||
& .links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
|
||||
/*
|
||||
* Margins here compensate for extra 8px margin on buttons
|
||||
* which are needed to center and space properly regardless of whether
|
||||
* buttons are wrapping to multiple lines or not.
|
||||
*/
|
||||
margin-top: calc(32px - 8px);
|
||||
margin-bottom: -8px;
|
||||
@media (--large) {
|
||||
margin-top: calc(40px - 8px);
|
||||
}
|
||||
|
||||
& .g-btn {
|
||||
/*
|
||||
* This ensures 16px between buttons at all times, while maintaining proper centering
|
||||
* when buttons wrap to multiple lines.
|
||||
* There will be an extra 8px space on all sides of the button group.
|
||||
* The top and bottom are accounted for by the -8px adjustment on `.action` margins.
|
||||
* The left and right excess is left as is - it's needed for proper centering when wrapping.
|
||||
*/
|
||||
margin: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
& .third-link {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 32px;
|
||||
& a {
|
||||
color: var(--gray-2);
|
||||
}
|
||||
& svg * {
|
||||
stroke: var(--gray-2) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.has-background {
|
||||
background-repeat: no-repeat;
|
||||
background-color: var(--gray-6);
|
||||
background-image: url(/img/hero/pattern-desktop.svg);
|
||||
width: 100%;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
|
||||
@media (max-width: 800px) {
|
||||
background-image: url(/img/hero/pattern-mobile.svg);
|
||||
}
|
||||
|
||||
& .g-btn {
|
||||
background: var(--gray-6);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
import s from './style.module.css'
|
||||
|
||||
interface Block {
|
||||
title: string
|
||||
description: string
|
||||
image: string
|
||||
}
|
||||
|
||||
interface BlockListProps {
|
||||
blocks: Block[]
|
||||
}
|
||||
|
||||
export default function BlockList({ blocks }: BlockListProps) {
|
||||
return (
|
||||
<div className={s.blocksContainer}>
|
||||
{blocks.map(({ image, title, description }) => (
|
||||
<div key={title} className={s.block}>
|
||||
<div className={s.imageContainer}>
|
||||
<img src={image} alt={title} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className={s.title}>{title}</h3>
|
||||
<p className={s.description}>{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
.blocksContainer {
|
||||
display: grid;
|
||||
row-gap: 64px;
|
||||
|
||||
& .block {
|
||||
display: flex;
|
||||
|
||||
& .imageContainer {
|
||||
margin-right: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
composes: g-type-display-5 from global;
|
||||
margin: 0 0 16px 0;
|
||||
}
|
||||
|
||||
.description {
|
||||
composes: g-type-body-small from global;
|
||||
margin: 0;
|
||||
color: var(--gray-2);
|
||||
}
|
|
@ -1,131 +0,0 @@
|
|||
.calloutBlade {
|
||||
padding-top: 56px;
|
||||
padding-bottom: 56px;
|
||||
|
||||
--shadow-level-3: 0 16px 28px rgba(37, 38, 45, 0.12);
|
||||
|
||||
& .contentWrapper {
|
||||
& > h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 48px;
|
||||
@media (max-width: 1000px) {
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.contentWrapper {
|
||||
composes: g-grid-container from global;
|
||||
}
|
||||
|
||||
.callouts {
|
||||
display: grid;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
&.twoUp {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-gap: 32px;
|
||||
|
||||
& .linkWrap {
|
||||
padding: 64px 32px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background: var(--gray-6);
|
||||
&:hover {
|
||||
background: var(--gray-5);
|
||||
box-shadow: var(--shadow-level-3);
|
||||
}
|
||||
|
||||
& .icon {
|
||||
margin-right: 48px;
|
||||
}
|
||||
@media (max-width: 1200px) {
|
||||
padding: 48px 32px;
|
||||
flex-direction: column;
|
||||
& .icon {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
&.threeUp {
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
grid-gap: 32px;
|
||||
|
||||
& .linkWrap {
|
||||
padding: 64px 32px;
|
||||
border: 1px solid var(--gray-5);
|
||||
border-radius: 2px;
|
||||
&:hover {
|
||||
background: var(--gray-6);
|
||||
box-shadow: var(--shadow-level-3);
|
||||
border-color: var(--gray-6);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1220px) {
|
||||
grid-template-columns: 1fr;
|
||||
& .linkWrap {
|
||||
padding: 48px 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .linkWrap {
|
||||
color: inherit;
|
||||
height: 100%;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& .icon {
|
||||
margin-bottom: 16px;
|
||||
& svg {
|
||||
height: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
& .flexWrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
justify-content: space-between;
|
||||
|
||||
& .infoWrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& > h5 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
& > p {
|
||||
color: var(--gray-3);
|
||||
margin-top: 0;
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
& .linkWrapper {
|
||||
& .eyebrow {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
& :global(.g-btn) {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
composes: g-type-label from global;
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
import classNames from 'classnames'
|
||||
import styles from './CalloutBlade.module.css'
|
||||
import Button from '@hashicorp/react-button'
|
||||
import InlineSvg from '@hashicorp/react-inline-svg'
|
||||
|
||||
export default function CalloutBlade({ title, callouts }) {
|
||||
return (
|
||||
<div className={styles.calloutBlade}>
|
||||
<div className={styles.contentWrapper}>
|
||||
<h3 className="g-type-display-3">{title}</h3>
|
||||
<ul
|
||||
className={classNames(styles.callouts, {
|
||||
[styles.twoUp]: callouts.length % 3 !== 0,
|
||||
[styles.threeUp]: callouts.length % 3 === 0,
|
||||
})}
|
||||
>
|
||||
{callouts.map((callout) => {
|
||||
return (
|
||||
<li key={callout.title}>
|
||||
<a className={styles.linkWrap} href={callout.link.url}>
|
||||
<InlineSvg src={callout.icon} className={styles.icon} />
|
||||
<div className={styles.flexWrapper}>
|
||||
<div className={styles.infoWrapper}>
|
||||
{callout.title && (
|
||||
<h5 className="g-type-display-5">{callout.title}</h5>
|
||||
)}
|
||||
<p>{callout.description}</p>
|
||||
</div>
|
||||
<div className={styles.linkWrapper}>
|
||||
<div className={styles.eyebrow}>{callout.eyebrow}</div>
|
||||
<Button
|
||||
title={callout.link.text}
|
||||
url={callout.link.url}
|
||||
linkType="inbound"
|
||||
theme={{
|
||||
brand: 'neutral',
|
||||
variant: 'tertiary',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
import s from './style.module.css'
|
||||
|
||||
interface Card {
|
||||
heading: string
|
||||
description: string
|
||||
url: string
|
||||
eyebrow: string
|
||||
}
|
||||
|
||||
interface CardListProps {
|
||||
title: string
|
||||
cards: Card[]
|
||||
className?: string
|
||||
}
|
||||
|
||||
export default function CardList({ title, cards, className }: CardListProps) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<h3 className={s.title}>{title}</h3>
|
||||
<div className={s.cardsWrapper}>
|
||||
{cards.map(({ heading, description, url, eyebrow }) => (
|
||||
<a
|
||||
href={url}
|
||||
key={eyebrow}
|
||||
className={s.card}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<div className={s.cardContent}>
|
||||
<span className={s.eyebrow}>{eyebrow}</span>
|
||||
<span className={s.heading}>{heading}</span>
|
||||
<p className={s.description}>{description}</p>
|
||||
</div>
|
||||
<img
|
||||
src={require('@hashicorp/mktg-logos/product/consul/logomark/color.svg')}
|
||||
alt="consul-icon"
|
||||
className={s.icon}
|
||||
/>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
.cardsWrapper {
|
||||
display: grid;
|
||||
column-gap: 40px;
|
||||
row-gap: 40px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(218px, 1fr));
|
||||
|
||||
& .card {
|
||||
border: 1px solid var(--gray-5);
|
||||
box-shadow: 0 2px 3px rgba(37, 41, 55, 0.08);
|
||||
border-radius: 1px;
|
||||
transition: box-shadow 0.25s, transform 0.25s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 24px 24px 28px;
|
||||
justify-content: space-between;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 16px 28px rgba(37, 38, 45, 0.12);
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
& .cardContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
& .icon {
|
||||
width: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
composes: g-type-display-3 from global;
|
||||
margin-top: 0;
|
||||
margin-bottom: 46px;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
composes: g-type-label from global;
|
||||
color: var(--gray-2);
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.heading {
|
||||
composes: g-type-display-6 from global;
|
||||
margin-top: 0;
|
||||
margin-bottom: 8px;
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
.description {
|
||||
composes: g-type-body from global;
|
||||
color: var(--gray-1);
|
||||
margin-top: 0;
|
||||
margin-bottom: 17px;
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
import InlineSvg from '@hashicorp/react-inline-svg'
|
||||
import Image from '@hashicorp/react-image'
|
||||
import Button from '@hashicorp/react-button'
|
||||
import QuoteMarksIcon from './img/quote.svg?include'
|
||||
|
||||
export default function CaseStudySlide({
|
||||
caseStudy: { person, quote, company, caseStudyURL },
|
||||
}) {
|
||||
return (
|
||||
<blockquote className="g-grid-container case-slide">
|
||||
<InlineSvg className="quotes" src={QuoteMarksIcon} />
|
||||
<h4 className="case g-type-display-4">{quote}</h4>
|
||||
<div className="case-content">
|
||||
<div className="person-container">
|
||||
<Image
|
||||
className="person-photo"
|
||||
url={person.photo}
|
||||
aspectRatio={[1, 1]}
|
||||
alt={`${person.firstName} ${person.lastName}`}
|
||||
/>
|
||||
<div className="person-name">
|
||||
<h5 className="g-type-display-5">
|
||||
{person.firstName} {person.lastName}
|
||||
</h5>
|
||||
<p>
|
||||
{person.title}, {company.name}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Image className="company-logo" url={company.logo} alt={company.name} />
|
||||
</div>
|
||||
<Button
|
||||
title="Read more"
|
||||
url={caseStudyURL}
|
||||
theme={{
|
||||
variant: 'tertiary',
|
||||
brand: 'consul',
|
||||
background: 'light',
|
||||
}}
|
||||
linkType="outbound"
|
||||
/>
|
||||
</blockquote>
|
||||
)
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="4" cy="4" r="4" fill="#333"/></svg>
|
Before Width: | Height: | Size: 139 B |
|
@ -1 +0,0 @@
|
|||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="4" cy="4" r="4" fill="#c4c4c4"/></svg>
|
Before Width: | Height: | Size: 142 B |
|
@ -1 +0,0 @@
|
|||
<svg width="56" height="56" xmlns="http://www.w3.org/2000/svg"><g stroke="var(--gray-2, #343536)" stroke-width="1.5" fill="none" fill-rule="evenodd"><path d="M.75 28c0 15.05 12.2 27.25 27.25 27.25S55.25 43.05 55.25 28 43.05.75 28 .75.75 12.95.75 28z" fill="#FFF"/><path d="M36 28H20M26 22l-6 6 6 6" stroke-linecap="round" stroke-linejoin="round"/></g></svg>
|
Before Width: | Height: | Size: 357 B |
|
@ -1,3 +0,0 @@
|
|||
<svg width="19" height="15" viewBox="0 0 19 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.15 0.5H3.95L0 14.35H6.95L9.15 0.5ZM18.7 0.5H13.45L9.55 14.35H16.5L18.7 0.5Z" fill="var(--consul, #dc477d)"></path>
|
||||
</svg>
|
Before Width: | Height: | Size: 229 B |
|
@ -1 +0,0 @@
|
|||
<svg width="56" height="56" xmlns="http://www.w3.org/2000/svg"><g stroke="var(--gray-2, #343536)" stroke-width="1.5" fill="none" fill-rule="evenodd"><path d="M55.25 28c0 15.05-12.2 27.25-27.25 27.25S.75 43.05.75 28 12.95.75 28 .75 55.25 12.95 55.25 28z" fill="#FFF"/><path d="M20 28h16M30 22l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></g></svg>
|
Before Width: | Height: | Size: 359 B |
|
@ -1,99 +0,0 @@
|
|||
import { useState } from 'react'
|
||||
import { isIE } from 'react-device-detect'
|
||||
|
||||
import Carousel from 'nuka-carousel'
|
||||
import CaseSlide from './case-study-slide'
|
||||
import Image from '@hashicorp/react-image'
|
||||
import InlineSvg from '@hashicorp/react-inline-svg'
|
||||
import ActiveControlDot from './img/active-control-dot.svg?include'
|
||||
import InactiveControlDot from './img/inactive-control-dot.svg?include'
|
||||
import LeftArrow from './img/left-arrow-control.svg?include'
|
||||
import RightArrow from './img/right-arrow-control.svg?include'
|
||||
|
||||
export default function CaseStudyCarousel({
|
||||
caseStudies,
|
||||
title,
|
||||
logoSection = { grayBackground: false, featuredLogos: [] },
|
||||
}) {
|
||||
const [slideIndex, setSlideIndex] = useState(0)
|
||||
const { grayBackground, featuredLogos } = logoSection
|
||||
|
||||
const caseStudySlides = caseStudies.map((caseStudy) => (
|
||||
<CaseSlide key={caseStudy.quote} caseStudy={caseStudy} />
|
||||
))
|
||||
const logoRows = featuredLogos && Math.ceil(featuredLogos.length / 3)
|
||||
|
||||
function renderControls() {
|
||||
return (
|
||||
<div className="carousel-controls">
|
||||
{caseStudies.map((caseStudy, stableIdx) => {
|
||||
return (
|
||||
<button
|
||||
key={caseStudy.quote}
|
||||
className="carousel-controls-button"
|
||||
onClick={() => setSlideIndex(stableIdx)}
|
||||
>
|
||||
<InlineSvg
|
||||
src={
|
||||
slideIndex === stableIdx
|
||||
? ActiveControlDot
|
||||
: InactiveControlDot
|
||||
}
|
||||
/>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function sideControls(icon, direction) {
|
||||
return (
|
||||
<button className="side-control" onClick={direction}>
|
||||
<InlineSvg src={icon} />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<section
|
||||
className={`g-case-carousel ${grayBackground ? 'has-background' : ''}`}
|
||||
style={{ '--background-height': `${300 + logoRows * 100}px` }}
|
||||
>
|
||||
<h2 className="g-type-display-2">{title}</h2>
|
||||
{!isIE ? (
|
||||
<Carousel
|
||||
cellAlign="left"
|
||||
wrapAround={true}
|
||||
heightMode="current"
|
||||
slideIndex={slideIndex}
|
||||
slidesToShow={1}
|
||||
autoGenerateStyleTag
|
||||
renderBottomCenterControls={() => renderControls()}
|
||||
renderCenterLeftControls={({ previousSlide }) => {
|
||||
return sideControls(LeftArrow, previousSlide)
|
||||
}}
|
||||
renderCenterRightControls={({ nextSlide }) => {
|
||||
return sideControls(RightArrow, nextSlide)
|
||||
}}
|
||||
afterSlide={(slideIndex) => setSlideIndex(slideIndex)}
|
||||
>
|
||||
{caseStudySlides}
|
||||
</Carousel>
|
||||
) : null}
|
||||
<div className="background-section">
|
||||
{featuredLogos && featuredLogos.length > 0 && (
|
||||
<div className="mono-logos">
|
||||
{featuredLogos.map((featuredLogo) => (
|
||||
<Image
|
||||
key={featuredLogo.url}
|
||||
url={featuredLogo.url}
|
||||
alt={featuredLogo.companyName}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
|
@ -1,283 +0,0 @@
|
|||
.g-case-carousel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
padding-top: 0 !important;
|
||||
padding-bottom: 0 !important;
|
||||
|
||||
& h2 {
|
||||
margin-bottom: 30px;
|
||||
max-width: 600px;
|
||||
text-align: center;
|
||||
white-space: pre-wrap;
|
||||
|
||||
@media (max-width: 800px) {
|
||||
margin-top: 64px;
|
||||
white-space: initial;
|
||||
margin-left: 24px;
|
||||
margin-right: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: var(--background-height);
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
&.has-background {
|
||||
&::after {
|
||||
content: '';
|
||||
background: var(--gray-6);
|
||||
}
|
||||
|
||||
& .background-section {
|
||||
background: var(--gray-6);
|
||||
padding-bottom: 64px;
|
||||
}
|
||||
}
|
||||
|
||||
& .background-section {
|
||||
width: 100%;
|
||||
|
||||
& .mono-logos {
|
||||
align-items: baseline;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
max-width: 750px;
|
||||
margin: 0 auto;
|
||||
margin-top: 70px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
& > img {
|
||||
height: 100%;
|
||||
max-height: 40px;
|
||||
width: 33.33%;
|
||||
padding: 0 30px;
|
||||
margin: 24px 0;
|
||||
|
||||
@media (max-width: 800px) {
|
||||
padding: 0 20px;
|
||||
max-height: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
& > picture {
|
||||
max-height: 40px;
|
||||
width: 33.33%;
|
||||
padding: 0 30px;
|
||||
margin: 24px 0;
|
||||
|
||||
@media (max-width: 800px) {
|
||||
padding: 0 20px;
|
||||
max-height: 28px;
|
||||
}
|
||||
|
||||
& > img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .slider-control-bottomcenter {
|
||||
bottom: -35px !important;
|
||||
}
|
||||
|
||||
/* Begin `nuka-carousel` styles */
|
||||
& .slider {
|
||||
max-width: 1200px;
|
||||
|
||||
&:focus {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
width: calc(100% - 48px) !important;
|
||||
}
|
||||
|
||||
& .slider-list {
|
||||
margin-bottom: 50px !important;
|
||||
|
||||
@media (max-width: 800px) {
|
||||
margin-bottom: 30px !important;
|
||||
}
|
||||
}
|
||||
|
||||
& .slider-frame:focus {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
& .slider-slide:focus {
|
||||
outline: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* End `nuka-carousel` styles */
|
||||
|
||||
& .side-control {
|
||||
border: none;
|
||||
background: none;
|
||||
margin: 20px;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:hover:not([disabled]) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
& svg path {
|
||||
stroke: var(--gray-2);
|
||||
}
|
||||
|
||||
&:disabled svg path {
|
||||
stroke: var(--gray-4);
|
||||
}
|
||||
}
|
||||
|
||||
& .case-slide {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
background: var(--white);
|
||||
padding: 64px;
|
||||
box-shadow: 0 8px 22px #dedede;
|
||||
|
||||
@media (max-width: 800px) {
|
||||
box-shadow: none;
|
||||
border: 1px solid var(--gray-5);
|
||||
padding: 48px;
|
||||
}
|
||||
|
||||
@media (--medium-up) {
|
||||
max-width: 750px;
|
||||
}
|
||||
|
||||
& button {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
& .quotes {
|
||||
display: flex;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
& h4 {
|
||||
margin: 0;
|
||||
|
||||
&.case {
|
||||
min-height: 130px;
|
||||
margin-bottom: 24px;
|
||||
color: var(--gray-2);
|
||||
|
||||
@media (max-width: 800px) {
|
||||
min-height: 155px;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
@media (max-width: 650px) {
|
||||
min-height: 190px;
|
||||
}
|
||||
|
||||
@media (max-width: 550px) {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 400px) {
|
||||
font-size: 18px;
|
||||
line-height: 28px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
& a {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
& .case-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
& .person-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& picture {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
& .person-photo {
|
||||
border-radius: 50%;
|
||||
max-height: 72px;
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
& .person-name {
|
||||
padding-right: 16px;
|
||||
|
||||
& h5 {
|
||||
margin: 0;
|
||||
|
||||
@media (max-width: 400px) {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 400px) {
|
||||
& p {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .company-logo {
|
||||
max-height: 40px;
|
||||
max-width: 180px;
|
||||
|
||||
@media (max-width: 800px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
& .case {
|
||||
color: var(--gray-4);
|
||||
font-size: 24px;
|
||||
line-height: 31px; /* Called for within the design, no custom property seemed appropriate */
|
||||
}
|
||||
}
|
||||
|
||||
& .carousel-controls {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: center;
|
||||
|
||||
& .carousel-controls-button {
|
||||
height: 20px;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
import Button from '@hashicorp/react-button'
|
||||
|
||||
export default function CloudOfferingsList({ offerings }) {
|
||||
return (
|
||||
<ul className="g-cloud-offerings-list">
|
||||
{offerings.map((offering) => (
|
||||
<li key={offering.title}>
|
||||
<a
|
||||
href={offering.link.url}
|
||||
rel={offering.link.type === 'outbound' ? 'noopener' : undefined}
|
||||
target={offering.link.type === 'outbound' ? '_blank' : undefined}
|
||||
>
|
||||
<img src={offering.image} alt={offering.title} />
|
||||
<span className="g-type-label-strong">{offering.eyebrow}</span>
|
||||
<h4 className="g-type-display-4">{offering.title}</h4>
|
||||
<p>{offering.description}</p>
|
||||
<Button
|
||||
title={offering.link.text}
|
||||
linkType={offering.link.type}
|
||||
theme={{ variant: 'tertiary', brand: 'consul' }}
|
||||
url={offering.link.url}
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
ul.g-cloud-offerings-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: -16px;
|
||||
display: flex;
|
||||
|
||||
@media (width < 769px) {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
& li {
|
||||
flex-grow: 1;
|
||||
margin: 16px;
|
||||
background: var(--white);
|
||||
border: 1px solid var(--gray-5);
|
||||
border-radius: 2px;
|
||||
text-align: center;
|
||||
transition: box-shadow 0.25s, transform 0.25s, -webkit-transform 0.25s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 16px 28px rgba(37, 38, 45, 0.12);
|
||||
transform: translateY(-4px);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
& > a {
|
||||
display: block;
|
||||
padding: 32px;
|
||||
color: inherit;
|
||||
|
||||
& > img {
|
||||
display: block;
|
||||
width: 400px;
|
||||
max-width: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
& > span {
|
||||
color: var(--gray-3);
|
||||
}
|
||||
|
||||
& > h4 {
|
||||
text-decoration: none;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
& > p {
|
||||
font-size: 19px;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,200 +0,0 @@
|
|||
import Tabs, { Tab } from '@hashicorp/react-tabs'
|
||||
import EnterpriseAlertBase from '@hashicorp/react-enterprise-alert'
|
||||
|
||||
/**
|
||||
* ConfigEntryReference renders the reference docs for a config entry.
|
||||
* It creates two tabs, one for HCL docs and one for Kubernetes docs.
|
||||
*
|
||||
* @param {array<object>} keys Array of objects, that describe all
|
||||
* keys that can be set for this config entry.
|
||||
* @param {boolean} topLevel Indicates this is a reference block that contains
|
||||
* the top level keys vs a reference block that documents
|
||||
* nested keys and that is separated out for clarity.
|
||||
*
|
||||
* The objects in the keys array support the following keys:
|
||||
* - name <required>: the name of the HCL key, e.g. Name, Listener. This case sensitive.
|
||||
* - description <required>: the description of the key. If this key has different descriptions
|
||||
* for HCL vs. Kube YAML then description can be an object:
|
||||
* description: {
|
||||
* hcl: 'HCL description',
|
||||
* yaml: 'YAML description'
|
||||
* }
|
||||
* - hcl <optional>: a boolean to indicate if this key should be shown in the HCL
|
||||
* documentation. Defaults to true.
|
||||
* - yaml <optional>: a boolean to indicate if this key should be shown in the YAML
|
||||
* documentation. Defaults to true.
|
||||
* - enterprise <optional>: a boolean to indicate if this key is Consul Enterprise
|
||||
* only. Defaults to false.
|
||||
* - children <optional>: accepts an array of keys that must be set under this key.
|
||||
* The schema for these keys is the same as the top level keys.
|
||||
* - type <optional>: the type and default of this key, e.g. string: "default".
|
||||
*/
|
||||
export default function ConfigEntryReference({ keys, topLevel = true }) {
|
||||
// Kube needs to have its non-top-level keys nested under a "spec" key.
|
||||
const kubeKeys = topLevel ? toKubeKeys(keys) : keys
|
||||
return (
|
||||
<Tabs>
|
||||
<Tab heading="HCL">{renderKeys(keys, true)}</Tab>
|
||||
<Tab heading="Kubernetes YAML">{renderKeys(kubeKeys, false)}</Tab>
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders keys as HTML. It works recursively through all keys.
|
||||
* @param {array} keys
|
||||
* @param {boolean} isHCLTab
|
||||
* @returns {JSX.Element|null}
|
||||
*/
|
||||
function renderKeys(keys, isHCLTab) {
|
||||
if (!keys) return null
|
||||
return <ul>{keys.map((key) => renderKey(key, isHCLTab))}</ul>
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a single key as its HTML element.
|
||||
*
|
||||
* @param {object} key
|
||||
* @param {boolean} isHCLTab
|
||||
* @returns {JSX.Element|null}
|
||||
*/
|
||||
function renderKey(key, isHCLTab) {
|
||||
if (!key.name) return null
|
||||
if (isHCLTab && key.hcl === false) return null
|
||||
if (!isHCLTab && key.yaml === false) return null
|
||||
|
||||
const keyName = isHCLTab ? key.name : toYAMLKeyName(key.name)
|
||||
|
||||
let description = ''
|
||||
if (key.description) {
|
||||
if (typeof key.description === 'string') {
|
||||
description = key.description
|
||||
} else if (!isHCLTab && key.description.yaml) {
|
||||
description = key.description.yaml
|
||||
} else if (key.description.hcl) {
|
||||
description = key.description.hcl
|
||||
}
|
||||
}
|
||||
|
||||
const htmlDescription = description && markdownToHtml(' - ' + description)
|
||||
const type = key.type && <code>{`(${key.type})`}</code>
|
||||
const enterpriseAlert = key.enterprise && <EnterpriseAlert inline />
|
||||
const keyLower = keyName.toLowerCase()
|
||||
|
||||
// NOTE: This code copies from https://github.com/hashicorp/remark-plugins/blob/df606efc844319a2532ec54e4cf6ff2d575108ff/plugins/anchor-links/index.js
|
||||
// to ensure the styling of each bullet is correct. The two locations should be kept
|
||||
// in sync.
|
||||
return (
|
||||
<li key={keyLower} className="g-type-long-body">
|
||||
<a id={keyLower} className="__target-lic" aria-hidden="" />
|
||||
<p>
|
||||
<a
|
||||
href={'#' + keyLower}
|
||||
aria-label={keyLower + ' permalink'}
|
||||
className="__permalink-lic"
|
||||
>
|
||||
<code>{keyName}</code>
|
||||
</a>{' '}
|
||||
{type}
|
||||
{enterpriseAlert}
|
||||
<span dangerouslySetInnerHTML={{ __html: htmlDescription }} />
|
||||
</p>
|
||||
{renderKeys(key.children, isHCLTab)}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a keys object for Kubernetes out of HCL keys.
|
||||
* Really all this entails is nesting the correct keys under the Kubernetes
|
||||
* 'spec' key since in HCL there is no 'spec' key.
|
||||
*
|
||||
* @param {array} keys
|
||||
* @returns {array}
|
||||
*/
|
||||
function toKubeKeys(keys) {
|
||||
const topLevelKeys = keys.filter((key) => isTopLevelKubeKey(key.name))
|
||||
const keysUnderSpec = keys.filter((key) => !isTopLevelKubeKey(key.name))
|
||||
return topLevelKeys.concat([{ name: 'spec', children: keysUnderSpec }])
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an HCL key name to a kube yaml key name.
|
||||
*
|
||||
* Examples:
|
||||
* - Protocol => protocol
|
||||
* - MeshGateway => meshGateway
|
||||
* - ACLToken => aclToken
|
||||
* - HTTP => http
|
||||
*
|
||||
* @param {string} hclKey
|
||||
* @returns {string}
|
||||
*/
|
||||
function toYAMLKeyName(hclKey) {
|
||||
// Handle something like HTTP.
|
||||
if (hclKey.toUpperCase() === hclKey) {
|
||||
return hclKey.toLowerCase()
|
||||
}
|
||||
|
||||
let indexFirstLowercaseChar = hclKey
|
||||
.split('')
|
||||
.findIndex((c) => c === c.toLowerCase())
|
||||
// Special case to handle something like ACLToken => aclToken.
|
||||
if (indexFirstLowercaseChar > 1) {
|
||||
indexFirstLowercaseChar--
|
||||
}
|
||||
|
||||
let lowercasePortion = ''
|
||||
for (let i = 0; i < indexFirstLowercaseChar; i++) {
|
||||
lowercasePortion += hclKey[i].toLowerCase()
|
||||
}
|
||||
return (
|
||||
lowercasePortion + hclKey.split('').slice(indexFirstLowercaseChar).join('')
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a markdown string to its HTML representation.
|
||||
* Currently it only supports inline code blocks (e.g. `code here`) and
|
||||
* links (e.g. [link text](http://link-url) because these were the most
|
||||
* commonly used markdown features in the key descriptions.
|
||||
*
|
||||
* @param {string} markdown the input markdown
|
||||
* @returns {string}
|
||||
*/
|
||||
function markdownToHtml(markdown) {
|
||||
let html = markdown
|
||||
|
||||
// Replace inline code blocks defined by backticks with <code></code>.
|
||||
while (html.indexOf('`') > 0) {
|
||||
html = html.replace('`', '<code>')
|
||||
if (html.indexOf('`') <= 0) {
|
||||
throw new Error(`'${markdown} does not have matching '\`' characters`)
|
||||
}
|
||||
html = html.replace('`', '</code>')
|
||||
}
|
||||
|
||||
// Replace links, e.g. [link text](http://link-url),
|
||||
// with <a href="http://link-url">link text</a>.
|
||||
return html.replace(/\[(.*?)]\((.*?)\)/g, '<a href="$2">$1</a>')
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if key is a key used at the top level of a CRD. By top level we
|
||||
* mean not nested under any other key.
|
||||
*
|
||||
* @param {string} name name of the key
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
function isTopLevelKubeKey(name) {
|
||||
return (
|
||||
name.toLowerCase() === 'metadata' ||
|
||||
name.toLowerCase() === 'kind' ||
|
||||
name.toLowerCase() === 'apiversion'
|
||||
)
|
||||
}
|
||||
|
||||
function EnterpriseAlert(props) {
|
||||
return <EnterpriseAlertBase product={'consul'} {...props} />
|
||||
}
|
Before Width: | Height: | Size: 247 KiB |
Before Width: | Height: | Size: 256 KiB |
Before Width: | Height: | Size: 249 KiB |
|
@ -1,98 +0,0 @@
|
|||
import Button from '@hashicorp/react-button'
|
||||
import ReactPlayer from 'react-player'
|
||||
import s from './style.module.css'
|
||||
|
||||
interface Cta {
|
||||
url: string
|
||||
text: string
|
||||
}
|
||||
|
||||
interface ConsulOnKubernetesHeroProps {
|
||||
title: string
|
||||
description: string
|
||||
ctas: Cta[]
|
||||
video: {
|
||||
src: string
|
||||
poster: string
|
||||
}
|
||||
}
|
||||
|
||||
export default function ConsulOnKubernetesHero({
|
||||
title,
|
||||
description,
|
||||
ctas,
|
||||
video,
|
||||
}: ConsulOnKubernetesHeroProps) {
|
||||
return (
|
||||
<div className={s.ckHero}>
|
||||
<div className={s.contentWrapper}>
|
||||
<div className={s.headline}>
|
||||
<h1 className={s.title}>{title}</h1>
|
||||
<p className={s.description}>{description}</p>
|
||||
<div className={s.buttons}>
|
||||
{ctas.map(({ text, url }, idx) => (
|
||||
<Button
|
||||
key={text}
|
||||
theme={{
|
||||
brand: 'consul',
|
||||
variant: idx === 0 ? 'primary' : 'tertiary-neutral',
|
||||
background: 'dark',
|
||||
}}
|
||||
linkType={idx === 0 ? null : 'inbound'}
|
||||
url={url}
|
||||
title={text}
|
||||
className={s.button}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.media}>
|
||||
<img
|
||||
src={require('./images/bg-top.svg')}
|
||||
alt="background top"
|
||||
className={s.bgTop}
|
||||
/>
|
||||
<img
|
||||
src={require('./images/bg-right.svg')}
|
||||
alt="background right"
|
||||
className={s.bgRight}
|
||||
/>
|
||||
<img
|
||||
src={require('./images/bg-dots.svg')}
|
||||
alt="background bottom"
|
||||
className={s.bgBottom}
|
||||
/>
|
||||
<img
|
||||
src={require('./images/bg-dots.svg')}
|
||||
alt="background left"
|
||||
className={s.bgLeft}
|
||||
/>
|
||||
<div className={s.video}>
|
||||
<ReactPlayer
|
||||
playing
|
||||
light={video.poster}
|
||||
url={video.src}
|
||||
width="100%"
|
||||
height="100%"
|
||||
controls
|
||||
className={s.player}
|
||||
playIcon={
|
||||
<svg
|
||||
aria-label="Play video"
|
||||
width="72"
|
||||
height="72"
|
||||
viewBox="0 0 72 72"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect width="72" height="72" rx="36" fill="#F85C94" />
|
||||
<path d="M56 36L26 53.3205L26 18.6795L56 36Z" fill="white" />
|
||||
</svg>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,162 +0,0 @@
|
|||
.ckHero {
|
||||
background-color: var(--black);
|
||||
color: var(--white);
|
||||
padding-top: 130px;
|
||||
padding-bottom: 142px;
|
||||
overflow: hidden;
|
||||
|
||||
@media (--medium) {
|
||||
padding-top: 78px;
|
||||
padding-bottom: 104px;
|
||||
}
|
||||
|
||||
@media (--small) {
|
||||
padding-top: 56px;
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
.contentWrapper {
|
||||
--columns: 1;
|
||||
|
||||
column-gap: 32px;
|
||||
composes: g-grid-container from global;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--columns), minmax(0, 1fr));
|
||||
row-gap: 48px;
|
||||
|
||||
@media (--medium-up) {
|
||||
--columns: 12;
|
||||
}
|
||||
|
||||
& .headline {
|
||||
text-align: center;
|
||||
grid-column: 1 / -1;
|
||||
margin: 0 auto;
|
||||
|
||||
@media (--large) {
|
||||
margin: 0;
|
||||
text-align: left;
|
||||
grid-column: 1 / 6;
|
||||
}
|
||||
|
||||
& .buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
@media (--large) {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
& .button:not(:last-of-type) {
|
||||
margin-right: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .media {
|
||||
position: relative;
|
||||
grid-column: 1 / -1;
|
||||
|
||||
@media (--medium) {
|
||||
grid-column: 3 / 11;
|
||||
}
|
||||
|
||||
@media (--large) {
|
||||
grid-column: 7 / -1;
|
||||
}
|
||||
|
||||
& > div {
|
||||
border: 1px var(--gray-3) solid;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
& .video {
|
||||
background-color: var(--black);
|
||||
position: relative;
|
||||
padding-top: 56.25%;
|
||||
width: 100%;
|
||||
|
||||
& .player {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
& div {
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
& iframe {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
& > * {
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
composes: g-type-display-1 from global;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.description {
|
||||
composes: g-type-body-large from global;
|
||||
margin-top: 16px;
|
||||
margin-bottom: 40px;
|
||||
color: var(--gray-5);
|
||||
max-width: 500px;
|
||||
|
||||
@media (--large) {
|
||||
max-width: 385px;
|
||||
}
|
||||
}
|
||||
|
||||
.backgroundImage {
|
||||
height: auto;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.bgTop {
|
||||
composes: backgroundImage;
|
||||
left: auto;
|
||||
right: 0;
|
||||
top: -130px;
|
||||
display: none;
|
||||
width: 75%;
|
||||
|
||||
@media (--large) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.bgRight {
|
||||
composes: backgroundImage;
|
||||
top: 20%;
|
||||
left: 99.5%;
|
||||
}
|
||||
|
||||
.bgBottom {
|
||||
composes: backgroundImage;
|
||||
width: auto;
|
||||
top: 80%;
|
||||
left: 8%;
|
||||
}
|
||||
|
||||
.bgLeft {
|
||||
composes: backgroundImage;
|
||||
width: auto;
|
||||
top: 86px;
|
||||
left: -77px;
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="262px" height="153px" viewBox="0 0 262 153" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 58 (84663) - https://sketch.com -->
|
||||
<title>Consul Stack</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<linearGradient x1="5.84301534%" y1="40.5603513%" x2="94.330705%" y2="59.1543948%" id="linearGradient-1">
|
||||
<stop stop-color="#ED3E72" offset="0%"></stop>
|
||||
<stop stop-color="#9E177E" stop-opacity="0.8" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Consul-Stack">
|
||||
<polygon id="Path" fill="#F2F2F3" fill-rule="nonzero" points="196.19 38.613 130.976 76.5954 196.19 114.567 261.415 76.5954"></polygon>
|
||||
<path d="M180.026,67.2899 L174.02,80.1543 L174.18,80.1543 L190.178,89.4811 L212.309,85.9969 L212.384,85.9969 L218.358,73.1325 L202.296,63.72 L180.026,67.2899 Z M203.965,73.9687 L202.583,77.2705 L198.384,77.8494 L195.429,78.3211 L200.744,81.4408 L199.203,85.4609 L188.477,79.2109 L189.54,76.1127 L196.981,74.8799 L191.666,71.7495 L193.26,67.7723 L198.862,70.9884 L203.965,73.9687 Z" id="Shape" fill="#000000" fill-rule="nonzero" opacity="0.16"></path>
|
||||
<polygon id="Path" fill="#F2F2F3" fill-rule="nonzero" points="65.7477 38.613 0.52255 76.5954 65.7477 114.567 130.973 76.5954"></polygon>
|
||||
<path d="M66.1399,70.9883 L53.8624,72.9072 L62.7809,78.0959 L75.0159,76.177 L66.1399,70.9883 Z M79.4486,68.4476 L70.4238,63.1946 L67.1392,70.3236 L76.164,75.5766 L79.4486,68.4476 Z M39.8734,75.062 L48.7813,80.2507 L61.027,78.3425 L52.151,73.1324 L39.8734,75.062 Z M76.3659,76.9381 L64.1096,78.8034 L73.0175,83.9921 L85.2632,82.0839 L76.3659,76.9381 Z" id="Shape" fill="#000000" opacity="0.16"></path>
|
||||
<polygon id="Path" fill="#F2F2F3" fill-rule="nonzero" points="130.971 0.64124 65.7455 38.6128 130.971 76.5952 196.185 38.6128"></polygon>
|
||||
<path d="M106.064,39.6634 L146.128,47.4786 L132.809,24.0974 L106.064,39.6634 Z M126.335,34.1102 L127.887,33.2097 L129.439,34.1102 L127.887,35.0108 L126.335,34.1102 Z M129.45,39.524 L127.898,40.4246 L126.356,39.524 L127.898,38.6128 L129.45,39.524 Z M127.122,38.1626 L125.58,39.0631 L124.029,38.1626 L125.58,37.262 L127.122,38.1626 Z M124.805,36.8118 L123.253,37.7123 L121.701,36.8118 L123.253,35.9113 L124.805,36.8118 Z M134.106,39.524 L132.554,40.4246 L131.002,39.524 L132.554,38.6128 L134.106,39.524 Z M131.778,38.1626 L130.226,39.0631 L128.674,38.1626 L130.226,37.262 L131.778,38.1626 Z M129.45,36.8118 L127.887,37.7552 L126.346,36.8547 L127.887,35.9542 L129.45,36.8118 Z M127.122,35.461 L125.58,36.3615 L124.029,35.461 L125.58,34.5498 L127.122,35.461 Z M128.663,35.461 L130.215,34.5605 L131.767,35.461 L130.215,36.3723 L128.663,35.461 Z M132.554,37.7123 L131.002,36.8118 L132.554,35.9113 L134.106,36.8118 L132.554,37.7123 Z" id="Shape" fill="#000000" fill-rule="nonzero" opacity="0.16"></path>
|
||||
<polygon id="Path" fill="#DBDBDC" fill-rule="nonzero" points="130.971 76.5947 65.7455 114.566 130.971 152.549 196.185 114.566"></polygon>
|
||||
<polygon id="Path" fill="url(#linearGradient-1)" fill-rule="nonzero" points="130.971 58.5663 65.7455 96.5379 130.971 134.52 196.185 96.5379"></polygon>
|
||||
<path d="M145.155,103.803 C137.331,108.348 124.618,108.348 116.805,103.803 C108.992,99.257 108.981,91.8389 116.805,87.2934 C120.901,85.061 125.479,83.8792 130.135,83.8522 L130.135,83.8522 L130.305,87.8509 C127.286,87.8804 124.321,88.6494 121.663,90.0914 C116.528,93.0824 116.528,97.9495 121.663,100.94 C126.797,103.931 135.152,103.931 140.286,100.94 C142.603,99.59 143.964,97.8101 144.124,95.9019 L151.001,95.9984 C150.788,98.968 148.716,101.723 145.155,103.803 Z" id="Path" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
<path d="M149.468,93.03 C149.107,93.2145 148.708,93.3107 148.304,93.3107 C147.899,93.3107 147.501,93.2145 147.14,93.03 C147.002,92.9797 146.883,92.8879 146.799,92.7671 C146.715,92.6462 146.67,92.5022 146.67,92.3546 C146.67,92.207 146.715,92.0629 146.799,91.9421 C146.883,91.8212 147.002,91.7294 147.14,91.6792 C147.501,91.4946 147.899,91.3984 148.304,91.3984 C148.708,91.3984 149.107,91.4946 149.468,91.6792 C149.605,91.7294 149.724,91.8212 149.808,91.9421 C149.893,92.0629 149.938,92.207 149.938,92.3546 C149.938,92.5022 149.893,92.6462 149.808,92.7671 C149.724,92.8879 149.605,92.9797 149.468,93.03 L149.468,93.03 Z" id="Path" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
<path d="M133.95,97.3821 C132.999,97.8681 131.949,98.121 130.883,98.121 C129.817,98.121 128.767,97.8681 127.816,97.3821 C126.126,96.3958 126.126,94.7985 127.816,93.8122 C128.767,93.3262 129.817,93.073 130.883,93.073 C131.949,93.073 132.999,93.3262 133.95,93.8122 C135.65,94.7985 135.65,96.3958 133.95,97.3821 Z" id="Path" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
<path d="M146.832,89.9423 C146.471,90.1269 146.073,90.2231 145.668,90.2231 C145.264,90.2231 144.865,90.1269 144.504,89.9423 C144.367,89.8921 144.248,89.8003 144.164,89.6794 C144.079,89.5586 144.034,89.4146 144.034,89.2669 C144.034,89.1193 144.079,88.9753 144.164,88.8544 C144.248,88.7336 144.367,88.6418 144.504,88.5916 C144.865,88.407 145.264,88.3108 145.668,88.3108 C146.073,88.3108 146.471,88.407 146.832,88.5916 C146.97,88.6418 147.089,88.7336 147.173,88.8544 C147.257,88.9753 147.302,89.1193 147.302,89.2669 C147.302,89.4146 147.257,89.5586 147.173,89.6794 C147.089,89.8003 146.97,89.8921 146.832,89.9423 L146.832,89.9423 Z" id="Path" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
<path d="M143.24,91.8721 C142.882,92.0574 142.485,92.1541 142.082,92.1541 C141.679,92.1541 141.282,92.0574 140.923,91.8721 C140.785,91.8219 140.666,91.7301 140.582,91.6092 C140.498,91.4884 140.453,91.3444 140.453,91.1967 C140.453,91.0491 140.498,90.9051 140.582,90.7842 C140.666,90.6634 140.785,90.5716 140.923,90.5214 C141.282,90.336 141.679,90.2394 142.082,90.2394 C142.485,90.2394 142.882,90.336 143.24,90.5214 C143.383,90.5663 143.508,90.6561 143.596,90.7776 C143.685,90.8991 143.733,91.0459 143.733,91.1967 C143.733,91.3476 143.685,91.4944 143.596,91.6159 C143.508,91.7374 143.383,91.8272 143.24,91.8721 Z" id="Path" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
<path d="M142.923,87.67 C142.565,87.8553 142.168,87.952 141.765,87.952 C141.362,87.952 140.965,87.8553 140.606,87.67 C140.468,87.6197 140.349,87.5279 140.265,87.4071 C140.181,87.2863 140.136,87.1422 140.136,86.9946 C140.136,86.847 140.181,86.7029 140.265,86.5821 C140.349,86.4612 140.468,86.3695 140.606,86.3192 C140.965,86.1339 141.362,86.0372 141.765,86.0372 C142.168,86.0372 142.565,86.1339 142.923,86.3192 C143.572,86.6837 143.572,87.2948 142.923,87.67 Z" id="Path" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
<path d="M139.582,89.7599 C139.22,89.9396 138.821,90.0331 138.418,90.0331 C138.014,90.0331 137.616,89.9396 137.254,89.7599 C137.112,89.7119 136.99,89.6204 136.903,89.4983 C136.816,89.3762 136.769,89.2296 136.769,89.0792 C136.769,88.9288 136.816,88.7822 136.903,88.6601 C136.99,88.5379 137.112,88.4464 137.254,88.3984 C137.616,88.2187 138.014,88.1252 138.418,88.1252 C138.821,88.1252 139.22,88.2187 139.582,88.3984 C139.723,88.4464 139.846,88.5379 139.933,88.6601 C140.02,88.7822 140.067,88.9288 140.067,89.0792 C140.067,89.2296 140.02,89.3762 139.933,89.4983 C139.846,89.6204 139.723,89.7119 139.582,89.7599 L139.582,89.7599 Z" id="Path" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
<path d="M137.751,86.1254 C137.39,86.31 136.992,86.4062 136.587,86.4062 C136.183,86.4062 135.784,86.31 135.423,86.1254 C135.285,86.0752 135.166,85.9834 135.082,85.8626 C134.998,85.7417 134.953,85.5977 134.953,85.4501 C134.953,85.3024 134.998,85.1584 135.082,85.0375 C135.166,84.9167 135.285,84.8249 135.423,84.7747 C135.784,84.5901 136.183,84.4939 136.587,84.4939 C136.992,84.4939 137.39,84.5901 137.751,84.7747 C137.889,84.8249 138.008,84.9167 138.092,85.0375 C138.176,85.1584 138.221,85.3024 138.221,85.4501 C138.221,85.5977 138.176,85.7417 138.092,85.8626 C138.008,85.9834 137.889,86.0752 137.751,86.1254 Z" id="Path" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 8.1 KiB |
|
@ -1,38 +0,0 @@
|
|||
import TextSplit from '@hashicorp/react-text-split'
|
||||
import Button from '@hashicorp/react-button'
|
||||
import s from './style.module.css'
|
||||
import InlineSvg from '@hashicorp/react-inline-svg'
|
||||
import ConsulStack from './img/consul-stack.svg?include'
|
||||
|
||||
export default function CtaHero({ title, description, links, cta }) {
|
||||
return (
|
||||
<div className={s.ctaHero}>
|
||||
<TextSplit
|
||||
product="consul"
|
||||
heading={title}
|
||||
content={description}
|
||||
links={links}
|
||||
linkStyle="buttons"
|
||||
>
|
||||
<CTA title={cta.title} description={cta.description} link={cta.link} />
|
||||
</TextSplit>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function CTA({ title, description, link }) {
|
||||
return (
|
||||
<div className={s.cta}>
|
||||
<InlineSvg className={s.stackIcon} src={ConsulStack} />
|
||||
<h3 className="g-type-display-3">{title}</h3>
|
||||
<p className={s.description}>{description}</p>
|
||||
<Button
|
||||
title={link.text}
|
||||
url={link.url}
|
||||
theme={{
|
||||
brand: 'neutral',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
.ctaHero {
|
||||
& :global(.g-text-split) :global(.g-grid-container) {
|
||||
@media (width < 1120px) {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
& > div {
|
||||
@media (768px < width < 1120px) {
|
||||
width: 40em;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
@media (width < 1120px) {
|
||||
margin-bottom: 64px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
& p {
|
||||
@media (width < 1120px) {
|
||||
margin: 16px auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* HACK:
|
||||
* Overrides the H2 with styling from
|
||||
* our global g-type-display-1 class.
|
||||
*
|
||||
* This was because there's no way to
|
||||
* override the heading in <TextSplit />
|
||||
* with the designed h1 styling.
|
||||
*
|
||||
* TODO:
|
||||
* Address this at the component
|
||||
* level or revert to just using h2
|
||||
* as is default.
|
||||
*/
|
||||
& h2 {
|
||||
font-size: 2.125rem;
|
||||
letter-spacing: -0.008em;
|
||||
line-height: 1.265em;
|
||||
|
||||
@media (--medium-up) {
|
||||
font-size: 2.625rem;
|
||||
letter-spacing: -0.01em;
|
||||
line-height: 1.19em;
|
||||
}
|
||||
|
||||
@media (--large) {
|
||||
font-size: 3.125rem;
|
||||
line-height: 1.2em;
|
||||
}
|
||||
}
|
||||
|
||||
& p {
|
||||
max-width: 440px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cta {
|
||||
max-width: 525px;
|
||||
border: var(--gray-5) 1px solid;
|
||||
padding: 32px;
|
||||
position: relative;
|
||||
margin-top: 44px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
||||
@media (min-width: 1120px) {
|
||||
margin-top: unset;
|
||||
margin-left: unset;
|
||||
margin-right: unset;
|
||||
|
||||
/* Pull this down on Desktop to line the
|
||||
* buttons up with the other CTA buttons */
|
||||
margin-bottom: -38px;
|
||||
}
|
||||
|
||||
& > h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
max-width: 135px;
|
||||
}
|
||||
|
||||
& > p {
|
||||
color: var(--gray-3);
|
||||
margin-top: 28px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
& .stackIcon {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
||||
& svg {
|
||||
position: absolute;
|
||||
max-width: 100%;
|
||||
left: 209px;
|
||||
top: -34px;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
right: 20px;
|
||||
left: unset;
|
||||
}
|
||||
|
||||
@media (max-width: 470px) {
|
||||
position: unset;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
composes: .g-type-body-small from global;
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
import Button from '@hashicorp/react-button'
|
||||
import s from './style.module.css'
|
||||
interface Doc {
|
||||
icon: {
|
||||
src: string
|
||||
alt: string
|
||||
}
|
||||
description: string
|
||||
cta: {
|
||||
text: string
|
||||
url: string
|
||||
}
|
||||
}
|
||||
|
||||
interface DocsListProps {
|
||||
title: string
|
||||
docs: Doc[]
|
||||
className?: string
|
||||
}
|
||||
|
||||
export default function DocsList({ title, docs, className }: DocsListProps) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<h3 className={s.title}>{title}</h3>
|
||||
<div className={s.docsList}>
|
||||
{docs.map(({ icon, description, cta }) => (
|
||||
<div key={cta.text}>
|
||||
<div className={s.image}>
|
||||
<img src={icon.src} alt={icon.alt} />
|
||||
</div>
|
||||
<p className={s.description}>{description}</p>
|
||||
<Button
|
||||
key="stable"
|
||||
url={cta.url}
|
||||
title={cta.text}
|
||||
linkType="inbound"
|
||||
theme={{
|
||||
variant: 'tertiary',
|
||||
brand: 'neutral',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
.docsList {
|
||||
display: grid;
|
||||
row-gap: 48px;
|
||||
}
|
||||
|
||||
.title {
|
||||
composes: g-type-display-3 from global;
|
||||
margin-top: 0;
|
||||
margin-bottom: 46px;
|
||||
}
|
||||
|
||||
.image {
|
||||
border: 1px solid var(--gray-5);
|
||||
box-sizing: border-box;
|
||||
border-radius: 3px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
& img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
composes: g-body from global;
|
||||
margin-top: 12px;
|
||||
margin-bottom: 24px;
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
import Button from '@hashicorp/react-button'
|
||||
import s from '../../pages/downloads/style.module.css'
|
||||
|
||||
export default function DownloadsProps(preMerchandisingSlot) {
|
||||
return {
|
||||
getStartedDescription:
|
||||
'Follow step-by-step tutorials on the essentials of Consul.',
|
||||
getStartedLinks: [
|
||||
{
|
||||
label: 'CLI Quickstart',
|
||||
href: 'https://learn.hashicorp.com/collections/consul/getting-started',
|
||||
},
|
||||
{
|
||||
label: 'HCP Consul',
|
||||
href:
|
||||
'https://learn.hashicorp.com/collections/consul/cloud-get-started',
|
||||
},
|
||||
{
|
||||
label: 'HCS on Azure',
|
||||
href: 'https://learn.hashicorp.com/collections/consul/hcs-azure',
|
||||
},
|
||||
{
|
||||
label: 'Kubernetes Quickstart',
|
||||
href:
|
||||
'https://learn.hashicorp.com/collections/consul/gs-consul-service-mesh',
|
||||
},
|
||||
{
|
||||
label: 'View all Consul tutorials',
|
||||
href: 'https://learn.hashicorp.com/consul',
|
||||
},
|
||||
],
|
||||
tutorialLink: {
|
||||
href: 'https://learn.hashicorp.com/consul',
|
||||
label: 'View Tutorials at HashiCorp Learn',
|
||||
},
|
||||
logo: (
|
||||
<img
|
||||
className={s.logo}
|
||||
alt="Consul"
|
||||
src={require('@hashicorp/mktg-logos/product/consul/primary/color.svg')}
|
||||
/>
|
||||
),
|
||||
merchandisingSlot: (
|
||||
<>
|
||||
{preMerchandisingSlot && preMerchandisingSlot}
|
||||
<div className={s.merchandisingSlot}>
|
||||
<div className={s.centerWrapper}>
|
||||
<p>
|
||||
Looking for a way to secure and automate application networking
|
||||
without the added complexity of managing the infrastructure?
|
||||
</p>
|
||||
<Button
|
||||
title="Try HCP Consul"
|
||||
linkType="inbound"
|
||||
url="https://portal.cloud.hashicorp.com/sign-up?utm_source=consul_io&utm_content=download_cta"
|
||||
theme={{
|
||||
variant: 'tertiary',
|
||||
brand: 'consul',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<a href="/docs/download-tools">» Download Consul Tools</a>
|
||||
</p>
|
||||
|
||||
<div className={s.releaseCandidate}>
|
||||
<p>Note for ARM users:</p>
|
||||
|
||||
<ul>
|
||||
<li>Use Armelv5 for all 32-bit armel systems</li>
|
||||
<li>Use Armhfv6 for all armhf systems with v6+ architecture</li>
|
||||
<li>Use Arm64 for all v8 64-bit architectures</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
The following commands can help determine the right version for your
|
||||
system:
|
||||
</p>
|
||||
|
||||
<code>$ uname -m</code>
|
||||
<br />
|
||||
<code>
|
||||
$ readelf -a /proc/self/exe | grep -q -c Tag_ABI_VFP_args && echo
|
||||
"armhf" || echo "armel"
|
||||
</code>
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<svg width="32" height="96" viewBox="0 0 32 96" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="32" height="96" />
|
||||
<circle cx="6" cy="6" r="6" fill="#DC477D"/>
|
||||
<circle cx="6" cy="70" r="6" fill="#DBDBDC"/>
|
||||
<circle cx="6" cy="26" r="6" fill="#DC477D"/>
|
||||
<circle cx="6" cy="90" r="6" fill="#DBDBDC"/>
|
||||
<circle cx="26" cy="6" r="6" fill="#DC477D"/>
|
||||
<circle cx="26" cy="70" r="6" fill="#DBDBDC"/>
|
||||
<circle cx="26" cy="26" r="6" fill="#DBDBDC"/>
|
||||
<circle cx="26" cy="90" r="6" fill="#DBDBDC"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 505 B |
|
@ -1,27 +0,0 @@
|
|||
<svg width="160" height="96" viewBox="0 0 160 96" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="160" height="96" />
|
||||
<circle cx="6" cy="6" r="6" fill="#DC477D"/>
|
||||
<circle cx="134" cy="6" r="6" fill="#DC477D"/>
|
||||
<circle cx="70" cy="6" r="6" fill="#DC477D"/>
|
||||
<circle cx="6" cy="70" r="6" fill="#DBDBDC"/>
|
||||
<circle cx="134" cy="70" r="6" fill="#DC477D"/>
|
||||
<circle cx="70" cy="70" r="6" fill="#DC477D"/>
|
||||
<circle cx="6" cy="26" r="6" fill="#DC477D"/>
|
||||
<circle cx="134" cy="26" r="6" fill="#DC477D"/>
|
||||
<circle cx="70" cy="26" r="6" fill="#DC477D"/>
|
||||
<circle cx="6" cy="90" r="6" fill="#DBDBDC"/>
|
||||
<circle cx="134" cy="90" r="6" fill="#DC477D"/>
|
||||
<circle cx="70" cy="90" r="6" fill="#DBDBDC"/>
|
||||
<circle cx="26" cy="6" r="6" fill="#DC477D"/>
|
||||
<circle cx="154" cy="6" r="6" fill="#DC477D"/>
|
||||
<circle cx="90" cy="6" r="6" fill="#DBDBDC"/>
|
||||
<circle cx="26" cy="70" r="6" fill="#DBDBDC"/>
|
||||
<circle cx="154" cy="70" r="6" fill="#DC477D"/>
|
||||
<circle cx="90" cy="70" r="6" fill="#DC477D"/>
|
||||
<circle cx="26" cy="26" r="6" fill="#DBDBDC"/>
|
||||
<circle cx="154" cy="26" r="6" fill="#DC477D"/>
|
||||
<circle cx="90" cy="26" r="6" fill="#DC477D"/>
|
||||
<circle cx="26" cy="90" r="6" fill="#DBDBDC"/>
|
||||
<circle cx="154" cy="90" r="6" fill="#DBDBDC"/>
|
||||
<circle cx="90" cy="90" r="6" fill="#DBDBDC"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.2 KiB |
|
@ -1,44 +0,0 @@
|
|||
import EnterpriseComparison from '../../enterprise-comparison'
|
||||
|
||||
export default function ConsulEnterpriseComparison() {
|
||||
return (
|
||||
<EnterpriseComparison
|
||||
title="When to consider Consul Enterprise"
|
||||
itemOne={{
|
||||
title: 'Technical Complexity',
|
||||
label: 'Open Source',
|
||||
imageUrl: require('./img/enterprise_complexity_1.svg?url'),
|
||||
description:
|
||||
'Consul Open Source enables individuals to discover services and securely manage connections between them across cloud, on-prem, and hybrid environments.',
|
||||
links: [
|
||||
{
|
||||
text: 'View Open Source Features',
|
||||
url: 'https://www.hashicorp.com/products/consul/pricing/',
|
||||
type: 'outbound',
|
||||
},
|
||||
],
|
||||
}}
|
||||
itemTwo={{
|
||||
title: 'Organizational Complexity',
|
||||
label: 'Enterprise',
|
||||
imageUrl: require('./img/enterprise_complexity_2.svg?url'),
|
||||
description:
|
||||
'Consul Enterprise provides the foundation for organizations to build an enterprise-ready service networking environment for multiple teams by enabling governance capabilities.',
|
||||
links: [
|
||||
{
|
||||
text: 'View Cloud Features',
|
||||
url:
|
||||
'https://cloud.hashicorp.com/?utm_source=consul_io&utm_content=ent_comparison',
|
||||
type: 'outbound',
|
||||
},
|
||||
{
|
||||
text: 'View Self-Managed Features',
|
||||
url: 'https://www.hashicorp.com/products/consul/pricing/',
|
||||
type: 'outbound',
|
||||
},
|
||||
],
|
||||
}}
|
||||
brand="consul"
|
||||
/>
|
||||
)
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
<svg width="128" height="18" viewBox="0 0 128 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<line x1="1.14564e-07" y1="9" x2="127.277" y2="9.00001" stroke="var(--gray-4, #bfbfc0)" stroke-width="2"/>
|
||||
<path d="M118 1L126.5 9L118 17" stroke="var(--gray-4, #bfbfc0)" stroke-width="2"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 295 B |
BIN
website/components/enterprise-comparison/img/complexity-advanced.png (Stored with Git LFS)
BIN
website/components/enterprise-comparison/img/complexity-basic.png (Stored with Git LFS)
|
@ -1,62 +0,0 @@
|
|||
import Image from '@hashicorp/react-image'
|
||||
import Button from '@hashicorp/react-button'
|
||||
import InlineSvg from '@hashicorp/react-inline-svg'
|
||||
import ArrowIcon from './img/arrow.svg?include'
|
||||
|
||||
export default function EnterpriseComparison({
|
||||
title,
|
||||
itemOne,
|
||||
itemTwo,
|
||||
brand,
|
||||
}) {
|
||||
return (
|
||||
<div className="g-enterprise-comparison">
|
||||
<div className="g-grid-container">
|
||||
<h2 className="g-type-display-2">{title}</h2>
|
||||
|
||||
<div className="content-container">
|
||||
<div className="item">
|
||||
<Image url={itemOne.imageUrl} />
|
||||
<div className="g-type-label-strong">{itemOne.label}</div>
|
||||
<h4 className="g-type-display-4">{itemOne.title}</h4>
|
||||
|
||||
<p className="g-type-body">{itemOne.description}</p>
|
||||
|
||||
{itemOne.links.map((link) => (
|
||||
<div key="stable">
|
||||
<Button
|
||||
url={link.url}
|
||||
title={link.text}
|
||||
linkType={link.type}
|
||||
theme={{ variant: 'tertiary', brand }}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="spacer">
|
||||
<div className="vertical-spacer"></div>
|
||||
<InlineSvg className="arrow" src={ArrowIcon} />
|
||||
</div>
|
||||
<div className="item">
|
||||
<Image url={itemTwo.imageUrl} />
|
||||
<div className="g-type-label-strong">{itemTwo.label}</div>
|
||||
<h4 className="g-type-display-4">{itemTwo.title}</h4>
|
||||
|
||||
<p className="g-type-body">{itemTwo.description}</p>
|
||||
{itemTwo.links.map((link) => (
|
||||
<div key="stable">
|
||||
<Button
|
||||
key="stable"
|
||||
url={link.url}
|
||||
title={link.text}
|
||||
linkType={link.type}
|
||||
theme={{ variant: 'tertiary', brand }}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
.g-enterprise-comparison {
|
||||
padding-top: 128px;
|
||||
padding-bottom: 128px;
|
||||
background: var(--gray-6);
|
||||
|
||||
& h2 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
padding-top: 64px;
|
||||
padding-bottom: 64px;
|
||||
}
|
||||
|
||||
& .content-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 0 auto 64px auto;
|
||||
|
||||
@media (max-width: 800px) {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
& .item {
|
||||
flex-basis: 50%;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
margin-top: 64px;
|
||||
|
||||
@media (max-width: 800px) {
|
||||
margin-top: 64px;
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
& .g-type-label-strong {
|
||||
margin-top: 64px;
|
||||
|
||||
@media (max-width: 800px) {
|
||||
margin-top: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
& h4 {
|
||||
white-space: pre;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
@media (max-width: 800px) {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
& picture {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
& img {
|
||||
max-width: 160px;
|
||||
max-height: 98px;
|
||||
}
|
||||
& p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 24px;
|
||||
|
||||
@media (max-width: 800px) {
|
||||
max-width: 600px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .spacer {
|
||||
& .vertical-spacer {
|
||||
height: 93px;
|
||||
}
|
||||
|
||||
& .arrow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .more-features-link {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
import { ReactNode } from 'react'
|
||||
import Button from '@hashicorp/react-button'
|
||||
import s from './style.module.css'
|
||||
|
||||
interface InfoSection {
|
||||
heading: string
|
||||
content: ReactNode
|
||||
}
|
||||
|
||||
interface Cta {
|
||||
text: string
|
||||
url: string
|
||||
}
|
||||
|
||||
export interface FeatureProps {
|
||||
number: number
|
||||
title: string
|
||||
subtitle: string
|
||||
infoSections: InfoSection[]
|
||||
cta: Cta
|
||||
image: string
|
||||
}
|
||||
|
||||
export default function Feature({
|
||||
number,
|
||||
title,
|
||||
subtitle,
|
||||
infoSections,
|
||||
cta,
|
||||
image,
|
||||
}: FeatureProps) {
|
||||
return (
|
||||
<div className={s.featureContainer}>
|
||||
<div className={s.imageContainer}>
|
||||
<img src={image} alt={title} />
|
||||
</div>
|
||||
<div className={s.featureTextContainer}>
|
||||
<div className={s.listNumber}>
|
||||
<span>{number}</span>
|
||||
</div>
|
||||
<div className={s.featureText}>
|
||||
<h3 className={s.featureTitle}>{title}</h3>
|
||||
<p className={s.featureSubtitle}>{subtitle}</p>
|
||||
<div className={s.infoSection}>
|
||||
{infoSections.map(({ heading, content }) => (
|
||||
<div key={heading}>
|
||||
<h4 className={s.infoTitle}>{heading}</h4>
|
||||
{content}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<Button
|
||||
title={cta.text}
|
||||
url={cta.url}
|
||||
linkType="inbound"
|
||||
theme={{
|
||||
variant: 'tertiary-neutral',
|
||||
background: 'dark',
|
||||
}}
|
||||
className={s.ctaButton}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
Before Width: | Height: | Size: 254 KiB |
Before Width: | Height: | Size: 497 KiB |
|
@ -1,28 +0,0 @@
|
|||
import Feature from './feature'
|
||||
import s from './style.module.css'
|
||||
import { FeatureProps } from './feature'
|
||||
|
||||
interface FeaturesListProps {
|
||||
title: string
|
||||
features: Omit<FeatureProps, 'number'>[]
|
||||
}
|
||||
|
||||
export default function FeaturesList({ title, features }: FeaturesListProps) {
|
||||
return (
|
||||
<div
|
||||
className={s.featureListContainer}
|
||||
style={{
|
||||
backgroundImage: `url(${require('./images/top-right-design.svg')}), url(${require('./images/bottom-left-design.svg')})`,
|
||||
}}
|
||||
>
|
||||
<div className={s.contentWrapper}>
|
||||
<h2 className={s.title}>{title}</h2>
|
||||
<div className={s.featuresContainer}>
|
||||
{features.map((feature, i) => (
|
||||
<Feature {...feature} number={i + 1} key={feature.title} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
.featureListContainer {
|
||||
background-color: #000;
|
||||
padding-top: 128px;
|
||||
background-position: right top, left bottom;
|
||||
background-repeat: no-repeat;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.contentWrapper {
|
||||
composes: g-grid-container from global;
|
||||
}
|
||||
|
||||
.featureContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
& .featureTextContainer {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
& .imageContainer {
|
||||
max-width: 490px;
|
||||
margin: 0 auto;
|
||||
padding-bottom: 40px;
|
||||
|
||||
& img {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (--large) {
|
||||
flex-wrap: nowrap;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: space-between;
|
||||
|
||||
& .featureTextContainer {
|
||||
margin-right: 60px;
|
||||
}
|
||||
|
||||
& .featureText {
|
||||
max-width: 488px;
|
||||
}
|
||||
|
||||
& .imageContainer {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.featuresContainer {
|
||||
padding-top: 157px;
|
||||
padding-bottom: 394px;
|
||||
display: grid;
|
||||
row-gap: 120px;
|
||||
}
|
||||
|
||||
.title {
|
||||
composes: g-type-display-1 from global;
|
||||
max-width: 488px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.listNumber {
|
||||
composes: g-type-display-5 from global;
|
||||
min-width: 40px;
|
||||
height: 40px;
|
||||
background-color: var(--consul);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 64px;
|
||||
margin-top: 10px;
|
||||
|
||||
@media (--small) {
|
||||
margin-right: 30px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.featureTitle {
|
||||
composes: g-type-display-2 from global;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.featureSubtitle {
|
||||
composes: g-type-body-large from global;
|
||||
margin-top: 16px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.infoTitle {
|
||||
composes: g-type-display-5 from global;
|
||||
margin-top: 0;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.infoSection {
|
||||
composes: g-type-body from global;
|
||||
margin-top: 32px;
|
||||
margin-bottom: 40px;
|
||||
display: grid;
|
||||
row-gap: 24px;
|
||||
|
||||
& p,
|
||||
& ul {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
import Link from 'next/link'
|
||||
|
||||
export default function Footer({ openConsentManager }) {
|
||||
return (
|
||||
<footer className="g-footer">
|
||||
<div className="g-grid-container">
|
||||
<div className="left">
|
||||
<Link href="/intro">
|
||||
<a>Intro</a>
|
||||
</Link>
|
||||
<Link href="/docs/guides">
|
||||
<a>Guides</a>
|
||||
</Link>
|
||||
<Link href="/docs">
|
||||
<a>Docs</a>
|
||||
</Link>
|
||||
<Link href="/community">
|
||||
<a>Community</a>
|
||||
</Link>
|
||||
<a href="https://hashicorp.com/privacy">Privacy</a>
|
||||
<Link href="/security">
|
||||
<a>Security</a>
|
||||
</Link>
|
||||
<Link href="https://www.hashicorp.com/brand">
|
||||
<a>Brand</a>
|
||||
</Link>
|
||||
<a onClick={openConsentManager}>Consent Manager</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
.g-footer {
|
||||
padding: 25px 0 17px 0;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
|
||||
& .g-grid-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
& a {
|
||||
color: black;
|
||||
opacity: 0.5;
|
||||
transition: opacity 0.25s ease;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
& .left > a {
|
||||
margin-right: 20px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
.hcpCalloutSection {
|
||||
composes: g-grid-container from global;
|
||||
padding-top: 88px;
|
||||
padding-bottom: 88px;
|
||||
|
||||
& .header {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 88px;
|
||||
@media (max-width: 1120px) {
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
& h2 {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
max-width: 450px;
|
||||
}
|
||||
}
|
||||
|
||||
& .content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
@media (max-width: 1120px) {
|
||||
flex-direction: column-reverse;
|
||||
|
||||
& .info {
|
||||
margin-top: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
& .info {
|
||||
max-width: 488px;
|
||||
margin-right: 32px;
|
||||
|
||||
& h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
& .chin {
|
||||
color: var(--gray-3);
|
||||
}
|
||||
& .description {
|
||||
color: var(--gray-2);
|
||||
margin-top: 28px;
|
||||
margin-bottom: 0;
|
||||
|
||||
@media (max-width: 900px) {
|
||||
margin-top: 18px;
|
||||
}
|
||||
}
|
||||
& .links {
|
||||
margin-top: 32px;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
|
||||
& > * {
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& > img {
|
||||
align-self: center;
|
||||
margin-right: -48px;
|
||||
@media (max-width: 670px) {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chin {
|
||||
composes: g-type-label from global;
|
||||
}
|
||||
|
||||
.description {
|
||||
composes: g-type-long-body from global;
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
import styles from './HCPCalloutSection.module.css'
|
||||
import Button from '@hashicorp/react-button'
|
||||
|
||||
export default function HcpCalloutSection({
|
||||
id,
|
||||
header,
|
||||
title,
|
||||
description,
|
||||
chin,
|
||||
image,
|
||||
links,
|
||||
}) {
|
||||
return (
|
||||
<div className={styles.hcpCalloutSection} id={id}>
|
||||
<div className={styles.header}>
|
||||
<h2 className="g-type-display-2">{header}</h2>
|
||||
</div>
|
||||
|
||||
<div className={styles.content}>
|
||||
<div className={styles.info}>
|
||||
<h1 className="g-type-display-1">{title}</h1>
|
||||
<span className={styles.chin}>{chin}</span>
|
||||
<p className={styles.description}>{description}</p>
|
||||
<div className={styles.links}>
|
||||
{links.map((link, index) => {
|
||||
const variant = index === 0 ? 'primary' : 'tertiary'
|
||||
return (
|
||||
<div key={link.text}>
|
||||
<Button
|
||||
title={link.text}
|
||||
linkType={link.type}
|
||||
url={link.url}
|
||||
theme={{ variant, brand: 'neutral', background: 'light' }}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<img alt={title} src={image} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
import s from './style.module.css'
|
||||
import Hero from '@hashicorp/react-hero'
|
||||
|
||||
export default function HomepageHero({
|
||||
title,
|
||||
description,
|
||||
links,
|
||||
uiVideo,
|
||||
cliVideo,
|
||||
alert,
|
||||
image,
|
||||
}) {
|
||||
return (
|
||||
<div className={s.consulHero}>
|
||||
<Hero
|
||||
data={{
|
||||
product: 'consul',
|
||||
alert: alert ? { ...alert, tagColor: 'consul-pink' } : null,
|
||||
title: title,
|
||||
description: description,
|
||||
buttons: links,
|
||||
backgroundTheme: 'light',
|
||||
centered: false,
|
||||
image: image ? { ...image } : null,
|
||||
videos: [
|
||||
...(uiVideo
|
||||
? [
|
||||
{
|
||||
name: uiVideo.name ?? 'UI',
|
||||
playbackRate: uiVideo.playbackRate,
|
||||
aspectRatio: uiVideo.aspectRatio,
|
||||
src: [
|
||||
{
|
||||
srcType: uiVideo.srcType,
|
||||
url: uiVideo.url,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(cliVideo
|
||||
? [
|
||||
{
|
||||
name: cliVideo.name ?? 'CLI',
|
||||
playbackRate: cliVideo.playbackRate,
|
||||
aspectRatio: cliVideo.aspectRatio,
|
||||
src: [
|
||||
{
|
||||
srcType: cliVideo.srcType,
|
||||
url: cliVideo.url,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
.consulHero {
|
||||
/* Customize the branding */
|
||||
& :global(.carousel .controls .control) {
|
||||
color: var(--gray-2);
|
||||
& :global(.progress-bar) {
|
||||
background: var(--gray-5);
|
||||
& span {
|
||||
background: var(--consul);
|
||||
}
|
||||
}
|
||||
}
|
||||
& :global(.g-hero .carousel) {
|
||||
& :global(.video-wrapper.is-active) {
|
||||
/* Padding % modifier differs slightly from react-hero to accommodate video heights */
|
||||
padding-top: calc((100% * 0.57) + 28px); /* !important; */
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
import * as React from 'react'
|
||||
import classNames from 'classnames'
|
||||
import Button from '@hashicorp/react-button'
|
||||
import IoCard, { IoCardProps } from 'components/io-card'
|
||||
import s from './style.module.css'
|
||||
|
||||
interface IoCardContaianerProps {
|
||||
theme?: 'light' | 'dark'
|
||||
heading?: string
|
||||
description?: string
|
||||
label?: string
|
||||
cta?: {
|
||||
url: string
|
||||
text: string
|
||||
}
|
||||
cardsPerRow: 3 | 4
|
||||
cards: Array<IoCardProps>
|
||||
}
|
||||
|
||||
export default function IoCardContaianer({
|
||||
theme = 'light',
|
||||
heading,
|
||||
description,
|
||||
label,
|
||||
cta,
|
||||
cardsPerRow = 3,
|
||||
cards,
|
||||
}: IoCardContaianerProps): React.ReactElement {
|
||||
return (
|
||||
<div className={classNames(s.cardContainer, s[theme])}>
|
||||
{heading || description ? (
|
||||
<header className={s.header}>
|
||||
{heading ? <h2 className={s.heading}>{heading}</h2> : null}
|
||||
{description ? <p className={s.description}>{description}</p> : null}
|
||||
</header>
|
||||
) : null}
|
||||
{cards.length ? (
|
||||
<>
|
||||
{label || cta ? (
|
||||
<header className={s.subHeader}>
|
||||
{label ? <h3 className={s.label}>{label}</h3> : null}
|
||||
{cta ? (
|
||||
<Button
|
||||
title={cta.text}
|
||||
url={cta.url}
|
||||
linkType="inbound"
|
||||
theme={{
|
||||
brand: 'neutral',
|
||||
variant: 'tertiary',
|
||||
background: theme,
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</header>
|
||||
) : null}
|
||||
<ul
|
||||
className={classNames(
|
||||
s.cardList,
|
||||
cardsPerRow === 3 && s.threeUp,
|
||||
cardsPerRow === 4 && s.fourUp
|
||||
)}
|
||||
style={
|
||||
{
|
||||
'--length': cards.length,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
{cards.map((card, index) => {
|
||||
return (
|
||||
// Index is stable
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<li key={index}>
|
||||
<IoCard variant={theme} {...card} />
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
.cardContainer {
|
||||
position: relative;
|
||||
|
||||
& + .cardContainer {
|
||||
margin-top: 64px;
|
||||
|
||||
@media (--medium-up) {
|
||||
margin-top: 132px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
margin: 0 auto 64px;
|
||||
text-align: center;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.heading {
|
||||
margin: 0;
|
||||
composes: g-type-display-2 from global;
|
||||
|
||||
@nest .dark & {
|
||||
color: var(--white);
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 8px 0 0;
|
||||
composes: g-type-body-large from global;
|
||||
|
||||
@nest .dark & {
|
||||
color: var(--gray-5);
|
||||
}
|
||||
}
|
||||
|
||||
.subHeader {
|
||||
margin: 0 0 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
@nest .dark & {
|
||||
color: var(--gray-5);
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
margin: 0;
|
||||
composes: g-type-display-4 from global;
|
||||
}
|
||||
|
||||
.cardList {
|
||||
list-style: none;
|
||||
|
||||
--minCol: 250px;
|
||||
--columns: var(--length);
|
||||
|
||||
position: relative;
|
||||
gap: 32px;
|
||||
padding: 0;
|
||||
|
||||
@media (--small) {
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
margin: 0;
|
||||
padding: 6px 24px;
|
||||
left: 50%;
|
||||
margin-left: -50vw;
|
||||
width: 100vw;
|
||||
|
||||
/* This is to ensure there is overflow padding right on mobile. */
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 1px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (--medium-up) {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--columns), minmax(var(--minCol), 1fr));
|
||||
}
|
||||
|
||||
&.threeUp {
|
||||
@media (--medium-up) {
|
||||
--columns: 3;
|
||||
--minCol: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.fourUp {
|
||||
@media (--medium-up) {
|
||||
--columns: 3;
|
||||
--minCol: 0;
|
||||
}
|
||||
|
||||
@media (--large) {
|
||||
--columns: 4;
|
||||
}
|
||||
}
|
||||
|
||||
& > li {
|
||||
display: flex;
|
||||
|
||||
@media (--small) {
|
||||
flex-shrink: 0;
|
||||
width: 250px;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
import * as React from 'react'
|
||||
import Link from 'next/link'
|
||||
import InlineSvg from '@hashicorp/react-inline-svg'
|
||||
import classNames from 'classnames'
|
||||
import { IconArrowRight24 } from '@hashicorp/flight-icons/svg-react/arrow-right-24'
|
||||
import { IconExternalLink24 } from '@hashicorp/flight-icons/svg-react/external-link-24'
|
||||
import { productLogos } from './product-logos'
|
||||
import s from './style.module.css'
|
||||
|
||||
export interface IoCardProps {
|
||||
variant?: 'light' | 'gray' | 'dark'
|
||||
products?: Array<{
|
||||
name: keyof typeof productLogos
|
||||
}>
|
||||
link: {
|
||||
url: string
|
||||
type: 'inbound' | 'outbound'
|
||||
}
|
||||
inset?: 'none' | 'sm' | 'md'
|
||||
eyebrow?: string
|
||||
heading?: string
|
||||
description?: string
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
function IoCard({
|
||||
variant = 'light',
|
||||
products,
|
||||
link,
|
||||
inset = 'md',
|
||||
eyebrow,
|
||||
heading,
|
||||
description,
|
||||
children,
|
||||
}: IoCardProps): React.ReactElement {
|
||||
const LinkWrapper = ({ className, children }) =>
|
||||
link.type === 'inbound' ? (
|
||||
<Link href={link.url}>
|
||||
<a className={className}>{children}</a>
|
||||
</Link>
|
||||
) : (
|
||||
<a
|
||||
className={className}
|
||||
href={link.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
)
|
||||
|
||||
return (
|
||||
<article className={classNames(s.card)}>
|
||||
<LinkWrapper className={classNames(s[variant], s[inset])}>
|
||||
{children ? (
|
||||
children
|
||||
) : (
|
||||
<>
|
||||
{eyebrow ? <Eyebrow>{eyebrow}</Eyebrow> : null}
|
||||
{heading ? <Heading>{heading}</Heading> : null}
|
||||
{description ? <Description>{description}</Description> : null}
|
||||
</>
|
||||
)}
|
||||
<footer className={s.footer}>
|
||||
{products && (
|
||||
<ul className={s.products}>
|
||||
{products.map(({ name }, index) => {
|
||||
const key = name.toLowerCase()
|
||||
const version = variant === 'dark' ? 'neutral' : 'color'
|
||||
return (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<li key={index}>
|
||||
<InlineSvg
|
||||
className={s.logo}
|
||||
src={productLogos[key][version]}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
)}
|
||||
<span className={s.linkType}>
|
||||
{link.type === 'inbound' ? (
|
||||
<IconArrowRight24 />
|
||||
) : (
|
||||
<IconExternalLink24 />
|
||||
)}
|
||||
</span>
|
||||
</footer>
|
||||
</LinkWrapper>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
|
||||
interface EyebrowProps {
|
||||
children: string
|
||||
}
|
||||
|
||||
function Eyebrow({ children }: EyebrowProps) {
|
||||
return <p className={s.eyebrow}>{children}</p>
|
||||
}
|
||||
|
||||
interface HeadingProps {
|
||||
as?: 'h2' | 'h3' | 'h4'
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
function Heading({ as: Component = 'h2', children }: HeadingProps) {
|
||||
return <Component className={s.heading}>{children}</Component>
|
||||
}
|
||||
|
||||
interface DescriptionProps {
|
||||
children: string
|
||||
}
|
||||
|
||||
function Description({ children }: DescriptionProps) {
|
||||
return <p className={s.description}>{children}</p>
|
||||
}
|
||||
|
||||
IoCard.Eyebrow = Eyebrow
|
||||
IoCard.Heading = Heading
|
||||
IoCard.Description = Description
|
||||
|
||||
export default IoCard
|
|
@ -1,34 +0,0 @@
|
|||
export const productLogos = {
|
||||
boundary: {
|
||||
color: require('@hashicorp/mktg-logos/product/boundary/logomark/color.svg?include'),
|
||||
neutral: require('@hashicorp/mktg-logos/product/boundary/logomark/white.svg?include'),
|
||||
},
|
||||
consul: {
|
||||
color: require('@hashicorp/mktg-logos/product/consul/logomark/color.svg?include'),
|
||||
neutral: require('@hashicorp/mktg-logos/product/consul/logomark/white.svg?include'),
|
||||
},
|
||||
nomad: {
|
||||
color: require('@hashicorp/mktg-logos/product/nomad/logomark/color.svg?include'),
|
||||
neutral: require('@hashicorp/mktg-logos/product/nomad/logomark/white.svg?include'),
|
||||
},
|
||||
packer: {
|
||||
color: require('@hashicorp/mktg-logos/product/packer/logomark/color.svg?include'),
|
||||
neutral: require('@hashicorp/mktg-logos/product/packer/logomark/white.svg?include'),
|
||||
},
|
||||
terraform: {
|
||||
color: require('@hashicorp/mktg-logos/product/terraform/logomark/color.svg?include'),
|
||||
neutral: require('@hashicorp/mktg-logos/product/terraform/logomark/white.svg?include'),
|
||||
},
|
||||
vagrant: {
|
||||
color: require('@hashicorp/mktg-logos/product/vagrant/logomark/color.svg?include'),
|
||||
neutral: require('@hashicorp/mktg-logos/product/vagrant/logomark/white.svg?include'),
|
||||
},
|
||||
vault: {
|
||||
color: require('@hashicorp/mktg-logos/product/vault/logomark/color.svg?include'),
|
||||
neutral: require('@hashicorp/mktg-logos/product/vault/logomark/white.svg?include'),
|
||||
},
|
||||
waypoint: {
|
||||
color: require('@hashicorp/mktg-logos/product/waypoint/logomark/color.svg?include'),
|
||||
neutral: require('@hashicorp/mktg-logos/product/waypoint/logomark/white.svg?include'),
|
||||
},
|
||||
}
|
|
@ -1,148 +0,0 @@
|
|||
.card {
|
||||
/* Radii */
|
||||
--token-radius: 6px;
|
||||
|
||||
/* Spacing */
|
||||
--token-spacing-03: 8px;
|
||||
--token-spacing-04: 16px;
|
||||
--token-spacing-05: 24px;
|
||||
--token-spacing-06: 32px;
|
||||
|
||||
/* Elevations */
|
||||
--token-elevation-mid: 0 2px 3px rgba(101, 106, 118, 0.1),
|
||||
0 8px 16px -10px rgba(101, 106, 118, 0.2);
|
||||
--token-elevation-high: 0 2px 3px rgba(101, 106, 118, 0.15),
|
||||
0 16px 16px -10px rgba(101, 106, 118, 0.2);
|
||||
|
||||
/* Transition */
|
||||
--token-transition: ease-in-out 0.2s;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
min-height: 300px;
|
||||
|
||||
& a {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
border-radius: var(--token-radius);
|
||||
box-shadow: 0 0 0 1px rgba(38, 53, 61, 0.1), var(--token-elevation-mid);
|
||||
transition: var(--token-transition);
|
||||
transition-property: background-color, box-shadow;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 2px rgba(38, 53, 61, 0.15), var(--token-elevation-high);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Variants */
|
||||
&.dark {
|
||||
background-color: var(--gray-1);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--gray-2);
|
||||
}
|
||||
}
|
||||
|
||||
&.gray {
|
||||
background-color: #f9f9fa;
|
||||
}
|
||||
|
||||
&.light {
|
||||
background-color: var(--white);
|
||||
}
|
||||
|
||||
/* Spacing */
|
||||
&.none {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&.sm {
|
||||
padding: var(--token-spacing-05);
|
||||
}
|
||||
|
||||
&.md {
|
||||
padding: var(--token-spacing-06);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
margin: 0;
|
||||
composes: g-type-label-small from global;
|
||||
color: var(--gray-3);
|
||||
|
||||
@nest .dark & {
|
||||
color: var(--gray-5);
|
||||
}
|
||||
}
|
||||
|
||||
.heading {
|
||||
margin: 0;
|
||||
composes: g-type-display-5 from global;
|
||||
color: var(--black);
|
||||
|
||||
@nest * + & {
|
||||
margin-top: var(--token-spacing-05);
|
||||
}
|
||||
|
||||
@nest .dark & {
|
||||
color: var(--white);
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 0;
|
||||
composes: g-type-body-small from global;
|
||||
color: var(--gray-3);
|
||||
|
||||
@nest * + & {
|
||||
margin-top: var(--token-spacing-03);
|
||||
}
|
||||
|
||||
@nest .dark & {
|
||||
color: var(--gray-5);
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
padding-top: 32px;
|
||||
}
|
||||
|
||||
.products {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
& > li {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
& .logo {
|
||||
display: flex;
|
||||
|
||||
& svg {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.linkType {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
color: var(--black);
|
||||
|
||||
@nest .dark & {
|
||||
color: var(--white);
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
import * as React from 'react'
|
||||
import { DialogOverlay, DialogContent, DialogOverlayProps } from '@reach/dialog'
|
||||
import { AnimatePresence, motion } from 'framer-motion'
|
||||
import s from './style.module.css'
|
||||
|
||||
export interface IoDialogProps extends DialogOverlayProps {
|
||||
label: string
|
||||
}
|
||||
|
||||
export default function IoDialog({
|
||||
isOpen,
|
||||
onDismiss,
|
||||
children,
|
||||
label,
|
||||
}: IoDialogProps): React.ReactElement {
|
||||
const AnimatedDialogOverlay = motion(DialogOverlay)
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{isOpen && (
|
||||
<AnimatedDialogOverlay
|
||||
className={s.dialogOverlay}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
onDismiss={onDismiss}
|
||||
>
|
||||
<div className={s.dialogWrapper}>
|
||||
<motion.div
|
||||
initial={{ y: 50 }}
|
||||
animate={{ y: 0 }}
|
||||
exit={{ y: 50 }}
|
||||
transition={{ min: 0, max: 100, bounceDamping: 8 }}
|
||||
style={{ width: '100%', maxWidth: 800 }}
|
||||
>
|
||||
<DialogContent className={s.dialogContent} aria-label={label}>
|
||||
<button onClick={onDismiss} className={s.dialogClose}>
|
||||
Close
|
||||
</button>
|
||||
{children}
|
||||
</DialogContent>
|
||||
</motion.div>
|
||||
</div>
|
||||
</AnimatedDialogOverlay>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
)
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
.dialogOverlay {
|
||||
background-color: rgba(0, 0, 0, 0.75);
|
||||
height: 100%;
|
||||
left: 0;
|
||||
overflow-y: auto;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: 666666667 /* higher than global nav */;
|
||||
}
|
||||
|
||||
.dialogWrapper {
|
||||
display: grid;
|
||||
min-height: 100vh;
|
||||
padding: 24px;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.dialogContent {
|
||||
background-color: var(--gray-1);
|
||||
color: var(--white);
|
||||
max-width: 800px;
|
||||
outline: none;
|
||||
overflow-y: auto;
|
||||
padding: 24px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
padding: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.dialogClose {
|
||||
appearance: none;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
composes: g-type-display-5 from global;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
color: var(--white);
|
||||
right: 24px;
|
||||
top: 24px;
|
||||
z-index: 1;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
right: 48px;
|
||||
top: 48px;
|
||||
}
|
||||
|
||||
@nest html[dir='rtl'] & {
|
||||
left: 24px;
|
||||
right: auto;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
left: 48px;
|
||||
right: auto;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
import ReactCallToAction from '@hashicorp/react-call-to-action'
|
||||
import { Products } from '@hashicorp/platform-product-meta'
|
||||
import s from './style.module.css'
|
||||
|
||||
interface IoHomeCallToActionProps {
|
||||
brand: Products
|
||||
heading: string
|
||||
content: string
|
||||
links: Array<{
|
||||
text: string
|
||||
url: string
|
||||
}>
|
||||
}
|
||||
|
||||
export default function IoHomeCallToAction({
|
||||
brand,
|
||||
heading,
|
||||
content,
|
||||
links,
|
||||
}: IoHomeCallToActionProps) {
|
||||
return (
|
||||
<div className={s.callToAction}>
|
||||
<ReactCallToAction
|
||||
variant="compact"
|
||||
heading={heading}
|
||||
content={content}
|
||||
product={brand}
|
||||
theme="dark"
|
||||
links={links.map(({ text, url }, index) => {
|
||||
return {
|
||||
text,
|
||||
url,
|
||||
type: index === 1 ? 'inbound' : null,
|
||||
}
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
.callToAction {
|
||||
margin: 60px auto;
|
||||
background-image: linear-gradient(52.3deg, #2c2d2f 39.83%, #626264 96.92%);
|
||||
|
||||
@media (--medium-up) {
|
||||
margin: 120px auto;
|
||||
}
|
||||
|
||||
& > * {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
import * as React from 'react'
|
||||
import Image from 'next/image'
|
||||
import { IconExternalLink16 } from '@hashicorp/flight-icons/svg-react/external-link-16'
|
||||
import { IconArrowRight16 } from '@hashicorp/flight-icons/svg-react/arrow-right-16'
|
||||
import s from './style.module.css'
|
||||
|
||||
interface IoHomeCaseStudiesProps {
|
||||
isInternalLink: (link: string) => boolean
|
||||
heading: string
|
||||
description: string
|
||||
primary: Array<{
|
||||
thumbnail: {
|
||||
url: string
|
||||
alt: string
|
||||
}
|
||||
link: string
|
||||
heading: string
|
||||
}>
|
||||
secondary: Array<{
|
||||
link: string
|
||||
heading: string
|
||||
}>
|
||||
}
|
||||
|
||||
export default function IoHomeCaseStudies({
|
||||
isInternalLink,
|
||||
heading,
|
||||
description,
|
||||
primary,
|
||||
secondary,
|
||||
}: IoHomeCaseStudiesProps): React.ReactElement {
|
||||
return (
|
||||
<section className={s.root}>
|
||||
<div className={s.container}>
|
||||
<header className={s.header}>
|
||||
<h2 className={s.heading}>{heading}</h2>
|
||||
<p className={s.description}>{description}</p>
|
||||
</header>
|
||||
<div className={s.caseStudies}>
|
||||
<ul className={s.primary}>
|
||||
{primary.map((item, index) => {
|
||||
return (
|
||||
<li key={index} className={s.primaryItem}>
|
||||
<a className={s.card} href={item.link}>
|
||||
<h3 className={s.cardHeading}>{item.heading}</h3>
|
||||
<Image
|
||||
className={s.cardThumbnail}
|
||||
src={item.thumbnail.url}
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
alt={item.thumbnail.alt}
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
|
||||
<ul className={s.secondary}>
|
||||
{secondary.map((item, index) => {
|
||||
return (
|
||||
<li key={index} className={s.secondaryItem}>
|
||||
<a className={s.link} href={item.link}>
|
||||
<span className={s.linkInner}>
|
||||
<h3 className={s.linkHeading}>{item.heading}</h3>
|
||||
{isInternalLink(item.link) ? (
|
||||
<IconArrowRight16 />
|
||||
) : (
|
||||
<IconExternalLink16 />
|
||||
)}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
|
@ -1,170 +0,0 @@
|
|||
.root {
|
||||
position: relative;
|
||||
margin: 60px auto;
|
||||
max-width: 1600px;
|
||||
|
||||
@media (--medium-up) {
|
||||
margin: 120px auto;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
composes: g-grid-container from global;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 32px;
|
||||
|
||||
@media (--medium-up) {
|
||||
max-width: calc(100% * 5 / 12);
|
||||
}
|
||||
}
|
||||
|
||||
.heading {
|
||||
margin: 0;
|
||||
composes: g-type-display-3 from global;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 8px 0 0;
|
||||
composes: g-type-body from global;
|
||||
color: var(--gray-3);
|
||||
}
|
||||
|
||||
.caseStudies {
|
||||
--columns: 1;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--columns), minmax(0, 1fr));
|
||||
gap: 32px;
|
||||
|
||||
@media (--medium-up) {
|
||||
--columns: 12;
|
||||
}
|
||||
}
|
||||
|
||||
.primary {
|
||||
--columns: 1;
|
||||
|
||||
grid-column: 1 / -1;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--columns), minmax(0, 1fr));
|
||||
gap: 32px;
|
||||
|
||||
@media (--medium-up) {
|
||||
--columns: 2;
|
||||
}
|
||||
|
||||
@media (--large) {
|
||||
grid-column: 1 / 9;
|
||||
}
|
||||
}
|
||||
|
||||
.primaryItem {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
justify-content: flex-end;
|
||||
padding: 32px;
|
||||
box-shadow: 0 8px 16px -10px rgba(101, 106, 118, 0.2);
|
||||
background-color: #000;
|
||||
border-radius: 6px;
|
||||
color: var(--white);
|
||||
transition: ease-in-out 0.2s;
|
||||
transition-property: box-shadow;
|
||||
min-height: 300px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 10;
|
||||
border-radius: 6px;
|
||||
background-image: linear-gradient(
|
||||
to bottom,
|
||||
rgba(0, 0, 0, 0),
|
||||
rgba(0, 0, 0, 0.45)
|
||||
);
|
||||
transition: opacity ease-in-out 0.2s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 2px 3px rgba(101, 106, 118, 0.15),
|
||||
0 16px 16px -10px rgba(101, 106, 118, 0.2);
|
||||
|
||||
&::before {
|
||||
opacity: 0.75;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cardThumbnail {
|
||||
transition: transform 0.4s;
|
||||
|
||||
@nest .card:hover & {
|
||||
transform: scale(1.04);
|
||||
}
|
||||
}
|
||||
|
||||
.cardHeading {
|
||||
margin: 0;
|
||||
composes: g-type-display-4 from global;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
grid-column: 1 / -1;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
@media (--large) {
|
||||
margin-top: -32px;
|
||||
grid-column: 9 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
.secondaryItem {
|
||||
border-bottom: 1px solid var(--gray-5);
|
||||
}
|
||||
|
||||
.link {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
.linkInner {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
padding-top: 32px;
|
||||
padding-bottom: 32px;
|
||||
transition: transform ease-in-out 0.2s;
|
||||
|
||||
@nest .link:hover & {
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
& svg {
|
||||
margin-top: 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.linkHeading {
|
||||
margin: 0 32px 0 0;
|
||||
composes: g-type-display-6 from global;
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
import * as React from 'react'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { IconArrowRight16 } from '@hashicorp/flight-icons/svg-react/arrow-right-16'
|
||||
import s from './style.module.css'
|
||||
|
||||
export interface IoHomeFeatureProps {
|
||||
isInternalLink: (link: string) => boolean
|
||||
link?: string
|
||||
image: {
|
||||
url: string
|
||||
alt: string
|
||||
}
|
||||
heading: string
|
||||
description: string
|
||||
}
|
||||
|
||||
export default function IoHomeFeature({
|
||||
isInternalLink,
|
||||
link,
|
||||
image,
|
||||
heading,
|
||||
description,
|
||||
}: IoHomeFeatureProps): React.ReactElement {
|
||||
return (
|
||||
<IoHomeFeatureWrap isInternalLink={isInternalLink} href={link}>
|
||||
<div className={s.featureMedia}>
|
||||
<Image
|
||||
src={image.url}
|
||||
width={400}
|
||||
height={200}
|
||||
layout="responsive"
|
||||
alt={image.alt}
|
||||
/>
|
||||
</div>
|
||||
<div className={s.featureContent}>
|
||||
<h3 className={s.featureHeading}>{heading}</h3>
|
||||
<p className={s.featureDescription}>{description}</p>
|
||||
{link ? (
|
||||
<span className={s.featureCta} aria-hidden={true}>
|
||||
Learn more{' '}
|
||||
<span>
|
||||
<IconArrowRight16 />
|
||||
</span>
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</IoHomeFeatureWrap>
|
||||
)
|
||||
}
|
||||
|
||||
interface IoHomeFeatureWrapProps {
|
||||
isInternalLink: (link: string) => boolean
|
||||
href: string
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
function IoHomeFeatureWrap({
|
||||
isInternalLink,
|
||||
href,
|
||||
children,
|
||||
}: IoHomeFeatureWrapProps) {
|
||||
if (!href) {
|
||||
return <div className={s.feature}>{children}</div>
|
||||
}
|
||||
|
||||
if (isInternalLink(href)) {
|
||||
return (
|
||||
<Link href={href}>
|
||||
<a className={s.feature}>{children}</a>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<a className={s.feature} href={href}>
|
||||
{children}
|
||||
</a>
|
||||
)
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
.feature {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
padding: 32px;
|
||||
gap: 24px 64px;
|
||||
border-radius: 6px;
|
||||
background-color: #f9f9fa;
|
||||
color: var(--black);
|
||||
box-shadow: 0 2px 3px rgba(101, 106, 118, 0.1),
|
||||
0 8px 16px -10px rgba(101, 106, 118, 0.2);
|
||||
|
||||
@media (--medium-up) {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.featureLink {
|
||||
transition: box-shadow ease-in-out 0.2s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 2px 3px rgba(101, 106, 118, 0.15),
|
||||
0 16px 16px -10px rgba(101, 106, 118, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.featureMedia {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--gray-5);
|
||||
|
||||
@media (--medium-up) {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
@media (--large) {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
& > * {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.featureContent {
|
||||
max-width: 520px;
|
||||
}
|
||||
|
||||
.featureHeading {
|
||||
margin: 0;
|
||||
composes: g-type-display-4 from global;
|
||||
}
|
||||
|
||||
.featureDescription {
|
||||
margin: 8px 0 24px;
|
||||
composes: g-type-body-small from global;
|
||||
color: var(--gray-3);
|
||||
}
|
||||
|
||||
.featureCta {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
& > span {
|
||||
display: flex;
|
||||
margin-left: 12px;
|
||||
|
||||
& > svg {
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
}
|
||||
|
||||
@nest .feature:hover & span svg {
|
||||
transform: translateX(2px);
|
||||
}
|
||||
}
|
|
@ -1,135 +0,0 @@
|
|||
import * as React from 'react'
|
||||
import { Products } from '@hashicorp/platform-product-meta'
|
||||
import Button from '@hashicorp/react-button'
|
||||
import classNames from 'classnames'
|
||||
import s from './style.module.css'
|
||||
|
||||
interface IoHomeHeroProps {
|
||||
pattern: string
|
||||
brand: Products | 'neutral'
|
||||
heading: string
|
||||
description: string
|
||||
ctas: Array<{
|
||||
title: string
|
||||
link: string
|
||||
}>
|
||||
cards: Array<IoHomeHeroCardProps>
|
||||
}
|
||||
|
||||
export default function IoHomeHero({
|
||||
pattern,
|
||||
brand,
|
||||
heading,
|
||||
description,
|
||||
ctas,
|
||||
cards,
|
||||
}: IoHomeHeroProps) {
|
||||
const [loaded, setLoaded] = React.useState(false)
|
||||
|
||||
React.useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setLoaded(true)
|
||||
}, 250)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<header
|
||||
className={classNames(s.hero, loaded && s.loaded)}
|
||||
style={
|
||||
{
|
||||
'--pattern': `url(${pattern})`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<span className={s.pattern} />
|
||||
<div className={s.container}>
|
||||
<div className={s.content}>
|
||||
<h1 className={s.heading}>{heading}</h1>
|
||||
<p className={s.description}>{description}</p>
|
||||
{ctas && (
|
||||
<div className={s.ctas}>
|
||||
{ctas.map((cta, index) => {
|
||||
return (
|
||||
<Button
|
||||
key={index}
|
||||
title={cta.title}
|
||||
url={cta.link}
|
||||
linkType="inbound"
|
||||
theme={{
|
||||
brand: 'neutral',
|
||||
variant: 'tertiary',
|
||||
background: 'light',
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{cards && (
|
||||
<div className={s.cards}>
|
||||
{cards.map((card, index) => {
|
||||
return (
|
||||
<IoHomeHeroCard
|
||||
key={index}
|
||||
index={index}
|
||||
heading={card.heading}
|
||||
description={card.description}
|
||||
cta={{
|
||||
brand: index === 0 ? 'neutral' : brand,
|
||||
title: card.cta.title,
|
||||
link: card.cta.link,
|
||||
}}
|
||||
subText={card.subText}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
interface IoHomeHeroCardProps {
|
||||
index?: number
|
||||
heading: string
|
||||
description: string
|
||||
cta: {
|
||||
title: string
|
||||
link: string
|
||||
brand?: 'neutral' | Products
|
||||
}
|
||||
subText: string
|
||||
}
|
||||
|
||||
function IoHomeHeroCard({
|
||||
index,
|
||||
heading,
|
||||
description,
|
||||
cta,
|
||||
subText,
|
||||
}: IoHomeHeroCardProps): React.ReactElement {
|
||||
return (
|
||||
<article
|
||||
className={s.card}
|
||||
style={
|
||||
{
|
||||
'--index': index,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<h2 className={s.cardHeading}>{heading}</h2>
|
||||
<p className={s.cardDescription}>{description}</p>
|
||||
<Button
|
||||
title={cta.title}
|
||||
url={cta.link}
|
||||
theme={{
|
||||
variant: 'primary',
|
||||
brand: cta.brand,
|
||||
}}
|
||||
/>
|
||||
<p className={s.cardSubText}>{subText}</p>
|
||||
</article>
|
||||
)
|
||||
}
|
|
@ -1,148 +0,0 @@
|
|||
.hero {
|
||||
position: relative;
|
||||
padding-top: 64px;
|
||||
padding-bottom: 64px;
|
||||
background: linear-gradient(180deg, #f9f9fa 0%, #fff 28.22%, #fff 100%);
|
||||
|
||||
@media (--medium-up) {
|
||||
padding-top: 128px;
|
||||
padding-bottom: 128px;
|
||||
}
|
||||
}
|
||||
|
||||
.pattern {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
max-width: 1600px;
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
|
||||
@media (--medium-up) {
|
||||
background-image: var(--pattern);
|
||||
background-repeat: no-repeat;
|
||||
background-position: top right;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
--columns: 1;
|
||||
|
||||
composes: g-grid-container from global;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--columns), minmax(0, 1fr));
|
||||
gap: 48px 32px;
|
||||
|
||||
@media (--medium-up) {
|
||||
--columns: 12;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
grid-column: 1 / -1;
|
||||
|
||||
@media (--medium-up) {
|
||||
grid-column: 1 / 6;
|
||||
}
|
||||
|
||||
& > * {
|
||||
max-width: 415px;
|
||||
}
|
||||
}
|
||||
|
||||
.heading {
|
||||
margin: 0;
|
||||
composes: g-type-display-1 from global;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 8px 0 0;
|
||||
composes: g-type-body-small from global;
|
||||
color: var(--gray-3);
|
||||
}
|
||||
|
||||
.ctas {
|
||||
margin-top: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.cards {
|
||||
--columns: 1;
|
||||
|
||||
grid-column: 1 / -1;
|
||||
align-self: start;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--columns), minmax(0, 1fr));
|
||||
gap: 32px;
|
||||
|
||||
@media (min-width: 600px) {
|
||||
--columns: 2;
|
||||
}
|
||||
|
||||
@media (--medium-up) {
|
||||
--columns: 1;
|
||||
|
||||
grid-column: 7 / -1;
|
||||
}
|
||||
|
||||
@media (--large) {
|
||||
--columns: 2;
|
||||
|
||||
grid-column: 6 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
--token-radius: 6px;
|
||||
--token-elevation-mid: 0 2px 3px rgba(101, 106, 118, 0.1),
|
||||
0 8px 16px -10px rgba(101, 106, 118, 0.2);
|
||||
|
||||
opacity: 0;
|
||||
padding: 40px 32px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
background-color: var(--white);
|
||||
border-radius: var(--token-radius);
|
||||
box-shadow: 0 0 0 1px rgba(38, 53, 61, 0.1), var(--token-elevation-mid);
|
||||
|
||||
@nest .loaded & {
|
||||
animation-name: slideIn;
|
||||
animation-duration: 0.5s;
|
||||
animation-delay: calc(var(--index) * 0.1s);
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
}
|
||||
|
||||
.cardHeading {
|
||||
margin: 0;
|
||||
composes: g-type-display-4 from global;
|
||||
}
|
||||
|
||||
.cardDescription {
|
||||
margin: 8px 0 16px;
|
||||
composes: g-type-display-6 from global;
|
||||
}
|
||||
|
||||
.cardSubText {
|
||||
margin: 32px 0 0;
|
||||
composes: g-type-body-small from global;
|
||||
color: var(--gray-3);
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(50px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
import * as React from 'react'
|
||||
import Image from 'next/image'
|
||||
import Button from '@hashicorp/react-button'
|
||||
import { Products } from '@hashicorp/platform-product-meta'
|
||||
import { IoCardProps } from 'components/io-card'
|
||||
import IoCardContainer from 'components/io-card-container'
|
||||
import s from './style.module.css'
|
||||
|
||||
interface IoHomeInPracticeProps {
|
||||
brand: Products
|
||||
pattern: string
|
||||
heading: string
|
||||
description: string
|
||||
cards: Array<IoCardProps>
|
||||
cta: {
|
||||
heading: string
|
||||
description: string
|
||||
link: string
|
||||
image: {
|
||||
url: string
|
||||
alt: string
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function IoHomeInPractice({
|
||||
brand,
|
||||
pattern,
|
||||
heading,
|
||||
description,
|
||||
cards,
|
||||
cta,
|
||||
}: IoHomeInPracticeProps) {
|
||||
return (
|
||||
<section
|
||||
className={s.inPractice}
|
||||
style={
|
||||
{
|
||||
'--pattern': `url(${pattern})`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<div className={s.container}>
|
||||
<IoCardContainer
|
||||
theme="dark"
|
||||
heading={heading}
|
||||
description={description}
|
||||
cardsPerRow={3}
|
||||
cards={cards}
|
||||
/>
|
||||
|
||||
{cta.heading ? (
|
||||
<div className={s.inPracticeCta}>
|
||||
<div className={s.inPracticeCtaContent}>
|
||||
<h3 className={s.inPracticeCtaHeading}>{cta.heading}</h3>
|
||||
{cta.description ? (
|
||||
<p className={s.inPracticeCtaDescription}>{cta.description}</p>
|
||||
) : null}
|
||||
{cta.link ? (
|
||||
<Button
|
||||
title="Learn more"
|
||||
url={cta.link}
|
||||
theme={{
|
||||
brand: brand,
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
{cta.image?.url ? (
|
||||
<div className={s.inPracticeCtaMedia}>
|
||||
<Image
|
||||
src={cta.image.url}
|
||||
width={cta.image.width}
|
||||
height={cta.image.height}
|
||||
alt={cta.image.alt}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
.inPractice {
|
||||
position: relative;
|
||||
margin: 60px auto;
|
||||
padding: 64px 0;
|
||||
max-width: 1600px;
|
||||
|
||||
@media (--medium-up) {
|
||||
padding: 80px 0;
|
||||
margin: 120px auto;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: var(--black);
|
||||
background-image: var(--pattern);
|
||||
background-repeat: no-repeat;
|
||||
background-size: 50%;
|
||||
background-position: top 200px left;
|
||||
|
||||
@media (--large) {
|
||||
border-radius: 6px;
|
||||
left: 24px;
|
||||
right: 24px;
|
||||
background-size: 35%;
|
||||
background-position: top 64px left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
composes: g-grid-container from global;
|
||||
}
|
||||
|
||||
.inPracticeCta {
|
||||
--columns: 1;
|
||||
|
||||
position: relative;
|
||||
margin-top: 64px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--columns), minmax(0, 1fr));
|
||||
gap: 64px 32px;
|
||||
|
||||
@media (--medium-up) {
|
||||
--columns: 12;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
bottom: -64px;
|
||||
background-image: radial-gradient(
|
||||
42.33% 42.33% at 50% 100%,
|
||||
#363638 0%,
|
||||
#000 100%
|
||||
);
|
||||
|
||||
@media (--medium-up) {
|
||||
bottom: -80px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.inPracticeCtaContent {
|
||||
position: relative;
|
||||
grid-column: 1 / -1;
|
||||
|
||||
@media (--medium-up) {
|
||||
grid-column: 1 / 5;
|
||||
}
|
||||
}
|
||||
|
||||
.inPracticeCtaMedia {
|
||||
grid-column: 1 / -1;
|
||||
|
||||
@media (--medium-up) {
|
||||
grid-column: 6 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
.inPracticeCtaHeading {
|
||||
margin: 0;
|
||||
color: var(--white);
|
||||
composes: g-type-display-3 from global;
|
||||
}
|
||||
|
||||
.inPracticeCtaDescription {
|
||||
margin: 8px 0 32px;
|
||||
color: var(--gray-5);
|
||||
composes: g-type-body from global;
|
||||
}
|
|
@ -1,155 +0,0 @@
|
|||
import * as React from 'react'
|
||||
import Image from 'next/image'
|
||||
import classNames from 'classnames'
|
||||
import { Products } from '@hashicorp/platform-product-meta'
|
||||
import Button from '@hashicorp/react-button'
|
||||
import IoVideoCallout, {
|
||||
IoHomeVideoCalloutProps,
|
||||
} from 'components/io-video-callout'
|
||||
import IoHomeFeature, { IoHomeFeatureProps } from 'components/io-home-feature'
|
||||
import s from './style.module.css'
|
||||
|
||||
interface IoHomeIntroProps {
|
||||
isInternalLink: (link: string) => boolean
|
||||
brand: Products
|
||||
heading: string
|
||||
description: string
|
||||
features?: Array<IoHomeFeatureProps>
|
||||
offerings?: {
|
||||
image: {
|
||||
src: string
|
||||
width: number
|
||||
height: number
|
||||
alt: string
|
||||
}
|
||||
list: Array<{
|
||||
heading: string
|
||||
description: string
|
||||
}>
|
||||
cta?: {
|
||||
title: string
|
||||
link: string
|
||||
}
|
||||
}
|
||||
video?: IoHomeVideoCalloutProps
|
||||
}
|
||||
|
||||
export default function IoHomeIntro({
|
||||
isInternalLink,
|
||||
brand,
|
||||
heading,
|
||||
description,
|
||||
features,
|
||||
offerings,
|
||||
video,
|
||||
}: IoHomeIntroProps) {
|
||||
return (
|
||||
<section
|
||||
className={classNames(
|
||||
s.root,
|
||||
s[brand],
|
||||
features && s.withFeatures,
|
||||
offerings && s.withOfferings
|
||||
)}
|
||||
style={
|
||||
{
|
||||
'--brand': `var(--${brand})`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<header className={s.header}>
|
||||
<div className={s.container}>
|
||||
<div className={s.headerInner}>
|
||||
<h2 className={s.heading}>{heading}</h2>
|
||||
<p className={s.description}>{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{features ? (
|
||||
<ul className={s.features}>
|
||||
{features.map((feature, index) => {
|
||||
return (
|
||||
// Index is stable
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<li key={index}>
|
||||
<div className={s.container}>
|
||||
<IoHomeFeature
|
||||
isInternalLink={isInternalLink}
|
||||
image={{
|
||||
url: feature.image.url,
|
||||
alt: feature.image.alt,
|
||||
}}
|
||||
heading={feature.heading}
|
||||
description={feature.description}
|
||||
link={feature.link}
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
) : null}
|
||||
|
||||
{offerings ? (
|
||||
<div className={s.offerings}>
|
||||
{offerings.image ? (
|
||||
<div className={s.offeringsMedia}>
|
||||
<Image
|
||||
src={offerings.image.src}
|
||||
width={offerings.image.width}
|
||||
height={offerings.image.height}
|
||||
alt={offerings.image.alt}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div className={s.offeringsContent}>
|
||||
<ul className={s.offeringsList}>
|
||||
{offerings.list.map((offering, index) => {
|
||||
return (
|
||||
// Index is stable
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<li key={index}>
|
||||
<h3 className={s.offeringsListHeading}>
|
||||
{offering.heading}
|
||||
</h3>
|
||||
<p className={s.offeringsListDescription}>
|
||||
{offering.description}
|
||||
</p>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
{offerings.cta ? (
|
||||
<div className={s.offeringsCta}>
|
||||
<Button
|
||||
title={offerings.cta.title}
|
||||
url={offerings.cta.link}
|
||||
theme={{
|
||||
brand: 'neutral',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{video ? (
|
||||
<div className={s.video}>
|
||||
<IoVideoCallout
|
||||
youtubeId={video.youtubeId}
|
||||
thumbnail={video.thumbnail}
|
||||
heading={video.heading}
|
||||
description={video.description}
|
||||
person={{
|
||||
name: video.person.name,
|
||||
description: video.person.description,
|
||||
avatar: video.person.avatar,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</section>
|
||||
)
|
||||
}
|
|
@ -1,169 +0,0 @@
|
|||
.root {
|
||||
position: relative;
|
||||
margin-bottom: 60px;
|
||||
|
||||
@media (--medium-up) {
|
||||
margin-bottom: 120px;
|
||||
}
|
||||
|
||||
&.withOfferings:not(.withFeatures)::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-image: radial-gradient(
|
||||
93.55% 93.55% at 50% 0%,
|
||||
var(--gray-6) 0%,
|
||||
rgba(242, 242, 243, 0) 100%
|
||||
);
|
||||
|
||||
@media (--large) {
|
||||
border-radius: 6px;
|
||||
left: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
composes: g-grid-container from global;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding-top: 64px;
|
||||
padding-bottom: 64px;
|
||||
text-align: center;
|
||||
|
||||
@nest .withFeatures & {
|
||||
background-color: var(--brand);
|
||||
}
|
||||
|
||||
@nest .withFeatures.consul & {
|
||||
color: var(--white);
|
||||
}
|
||||
}
|
||||
|
||||
.headerInner {
|
||||
margin: auto;
|
||||
|
||||
@media (--medium-up) {
|
||||
max-width: calc(100% * 7 / 12);
|
||||
}
|
||||
}
|
||||
|
||||
.heading {
|
||||
margin: 0;
|
||||
composes: g-type-display-2 from global;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 24px 0 0;
|
||||
composes: g-type-body-large from global;
|
||||
|
||||
@nest .withOfferings:not(.withFeatures) & {
|
||||
color: var(--gray-3);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Features
|
||||
*/
|
||||
|
||||
.features {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
gap: 32px;
|
||||
|
||||
& li:first-of-type {
|
||||
background-image: linear-gradient(
|
||||
to bottom,
|
||||
var(--brand) 50%,
|
||||
var(--white) 50%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Offerings
|
||||
*/
|
||||
|
||||
.offerings {
|
||||
--columns: 1;
|
||||
|
||||
composes: g-grid-container from global;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--columns), minmax(0, 1fr));
|
||||
gap: 64px 32px;
|
||||
|
||||
@media (--medium-up) {
|
||||
--columns: 12;
|
||||
}
|
||||
|
||||
@nest .features + & {
|
||||
margin-top: 60px;
|
||||
|
||||
@media (--medium-up) {
|
||||
margin-top: 120px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.offeringsMedia {
|
||||
grid-column: 1 / -1;
|
||||
|
||||
@media (--medium-up) {
|
||||
grid-column: 1 / 6;
|
||||
}
|
||||
}
|
||||
|
||||
.offeringsContent {
|
||||
grid-column: 1 / -1;
|
||||
|
||||
@media (--medium-up) {
|
||||
grid-column: 7 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
.offeringsList {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 32px;
|
||||
|
||||
@media (--small) {
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.offeringsListHeading {
|
||||
margin: 0;
|
||||
composes: g-type-display-4 from global;
|
||||
}
|
||||
|
||||
.offeringsListDescription {
|
||||
margin: 16px 0 0;
|
||||
composes: g-type-body-small from global;
|
||||
}
|
||||
|
||||
.offeringsCta {
|
||||
margin-top: 48px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Video
|
||||
*/
|
||||
|
||||
.video {
|
||||
margin-top: 60px;
|
||||
composes: g-grid-container from global;
|
||||
|
||||
@media (--medium-up) {
|
||||
margin-top: 120px;
|
||||
}
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
import * as React from 'react'
|
||||
import classNames from 'classnames'
|
||||
import { Products } from '@hashicorp/platform-product-meta'
|
||||
import { IconArrowRight16 } from '@hashicorp/flight-icons/svg-react/arrow-right-16'
|
||||
import s from './style.module.css'
|
||||
|
||||
interface IoHomePreFooterProps {
|
||||
brand: Products
|
||||
heading: string
|
||||
description: string
|
||||
ctas: [IoHomePreFooterCard, IoHomePreFooterCard, IoHomePreFooterCard]
|
||||
}
|
||||
|
||||
export default function IoHomePreFooter({
|
||||
brand,
|
||||
heading,
|
||||
description,
|
||||
ctas,
|
||||
}: IoHomePreFooterProps) {
|
||||
return (
|
||||
<div className={classNames(s.preFooter, s[brand])}>
|
||||
<div className={s.container}>
|
||||
<div className={s.content}>
|
||||
<h2 className={s.heading}>{heading}</h2>
|
||||
<p className={s.description}>{description}</p>
|
||||
</div>
|
||||
<div className={s.cards}>
|
||||
{ctas.map((cta, index) => {
|
||||
return (
|
||||
<IoHomePreFooterCard
|
||||
key={index}
|
||||
brand={brand}
|
||||
link={cta.link}
|
||||
heading={cta.heading}
|
||||
description={cta.description}
|
||||
cta={cta.cta}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface IoHomePreFooterCard {
|
||||
brand?: string
|
||||
link: string
|
||||
heading: string
|
||||
description: string
|
||||
cta: string
|
||||
}
|
||||
|
||||
function IoHomePreFooterCard({
|
||||
brand,
|
||||
link,
|
||||
heading,
|
||||
description,
|
||||
cta,
|
||||
}: IoHomePreFooterCard): React.ReactElement {
|
||||
return (
|
||||
<a
|
||||
href={link}
|
||||
className={s.card}
|
||||
style={
|
||||
{
|
||||
'--primary': `var(--${brand})`,
|
||||
'--secondary': `var(--${brand}-secondary)`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<h3 className={s.cardHeading}>{heading}</h3>
|
||||
<p className={s.cardDescription}>{description}</p>
|
||||
<span className={s.cardCta}>
|
||||
{cta} <IconArrowRight16 />
|
||||
</span>
|
||||
</a>
|
||||
)
|
||||
}
|
|
@ -1,122 +0,0 @@
|
|||
.preFooter {
|
||||
margin: 60px auto;
|
||||
}
|
||||
|
||||
.container {
|
||||
--columns: 1;
|
||||
|
||||
composes: g-grid-container from global;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--columns), minmax(0, 1fr));
|
||||
gap: 32px;
|
||||
|
||||
@media (--medium-up) {
|
||||
--columns: 12;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
grid-column: 1 / -1;
|
||||
|
||||
@media (--medium-up) {
|
||||
grid-column: 1 / 6;
|
||||
}
|
||||
|
||||
@media (--large) {
|
||||
grid-column: 1 / 4;
|
||||
}
|
||||
}
|
||||
|
||||
.heading {
|
||||
margin: 0;
|
||||
composes: g-type-display-1 from global;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 24px 0 0;
|
||||
composes: g-type-body from global;
|
||||
color: var(--gray-3);
|
||||
}
|
||||
|
||||
.cards {
|
||||
grid-column: 1 / -1;
|
||||
|
||||
--columns: 1;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--columns), minmax(0, 1fr));
|
||||
gap: 32px;
|
||||
|
||||
@media (--medium-up) {
|
||||
--columns: 3;
|
||||
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
@media (--large) {
|
||||
grid-column: 5 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
padding: 32px 24px;
|
||||
background-color: var(--primary);
|
||||
color: var(--black);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 3px rgba(101, 106, 118, 0.1),
|
||||
0 8px 16px -10px rgba(101, 106, 118, 0.2);
|
||||
transition: ease-in-out 0.2s;
|
||||
transition-property: box-shadow;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 2px 3px rgba(101, 106, 118, 0.15),
|
||||
0 16px 16px -10px rgba(101, 106, 118, 0.2);
|
||||
}
|
||||
|
||||
&:nth-of-type(1) {
|
||||
color: var(--white);
|
||||
|
||||
@nest .vault & {
|
||||
color: var(--black);
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-of-type(2) {
|
||||
background-color: var(--secondary);
|
||||
}
|
||||
|
||||
&:nth-of-type(3) {
|
||||
background-color: var(--gray-6);
|
||||
}
|
||||
}
|
||||
|
||||
.cardHeading {
|
||||
margin: 0;
|
||||
composes: g-type-display-4 from global;
|
||||
}
|
||||
|
||||
.cardDescription {
|
||||
margin: 8px 0 0;
|
||||
padding-bottom: 48px;
|
||||
color: inherit;
|
||||
composes: g-type-display-6 from global;
|
||||
}
|
||||
|
||||
.cardCta {
|
||||
margin-top: auto;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
composes: g-type-buttons-and-standalone-links from global;
|
||||
|
||||
& svg {
|
||||
margin-left: 12px;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
@nest .card:hover & svg {
|
||||
transform: translate(2px);
|
||||
}
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
import Image from 'next/image'
|
||||
import * as React from 'react'
|
||||
import classNames from 'classnames'
|
||||
import Button from '@hashicorp/react-button'
|
||||
import s from './style.module.css'
|
||||
|
||||
interface IoUsecaseCallToActionProps {
|
||||
brand: string
|
||||
theme?: 'light' | 'dark'
|
||||
heading: string
|
||||
description: string
|
||||
links: Array<{
|
||||
text: string
|
||||
url: string
|
||||
}>
|
||||
pattern: string
|
||||
}
|
||||
|
||||
export default function IoUsecaseCallToAction({
|
||||
brand,
|
||||
theme,
|
||||
heading,
|
||||
description,
|
||||
links,
|
||||
pattern,
|
||||
}: IoUsecaseCallToActionProps): React.ReactElement {
|
||||
return (
|
||||
<div
|
||||
className={classNames(s.callToAction, s[theme])}
|
||||
style={
|
||||
{
|
||||
'--background-color': `var(--${brand})`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<h2 className={s.heading}>{heading}</h2>
|
||||
<div className={s.content}>
|
||||
<p className={s.description}>{description}</p>
|
||||
<div className={s.links}>
|
||||
{links.map((link, index) => {
|
||||
return (
|
||||
<Button
|
||||
// Index is stable
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
key={index}
|
||||
title={link.text}
|
||||
url={link.url}
|
||||
theme={{
|
||||
brand: 'neutral',
|
||||
variant: index === 0 ? 'primary' : 'secondary',
|
||||
background: theme,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.pattern}>
|
||||
<Image
|
||||
src={pattern}
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
objectPosition="center left"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
.callToAction {
|
||||
--columns: 1;
|
||||
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--columns), minmax(0, 1fr));
|
||||
gap: 0 32px;
|
||||
padding: 32px;
|
||||
background-color: var(--background-color);
|
||||
border-radius: 6px;
|
||||
|
||||
&.light {
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
&.dark {
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
@media (--medium-up) {
|
||||
--columns: 12;
|
||||
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.heading {
|
||||
grid-column: 1 / -1;
|
||||
margin: 0 0 16px;
|
||||
composes: g-type-display-3 from global;
|
||||
|
||||
@media (--medium-up) {
|
||||
grid-column: 1 / 6;
|
||||
padding: 88px 32px 88px 64px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
grid-column: 1 / -1;
|
||||
|
||||
@media (--medium-up) {
|
||||
grid-column: 6 / 11;
|
||||
padding: 88px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 0 0 32px;
|
||||
composes: g-type-body-large from global;
|
||||
}
|
||||
|
||||
.links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px 32px;
|
||||
}
|
||||
|
||||
.pattern {
|
||||
position: relative;
|
||||
display: none;
|
||||
|
||||
@media (--medium-up) {
|
||||
grid-column: 11 / -1;
|
||||
display: flex;
|
||||
}
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
import * as React from 'react'
|
||||
import Image from 'next/image'
|
||||
import Button from '@hashicorp/react-button'
|
||||
import s from './style.module.css'
|
||||
|
||||
interface IoUsecaseCustomerProps {
|
||||
media: {
|
||||
src: string
|
||||
width: string
|
||||
height: string
|
||||
alt: string
|
||||
}
|
||||
logo: {
|
||||
src: string
|
||||
width: string
|
||||
height: string
|
||||
alt: string
|
||||
}
|
||||
heading: string
|
||||
description: string
|
||||
stats?: Array<{
|
||||
value: string
|
||||
key: string
|
||||
}>
|
||||
link: string
|
||||
}
|
||||
|
||||
export default function IoUsecaseCustomer({
|
||||
media,
|
||||
logo,
|
||||
heading,
|
||||
description,
|
||||
stats,
|
||||
link,
|
||||
}: IoUsecaseCustomerProps): React.ReactElement {
|
||||
return (
|
||||
<section className={s.customer}>
|
||||
<div className={s.container}>
|
||||
<div className={s.columns}>
|
||||
<div className={s.media}>
|
||||
{/* eslint-disable-next-line jsx-a11y/alt-text */}
|
||||
<Image {...media} layout="responsive" />
|
||||
</div>
|
||||
<div className={s.content}>
|
||||
<div className={s.eyebrow}>
|
||||
<div className={s.eyebrowLogo}>
|
||||
{/* eslint-disable-next-line jsx-a11y/alt-text */}
|
||||
<Image {...logo} />
|
||||
</div>
|
||||
<span className={s.eyebrowLabel}>Customer case study</span>
|
||||
</div>
|
||||
<h2 className={s.heading}>{heading}</h2>
|
||||
<p className={s.description}>{description}</p>
|
||||
{link ? (
|
||||
<div className={s.cta}>
|
||||
<Button
|
||||
title="Read more"
|
||||
url={link}
|
||||
theme={{
|
||||
brand: 'neutral',
|
||||
variant: 'secondary',
|
||||
background: 'dark',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
{stats.length > 0 ? (
|
||||
<ul className={s.stats}>
|
||||
{stats.map(({ key, value }, index) => {
|
||||
return (
|
||||
// Index is stable
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<li key={index}>
|
||||
<p className={s.value}>{value}</p>
|
||||
<p className={s.key}>{key}</p>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
) : null}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
|
@ -1,119 +0,0 @@
|
|||
.customer {
|
||||
position: relative;
|
||||
background-color: var(--black);
|
||||
color: var(--white);
|
||||
padding-bottom: 64px;
|
||||
|
||||
@media (--medium-up) {
|
||||
padding-bottom: 132px;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
composes: g-grid-container from global;
|
||||
}
|
||||
|
||||
.columns {
|
||||
--columns: 1;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--columns), minmax(0, 1fr));
|
||||
gap: 64px 32px;
|
||||
|
||||
@media (--medium-up) {
|
||||
--columns: 12;
|
||||
}
|
||||
}
|
||||
|
||||
.media {
|
||||
margin-top: -64px;
|
||||
grid-column: 1 / -1;
|
||||
|
||||
@media (--medium-up) {
|
||||
grid-column: 1 / 7;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
grid-column: 1 / -1;
|
||||
|
||||
@media (--medium-up) {
|
||||
padding-top: 64px;
|
||||
grid-column: 8 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.eyebrowLogo {
|
||||
display: flex;
|
||||
max-width: 120px;
|
||||
}
|
||||
|
||||
.eyebrowLabel {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
padding-left: 12px;
|
||||
margin-left: 12px;
|
||||
border-left: 1px solid var(--gray-5);
|
||||
align-self: center;
|
||||
composes: g-type-label-small-strong from global;
|
||||
}
|
||||
|
||||
.heading {
|
||||
margin: 32px 0 24px;
|
||||
composes: g-type-display-2 from global;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 0;
|
||||
composes: g-type-body from global;
|
||||
}
|
||||
|
||||
.cta {
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.stats {
|
||||
--columns: 1;
|
||||
|
||||
list-style: none;
|
||||
margin: 64px 0 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--columns), minmax(0, 1fr));
|
||||
gap: 32px;
|
||||
|
||||
@media (--medium-up) {
|
||||
--columns: 12;
|
||||
|
||||
margin-top: 132px;
|
||||
}
|
||||
|
||||
& > li {
|
||||
border-top: 1px solid var(--gray-2);
|
||||
grid-column: span 4;
|
||||
}
|
||||
}
|
||||
|
||||
.value {
|
||||
margin: 0;
|
||||
padding-top: 32px;
|
||||
font-family: var(--font-display);
|
||||
font-size: 50px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
|
||||
@media (--large) {
|
||||
font-size: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
.key {
|
||||
margin: 12px 0 0;
|
||||
composes: g-type-display-4 from global;
|
||||
color: var(--gray-3);
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
import * as React from 'react'
|
||||
import Image from 'next/image'
|
||||
import s from './style.module.css'
|
||||
|
||||
interface IoUsecaseHeroProps {
|
||||
eyebrow: string
|
||||
heading: string
|
||||
description: string
|
||||
pattern?: string
|
||||
}
|
||||
|
||||
export default function IoUsecaseHero({
|
||||
eyebrow,
|
||||
heading,
|
||||
description,
|
||||
pattern,
|
||||
}: IoUsecaseHeroProps): React.ReactElement {
|
||||
return (
|
||||
<header className={s.hero}>
|
||||
<div className={s.container}>
|
||||
<div className={s.pattern}>
|
||||
{pattern ? (
|
||||
<Image
|
||||
src={pattern}
|
||||
layout="responsive"
|
||||
width={420}
|
||||
height={500}
|
||||
priority={true}
|
||||
alt=""
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<div className={s.content}>
|
||||
<p className={s.eyebrow}>{eyebrow}</p>
|
||||
<h1 className={s.heading}>{heading}</h1>
|
||||
<p className={s.description}>{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
Before Width: | Height: | Size: 6.3 KiB |
|
@ -1,83 +0,0 @@
|
|||
.hero {
|
||||
position: relative;
|
||||
max-width: 1600px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-image: radial-gradient(
|
||||
95.97% 95.97% at 50% 100%,
|
||||
#f2f2f3 0%,
|
||||
rgba(242, 242, 243, 0) 100%
|
||||
);
|
||||
|
||||
@media (--medium-up) {
|
||||
border-radius: 6px;
|
||||
left: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
@media (--medium-up) {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr max-content 1fr;
|
||||
gap: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.pattern {
|
||||
margin-left: 24px;
|
||||
transform: translateY(24px);
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
|
||||
@media (--small) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (--medium) {
|
||||
& > * {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
max-width: 520px;
|
||||
width: 100%;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
padding: 64px 24px;
|
||||
|
||||
@media (--medium-up) {
|
||||
padding-top: 132px;
|
||||
padding-bottom: 132px;
|
||||
}
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
margin: 0;
|
||||
composes: g-type-label-strong from global;
|
||||
}
|
||||
|
||||
.heading {
|
||||
margin: 24px 0;
|
||||
composes: g-type-display-1 from global;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 0;
|
||||
composes: g-type-body-large from global;
|
||||
color: var(--gray-2);
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
import * as React from 'react'
|
||||
import { Products } from '@hashicorp/platform-product-meta'
|
||||
import classNames from 'classnames'
|
||||
import Image from 'next/image'
|
||||
import Button from '@hashicorp/react-button'
|
||||
import s from './style.module.css'
|
||||
|
||||
interface IoUsecaseSectionProps {
|
||||
brand?: Products | 'neutral'
|
||||
bottomIsFlush?: boolean
|
||||
eyebrow: string
|
||||
heading: string
|
||||
description: string
|
||||
media?: {
|
||||
src: string
|
||||
width: string
|
||||
height: string
|
||||
alt: string
|
||||
}
|
||||
cta?: {
|
||||
text: string
|
||||
link: string
|
||||
}
|
||||
}
|
||||
|
||||
export default function IoUsecaseSection({
|
||||
brand = 'neutral',
|
||||
bottomIsFlush = false,
|
||||
eyebrow,
|
||||
heading,
|
||||
description,
|
||||
media,
|
||||
cta,
|
||||
}: IoUsecaseSectionProps): React.ReactElement {
|
||||
return (
|
||||
<section
|
||||
className={classNames(s.section, s[brand], bottomIsFlush && s.isFlush)}
|
||||
>
|
||||
<div className={s.container}>
|
||||
<p className={s.eyebrow}>{eyebrow}</p>
|
||||
<div className={s.columns}>
|
||||
<div className={s.column}>
|
||||
<h2 className={s.heading}>{heading}</h2>
|
||||
{media?.src ? (
|
||||
<div
|
||||
className={s.description}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: description,
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{cta?.link && cta?.text ? (
|
||||
<div className={s.cta}>
|
||||
<Button
|
||||
title={cta.text}
|
||||
url={cta.link}
|
||||
theme={{
|
||||
brand: brand,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className={s.column}>
|
||||
{media?.src ? (
|
||||
// eslint-disable-next-line jsx-a11y/alt-text
|
||||
<Image {...media} />
|
||||
) : (
|
||||
<div
|
||||
className={s.description}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: description,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
.section {
|
||||
position: relative;
|
||||
max-width: 1600px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
padding-top: 64px;
|
||||
padding-bottom: 64px;
|
||||
|
||||
@media (--medium-up) {
|
||||
padding-top: 132px;
|
||||
padding-bottom: 132px;
|
||||
}
|
||||
|
||||
& + .section {
|
||||
padding-bottom: 132px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--gray-6);
|
||||
opacity: 0.4;
|
||||
|
||||
@media (--medium-up) {
|
||||
border-radius: 6px;
|
||||
left: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.isFlush {
|
||||
padding-bottom: 96px;
|
||||
|
||||
@media (--medium-up) {
|
||||
padding-bottom: 164px;
|
||||
}
|
||||
|
||||
&::before {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
composes: g-grid-container from global;
|
||||
}
|
||||
|
||||
.columns {
|
||||
--columns: 1;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--columns), minmax(0, 1fr));
|
||||
gap: 32px;
|
||||
|
||||
@media (--medium-up) {
|
||||
--columns: 12;
|
||||
}
|
||||
}
|
||||
|
||||
.column {
|
||||
&:nth-child(1) {
|
||||
@media (--medium-up) {
|
||||
grid-column: 1 / 7;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
@media (--medium-up) {
|
||||
grid-column: 8 / -1;
|
||||
padding-top: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
margin: 0;
|
||||
composes: g-type-display-5 from global;
|
||||
}
|
||||
|
||||
.heading {
|
||||
margin: 16px 0 32px;
|
||||
padding-bottom: 32px;
|
||||
composes: g-type-display-3 from global;
|
||||
border-bottom: 1px solid var(--black);
|
||||
}
|
||||
|
||||
.description {
|
||||
composes: g-type-body from global;
|
||||
|
||||
& > p {
|
||||
margin: 0;
|
||||
|
||||
& + p {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cta {
|
||||
margin-top: 32px;
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
import * as React from 'react'
|
||||
import Image from 'next/image'
|
||||
import ReactPlayer from 'react-player'
|
||||
import VisuallyHidden from '@reach/visually-hidden'
|
||||
import IoDialog from 'components/io-dialog'
|
||||
import PlayIcon from './play-icon'
|
||||
import s from './style.module.css'
|
||||
|
||||
export interface IoHomeVideoCalloutProps {
|
||||
youtubeId: string
|
||||
thumbnail: string
|
||||
heading: string
|
||||
description: string
|
||||
person: {
|
||||
avatar: string
|
||||
name: string
|
||||
description: string
|
||||
}
|
||||
}
|
||||
|
||||
export default function IoVideoCallout({
|
||||
youtubeId,
|
||||
thumbnail,
|
||||
heading,
|
||||
description,
|
||||
person,
|
||||
}: IoHomeVideoCalloutProps): React.ReactElement {
|
||||
const [showDialog, setShowDialog] = React.useState(false)
|
||||
const showVideo = () => setShowDialog(true)
|
||||
const hideVideo = () => setShowDialog(false)
|
||||
return (
|
||||
<>
|
||||
<figure className={s.videoCallout}>
|
||||
<button className={s.thumbnail} onClick={showVideo}>
|
||||
<VisuallyHidden>Play video</VisuallyHidden>
|
||||
<PlayIcon />
|
||||
<Image src={thumbnail} layout="fill" objectFit="cover" alt="" />
|
||||
</button>
|
||||
<figcaption className={s.content}>
|
||||
<h3 className={s.heading}>{heading}</h3>
|
||||
<p className={s.description}>{description}</p>
|
||||
{person && (
|
||||
<div className={s.person}>
|
||||
{person.avatar ? (
|
||||
<div className={s.personThumbnail}>
|
||||
<Image
|
||||
src={person.avatar}
|
||||
width={52}
|
||||
height={52}
|
||||
alt={`${person.name} avatar`}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div>
|
||||
<p className={s.personName}>{person.name}</p>
|
||||
<p className={s.personDescription}>{person.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</figcaption>
|
||||
</figure>
|
||||
<IoDialog
|
||||
isOpen={showDialog}
|
||||
onDismiss={hideVideo}
|
||||
label={`${heading} video}`}
|
||||
>
|
||||
<h2 className={s.videoHeading}>{heading}</h2>
|
||||
<div className={s.video}>
|
||||
<ReactPlayer
|
||||
url={`https://www.youtube.com/watch?v=${youtubeId}`}
|
||||
width="100%"
|
||||
height="100%"
|
||||
playing={true}
|
||||
controls={true}
|
||||
/>
|
||||
</div>
|
||||
</IoDialog>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
import * as React from 'react'
|
||||
|
||||
export default function PlayIcon(): React.ReactElement {
|
||||
return (
|
||||
<svg
|
||||
width="96"
|
||||
height="96"
|
||||
viewBox="0 0 96 96"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="48" cy="48" r="48" fill="#fff" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="m63.254 46.653-22.75-14.4a1.647 1.647 0 0 0-1.657-.057c-.522.28-.847.82-.847 1.405V62.4c0 .584.325 1.123.847 1.403a1.639 1.639 0 0 0 1.657-.057l22.75-14.4c.465-.294.746-.802.746-1.346 0-.545-.281-1.052-.746-1.347Z"
|
||||
fill="#fff"
|
||||
stroke="#000"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
|
@ -1,128 +0,0 @@
|
|||
.videoCallout {
|
||||
--columns: 1;
|
||||
|
||||
margin: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--columns), minmax(0, 1fr));
|
||||
gap: 32px;
|
||||
background-color: var(--black);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
|
||||
@media (--medium-up) {
|
||||
--columns: 12;
|
||||
}
|
||||
}
|
||||
|
||||
.thumbnail {
|
||||
position: relative;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
grid-column: 1 / -1;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
padding: 96px 32px;
|
||||
min-height: 300px;
|
||||
|
||||
@media (--medium-up) {
|
||||
grid-column: 1 / 7;
|
||||
}
|
||||
|
||||
@media (--large) {
|
||||
grid-column: 1 / 9;
|
||||
}
|
||||
|
||||
& > svg {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 1;
|
||||
|
||||
@media (--small) {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #000;
|
||||
opacity: 0.45;
|
||||
transition: opacity ease-in-out 0.2s;
|
||||
}
|
||||
|
||||
&:hover::after {
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 32px;
|
||||
grid-column: 1 / -1;
|
||||
|
||||
@media (--medium-up) {
|
||||
padding: 80px 32px;
|
||||
grid-column: 7 / -1;
|
||||
}
|
||||
|
||||
@media (--large) {
|
||||
grid-column: 9 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
.heading {
|
||||
margin: 0;
|
||||
composes: g-type-display-4 from global;
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 8px 0 0;
|
||||
composes: g-type-body-small from global;
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.person {
|
||||
margin-top: 64px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.personThumbnail {
|
||||
display: flex;
|
||||
border-radius: 9999px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.personName {
|
||||
margin: 0;
|
||||
composes: g-type-body-strong from global;
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.personDescription {
|
||||
margin: 4px 0 0;
|
||||
composes: g-type-label-strong from global;
|
||||
color: var(--gray-3);
|
||||
}
|
||||
|
||||
.videoHeading {
|
||||
margin-top: 0;
|
||||
margin-bottom: 32px;
|
||||
padding-right: 100px;
|
||||
composes: g-type-display-4 from global;
|
||||
}
|
||||
|
||||
.video {
|
||||
position: relative;
|
||||
background-color: var(--gray-2);
|
||||
aspect-ratio: 16 / 9;
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
import Button from '@hashicorp/react-button'
|
||||
|
||||
export default function MiniCTA({ title, description, link }) {
|
||||
return (
|
||||
<div className="g-mini-cta">
|
||||
<div className="g-grid-container">
|
||||
<hr />
|
||||
<h5 className="g-type-display-4">{title}</h5>
|
||||
{description && <p className="g-type-body">{description}</p>}
|
||||
<Button
|
||||
title={link.text}
|
||||
url={link.url}
|
||||
theme={{
|
||||
variant: 'tertiary-neutral',
|
||||
brand: 'neutral',
|
||||
background: 'light'
|
||||
}}
|
||||
linkType={link.type}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
.g-mini-cta {
|
||||
background: var(--gray-6);
|
||||
text-align: center;
|
||||
padding-bottom: 64px;
|
||||
padding-top: 48px;
|
||||
|
||||
& hr {
|
||||
width: 64px;
|
||||
color: var(--gray-4);
|
||||
margin: 0 auto 64px auto;
|
||||
|
||||
@media (max-width: 800px) {
|
||||
margin: 0 auto 24px auto;
|
||||
}
|
||||
}
|
||||
|
||||
& h5 {
|
||||
margin: 0;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
& p {
|
||||
margin: 0;
|
||||
margin-bottom: 24px;
|
||||
|
||||
@media (max-width: 800px) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
& .g-btn {
|
||||
& span {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
import CallToAction from '@hashicorp/react-call-to-action'
|
||||
|
||||
export default function PrefooterCTA() {
|
||||
return (
|
||||
<CallToAction
|
||||
heading="Ready to get started?"
|
||||
content="Consul Open Source addresses the technical complexity of managing production services by providing a way to discover, automate, secure and connect applications and networking configurations across distributed infrastructure and clouds."
|
||||
product="consul"
|
||||
links={[
|
||||
{
|
||||
text: 'Explore HashiCorp Learn',
|
||||
url: 'https://learn.hashicorp.com/consul',
|
||||
type: 'outbound',
|
||||
},
|
||||
{
|
||||
text: 'Explore Documentation',
|
||||
url: '/docs',
|
||||
type: 'inbound',
|
||||
},
|
||||
]}
|
||||
variant="compact"
|
||||
theme="light"
|
||||
/>
|
||||
)
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
import { ReactNode } from 'react'
|
||||
import classNames from 'classnames'
|
||||
import s from './style.module.css'
|
||||
|
||||
interface SideBySideProps {
|
||||
left: ReactNode
|
||||
right: ReactNode
|
||||
}
|
||||
|
||||
export default function SideBySide({ left, right }: SideBySideProps) {
|
||||
return (
|
||||
<div className={s.sideBySide}>
|
||||
<div className={classNames(s.sideWrapper, s.leftSide)}>
|
||||
<div className={s.side}>{left}</div>
|
||||
</div>
|
||||
<div className={classNames(s.sideWrapper, s.rightSide)}>
|
||||
<div className={s.side}>{right}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
.sideBySide {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@media (--large) {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
& .sideWrapper {
|
||||
padding: 105px 0;
|
||||
width: 100%;
|
||||
|
||||
@media (--large) {
|
||||
width: 50%;
|
||||
padding-bottom: 176px;
|
||||
}
|
||||
|
||||
&.leftSide {
|
||||
background: var(--consul-secondary);
|
||||
|
||||
@media (--large) {
|
||||
padding-left: 48px;
|
||||
padding-right: 104px;
|
||||
}
|
||||
}
|
||||
|
||||
&.rightSide {
|
||||
@media (--large) {
|
||||
padding-right: 48px;
|
||||
padding-left: 75px;
|
||||
}
|
||||
}
|
||||
|
||||
& .side {
|
||||
margin: 0 auto;
|
||||
|
||||
@media (--small) {
|
||||
max-width: 616px;
|
||||
padding-left: 24px;
|
||||
padding-right: 24px;
|
||||
}
|
||||
|
||||
@media (--medium) {
|
||||
max-width: 944px;
|
||||
padding-left: 40px;
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
@media (--large) {
|
||||
margin: 0;
|
||||
max-width: 490px;
|
||||
}
|
||||
}
|
||||
|
||||
&:first-child .side {
|
||||
@media (--large) {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
import Image from '@hashicorp/react-image'
|
||||
import InlineSvg from '@hashicorp/react-inline-svg'
|
||||
import alertIcon from 'public/img/static-dynamic-diagram/alert.svg?include'
|
||||
import checkIcon from 'public/img/static-dynamic-diagram/check.svg?include'
|
||||
import s from './before-after-diagram.module.css'
|
||||
|
||||
export default function BeforeAfterDiagram({
|
||||
beforeHeadline,
|
||||
beforeContent,
|
||||
beforeImage,
|
||||
afterHeadline,
|
||||
afterContent,
|
||||
afterImage,
|
||||
}) {
|
||||
return (
|
||||
<div className={s.beforeAfterDiagram}>
|
||||
<div className={s.beforeSide}>
|
||||
<div className={s.image}>
|
||||
<div>
|
||||
<Image {...beforeImage} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.contentContainer}>
|
||||
<span className={s.iconLineContainer}>
|
||||
<InlineSvg className={s.beforeIcon} src={alertIcon} />
|
||||
<span className={s.lineSegment} />
|
||||
</span>
|
||||
<div>
|
||||
{beforeHeadline && (
|
||||
<h3
|
||||
className={s.contentHeadline}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: beforeHeadline,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{beforeContent && (
|
||||
<div
|
||||
className={s.beforeContent}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: beforeContent,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.afterSide}>
|
||||
<div className={s.image}>
|
||||
<div>
|
||||
<Image {...afterImage} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.contentContainer}>
|
||||
<span className={s.iconLineContainer}>
|
||||
<InlineSvg className={s.afterIcon} src={checkIcon} />
|
||||
</span>
|
||||
<div>
|
||||
{afterHeadline && (
|
||||
<h3
|
||||
className={s.contentHeadline}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: afterHeadline,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{afterContent && (
|
||||
<div
|
||||
className={s.afterContent}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: afterContent,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,351 +0,0 @@
|
|||
.beforeAfterDiagram {
|
||||
/* CSS custom properties to control theming */
|
||||
--product-color: var(--black);
|
||||
--gray-6-transparent: rgba(210, 212, 219, 0);
|
||||
--after-bullet-background: url('/img/static-dynamic-diagram/check-square.svg');
|
||||
--after-bullet-height: 18px;
|
||||
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 -16px;
|
||||
position: relative;
|
||||
|
||||
@media (max-width: 1023px) {
|
||||
margin-left: -12px;
|
||||
margin-right: -12px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
flex-direction: column;
|
||||
margin-left: 40px;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
--after-bullet-background: url('/img/static-dynamic-diagram/check-square-consul.svg');
|
||||
--after-bullet-height: 19px;
|
||||
}
|
||||
|
||||
/* Before and after columns */
|
||||
|
||||
.side {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0 16px;
|
||||
position: relative;
|
||||
width: calc(50% - 32px);
|
||||
|
||||
@media (max-width: 1023px) {
|
||||
margin: 0 12px;
|
||||
width: calc(50% - 24px);
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.beforeSide {
|
||||
composes: side;
|
||||
@media (max-width: 767px) {
|
||||
margin-bottom: 62px;
|
||||
}
|
||||
}
|
||||
|
||||
.afterSide {
|
||||
composes: side;
|
||||
}
|
||||
|
||||
/* Diagram images */
|
||||
|
||||
.image {
|
||||
align-items: flex-end;
|
||||
display: flex;
|
||||
height: 320px;
|
||||
justify-content: center;
|
||||
margin-bottom: 96px;
|
||||
|
||||
@media (max-width: 767px) {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
height: 284px;
|
||||
}
|
||||
|
||||
@media (max-width: 540px) {
|
||||
height: 238px;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
height: 211px;
|
||||
}
|
||||
|
||||
@media (max-width: 375px) {
|
||||
height: 163px;
|
||||
}
|
||||
|
||||
& div {
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
& picture {
|
||||
height: 100%;
|
||||
}
|
||||
& img,
|
||||
& svg {
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
@media (--medium-up) {
|
||||
height: unset;
|
||||
|
||||
& div {
|
||||
height: unset;
|
||||
}
|
||||
|
||||
& picture {
|
||||
height: unset;
|
||||
}
|
||||
& img,
|
||||
& svg {
|
||||
height: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* icon / line container above content */
|
||||
|
||||
.iconLineContainer {
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: -75px;
|
||||
width: 100%;
|
||||
|
||||
@media (max-width: 767px) {
|
||||
height: 100%;
|
||||
left: -28px;
|
||||
right: auto;
|
||||
top: 28px;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* Line segment above content (before side only) */
|
||||
|
||||
.lineSegment {
|
||||
background: black;
|
||||
display: block;
|
||||
height: 2px;
|
||||
left: calc(50% + 30px);
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
width: calc(100% - 24px);
|
||||
|
||||
@media (max-width: 767px) {
|
||||
height: calc(100% + 375px);
|
||||
left: auto;
|
||||
top: 38px;
|
||||
width: 2px;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
height: calc(100% + 339px);
|
||||
}
|
||||
|
||||
@media (max-width: 540px) {
|
||||
height: calc(100% + 293px);
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
height: calc(100% + 266px);
|
||||
}
|
||||
|
||||
@media (max-width: 375px) {
|
||||
height: calc(100% + 218px);
|
||||
}
|
||||
|
||||
&::before {
|
||||
border-radius: 100%;
|
||||
border-style: solid;
|
||||
border-width: 5.5px 0 5.5px 8px;
|
||||
border-width: 2px;
|
||||
content: '';
|
||||
height: 8px;
|
||||
left: -8px;
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
width: 8px;
|
||||
|
||||
@media (max-width: 767px) {
|
||||
left: -3px;
|
||||
top: -8px;
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
border-color: transparent transparent transparent var(--product-color);
|
||||
border-style: solid;
|
||||
border-width: 6px 0 6px 8px;
|
||||
content: '';
|
||||
height: 0;
|
||||
position: absolute;
|
||||
right: -8px;
|
||||
top: -5px;
|
||||
width: 0;
|
||||
|
||||
@media (max-width: 767px) {
|
||||
bottom: -8px;
|
||||
right: -4px;
|
||||
top: auto;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Icon above each content container */
|
||||
|
||||
.contentIcon {
|
||||
& svg {
|
||||
left: 50%;
|
||||
margin: 0 0 0 -11px;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
.beforeIcon {
|
||||
composes: contentIcon;
|
||||
}
|
||||
|
||||
.afterIcon {
|
||||
composes: contentIcon;
|
||||
& svg path:first-child {
|
||||
fill: var(--product-color);
|
||||
stroke: var(--product-color);
|
||||
}
|
||||
}
|
||||
|
||||
/* Content container */
|
||||
|
||||
.contentContainer {
|
||||
border: 1px solid var(--gray-5);
|
||||
flex-grow: 1;
|
||||
padding: 24px 32px 20px;
|
||||
position: relative;
|
||||
|
||||
@media (max-width: 1023px) {
|
||||
padding-left: 24px;
|
||||
padding-right: 24px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
border: solid transparent;
|
||||
bottom: 100%;
|
||||
content: '';
|
||||
height: 0;
|
||||
left: 50%;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
&::before {
|
||||
border-color: rgba(229, 230, 235, 0);
|
||||
border-bottom-color: var(--gray-5);
|
||||
border-width: 18px;
|
||||
margin-left: -18px;
|
||||
}
|
||||
|
||||
&::after {
|
||||
border-color: rgba(255, 255, 255, 0);
|
||||
border-bottom-color: var(--white);
|
||||
border-width: 17px;
|
||||
margin-left: -17px;
|
||||
}
|
||||
|
||||
& > div {
|
||||
height: 100%;
|
||||
|
||||
& > div {
|
||||
@media (min-width: 768px) {
|
||||
margin: 0 auto;
|
||||
max-width: 480px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Content headline */
|
||||
|
||||
.contentHeadline {
|
||||
border-bottom: 1px solid var(--gray-5);
|
||||
color: var(--black);
|
||||
composes: g-type-display-3 from global;
|
||||
margin: 0 0 24px;
|
||||
padding-bottom: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Content styles (for rendered markdown) */
|
||||
|
||||
.content {
|
||||
& :global(.__permalink-h) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
& :global(.g-type-label) {
|
||||
margin: 24px 0 26px 0;
|
||||
}
|
||||
|
||||
& ul,
|
||||
& ol {
|
||||
list-style: none;
|
||||
padding-left: 32px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
& li {
|
||||
margin: 8px 0;
|
||||
|
||||
&::before {
|
||||
background-repeat: no-repeat;
|
||||
content: '';
|
||||
left: 0;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.beforeContent {
|
||||
composes: content;
|
||||
|
||||
& li::before {
|
||||
background: url('/img/static-dynamic-diagram/alert-check.svg');
|
||||
background-repeat: no-repeat;
|
||||
height: var(--after-bullet-height);
|
||||
margin-top: 3px;
|
||||
width: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.afterContent {
|
||||
composes: content;
|
||||
|
||||
& li::before {
|
||||
background: var(--after-bullet-background);
|
||||
height: var(--after-bullet-height);
|
||||
margin-top: 4px;
|
||||
width: 18px;
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
import BeforeAfterDiagram from './before-after-diagram'
|
||||
import s from './style.module.css'
|
||||
|
||||
export default function StaticDynamicDiagram({
|
||||
heading,
|
||||
description,
|
||||
diagrams,
|
||||
}) {
|
||||
return (
|
||||
<div className={s.staticDynamic}>
|
||||
<div className={s.content}>
|
||||
<h2 className={s.heading}>{heading}</h2>
|
||||
{description && <p className={s.description}>{description}</p>}
|
||||
</div>
|
||||
<BeforeAfterDiagram
|
||||
{...diagrams}
|
||||
beforeImage={{
|
||||
format: 'png',
|
||||
url: '/img/static-dynamic-diagram/consul_static_isometric@2x.png',
|
||||
}}
|
||||
afterImage={{
|
||||
format: 'png',
|
||||
url: '/img/static-dynamic-diagram/consul_dynamic_isometric@2x.png',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
.staticDynamic {
|
||||
composes: g-grid-container from global;
|
||||
display: grid;
|
||||
grid-gap: 64px;
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
.content {
|
||||
max-width: 784px;
|
||||
text-align: center;
|
||||
}
|
||||
.description {
|
||||
composes: g-type-body-large from global;
|
||||
color: var(--gray-2);
|
||||
}
|
||||
.heading {
|
||||
composes: g-type-display-2 from global;
|
||||
margin: 0;
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
import Subnav from '@hashicorp/react-subnav'
|
||||
import { useRouter } from 'next/router'
|
||||
import s from './style.module.css'
|
||||
|
||||
export default function ConsulSubnav({ menuItems }) {
|
||||
const router = useRouter()
|
||||
return (
|
||||
<Subnav
|
||||
className={s.subnav}
|
||||
hideGithubStars={true}
|
||||
titleLink={{
|
||||
text: 'HashiCorp Consul',
|
||||
url: '/',
|
||||
}}
|
||||
ctaLinks={[
|
||||
{
|
||||
text: 'GitHub',
|
||||
url: 'https://www.github.com/hashicorp/consul',
|
||||
},
|
||||
|
||||
{ text: 'Download', url: '/downloads' },
|
||||
{
|
||||
text: 'Try HCP Consul',
|
||||
url:
|
||||
'https://cloud.hashicorp.com/?utm_source=consul_io&utm_content=top_nav_consul',
|
||||
theme: {
|
||||
brand: 'consul',
|
||||
},
|
||||
},
|
||||
]}
|
||||
currentPath={router.asPath}
|
||||
menuItemsAlign="right"
|
||||
menuItems={menuItems}
|
||||
constrainWidth
|
||||
matchOnBasePath
|
||||
/>
|
||||
)
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
.subnav {
|
||||
border-top: 1px solid transparent;
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
import BasicHero from 'components/basic-hero'
|
||||
import PrefooterCTA from 'components/prefooter-cta'
|
||||
import ConsulEnterpriseComparison from 'components/enterprise-comparison/consul'
|
||||
import Head from 'next/head'
|
||||
import HashiHead from '@hashicorp/react-head'
|
||||
|
||||
export default function UseCaseLayout({
|
||||
title,
|
||||
description,
|
||||
guideLink,
|
||||
children,
|
||||
}) {
|
||||
const pageTitle = `Consul ${title}`
|
||||
return (
|
||||
<>
|
||||
<HashiHead is={Head} title={pageTitle} description={description}>
|
||||
<meta name="og:title" property="og:title" content={pageTitle} />
|
||||
</HashiHead>
|
||||
|
||||
<div id="p-use-case">
|
||||
<BasicHero
|
||||
heading={title}
|
||||
content={description}
|
||||
brand="consul"
|
||||
links={[
|
||||
{
|
||||
text: 'Explore HashiCorp Learn',
|
||||
url: guideLink,
|
||||
type: 'outbound',
|
||||
},
|
||||
{
|
||||
text: 'Explore Documentation',
|
||||
url: '/docs',
|
||||
type: 'inbound',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<div className="g-grid-container">
|
||||
<h2 className="g-type-display-2 features-header">Features</h2>
|
||||
</div>
|
||||
{children}
|
||||
<ConsulEnterpriseComparison />
|
||||
<PrefooterCTA />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
#p-use-case {
|
||||
& .features-header {
|
||||
text-align: center;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Overriding the g-text-split component to have
|
||||
* a header size closer to a h3 than a h2, as within
|
||||
* the context of this page this text-split is more deeply
|
||||
* nested within the page than we normally have it.
|
||||
* */
|
||||
& .g-text-split {
|
||||
& h2 {
|
||||
font-size: 1.5rem;
|
||||
letter-spacing: -0.004em;
|
||||
line-height: 1.375em;
|
||||
|
||||
@media (--medium-up) {
|
||||
font-size: 1.75rem;
|
||||
line-height: 1.321em;
|
||||
}
|
||||
|
||||
@media (--large) {
|
||||
font-size: 2rem;
|
||||
letter-spacing: -0.006em;
|
||||
line-height: 1.313em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
export const ALERT_BANNER_ACTIVE = false
|
||||
|
||||
// https://github.com/hashicorp/web-components/tree/master/packages/alert-banner
|
||||
export default {
|
||||
tag: 'Blog post',
|
||||
url: 'https://www.hashicorp.com/blog/a-new-chapter-for-hashicorp',
|
||||
text:
|
||||
'HashiCorp shares have begun trading on the Nasdaq. Read the blog from our founders, Mitchell Hashimoto and Armon Dadgar.',
|
||||
linkText: 'Read the post',
|
||||
// Set the expirationDate prop with a datetime string (e.g. '2020-01-31T12:00:00-07:00')
|
||||
// if you'd like the component to stop showing at or after a certain date
|
||||
expirationDate: '2021-12-17T23:00:00-07:00',
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
export const productName = 'Consul'
|
||||
export const productSlug = 'consul'
|
|
@ -1 +0,0 @@
|
|||
export default '1.11.5'
|