website: remove source code and related CI jobs (#12596)

* remove website source code and related circle jobs

* remove data files

* updates platform-cli

* update local instructions

* updates package-lock
This commit is contained in:
Bryce Kalow 2022-05-05 09:53:22 -05:00 committed by GitHub
parent a05114fdac
commit e9319abc78
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
137 changed files with 9388 additions and 28423 deletions

View File

@ -158,43 +158,6 @@ commands:
fi
jobs:
website-docker-image:
docker:
- image: docker.mirror.hashicorp.services/circleci/buildpack-deps
shell: /usr/bin/env bash -euo pipefail -c
steps:
- checkout
- run:
name: Skip building if nothing changed
command: |
# There is an edge case that would cause an issue here - if dependencies are updated to an exact copy
# of a previous version, for example if packge-lock.json is reverted, we need to manually push the new
# image to the "latest" tag
# 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/nomad.git" ]; then
echo "Not Nomad OSS Repo, not building website docker image"
circleci-agent step halt
elif curl https://hub.docker.com/v2/repositories/hashicorp/nomad-website/tags/$IMAGE_TAG -fsL > /dev/null; then
echo "Dependencies have not changed, not building a new website docker image."
circleci-agent step halt
fi
- setup_remote_docker
- run:
name: Build Docker Image
command: |
IMAGE_TAG=$(cat website/Dockerfile website/package-lock.json | sha256sum | awk '{print $1;}')
echo "Using $IMAGE_TAG"
cd website/
docker build -t hashicorp/nomad-website:$IMAGE_TAG .
docker tag hashicorp/nomad-website:$IMAGE_TAG hashicorp/nomad-website:latest
docker login -u $WEBSITE_DOCKER_USER -p $WEBSITE_DOCKER_PASS
docker push hashicorp/nomad-website
test-windows:
executor: go-windows
@ -489,22 +452,6 @@ jobs:
- store_artifacts:
path: /tmp/ui-assets
destination: /ui-assets
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/nomad.git" ]; then
echo "Not Nomad OSS Repo, not indexing Algolia"
exit 0
fi
cd website/
npm install
node scripts/index_search_content.js
executors:
go:
working_directory: /go/src/github.com/hashicorp/nomad
@ -642,20 +589,3 @@ workflows:
test_packages: "./client/fingerprint"
goarch: "386"
filters: *backend_test_branches_filter
website:
when:
equal: [ "https://github.com/hashicorp/nomad", << pipeline.project.git_url >> ]
jobs:
- website-docker-image:
context: static-sites
filters:
branches:
only:
- main
- algolia_index:
context: static-sites
filters:
branches:
only:
- stable-website

2
website/.gitignore vendored
View File

@ -7,3 +7,5 @@ out
# As per Next.js conventions (https://nextjs.org/docs/basic-features/environment-variables#default-environment-variables)
.env*.local
!.env*
website-preview

View File

@ -1,8 +0,0 @@
FROM docker.mirror.hashicorp.services/node:14.17.0-alpine
RUN apk add --update --no-cache git make g++ automake autoconf libtool nasm libpng-dev
COPY ./package.json /website/package.json
COPY ./package-lock.json /website/package-lock.json
WORKDIR /website
RUN npm install -g npm@latest
RUN npm install

View File

@ -1,54 +1,38 @@
.DEFAULT_GOAL := website
PWD=$$(pwd)
DOCKER_IMAGE="hashicorp/dev-portal"
DOCKER_IMAGE_LOCAL="dev-portal-local"
DOCKER_RUN_FLAGS=-it \
--publish "3000:3000" \
--rm \
--tty \
--volume "$(PWD)/content:/app/content" \
--volume "$(PWD)/public:/app/public" \
--volume "$(PWD)/data:/app/data" \
--volume "$(PWD)/redirects.js:/app/redirects.js" \
--volume "next-dir:/app/website-preview/.next" \
--volume "$(PWD)/.env:/app/.env" \
-e "REPO=nomad"
# Default: run this if working on the website locally to run in watch mode.
.PHONY: website
website:
@echo "==> Downloading latest Docker image..."
@docker pull hashicorp/nomad-website
@echo "==> Starting website in Docker..."
@docker run \
--interactive \
--rm \
--tty \
--workdir "/website" \
--volume "$(shell pwd):/website" \
--volume "/website/node_modules" \
--publish "3000:3000" \
hashicorp/nomad-website \
npm start
@docker pull $(DOCKER_IMAGE)
@echo "==> Starting website..."
@docker run $(DOCKER_RUN_FLAGS) $(DOCKER_IMAGE)
# This command will generate a static version of the website to the "out" folder.
build:
@echo "==> Downloading latest Docker image..."
@docker pull hashicorp/nomad-website
@echo "==> Starting build in Docker..."
@docker run \
--interactive \
--rm \
--tty \
--workdir "/website" \
--volume "$(shell pwd):/website" \
--volume "/website/node_modules" \
hashicorp/nomad-website \
npm run static
# Use this if you have run `website/build-local` to use the locally built image.
.PHONY: website/local
website/local:
@echo "==> Starting website from local image..."
@docker run $(DOCKER_RUN_FLAGS) $(DOCKER_IMAGE_LOCAL)
# If you are changing node dependencies locally, run this to generate a new
# local Docker image with the dependency changes included.
build-image:
@echo "==> Building Docker image..."
@docker build -t hashicorp-nomad-website-local .
# Run this to generate a new local Docker image.
.PHONY: website/build-local
website/build-local:
@echo "==> Building local Docker image"
@docker build https://github.com/hashicorp/dev-portal.git\#main \
-t $(DOCKER_IMAGE_LOCAL)
# Use this if you have run `build-image` to use the locally built image
# rather than our CI-generated image to test dependency changes.
website-local:
@echo "==> Starting website in Docker..."
@docker run \
--interactive \
--rm \
--tty \
--workdir "/website" \
--volume "$(shell pwd):/website" \
--volume "/website/node_modules" \
--publish "3000:3000" \
hashicorp-nomad-website-local \
npm start
.DEFAULT_GOAL := website
.PHONY: build build-image website website-local

View File

@ -1,6 +1,6 @@
# Nomad Documentation Website
This subdirectory contains the entire source for the [Nomad Website](https://nomadproject.io/). This is a [NextJS](https://nextjs.org/) project, which builds a static site from these source files.
This subdirectory contains the content for the [Nomad Website](https://nomadproject.io/).
<!--
This readme file contains several blocks of generated text, to make it easier to share common information
@ -40,22 +40,35 @@ The website can be run locally through node.js or [Docker](https://www.docker.co
> **Note:** If you are using a text editor that uses a "safe write" save style such as **vim** or **goland**, this can cause issues with the live reload in development. If you turn off safe write, this should solve the problem. In vim, this can be done by running `:set backupcopy=yes`. In goland, search the settings for "safe write" and turn that setting off.
### With Docker
## Using Docker
Running the site locally is simple. Provided you have Docker installed, clone this repo, run `make`, and then visit `http://localhost:3000`.
If you wish to run the site in a container, you can run the site locally via
`make`.
The docker image is pre-built with all the website dependencies installed, which is what makes it so quick and simple, but also means if you need to change dependencies and test the changes within Docker, you'll need a new image. If this is something you need to do, you can run `make build-image` to generate a local Docker image with updated dependencies, then `make website-local` to use that image and preview.
- `make website` (default, recommended)
- This command will pull and run the latest website container.
- This includes live reload which will load your changes as you make them.
- `make website/local`
- This command will run the website locally using a locally built image
- This includes live reload which will load your changes as you make them.
- `make website/build-local`
- This command will build a local image of the website from `hashicorp/dev-portal.git`.
### With Node
...and then visit `http://localhost:3000`.
If your local development environment has a supported version (v10.0.0+) of [node installed](https://nodejs.org/en/) you can run:
There's no need to re-run `make website` each time the site is run, only the
first time. Changes to content will reflect locally on refresh.
## Using Node
Make sure your local development environment has a supported version (v14.0.0+) of [node installed](https://nodejs.org/en/). You can then run:
- `npm install`
- `npm start`
...and then visit `http://localhost:3000`.
If you pull down new code from github, you should run `npm install` again. Otherwise, there's no need to re-run `npm install` each time the site is run, you can just run `npm start` to get it going.
There's no need to re-run `npm install` each time the site is run, only the first time. Going forward, you can just run `npm start` to get it going.
<!-- END: local-development -->
@ -77,7 +90,6 @@ This file can be standard Markdown and also supports [YAML frontmatter](https://
title: 'My Title'
description: "A thorough, yet succinct description of the page's contents"
---
```
The significant keys in the YAML frontmatter are:

View File

@ -1,34 +0,0 @@
import Button from '@hashicorp/react-button'
export default function BasicHero({ heading, content, links }) {
return (
<div className="g-basic-hero">
<div className="g-grid-container">
<h1 className="g-type-display-1">{heading}</h1>
<p className="g-type-body-large">{content}</p>
{links && (
<div className="links">
{links.map((link, stableIdx) => {
const buttonVariant = stableIdx === 0 ? 'primary' : 'secondary'
const linkType = link.type || 'inbound'
return (
<Button
// eslint-disable-next-line react/no-array-index-key
key={stableIdx}
linkType={linkType}
theme={{
variant: buttonVariant,
brand: 'nomad',
background: 'light',
}}
title={link.text}
url={link.url}
/>
)
})}
</div>
)}
</div>
</div>
)
}

View File

@ -1,36 +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;
gap: 16px;
/*
* 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);
@media (--large) {
margin-top: calc(40px - 8px);
}
}
}

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: 'nomad',
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(--nomad, #60dea9)"/>
</svg>

Before

Width:  |  Height:  |  Size: 223 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,90 +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,
featuredLogos,
}) {
const [slideIndex, setSlideIndex] = useState(0)
const caseStudySlides = caseStudies.map((caseStudy) => (
<CaseSlide key={caseStudy.quote} caseStudy={caseStudy} />
))
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">
<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">
<div className="mono-logos">
{featuredLogos.map((featuredLogo) => (
<Image
key={featuredLogo.url}
url={featuredLogo.url}
alt={featuredLogo.companyName}
/>
))}
</div>
</div>
</section>
)
}

View File

@ -1,251 +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-top: 128px;
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: '';
background: var(--gray-6);
width: 100%;
height: 500px;
position: absolute;
bottom: 0;
z-index: -1;
}
& .background-section {
width: 100%;
background: var(--gray-6);
& .mono-logos {
display: flex;
justify-content: center;
max-width: 750px;
margin: 0 auto;
margin-top: 70px;
flex-wrap: wrap;
& img {
max-height: 40px;
width: 33.33%;
padding: 0 30px;
margin: 24px 0;
@media (max-width: 800px) {
padding: 0 20px;
max-height: 28px;
}
}
}
}
& .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 {
& 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,38 +0,0 @@
import s from './style.module.css'
import Image from '@hashicorp/react-image'
import Button from '@hashicorp/react-button'
export default function ComparisonCallouts({ heading, details, items }) {
return (
<div className={s.comparisonCallouts}>
<div className={s.content}>
<div className={s.description}>
<h2 className="g-type-display-2">{heading}</h2>
<div className={s.details}>{details}</div>
</div>
<div className={s.comparisonItems}>
{items.map((item) => (
<ComparisonItem key={item.title} {...item} />
))}
</div>
</div>
</div>
)
}
function ComparisonItem({ imageUrl, title, description, link }) {
return (
<div className={s.comparisonItem}>
<Image url={imageUrl} />
<h4 className="g-type-display-4">{title}</h4>
<p className="g-type-body">{description}</p>
<Button
url={link.url}
title={link.text}
linkType={link.type}
theme={{ variant: 'tertiary', brand: 'nomad' }}
/>
</div>
)
}

View File

@ -1,60 +0,0 @@
.comparisonCallouts {
padding-top: 128px;
padding-bottom: 128px;
background: var(--gray-6);
}
.content {
composes: g-grid-container from global;
text-align: center;
}
.description {
margin-bottom: 64px;
& h2 {
margin-top: 0;
margin-bottom: 24px;
}
}
.details {
margin: 0 auto;
composes: g-type-body-large from global;
max-width: 560px;
& a {
color: var(--nomad-link);
}
}
.comparisonItems {
display: flex;
justify-content: center;
margin: 0 auto;
}
.comparisonItem {
max-width: 280px;
padding: 48px 24px;
background-color: var(--white);
box-shadow: 0 2px 3px rgba(37, 41, 55, 0.08);
border: 1px solid var(--gray-5);
border-radius: 1px;
transition-duration: 0.25s;
transition-property: box-shadow, transform;
&:first-child {
margin-right: 32px;
}
&:hover {
box-shadow: 0 16px 28px rgba(37, 38, 45, 0.12);
transform: translateY(-4px);
}
& h4 {
margin-top: 22px;
margin-bottom: 8px;
}
}

View File

@ -1,38 +0,0 @@
import s from '../../pages/downloads/style.module.css'
export default function DownloadsProps(preMerchandisingSlot) {
return {
getStartedDescription:
'Follow step-by-step tutorials on the essentials of Nomad.',
getStartedLinks: [
{
label: 'Getting Started',
href: 'https://learn.hashicorp.com/collections/nomad/get-started',
},
{
label: 'Deploy and Manage Nomad Jobs',
href: 'https://learn.hashicorp.com/collections/nomad/manage-jobs',
},
{
label: 'Explore the Nomad Web UI',
href: 'https://learn.hashicorp.com/collections/nomad/web-ui',
},
{
label: 'View all Nomad tutorials',
href: 'https://learn.hashicorp.com/nomad',
},
],
logo: (
<img
className={s.logo}
alt="Nomad"
src={require('@hashicorp/mktg-logos/product/nomad/primary/color.svg')}
/>
),
tutorialLink: {
href: 'https://learn.hashicorp.com/nomad',
label: 'View Tutorials at HashiCorp Learn',
},
merchandisingSlot: preMerchandisingSlot && preMerchandisingSlot,
}
}

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

View File

@ -1 +0,0 @@
<svg fill="none" height="96" viewBox="0 0 32 96" width="32" xmlns="http://www.w3.org/2000/svg"><path d="m0 0h32v96h-32z" /><circle cx="6" cy="6" fill="#00ca8e" r="6"/><circle cx="6" cy="70" fill="#dbdbdc" r="6"/><circle cx="6" cy="26" fill="#00ca8e" r="6"/><circle cx="6" cy="90" fill="#dbdbdc" r="6"/><circle cx="26" cy="6" fill="#00ca8e" r="6"/><g fill="#dbdbdc"><circle cx="26" cy="70" r="6"/><circle cx="26" cy="26" r="6"/><circle cx="26" cy="90" r="6"/></g></svg>

Before

Width:  |  Height:  |  Size: 468 B

View File

@ -1 +0,0 @@
<svg fill="none" height="96" viewBox="0 0 160 96" width="160" xmlns="http://www.w3.org/2000/svg"><path d="m0 0h160v96h-160z" /><circle cx="6" cy="6" fill="#00ca8e" r="6"/><circle cx="134" cy="6" fill="#00ca8e" r="6"/><circle cx="70" cy="6" fill="#00ca8e" r="6"/><circle cx="6" cy="70" fill="#dbdbdc" r="6"/><g fill="#00ca8e"><circle cx="134" cy="70" r="6"/><circle cx="70" cy="70" r="6"/><circle cx="6" cy="26" r="6"/><circle cx="134" cy="26" r="6"/><circle cx="70" cy="26" r="6"/></g><circle cx="6" cy="90" fill="#dbdbdc" r="6"/><circle cx="134" cy="90" fill="#00ca8e" r="6"/><circle cx="70" cy="90" fill="#dbdbdc" r="6"/><circle cx="26" cy="6" fill="#00ca8e" r="6"/><circle cx="154" cy="6" fill="#00ca8e" r="6"/><circle cx="90" cy="6" fill="#dbdbdc" r="6"/><circle cx="26" cy="70" fill="#dbdbdc" r="6"/><circle cx="154" cy="70" fill="#00ca8e" r="6"/><circle cx="90" cy="70" fill="#00ca8e" r="6"/><circle cx="26" cy="26" fill="#dbdbdc" r="6"/><circle cx="154" cy="26" fill="#00ca8e" r="6"/><circle cx="90" cy="26" fill="#00ca8e" r="6"/><circle cx="26" cy="90" fill="#dbdbdc" r="6"/><circle cx="154" cy="90" fill="#dbdbdc" r="6"/><circle cx="90" cy="90" fill="#dbdbdc" r="6"/></svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,47 +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 EnterpriseInfo({ title, itemOne, itemTwo }) {
return (
<div className="g-enterprise-info">
<div className=" g-grid-container">
<h2 className="g-type-display-2">{title}</h2>
<div className="complexity-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>
<Button
url={itemOne.link.url}
title={itemOne.link.text}
linkType={itemOne.link.type}
theme={{ variant: 'tertiary', brand: 'nomad' }}
/>
</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>
<Button
url={itemTwo.link.url}
title={itemTwo.link.text}
linkType={itemTwo.link.type}
theme={{ variant: 'tertiary', brand: 'nomad' }}
/>
</div>
</div>
</div>
</div>
)
}

View File

@ -1,37 +0,0 @@
import EnterpriseInfo from './index.jsx'
const technicalComplexity = {
title: 'Technical Complexity',
label: 'Open Source',
imageUrl: require('./img/basic.svg?url'),
description:
'Nomad Open Source addresses the technical complexity of workload orchestration across the cloud, on-prem, and hybrid infrastructure.',
link: {
text: 'View Open Source Features',
url: 'https://www.hashicorp.com/products/nomad/pricing/',
type: 'outbound',
},
}
const organizationalComplexity = {
title: 'Organizational Complexity',
label: 'Enterprise',
imageUrl: require('./img/complex.svg?url'),
description:
'Nomad Enterprise addresses the complexity of collaboration and governance across multi-team and multi-cluster deployments.',
link: {
text: 'View Enterprise Features',
url: 'https://www.hashicorp.com/products/nomad/pricing/',
type: 'outbound',
},
}
export default function NomadEnterpriseInfo() {
return (
<EnterpriseInfo
title="When to consider Nomad Enterprise?"
itemOne={technicalComplexity}
itemTwo={organizationalComplexity}
/>
)
}

View File

@ -1,92 +0,0 @@
.g-enterprise-info {
padding-top: 128px;
padding-bottom: 128px;
background: var(--gray-6);
& h2 {
text-align: center;
}
@media (max-width: 800px) {
padding-top: 64px;
padding-bottom: 64px;
}
& .complexity-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,27 +0,0 @@
export default function FeaturesList({ title, items, intro }) {
return (
<div className="g-features-list g-grid-container">
<h2 className="g-type-display-2">{title}</h2>
<div
className="intro-container"
dangerouslySetInnerHTML={{ __html: intro }}
/>
<div className="items-container">
{items.map(({ title, content, icon }) => (
<div key={title} className="item">
<div className="item-icon">
<img src={icon} alt={title} />
</div>
<div className="content">
<h4 className="g-type-display-4">{title}</h4>
<p
className="g-type-body-small"
dangerouslySetInnerHTML={{ __html: content }}
/>
</div>
</div>
))}
</div>
</div>
)
}

View File

@ -1,99 +0,0 @@
.g-features-list {
padding-top: 64px;
padding-bottom: 24px;
& h2 {
text-align: center;
margin-top: 0;
margin-bottom: 32px;
}
& .intro-container {
text-align: center;
max-width: 600px;
margin: 0 auto;
margin-bottom: 64px;
& a {
color: var(--nomad);
}
}
& .items-container {
display: flex;
flex-wrap: wrap;
& .item {
border: 1px solid var(--gray-5);
flex-basis: calc(50% - 16px);
margin-bottom: 32px;
padding: 40px;
display: flex;
@media (max-width: 468px) {
flex-direction: column;
}
& h4 {
margin: 0;
margin-bottom: 8px;
}
& p {
margin: 0;
}
& a {
color: var(--nomad-link);
}
& .item-icon img {
width: 72px;
height: 72px;
margin-right: 32px;
@media (max-width: 468px) {
margin-bottom: 18px;
}
}
&:nth-child(odd) {
margin-right: 16px;
}
&:nth-child(even) {
margin-left: 16px;
}
@media (max-width: 1200px) {
padding: 32px;
& .item-icon img {
display: flex;
width: 50px;
height: 50px;
margin-right: 24px;
}
}
@media (max-width: 991px) {
flex-basis: 100%;
padding: 32px;
margin-bottom: 36px;
& .item-icon img {
display: flex;
width: 72px;
height: 72px;
margin-right: 24px;
}
&:nth-child(odd) {
margin-right: 0;
}
&:nth-child(even) {
margin-left: 0;
}
}
}
}
}

View File

@ -1,29 +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="/docs">
<a>Docs</a>
</Link>
<Link href="/api-docs">
<a>API</a>
</Link>
<Link href="/resources">
<a>Resources</a>
</Link>
<a href="https://hashicorp.com/privacy">Privacy</a>
<Link href="/security">
<a>Security</a>
</Link>
<Link href="/files/press-kit.zip">
<a>Press Kit</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,33 +0,0 @@
import Button from '@hashicorp/react-button'
export default function HomepageHero({ title, description, links }) {
return (
<div className="g-homepage-hero">
<div className="g-grid-container">
<h1 data-testid="heading" className="g-type-display-1">
{title}
</h1>
<div className="content-and-links">
<p data-testid="content" className="g-type-body-large">
{description}
</p>
<div data-testid="links" className="links">
{links.map((link, index) => {
const brand = index === 0 ? 'nomad' : 'neutral'
const variant = index === 0 ? 'primary' : 'secondary'
return (
<Button
key={link.text}
title={link.text}
linkType={link.type}
url={link.url}
theme={{ variant, brand }}
/>
)
})}
</div>
</div>
</div>
</div>
)
}

View File

@ -1,39 +0,0 @@
.g-homepage-hero {
background-repeat: no-repeat;
background-color: var(--nomad-secondary);
width: 100%;
background-size: cover;
background-position: center;
padding: 88px 0;
@media (--medium-up) {
background-image: url(/img/nomad-bg-pattern.svg);
}
& h1 {
text-align: center;
white-space: pre-wrap;
}
& p {
margin: 0 auto 0 auto;
text-align: center;
max-width: 40em;
}
& .g-grid-container {
max-width: 700px;
}
& .links {
display: flex;
flex-wrap: wrap;
justify-content: center;
margin-top: calc(32px - 8px);
margin-bottom: -8px;
& a {
margin: 8px;
}
}
}

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,80 +0,0 @@
import * as React from 'react'
import Image from 'next/image'
import { isInternalLink } from 'lib/utils'
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 {
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({
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,171 +0,0 @@
.root {
position: relative;
margin: 0 auto;
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,71 +0,0 @@
import * as React from 'react'
import Image from 'next/image'
import Link from 'next/link'
import { isInternalLink } from 'lib/utils'
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>
)
}
function IoHomeFeatureWrap({ isInternalLink, href, children }) {
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: url('/img/practice-pattern.svg');
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,151 +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.youtubeId && video.thumbnail ? (
<div className={s.video}>
<IoVideoCallout
youtubeId={video.youtubeId}
thumbnail={video.thumbnail}
heading={video.heading}
description={video.description}
person={video.person}
/>
</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,119 +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) {
@nest .consul & {
color: var(--white);
}
}
&: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;
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,71 +0,0 @@
import Image from 'next/image'
import * as React from 'react'
import { Products } from '@hashicorp/platform-product-meta'
import classNames from 'classnames'
import Button from '@hashicorp/react-button'
import s from './style.module.css'
interface IoUsecaseCallToActionProps {
brand: Products
theme?: 'light' | 'dark'
heading: string
description: string
links: Array<{
text: string
url: string
}>
// TODO document intended usage
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,118 +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;
}
.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,84 +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>
{person.name ? (
<p className={s.personName}>{person.name}</p>
) : null}
{person.description ? (
<p className={s.personDescription}>{person.description}</p>
) : null}
</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,42 +0,0 @@
import { Fragment } from 'react'
export default function PlacementTable({ groups = [] }) {
return (
<table className="g-placement-table">
<thead>
<tr>
<td width="120" className="head">
Placement
</td>
<td>
{Array.isArray(groups[0]) ? (
groups.map((subgroup) => {
return (
<Fragment key={subgroup.join('')}>
<code
dangerouslySetInnerHTML={{
__html: wrapLastItem(subgroup, 'strong').join(' -> '),
}}
/>
<br />
</Fragment>
)
})
) : (
<code
dangerouslySetInnerHTML={{
__html: wrapLastItem(groups, 'strong').join(' -> '),
}}
/>
)}
</td>
</tr>
</thead>
</table>
)
}
function wrapLastItem(arr, wrapper) {
arr[arr.length - 1] = `<${wrapper}>${arr[arr.length - 1]}</${wrapper}>`
return arr
}

View File

@ -1,13 +0,0 @@
.g-placement-table {
box-sizing: border-box;
& td,
& th {
padding: 8px 20px;
border-right: 1px solid #ddd;
}
& .head {
font-weight: bold;
}
}

View File

@ -1,32 +0,0 @@
import Subnav from '@hashicorp/react-subnav'
import { useRouter } from 'next/router'
import s from './style.module.css'
export default function NomadSubnav({ menuItems }) {
const router = useRouter()
return (
<Subnav
className={s.subnav}
hideGithubStars={true}
titleLink={{
text: 'HashiCorp Nomad',
url: '/',
}}
ctaLinks={[
{ text: 'GitHub', url: 'https://www.github.com/hashicorp/nomad' },
{
text: 'Download',
url: '/downloads',
theme: {
brand: 'nomad',
},
},
]}
currentPath={router.asPath}
menuItemsAlign="right"
menuItems={menuItems}
constrainWidth
matchOnBasePath
/>
)
}

View File

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

View File

@ -1,49 +0,0 @@
import CallToAction from '@hashicorp/react-call-to-action'
import NomadEnterpriseInfo from 'components/enterprise-info/nomad'
import BasicHero from 'components/basic-hero'
export default function UseCaseLayout({ title, description, children }) {
return (
<div id="p-use-case">
<BasicHero
heading={title}
content={description}
links={[
{
text: 'Explore HashiCorp Learn',
url: 'https://learn.hashicorp.com/nomad',
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}
<NomadEnterpriseInfo />
<CallToAction
variant="compact"
heading="Ready to get started?"
content="Nomad Open Source addresses the technical complexity of managing a mixed type of workloads in production at scale by providing a simple and flexible workload orchestrator across distributed infrastructure and clouds."
product="nomad"
links={[
{
text: 'Explore HashiCorp Learn',
type: 'outbound',
url: 'https://learn.hashicorp.com/nomad',
},
{
text: 'Explore Documentation',
type: 'inbound',
url: '/docs',
},
]}
/>
</div>
)
}

View File

@ -1,40 +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;
}
}
}
& .with-border {
& .g-text-split {
& .children {
border-width: 1px;
border-color: rgba(174, 176, 183, 0.45);
border-style: solid;
}
}
}
}

View File

@ -1,11 +0,0 @@
export const ALERT_BANNER_ACTIVE = true
// https://github.com/hashicorp/web-components/tree/master/packages/alert-banner
export default {
tag: 'Survey',
url: 'https://docs.google.com/forms/d/e/1FAIpQLSeyDEyQXzkijZnkXjj8qVb_5IydajRkFnOrPjDNoysFs-6jDQ/viewform',
text: 'Using Nomad for edge workloads? We want to hear about it!',
linkText: 'Fill out our user survey',
// 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: '2022-02-08T23:00:00-07:00',
}

View File

@ -1,2 +0,0 @@
export const productName = 'Nomad'
export const productSlug = 'nomad'

View File

@ -1,41 +0,0 @@
const subnavData = [
{ text: 'Overview', url: '/', type: 'inbound' },
{
text: 'Enterprise',
url: 'https://www.hashicorp.com/products/nomad/',
type: 'outbound',
},
'divider',
{
text: 'Tutorials',
url: 'https://learn.hashicorp.com/nomad',
type: 'outbound',
},
{
text: 'Docs',
url: '/docs',
type: 'inbound',
},
{
text: 'API',
url: '/api-docs',
type: 'inbound',
},
{
text: 'Plugins',
url: '/plugins',
type: 'inbound',
},
{
text: 'Tools',
url: '/tools',
type: 'inbound',
},
{
text: 'Community',
url: '/community',
type: 'inbound',
},
]
export default subnavData

View File

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

1
website/global.d.ts vendored
View File

@ -1 +0,0 @@
/// <reference types="@hashicorp/platform-types" />

View File

@ -1,6 +0,0 @@
{
"compilerOptions": {
"baseUrl": "."
},
"exclude": ["node_modules", ".next", "out"]
}

View File

@ -1,78 +0,0 @@
import query from './query.graphql'
import ProductSubnav from 'components/subnav'
import Footer from 'components/footer'
import { open } from '@hashicorp/react-consent-manager'
export default function StandardLayout(props: Props): React.ReactElement {
const { useCaseNavItems } = props.data
return (
<>
<ProductSubnav
menuItems={[
{ text: 'Overview', url: '/', type: 'inbound' },
{
text: 'Use Cases',
submenu: [
...useCaseNavItems.map((item) => {
return {
text: item.text,
url: `/use-cases/${item.url}`,
}
}),
].sort((a, b) => a.text.localeCompare(b.text)),
},
{
text: 'Enterprise',
url: 'https://www.hashicorp.com/products/nomad/',
type: 'outbound',
},
'divider',
{
text: 'Tutorials',
url: 'https://learn.hashicorp.com/nomad',
type: 'outbound',
},
{
text: 'Docs',
url: '/docs',
type: 'inbound',
},
{
text: 'API',
url: '/api-docs',
type: 'inbound',
},
{
text: 'Plugins',
url: '/plugins',
type: 'inbound',
},
{
text: 'Tools',
url: '/tools',
type: 'inbound',
},
{
text: 'Community',
url: '/community',
type: 'inbound',
},
]}
/>
{props.children}
<Footer openConsentManager={open} />
</>
)
}
StandardLayout.rivetParams = {
query,
dependencies: [],
}
interface Props {
children: React.ReactChildren
data: {
useCaseNavItems: Array<{ url: string; text: string }>
}
}

View File

@ -1,6 +0,0 @@
query UseCasesQuery {
useCaseNavItems: allNomadUseCases {
url: slug
text: heroHeading
}
}

View File

@ -1,15 +0,0 @@
import { ConsentManagerService } from '@hashicorp/react-consent-manager/types'
const localConsentManagerServices: ConsentManagerService[] = [
{
name: 'Demandbase Tag',
description:
'The Demandbase tag is a tracking service to identify website visitors and measure interest on our website.',
category: 'Analytics',
url: 'https://tag.demandbase.com/960ab0a0f20fb102.min.js',
async: true,
},
]
export default localConsentManagerServices

View File

@ -1,11 +0,0 @@
export const isInternalLink = (link: string): boolean => {
if (
link.startsWith('/') ||
link.startsWith('#') ||
link.startsWith('https://nomadproject.io') ||
link.startsWith('https://www.nomadproject.io')
) {
return true
}
return false
}

View File

@ -1,5 +0,0 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@ -1,34 +0,0 @@
const withHashicorp = require('@hashicorp/platform-nextjs-plugin')
const redirects = require('./redirects')
module.exports = withHashicorp({
dato: {
// This token is safe to be in this public repository, it only has access to content that is publicly viewable on the website
token: '88b4984480dad56295a8aadae6caad',
},
defaultLayout: true,
nextOptimizedImages: true,
transpileModules: ['@hashicorp/flight-icons'],
})({
redirects() {
return redirects
},
svgo: {
plugins: [
{
removeViewBox: false,
},
],
},
env: {
HASHI_ENV: process.env.HASHI_ENV || 'development',
SEGMENT_WRITE_KEY: 'qW11yxgipKMsKFKQUCpTVgQUYftYsJj0',
BUGSNAG_CLIENT_KEY: '4fa712dfcabddd05da29fd1f5ea5a4c0',
BUGSNAG_SERVER_KEY: '61141296f1ba00a95a8788b7871e1184',
ENABLE_VERSIONED_DOCS: process.env.ENABLE_VERSIONED_DOCS || false,
},
images: {
domains: ['www.datocms-assets.com'],
disableStaticImages: true,
},
})

31549
website/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -7,63 +7,12 @@
"node": "14.x - 16.x",
"npm": ">=7.0.0"
},
"dependencies": {
"@hashicorp/flight-icons": "^2.0.2",
"@hashicorp/mktg-global-styles": "^4.0.0",
"@hashicorp/mktg-logos": "^1.2.0",
"@hashicorp/nextjs-scripts": "^19.0.3",
"@hashicorp/platform-analytics": "^0.2.0",
"@hashicorp/platform-code-highlighting": "^0.1.2",
"@hashicorp/platform-runtime-error-monitoring": "^0.1.0",
"@hashicorp/platform-util": "^0.1.0",
"@hashicorp/react-alert-banner": "^7.0.1",
"@hashicorp/react-button": "^6.0.1",
"@hashicorp/react-call-to-action": "^4.0.0",
"@hashicorp/react-code-block": "^4.1.5",
"@hashicorp/react-consent-manager": "^7.1.2",
"@hashicorp/react-content": "^8.0.2",
"@hashicorp/react-docs-page": "^14.14.3",
"@hashicorp/react-featured-slider": "^5.0.1",
"@hashicorp/react-hashi-stack-menu": "^2.1.2",
"@hashicorp/react-head": "^3.1.2",
"@hashicorp/react-hero": "^8.0.2",
"@hashicorp/react-image": "^4.0.3",
"@hashicorp/react-inline-svg": "^6.0.3",
"@hashicorp/react-learn-callout": "^2.0.1",
"@hashicorp/react-markdown-page": "^1.4.3",
"@hashicorp/react-product-downloads-page": "^2.5.3",
"@hashicorp/react-search": "^6.1.1",
"@hashicorp/react-section-header": "^5.0.4",
"@hashicorp/react-subnav": "^9.3.4",
"@hashicorp/react-tabs": "^7.0.1",
"@hashicorp/react-text-split": "^4.0.0",
"@hashicorp/react-text-split-with-code": "^3.3.8",
"@hashicorp/react-text-split-with-image": "^4.2.5",
"@hashicorp/react-text-split-with-logo-grid": "^5.1.5",
"@hashicorp/react-use-cases": "^5.0.0",
"@hashicorp/react-vertical-text-block-list": "^7.0.0",
"@reach/dialog": "^0.16.2",
"framer-motion": "^5.6.0",
"marked": "0.7.0",
"next": "^11.1.2",
"next-mdx-remote": "3.0.1",
"next-remote-watch": "^1.0.0",
"nuka-carousel": "4.7.7",
"react": "^17.0.2",
"react-datocms": "^2.0.1",
"react-device-detect": "1.17.0",
"react-dom": "^17.0.2",
"react-player": "^2.9.0"
},
"dependencies": {},
"devDependencies": {
"@hashicorp/platform-cli": "^1.2.0",
"@hashicorp/platform-nextjs-plugin": "^1.0.1",
"@hashicorp/platform-types": "^0.1.1",
"@types/react": "^17.0.3",
"@hashicorp/platform-cli": "^2.1.0",
"dart-linkcheck": "2.0.15",
"husky": "^6.0.0",
"prettier": "2.2.1",
"typescript": "^4.3.5"
"husky": "4.3.8",
"prettier": "2.2.1"
},
"husky": {
"hooks": {
@ -71,14 +20,12 @@
}
},
"scripts": {
"build": "node --max-old-space-size=2048 ./node_modules/.bin/next build",
"export": "node --max-old-space-size=2048 ./node_modules/.bin/next export",
"build": "./scripts/website-build.sh",
"format": "next-hashicorp format",
"generate:component": "next-hashicorp generate component",
"generate:readme": "next-hashicorp markdown-blocks README.md",
"lint": "next-hashicorp lint",
"start": "next-remote-watch './content/**/*.mdx'",
"static": "npm run build && npm run export",
"start": "./scripts/website-start.sh",
"linkcheck": "linkcheck https://www.nomadproject.io"
}
}

View File

@ -1,2 +0,0 @@
import NotFound from './not-found'
export default NotFound

View File

@ -1,69 +0,0 @@
import './style.css'
import '@hashicorp/platform-util/nprogress/style.css'
import Router from 'next/router'
import Head from 'next/head'
import rivetQuery from '@hashicorp/nextjs-scripts/dato/client'
import NProgress from '@hashicorp/platform-util/nprogress'
import { ErrorBoundary } from '@hashicorp/platform-runtime-error-monitoring'
import createConsentManager from '@hashicorp/react-consent-manager/loader'
import localConsentManagerServices from 'lib/consent-manager-services'
import useFathomAnalytics from '@hashicorp/platform-analytics'
import useAnchorLinkAnalytics from '@hashicorp/platform-util/anchor-link-analytics'
import HashiStackMenu from '@hashicorp/react-hashi-stack-menu'
import AlertBanner from '@hashicorp/react-alert-banner'
import HashiHead from '@hashicorp/react-head'
import ProductSubnav from 'components/subnav'
import Error from './_error'
import alertBannerData, { ALERT_BANNER_ACTIVE } from 'data/alert-banner'
import StandardLayout from 'layouts/standard'
NProgress({ Router })
const { ConsentManager } = createConsentManager({
preset: 'oss',
otherServices: [...localConsentManagerServices],
})
export default function App({ Component, pageProps, layoutData }) {
useFathomAnalytics()
useAnchorLinkAnalytics()
const Layout = Component.layout ?? StandardLayout
return (
<ErrorBoundary FallbackComponent={Error}>
<HashiHead
is={Head}
title="Nomad by HashiCorp"
siteName="Nomad by HashiCorp"
description="Nomad is a highly available, distributed, data-center aware cluster and application scheduler designed to support the modern datacenter with support for long-running services, batch jobs, and much more."
image="https://www.nomadproject.io/img/og-image.png"
icon={[{ href: '/_favicon.ico' }]}
/>
{ALERT_BANNER_ACTIVE && (
<AlertBanner {...alertBannerData} product="nomad" hideOnMobile />
)}
<Layout {...(layoutData && { data: layoutData })}>
<div className={`content${ALERT_BANNER_ACTIVE ? ' banner' : ''}`}>
<Component {...pageProps} />
</div>
</Layout>
<ConsentManager />
</ErrorBoundary>
)
}
App.getInitialProps = async ({ Component, ctx }) => {
const layoutQuery = Component.layout
? Component.layout?.rivetParams ?? null
: StandardLayout.rivetParams
const layoutData = layoutQuery ? await rivetQuery(layoutQuery) : null
let pageProps = {}
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx)
}
return { pageProps, layoutData }
}

View File

@ -1,29 +0,0 @@
import Document, { Html, Head, Main, NextScript } from 'next/document'
import HashiHead from '@hashicorp/react-head'
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render() {
return (
<Html>
<Head>
<HashiHead />
</Head>
<body>
<Main />
<NextScript />
<script
noModule
dangerouslySetInnerHTML={{
__html: `window.MSInputMethodContext && document.documentMode && document.write('<script src="/ie-warning.js"><\\x2fscript>');`,
}}
/>
</body>
</Html>
)
}
}

View File

@ -1,14 +0,0 @@
import NotFound from './404'
import Bugsnag from '@hashicorp/platform-runtime-error-monitoring'
function Error({ statusCode }) {
return <NotFound statusCode={statusCode} />
}
Error.getInitialProps = ({ res, err }) => {
if (err) Bugsnag.notify(err)
const statusCode = res ? res.statusCode : err ? err.statusCode : 404
return { statusCode }
}
export default Error

View File

@ -1,36 +0,0 @@
import { productName, productSlug } from 'data/metadata'
import DocsPage from '@hashicorp/react-docs-page'
import { getStaticGenerationFunctions } from '@hashicorp/react-docs-page/server'
const NAV_DATA_FILE = 'data/api-docs-nav-data.json'
const CONTENT_DIR = 'content/api-docs'
const basePath = 'api-docs'
export default function DocsLayout(props) {
return (
<DocsPage
product={{ name: productName, slug: productSlug }}
baseRoute={basePath}
staticProps={props}
/>
)
}
const { getStaticPaths, getStaticProps } = getStaticGenerationFunctions(
process.env.ENABLE_VERSIONED_DOCS === 'true'
? {
strategy: 'remote',
basePath: basePath,
fallback: 'blocking',
revalidate: 360, // 1 hour
product: productSlug,
}
: {
strategy: 'fs',
localContentDir: CONTENT_DIR,
navDataFile: NAV_DATA_FILE,
product: productSlug,
}
)
export { getStaticPaths, getStaticProps }

View File

@ -1,49 +0,0 @@
import VerticalTextBlockList from '@hashicorp/react-vertical-text-block-list'
import SectionHeader from '@hashicorp/react-section-header'
import Head from 'next/head'
import s from './style.module.css'
export default function CommunityPage() {
return (
<div className={s.root}>
<Head>
<title key="title">Community | Nomad by HashiCorp</title>
</Head>
<SectionHeader
headline="Community"
description="Nomad is an open-source project with a thriving community where active users are willing to help you via various mediums"
use_h1={true}
/>
<VerticalTextBlockList
product="nomad"
data={[
{
header: 'Community Forum',
body:
'<a href="https://discuss.hashicorp.com/c/nomad">Nomad Community Forum</a>',
},
{
header: 'Office Hours',
body:
'<a href="https://www.hashicorp.com/community/office-hours">Ask a question</a> during community office hours',
},
{
header: 'Announcement List',
body:
'High-priority, low-volume <a href="https://groups.google.com/g/hashicorp-announce">announcements about HashiCorp products</a>, including release information and security bulletins.',
},
{
header: 'Bug Tracker',
body:
'<a href="https://github.com/hashicorp/nomad/issues">Issue tracker on GitHub</a>. Please only use this for reporting bugs. Do not ask for general help here; use the <a href="https://discuss.hashicorp.com/c/nomad">Community Forum</a> or the mailing list for that.',
},
{
header: 'Webinars',
body:
'<a href="https://www.hashicorp.com/events?product=nomad&type=all">Register for webinars</a> or <a href="https://www.hashicorp.com/events/webinars/recorded?product=nomad&type=all">watch recorded webinars</a>.',
},
]}
/>
</div>
)
}

View File

@ -1,9 +0,0 @@
.root {
composes: g-grid-container from global;
margin-top: 72px;
margin-bottom: 72px;
& :global(.g-section-header) {
margin-bottom: 100px;
}
}

View File

@ -1,39 +0,0 @@
import { productName, productSlug } from 'data/metadata'
import DocsPage from '@hashicorp/react-docs-page'
import { getStaticGenerationFunctions } from '@hashicorp/react-docs-page/server'
import Placement from 'components/placement-table'
const NAV_DATA_FILE = 'data/docs-nav-data.json'
const CONTENT_DIR = 'content/docs'
const basePath = 'docs'
const additionalComponents = { Placement }
export default function DocsLayout(props) {
return (
<DocsPage
product={{ name: productName, slug: productSlug }}
baseRoute={basePath}
staticProps={props}
additionalComponents={additionalComponents}
/>
)
}
const { getStaticPaths, getStaticProps } = getStaticGenerationFunctions(
process.env.ENABLE_VERSIONED_DOCS === 'true'
? {
strategy: 'remote',
basePath: basePath,
fallback: 'blocking',
revalidate: 360, // 1 hour
product: productSlug,
}
: {
strategy: 'fs',
localContentDir: CONTENT_DIR,
navDataFile: NAV_DATA_FILE,
product: productSlug,
}
)
export { getStaticPaths, getStaticProps }

View File

@ -1,38 +0,0 @@
import VERSION from 'data/version'
import { productSlug } from 'data/metadata'
import ProductDownloadsPage from '@hashicorp/react-product-downloads-page'
import { generateStaticProps } from '@hashicorp/react-product-downloads-page/server'
import baseProps from 'components/downloads-props'
import s from './style.module.css'
export default function DownloadsPage(staticProps) {
return (
<>
<ProductDownloadsPage
enterpriseMode={true}
{...baseProps(
<p className={s.legalNotice}>
<em>
The following shall apply unless your organization has a
separately signed Enterprise License Agreement or Evaluation
Agreement governing your use of the package: Enterprise packages
in this repository are subject to the license terms located in the
package. Please read the license terms prior to using the package.
Your installation and use of the package constitutes your
acceptance of these terms. If you do not accept the terms, do not
use the package.
</em>
</p>
)}
{...staticProps}
/>
</>
)
}
export async function getStaticProps() {
return generateStaticProps({
product: productSlug,
latestVersion: VERSION,
})
}

View File

@ -1,30 +0,0 @@
import VERSION from 'data/version'
import { productSlug } from 'data/metadata'
import ProductDownloadsPage from '@hashicorp/react-product-downloads-page'
import { generateStaticProps } from '@hashicorp/react-product-downloads-page/server'
import baseProps from 'components/downloads-props'
import s from './style.module.css'
export default function DownloadsPage(staticProps) {
return <ProductDownloadsPage
{...baseProps()}
merchandisingSlot={
<div className={s.releaseCandidate}>
<p>
A beta for Nomad v1.3.0 is available! The release can be{' '}
<a href="https://releases.hashicorp.com/nomad/1.3.0-beta.1/">
downloaded here.
</a>
</p>
</div>
}
{...staticProps}
/>
}
export async function getStaticProps() {
return generateStaticProps({
product: productSlug,
latestVersion: VERSION,
})
}

View File

@ -1,24 +0,0 @@
.root {
composes: .g-grid-container from global;
margin-top: 72px;
margin-bottom: 72px;
}
.logo {
width: 105px;
}
.releaseCandidate {
background-color: var(--gray-6);
padding: 1rem;
border-radius: 3px;
margin: 24px 0;
& :first-child {
margin-top: 0;
}
& :last-child {
margin-bottom: 0;
}
}

View File

@ -1,5 +0,0 @@
<svg width="120" height="100" viewBox="0 0 120 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M114.219 38.1574C114.954 38.5264 114.954 39.5759 114.219 39.9449L97.411 48.3797C97.1304 48.5629 96.9579 48.8769 96.9579 49.217V66.4843C96.9579 66.8631 96.7439 67.2093 96.4051 67.3787L60.5495 85.3065C60.268 85.4473 59.9366 85.4473 59.6551 85.3065L29.9421 70.45C29.6033 70.2806 29.3893 69.9343 29.3893 69.5556V51.7458L29.3895 51.7459L35.697 48.4965C35.9795 48.351 36.3144 48.3484 36.5991 48.4895L59.6564 59.9196C59.9373 60.0589 60.2673 60.0583 60.5477 59.9181L101.387 39.4984C101.756 39.3141 101.756 38.7882 101.387 38.6039L60.5478 18.1842C60.2673 18.044 59.9373 18.0435 59.6563 18.1828L10.9617 42.3272L5.86485 39.9287C5.10928 39.5731 5.09655 38.5029 5.84344 38.1295L59.6544 11.224C59.9364 11.083 60.2684 11.0832 60.5501 11.2246L114.219 38.1574Z" fill="var(--gray-2, #343536)"/>
<path opacity="0.5" d="M10.9615 42.3271L59.6562 18.1826C59.9372 18.0433 60.2672 18.0439 60.5477 18.1841L101.387 38.6038C101.756 38.7881 101.756 39.314 101.387 39.4982L60.5476 59.9179C60.2672 60.0582 59.9372 60.0587 59.6563 59.9195L36.1462 48.2649L29.3893 51.7457L10.9615 42.3271Z" fill="white"/>
<path d="M43.781 33.3855C43.4888 33.2158 43.1307 33.2052 42.829 33.3572L17.044 46.3481C16.7046 46.5191 16.4915 46.8676 16.4939 47.2476L16.715 81.9796C16.7172 82.3225 16.8949 82.6403 17.1858 82.8218L22.4823 86.1248C22.8153 86.3325 23.2468 86.0931 23.2468 85.7006V50.416C23.2468 50.2272 23.3532 50.0544 23.5219 49.9695L48.8356 37.2145C49.1902 37.0358 49.2051 36.5349 48.8617 36.3356L43.781 33.3855Z" fill="var(--nomad, #60dea9)"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1,9 +0,0 @@
<svg width="120" height="100" viewBox="0 0 120 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.15" d="M96.4472 45.7764C96.1657 45.9172 95.8343 45.9172 95.5528 45.7764L84 40V37L82 33.5L84 31.5V16.809C84 16.4373 83.6088 16.1956 83.2764 16.3618L60.4472 27.7764C60.1657 27.9172 59.8343 27.9172 59.5528 27.7764L36.7236 16.3618C36.3912 16.1956 36 16.4373 36 16.809V30L38 34L36 37V40L24.4472 45.7764C24.1657 45.9172 23.8343 45.9172 23.5528 45.7764L0.723607 34.3618C0.391157 34.1956 0 34.4373 0 34.809V63.382C0 63.7607 0.214002 64.107 0.552786 64.2764L23.5528 75.7764C23.8343 75.9172 24.1657 75.9172 24.4472 75.7764L36 70H84L95.5528 75.7764C95.8343 75.9172 96.1657 75.9172 96.4472 75.7764L119.447 64.2764C119.786 64.107 120 63.7607 120 63.382V34.809C120 34.4373 119.609 34.1956 119.276 34.3618L96.4472 45.7764Z" fill="var(--gray-3, #727274)"/>
<path d="M59.5528 9.82361C59.8343 9.68284 60.1657 9.68284 60.4472 9.82361L71.9056 15.5528C72.2741 15.737 72.2741 16.263 71.9056 16.4472L60.4472 22.1764C60.1657 22.3172 59.8343 22.3172 59.5528 22.1764L48.0944 16.4472C47.7259 16.263 47.7259 15.737 48.0944 15.5528L59.5528 9.82361ZM60.4472 4.22361C60.1657 4.08284 59.8343 4.08284 59.5528 4.22361L36.8944 15.5528C36.5259 15.737 36.5259 16.263 36.8944 16.4472L59.5528 27.7764C59.8343 27.9172 60.1657 27.9172 60.4472 27.7764L83.1056 16.4472C83.4741 16.263 83.4741 15.737 83.1056 15.5528L60.4472 4.22361Z" fill="var(--gray-2, #343536)"/>
<path d="M23.5528 27.8236C23.8343 27.6828 24.1657 27.6828 24.4472 27.8236L35.9056 33.5528C36.2741 33.737 36.2741 34.263 35.9056 34.4472L24.4472 40.1764C24.1657 40.3172 23.8343 40.3172 23.5528 40.1764L12.0944 34.4472C11.7259 34.263 11.7259 33.737 12.0944 33.5528L23.5528 27.8236ZM24.4472 22.2236C24.1657 22.0828 23.8343 22.0828 23.5528 22.2236L0.894427 33.5528C0.525903 33.737 0.525903 34.263 0.894427 34.4472L23.5528 45.7764C23.8343 45.9172 24.1657 45.9172 24.4472 45.7764L47.1056 34.4472C47.4741 34.263 47.4741 33.737 47.1056 33.5528L24.4472 22.2236Z" fill="var(--gray-2, #343536)"/>
<path d="M95.5528 27.8236C95.8343 27.6828 96.1657 27.6828 96.4472 27.8236L107.906 33.5528C108.274 33.737 108.274 34.263 107.906 34.4472L96.4472 40.1764C96.1657 40.3172 95.8343 40.3172 95.5528 40.1764L84.0944 34.4472C83.7259 34.263 83.7259 33.737 84.0944 33.5528L95.5528 27.8236ZM96.4472 22.2236C96.1657 22.0828 95.8343 22.0828 95.5528 22.2236L72.8944 33.5528C72.5259 33.737 72.5259 34.263 72.8944 34.4472L95.5528 45.7764C95.8343 45.9172 96.1657 45.9172 96.4472 45.7764L119.106 34.4472C119.474 34.263 119.474 33.737 119.106 33.5528L96.4472 22.2236Z" fill="var(--gray-2, #343536)"/>
<path d="M60.4472 40.2236C60.1657 40.0828 59.8343 40.0828 59.5528 40.2236L37.7889 51.1056C37.0518 51.4741 37.0518 52.5259 37.7889 52.8944L59.5528 63.7764C59.8343 63.9172 60.1657 63.9172 60.4472 63.7764L82.2111 52.8944C82.9482 52.5259 82.9482 51.4741 82.2111 51.1056L60.4472 40.2236Z" fill="white"/>
<path d="M59.5528 45.8236C59.8343 45.6828 60.1657 45.6828 60.4472 45.8236L71.9056 51.5528C72.2741 51.7371 72.2741 52.263 71.9056 52.4472L60.4472 58.1764C60.1657 58.3172 59.8343 58.3172 59.5528 58.1764L48.0944 52.4472C47.7259 52.263 47.7259 51.7371 48.0944 51.5528L59.5528 45.8236ZM60.4472 40.2236C60.1657 40.0828 59.8343 40.0828 59.5528 40.2236L36.8944 51.5528C36.5259 51.7371 36.5259 52.263 36.8944 52.4472L59.5528 63.7764C59.8343 63.9172 60.1657 63.9172 60.4472 63.7764L83.1056 52.4472C83.4741 52.263 83.4741 51.7371 83.1056 51.5528L60.4472 40.2236Z" fill="var(--nomad, #60dea9)"/>
<path d="M36.7236 58.3618C36.3912 58.1956 36 58.4373 36 58.809V83.382C36 83.7607 36.214 84.107 36.5528 84.2764L59.5528 95.7764C59.8343 95.9172 60.1657 95.9172 60.4472 95.7764L83.4472 84.2764C83.786 84.107 84 83.7607 84 83.382V58.809C84 58.4373 83.6088 58.1956 83.2764 58.3618L60.4472 69.7764C60.1657 69.9172 59.8343 69.9172 59.5528 69.7764L36.7236 58.3618Z" fill="var(--nomad, #60dea9)"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -1 +0,0 @@
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M0 0h30v30H0z"/><mask id="prefix__a" fill="#fff"><rect width="12" height="12" rx="1"/></mask><rect width="12" height="12" rx="1" stroke="var(--gray-3, #727274)" stroke-width="4" mask="url(#prefix__a)"/><mask id="prefix__b" fill="#fff"><rect x="18" y="18" width="12" height="12" rx="1"/></mask><rect x="18" y="18" width="12" height="12" rx="1" stroke="var(--nomad, #60dea9)" stroke-width="4" mask="url(#prefix__b)"/><path d="M16 3h4a4 4 0 014 4v7" stroke="var(--gray-3, #727274)" stroke-width="2" stroke-linecap="round"/><path d="M2.293 18.293a1 1 0 101.414 1.414l-1.414-1.414zM6 16l.707-.707a1 1 0 00-1.414 0L6 16zm2.293 3.707a1 1 0 001.414-1.414l-1.414 1.414zm-4.586 0l3-3-1.414-1.414-3 3 1.414 1.414zm1.586-3l3 3 1.414-1.414-3-3-1.414 1.414z" fill="var(--nomad, #60dea9)"/><path d="M21.707 10.293a1 1 0 10-1.414 1.414l1.414-1.414zM24 14l-.707.707a1 1 0 001.414 0L24 14zm3.707-2.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 0l3 3 1.414-1.414-3-3-1.414 1.414zm4.414 3l3-3-1.414-1.414-3 3 1.414 1.414z" fill="var(--gray-3, #727274)"/><path d="M6 16v7a4 4 0 004 4h4" stroke="var(--nomad, #60dea9)" stroke-width="2" stroke-linecap="round"/></svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1 +0,0 @@
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M0 0h30v30H0z"/><mask id="prefix__a" fill="#fff"><rect x="18" width="12" height="12" rx="1"/></mask><rect x="18" width="12" height="12" rx="1" fill="#fff" stroke="var(--gray-3, #727274)" stroke-width="4" mask="url(#prefix__a)"/><mask id="prefix__b" fill="#fff"><rect width="12" height="12" rx="1"/></mask><rect width="12" height="12" rx="1" fill="#fff" stroke="var(--gray-3, #727274)" stroke-width="4" mask="url(#prefix__b)"/><mask id="prefix__c" fill="#fff"><rect y="18" width="12" height="12" rx="1"/></mask><rect y="18" width="12" height="12" rx="1" fill="#fff" stroke="var(--gray-3, #727274)" stroke-width="4" mask="url(#prefix__c)"/><rect x="23" y="18" width="2" height="12" rx="1" fill="var(--nomad, #60dea9)"/><rect x="18" y="23" width="12" height="2" rx="1" fill="var(--nomad, #60dea9)"/></svg>

Before

Width:  |  Height:  |  Size: 919 B

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