website: remove source code (#12806)

This commit is contained in:
Bryce Kalow 2022-04-19 12:32:02 -05:00 committed by GitHub
parent 3badd4c35c
commit 9a61976a38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
141 changed files with 7094 additions and 22722 deletions

View File

@ -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:

View File

@ -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

View File

@ -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>
)
}

View File

@ -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);
}
}
}

View File

@ -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>
)
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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>
)
}

View File

@ -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>
)
}

View File

@ -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;
}

View File

@ -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>
)
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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>
)
}

View File

@ -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;
}
}
}

View File

@ -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>
)
}

View File

@ -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;
}
}
}
}

View File

@ -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} />
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 247 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 256 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 249 KiB

View File

@ -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>
)
}

View File

@ -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;
}

View File

@ -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

View File

@ -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>
)
}

View File

@ -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;
}

View File

@ -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>
)
}

View File

@ -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;
}

View File

@ -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">&raquo; 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
&quot;armhf&quot; || echo &quot;armel&quot;
</code>
</div>
</>
),
}
}

View File

@ -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

View File

@ -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

View File

@ -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"
/>
)
}

View File

@ -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

Binary file not shown.

Binary file not shown.

View File

@ -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>
)
}

View File

@ -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;
}
}

View File

@ -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>
)
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 254 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 497 KiB

View File

@ -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>
)
}

View File

@ -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;
}
}

View File

@ -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>
)
}

View File

@ -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;
}
}
}

View File

@ -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;
}

View File

@ -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>
)
}

View File

@ -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>
)
}

View File

@ -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; */
}
}
}

View File

@ -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>
)
}

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -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'),
},
}

View File

@ -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);
}
}

View File

@ -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>
)
}

View File

@ -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;
}
}
}

View File

@ -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>
)
}

View File

@ -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;
}
}

View File

@ -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>
)
}

View File

@ -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;
}

View File

@ -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>
)
}

View File

@ -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);
}
}

View File

@ -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>
)
}

View File

@ -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);
}
}

View File

@ -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>
)
}

View File

@ -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;
}

View File

@ -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>
)
}

View File

@ -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;
}
}

View File

@ -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>
)
}

View File

@ -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);
}
}

View File

@ -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>
)
}

View File

@ -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;
}
}

View File

@ -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>
)
}

View File

@ -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);
}

View File

@ -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>
)
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.3 KiB

View File

@ -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);
}

View File

@ -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>
)
}

View File

@ -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;
}

View File

@ -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>
</>
)
}

View File

@ -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>
)
}

View File

@ -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;
}

View File

@ -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>
)
}

View File

@ -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;
}
}
}

View File

@ -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"
/>
)
}

View File

@ -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>
)
}

View File

@ -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;
}
}
}
}

View File

@ -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>
)
}

View File

@ -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;
}
}

View File

@ -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>
)
}

View File

@ -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;
}

View File

@ -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
/>
)
}

View File

@ -1,3 +0,0 @@
.subnav {
border-top: 1px solid transparent;
}

View File

@ -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>
</>
)
}

View File

@ -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;
}
}
}
}

View File

@ -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',
}

View File

@ -1,2 +0,0 @@
export const productName = 'Consul'
export const productSlug = 'consul'

View File

@ -1 +0,0 @@
export default '1.11.5'

Some files were not shown because too many files have changed in this diff Show More