Showing source for: https://eightsleep.dexecure.net/
Duration: 0.570289s
Server: cloudflare

<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8" />
<title>Shopify Imagery</title>
<meta name="description" content="Shopify's content delivery network" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="Shopify" />
<meta property="og:title" content="Shopify's content delivery network" />
<meta property="og:description" content="Optimized, fast, and dynamic file transformation." />
<meta property="og:image" content="https://cdn.shopify.com/static/imagery-landing/social_header.jpg" />
<meta property="og:url" content="https://cdn.shopify.com" />
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:site" content="@Shopify" />
<meta property="twitter:title" content="Shopify's content delivery network" />
<meta property="twitter:description" content="Optimized, fast, and dynamic file transformation." />
<meta property="twitter:image" content="https://cdn.shopify.com/static/imagery-landing/social_header.jpg" />
<link rel="icon" type="image/png" href="https://cdn.shopify.com/static/shopify-favicon.png" />
<link rel="stylesheet" href="https://cdn.shopify.com/static/imagery-landing/croppr/croppr.2.3.1.css" />
<script type="text/javascript" src="https://cdn.shopify.com/static/imagery-landing/croppr/croppr.2.3.1.js"></script>
<script type="text/javascript" src="https://cdn.shopify.com/static/imagery-landing/prism/prism.1.27.0.js"></script>
<style data-code-theme>
      /**
 * Dracula Theme originally by Zeno Rocha [@zenorocha]
 * https://draculatheme.com/
 *
 * Ported for PrismJS by Albert Vallverdu [@byverdu]
 */

      code[class*="language-"],
      pre[class*="language-"] {
        color: #f8f8f2;
        background: none;
        text-shadow: 0 1px rgba(0, 0, 0, 0.3);
        font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
        text-align: left;
        white-space: pre-wrap;
        word-spacing: normal;
        word-break: normal;
        word-wrap: normal;
        line-height: 1.5;
        -moz-tab-size: 4;
        -o-tab-size: 4;
        tab-size: 4;
        -webkit-hyphens: none;
        -moz-hyphens: none;
        -ms-hyphens: none;
        hyphens: none;
      }

      /* Code blocks */
      pre[class*="language-"] {
        padding: 0;
        margin: 0;
        overflow: auto;
        border-radius: 0.3em;
      }

      :not(pre) > code[class*="language-"],
      pre[class*="language-"] {
        background: var(--c-surface);
      }

      /* Inline code */
      :not(pre) > code[class*="language-"] {
        padding: 0.1em;
        border-radius: 0.3em;
        white-space: normal;
      }

      .token.comment,
      .token.prolog,
      .token.doctype,
      .token.cdata {
        color: #6272a4;
      }

      .token.punctuation {
        color: #f8f8f2;
      }

      .namespace {
        opacity: 0.7;
      }

      .token.property,
      .token.tag,
      .token.constant,
      .token.symbol,
      .token.deleted {
        color: #ff79c6;
      }

      .token.boolean,
      .token.number {
        color: #bd93f9;
      }

      .token.selector,
      .token.attr-name,
      .token.string,
      .token.char,
      .token.builtin,
      .token.inserted {
        color: #50fa7b;
      }

      .token.operator,
      .token.entity,
      .token.url,
      .language-css .token.string,
      .style .token.string,
      .token.variable {
        color: #f8f8f2;
      }

      .token.atrule,
      .token.attr-value,
      .token.function,
      .token.class-name {
        color: #f1fa8c;
      }

      .token.keyword {
        color: #8be9fd;
      }

      .token.regex,
      .token.important {
        color: #ffb86c;
      }

      .token.important,
      .token.bold {
        font-weight: bold;
      }

      .token.italic {
        font-style: italic;
      }

      .token.entity {
        cursor: help;
      }
    </style>
<style data-app-styles>
      :root {
        /* Colors */
        --color-background-default: #141514;
        --color-surface-default: #1a1a1a;
        --color-surface-subdued: #303030;
        --color-surface-active: #2e3739;

        --color-text-default: #ffffff;
        --color-text-subdued: #8a8f93;
        --color-text-highlight: #33e19b;

        --color-text-headline-default: #eef1f1;
        --color-text-headline-subdued: rgba(255, 255, 255, 0.85);
        --color-text-headline-minor: rgba(255, 255, 255, 0.6);

        --color-text-edit-default: #70e2f4;

        --color-divider-default: rgba(255, 255, 255, 0.2);
        --color-border-default: #212021;
        --color-border-hover: #141514;
        --color-border-active: #01ffff;

        --color-fill-default: #354cf6;
        --color-fill-icon-default: #8c9196;
        --color-fill-icon-hover: #babec3;
        --color-fill-icon-active: #ffffff;

        /* Input Colors */
        --color-input-background: #3f3f3f;

        /* Text colors */
        --c-text-primary: #ffffff;
        --c-text-2: #d7d7db;
        --c-text-highlight: #70e2f4;

        /* Background colours */
        --c-surface: #141514;

        /* Borders */
        --c-border: #212021;

        --border-radius-half: calc(var(--border-radius) / 2);
        --border-radius: 8px;
        --border-radius-double: calc(var(--border-radius) * 2);

        /* Spacing */
        --spacing-half: calc(var(--spacing) / 2);
        --spacing: 8px;
        --spacing-double: calc(var(--spacing) * 2);
        --spacing-triple: calc(var(--spacing) * 3);
        --spacing-quadruple: calc(var(--spacing) * 4);
        --spacing-6: calc(var(--spacing) * 6);

        /* Fonts */
        --text-screen-calc: 6 * ((100vw - 320px) / 680);
        --text-size-small: 12px;

        --text-size-body: 14px;
        --text-size-body__lineheight: 20px;
        --text-size-body__weight: 400;

        --text-size-title: 40px;
        --text-size-title__lineheight: 48px;
        --text-size-title__weight: 600;

        --text-size-subtitle: 14px;
        --text-size-subtitle__lineheight: 20px;
        --text-size-subtitle__weight: 600;

        --text-size-stat-title: 48px;
        --text-size-stat-title__lineheight: 72px;
        --text-size-stat-title__weight: 800;

        --text-s-ze-stat: 16px;
        --text-size-stat__lineheight: 24px;

        --text-size-features-title: 12px;
        --text-size-features-title__lineheight: 44px;
        --text-size-features-title__weight: 400;

        --text-size-features-heading: 28px;
        --text-size-features-heading__lineheight: 36px;
        --text-size-features-heading__weight: 600;
      }

      @media screen and (min-width: 730px) {
        :root {
          --text-size-small: 12px;

          --text-size-body: 18px;
          --text-size-body__lineheight: 27px;
          --text-size-body__weight: 400;

          --text-size-title: 80px;
          --text-size-title__weight: 700;
          --text-size-title__lineheight: 88px;

          --text-size-subtitle: 24px;
          --text-size-subtitle__lineheight: 40px;
          --text-size-subtitle__weight: 600;

          --text-size-stat-title: 80px;
          --text-size-stat-title__lineheight: 80px;

          --text-size-stat: 24px;
          --text-size-stat__lineheight: 40px;

          --text-size-features-title: 24px;
          --text-size-features-title__lineheight: auto;

          --text-size-features-heading: 64px;
          --text-size-features-heading__lineheight: auto;
          --text-size-features-heading__weight: 800;
        }
      }

      html {
        background: var(--color-background-default);
        box-sizing: border-box;
      }

      html *,
      html *::before,
      html *::after {
        box-sizing: inherit;
      }

      body {
        position: relative;
        margin: 0;
        font-family: -apple-system, "BlinkMacSystemFont", "San Francisco",
          "Segoe UI", "Roboto", "Helvetica Neue", sans-serif;

        color: var(--color-text-default);
      }

      svg {
        display: block;
        fill: var(--color-fill-icon-default);
      }

      input,
      select {
        font-size: 16px;
      }

      select {
        cursor: pointer;
      }

      input[type="text"],
      input[type="number"],
      select {
        width: 100%;
        min-width: 0;
        padding: var(--spacing);

        -webkit-appearance: none;
      }

      input,
      button {
        margin: 0;
        padding: 0;
      }

      button {
        border: none;
        background: none;
        cursor: pointer;
      }

      .visually-hidden {
        position: absolute !important;
        width: 1px !important;
        height: 1px !important;
        padding: 0 !important;
        margin: -1px !important;
        overflow: hidden !important;
        clip: rect(0, 0, 0, 0) !important;
        white-space: nowrap !important;
        border: 0 !important;
      }

      .animation {
        position: absolute;
        width: 100%;
        z-index: -1;
      }

      .AppContainer {
        position: relative;
        background: var(--color-surface-default);

        margin: 20px auto;
        padding: var(--spacing-double);
        filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25));
        z-index: 1;
      }

      @media screen and (min-width: 730px) {
        .AppContainer {
          border-radius: var(--border-radius-double);
          width: 90%;
          max-width: 1200px;
        }
      }

      .ImageEditor {
        display: grid;
        grid-column-gap: var(--spacing);
        grid-row-gap: var(--spacing);
        grid-template-areas:
          "url"
          "preview"
          "tools"
          "code";
      }

      .Templates {
        display: flex;
      }

      .ExampleImageButton {
        position: relative;
        background: none;
        box-sizing: content-box;
        padding: var(--spacing);
        border-radius: var(--border-radius) var(--border-radius) 0 0;
        width: 40px;

        border: none;
        margin: 0;
        -webkit-appearance: none;
        cursor: pointer;
      }

      .ExampleImageButton {
        position: relative;
        border: 1px solid transparent;
        border-bottom: 0;
        z-index: 1;
        margin-bottom: -1px;
      }

      .ExampleImageButton:not(.active)::before {
        content: "";
        position: absolute;
        inset: 0 0 0 0;
        background: rgba(0, 0, 0, 0.4);
        margin: var(--spacing);
        border-radius: var(--border-radius-half);
      }

      .ExampleImageButton.active {
        background: var(--c-surface);
        border: 1px solid var(--c-border);
        border-bottom: 0;
      }

      .ExampleImageButton img {
        display: block;
        border-radius: var(--border-radius-half);
        max-width: 100%;
      }

      .Preview {
        grid-area: preview;
        display: flex;
        flex-direction: column;
      }

      .Preview__Image img {
        max-width: 100%;
        border-radius: var(--border-radius-half);
      }

      .EditorUrl__Bar {
        position: relative;
        z-index: 1;
      }

      .EditorUrl input[type="text"] {
        background: var(--color-input-background);
        color: var(--color-text-default);
        border-radius: var(--border-radius);
        border: 1px solid #141514;
        padding-left: calc(var(--spacing) + 24px);
      }

      .EditorUrl__Icon {
        position: absolute;
        left: var(--spacing);
        top: 50%;
        transform: translateY(-50%);
        pointer-events: none;
      }

      .EditorUrl__Icon svg {
        width: 20px;
        height: 20px;
      }

      .EditorUrl__HelpText {
        position: relative;
        background: #1f1f1f;
        color: var(--c-text-primary);
        padding: var(--spacing-double) var(--spacing) var(--spacing);
        margin-top: calc(var(--spacing) * -1);

        border-bottom-left-radius: var(--border-radius);
        border-bottom-right-radius: var(--border-radius);
      }

      .EditorUrl__HelpText span {
        opacity: 0.6;
      }

      .Preview__Image {
        background: var(--c-surface);
        border: 1px solid var(--c-border);
        border-radius: var(--border-radius);

        padding: var(--spacing);
        position: relative;
        z-index: 0;
      }

      .Preview__Image.active {
        border-top-left-radius: 0;
      }

      .Preview__ImageContainer {
        display: flex;
        align-items: center;
        justify-content: center;
      }

      .Preview__ImageContainer img {
        display: block;
        max-height: 60vh;
      }

      .EditorUrl {
        grid-area: url;
      }

      .Tools {
        display: flex;
        flex-direction: column;
        grid-area: tools;
        background: var(--c-surface);
        color: var(--c-text-primary);
        border-radius: var(--border-radius);
        border: 1px solid var(--c-border);
      }

      .Tools input[type="text"],
      .Tools input[type="number"],
      .Tools select {
        background: var(--color-input-background);
        color: var(--c-text-primary);
        border-radius: var(--border-radius);

        border: none;
      }

      .CodeBlock {
        display: flex;
        flex-direction: column;
        color: #d7d7db;
      }

      .CodeBlock > *:last-child {
        align-self: flex-end;
      }

      .CodeBlock__Code {
        min-height: 130px;
      }

      .CodePreview {
        grid-area: code;
        background: var(--c-surface);
        border-radius: var(--border-radius);
        border: 1px solid var(--c-border);
      }

      .CodePreview__Tabs {
        border-bottom: 1px solid var(--c-border);
        padding: var(--spacing) var(--spacing) 0;
        display: flex;
      }

      .CodePreview__Tabs button {
        flex: 1;
        background: var(--c-surface);
        color: var(--color-text-subdued);

        border: none;
        border-bottom: 2px solid transparent;

        padding-top: var(--spacing);
        padding-bottom: 2px;

        font-size: 16px;
        cursor: pointer;
      }

      @media screen and (min-width: 730px) {
        .CodePreview__Tabs {
          display: block;
          padding-left: var(--spacing-double);
        }
        .CodePreview__Tabs button + button {
          margin-left: calc(var(--spacing-half) * 3);
        }
      }

      .CodePreview__Tabs button.active {
        color: #f1f8fd;
        border-bottom: 2px solid #f1f8fd;
      }

      .CodePreview__Preview {
        padding: var(--spacing-double) var(--spacing-triple);
      }

      .CopyButton {
        align-self: flex-end;
        margin-top: var(--spacing);
        background: #354cf6;
        border-radius: 4px;
        color: var(--c-text-primary);

        padding: calc(var(--spacing) / 2) var(--spacing)
          calc(var(--spacing) / 2) calc(var(--spacing) / 2);

        display: flex;
        font-size: 16px;
      }

      .CopyButton svg {
        width: 20px;
        fill: var(--color-fill-icon-active);
      }

      .CopyButton span {
        margin-left: calc(var(--spacing) / 2);
      }

      .SegmentedUrl {
        word-break: break-all;
        font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
        padding: 1em 0;
      }

      .SegmentedUrl span {
        color: var(--c-text-highlight);
      }

      .Tools__Inputs {
        position: relative;
        padding: 0 var(--spacing-double);
        margin-bottom: var(--spacing-triple);
        margin-top: var(--spacing-triple);
        flex: 1;
      }

      .Tools__Inputs::before {
        content: "";
        background: var(--c-border);
        position: absolute;
        top: calc(var(--spacing-triple) * -1);
        left: 50%;
        transform: translateX(-50%);
        width: calc(100% - var(--spacing-quadruple));
        height: 1px;
      }

      .Tools__Inputs > * + * {
        margin-top: calc(var(--spacing-half) * 3);
      }

      .Tools__InputWrapper,
      .PaddingInput {
        display: grid;
        grid-template-columns: 1fr 1fr;
        align-items: center;
      }

      .Tools__InputWrapper + .Tools__InputWrapper {
        margin-top: calc(var(--spacing-half) * 3);
      }

      .Tools__InputWrapper > * {
        flex: 1;
      }

      .Tools__InputWrapper input,
      .Tools__InputWrapper select {
        width: auto;
      }

      .Tools__InputWrapper [disabled] {
        opacity: 0.4;
        cursor: default;
      }

      .CropButtonWrapper {
        transform: translate(-100%, var(--spacing));
        z-index: 1;
      }

      .CropButton {
        background: var(--color-fill-default);
        color: var(--color-text-default);
        border-radius: 4px;
        font-size: 16px;
        padding: var(--spacing);
      }

      .EditRegionButton {
        background: var(--color-surface-default);
        color: var(--color-text-default);
        border-radius: var(--border-radius);
        padding: var(--spacing);

        font-size: 16px;
        grid-column: 2;
      }

      .Select {
        position: relative;
      }

      .Select select {
        width: 100%;
      }

      .Select__Icon {
        right: var(--spacing);
        position: absolute;
        top: 50%;
        transform: translateY(-50%);
        pointer-events: none;
      }

      .Select__Icon svg {
        width: 20px;
        height: 20px;
      }

      .ResetButtonContainer {
        padding: 0 var(--spacing-double) var(--spacing-double);
      }

      .ResetButton {
        display: flex;
        border: 1px solid #303030;
        border-radius: var(--border-radius-half);
        color: var(--c-text-primary);
        font-size: 16px;
        padding: var(--spacing-half) var(--spacing);
      }

      .ResetButton svg {
        width: 20px;
        height: 20px;
        margin-right: var(--spacing-half);
        fill: var(--color-fill-icon-active);
      }

      .Details {
        grid-area: details;
        padding: var(--spacing-double);
      }

      .Details__Before {
        color: var(--color-text-default);
      }

      .Details__Before + .Details__Optimized {
        margin-top: var(--spacing-double);
      }

      .Details__OptimizedWrapper {
        flex: 1;
      }

      .Details__Optimized {
        color: #33e19b;
        display: flex;
        align-items: center;
      }

      .Details__OptimizedTitle {
        display: flex;
        align-items: center;
        font-size: 1rem;
      }

      .Details__OptimizedInfo {
        font-size: 1.06rem;
        margin-top: 2px;
      }

      .Details__OptimizedDiff {
        display: flex;
        align-items: center;
        margin-left: var(--spacing);
        padding-left: var(--spacing-half);
        border-left: 1px solid var(--c-border);
      }

      .Details__OptimizedDiffIcon {
        width: 20px;
        height: 20px;
        margin-right: var(--spacing-half);
      }

      .Details__OptimizedDiffIcon svg {
        fill: currentColor;
      }

      .PaddingInput {
        position: relative;
      }

      .PaddingInput__Wrapper {
        flex: 1;
        display: flex;
        justify-content: space-between;
        background: var(--color-input-background);
        border-radius: var(--border-radius);
        padding: 8px;
        color: var(--c-text-primary);
        cursor: pointer;
      }

      .PaddingInput__Label {
        flex: 1;
      }

      .PaddingInput__Color {
        font-variant-numeric: tabular-nums;
        position: relative;
        padding-left: 24px;
      }

      .PaddingInput__Color--NoValue {
        --padding-color: #959696 !important;
        color: #bfbfbf;
      }

      .PaddingInput__Color::before {
        content: "";
        background: var(--padding-color);
        position: absolute;
        left: 0;
        top: 50%;
        transform: translateY(-50%);
        border-radius: 4px;
        border: 1px solid #9fa0a0;
        width: 20px;
        height: 20px;
      }

      .PaddingInput__Cancel {
        display: flex;
        justify-content: center;
        align-items: center;
      }

      .PaddingInput__Cancel {
        display: block;
        width: 20px;
        height: 20px;
      }

      @media screen and (min-width: 320px) {
        .ImageEditor {
          grid-template-columns: 100%;
        }
      }
      @media screen and (min-width: 730px) {
        .ImageEditor {
          grid-template-columns: auto 30%;
          grid-template-rows: auto 1fr;
          grid-row-gap: var(--spacing);
          grid-template-areas:
            "url url"
            "preview tools"
            "code code";
        }
      }

      .ExampleImageButton {
        width: 48px;
      }

      /* Page styles */
      .PageTitle {
        position: relative;
        display: flex;
        justify-content: center;
        align-items: center;
        padding: var(--spacing-double) 0;
      }

      .PageTitle::after {
        content: "";
        background: #5b5b5b;

        position: absolute;
        bottom: 0;
        left: 50%;
        transform: translateX(-50%);
        height: 1px;
        width: 100%;
      }

      .PageTitle__Logo img {
        height: 20px;
      }

      @media screen and (min-width: 730px) {
        .PageTitle {
          height: 100px;
          padding: 0;
        }
        .PageTitle::after {
          width: 100%;
        }
        .PageTitle__Logo img {
          height: 35px;
        }
      }

      .PageTitle__Logo {
        max-height: 100%;
      }

      .PageHero {
        color: var(--color-text-headline-default);
        text-align: center;
        width: 80%;
        margin: 100px auto;
        max-width: 1200px;
      }

      .PageHero h1 {
        font-size: var(--text-size-subtitle);
        line-height: var(--text-size-subtitle__lineheight);
        font-weight: var(--text-size-subtitle__weight);
        margin: 0;
      }

      .PageHero__Heading {
        color: var(--color-text-headline-subdued);
        text-transform: uppercase;
      }

      .PageHero__Detail {
        margin-top: var(--spacing);
        font-size: var(--text-size-title);
        line-height: var(--text-size-title__lineheight);
        font-weight: var(--text-size-title__weight);
      }

      .PageHero__Detail p {
        margin: 0;
      }

      .ImageryStats {
        background: #202120;
        padding: var(--spacing-triple);
      }

      .ImageryStats__Container {
        margin: 0 auto;
        display: grid;
        grid-row-gap: 56px;
      }

      .ImageryStats__Item {
        position: relative;
        padding-left: 20px;
      }

      .ImageryStats__Item::before {
        content: "";
        position: absolute;
        background: var(--color-fill-default);
        border-radius: 2px;
        left: 0;
        top: 50%;
        transform: translateY(-50%);
        width: 4px;
        height: 100%;
      }

      .ImageryStats__Stat {
        position: relative;
        font-size: var(--text-size-stat-title);
        font-weight: var(--text-size-stat-title__weight);
        line-height: var(--text-size-stat-title__lineheight);
      }

      .ImageryStats__Detail {
        font-size: var(--text-size-stat);
        font-weight: var(--text-size-stat__weight);
        line-height: var(--text-size-stat__lineheight);
      }

      [data-bulk-title] {
        position: relative;
      }

      .ImageryFeaturesContainer {
        overflow-x: hidden;
        overflow-x: clip;
      }
      .ImageryFeatures {
        margin: var(--spacing-triple) auto 0 auto;
        max-width: 90%;
      }

      .ImageryFeatures__Title {
        position: relative;
        font-size: var(--text-size-features-title);
        font-weight: var(--text-size-features__weight);
        line-height: var(--text-size-features-title__lineheight);
        width: 90%;
        margin: var(--spacing-quadruple) 0 0 calc(40px + var(--spacing-double));
      }

      .ImageryFeatures__Title::before {
        content: "";
        background: var(--c-text-primary);
        position: absolute;
        top: 50%;
        left: calc(var(--spacing-double) * -1);
        transform: translateX(-100%);
        width: 40px;
        height: 2px;
      }

      .ImageryFeatures__List {
        list-style-type: none;
        width: 90%;
        margin-top: var(--spacing-double);
        padding: 0;
      }

      .ImageryFeatures__List li + li {
        margin-top: var(--spacing-6);
      }

      .ImageryFeatures__List h3 {
        font-size: var(--text-size-features-heading);
        font-weight: var(--text-size-features-heading__weight);
        line-height: var(--text-size-features-heading__lineheight);
        margin: 0;
      }

      .ImageryFeatures__List p {
        color: rgba(255, 255, 255, 0.9);
        font-size: var(--text-size-body);
        line-height: var(--text-size-body__lineheight);
        font-weight: var(--text-size-body__weight);
        margin-top: var(--spacing-quadruple);
      }

      .LinedBackground {
        background-image: linear-gradient(
            rgba(41, 42, 41, 1),
            rgba(41, 42, 41, 1)
          ),
          linear-gradient(rgba(41, 42, 41, 1), rgba(41, 42, 41, 1));

        background-size: 1px, 1px;
        background-position-x: 25%, 75%;
        background-repeat: repeat-y;
      }

      @media screen and (min-width: 730px) {
        .PageTitle::after {
          width: calc(100% - 200px);
        }
        .LinedBackground {
          background-image: linear-gradient(
              rgba(41, 42, 41, 1),
              rgba(41, 42, 41, 1)
            ),
            linear-gradient(rgba(41, 42, 41, 1), rgba(41, 42, 41, 1)),
            linear-gradient(rgba(41, 42, 41, 1), rgba(41, 42, 41, 1)),
            linear-gradient(rgba(41, 42, 41, 1), rgba(41, 42, 41, 1));

          background-size: 1px, 1px, 1px, 1px;
          background-position-x: 100px, 33%, 66%, calc(100% - 100px);
        }
        .ImageryStats {
          padding: 6rem 6vw;
          margin-top: 6rem;
        }
        .ImageryStats__Container {
          display: flex;
          justify-content: space-between;
          max-width: 80%;
        }
        .ImageryStats__Item {
          padding-left: 30px;
        }

        .ImageryFeatures {
          margin: 6rem auto 0 auto;
          padding-bottom: 6rem;
          max-width: 70%;
        }

        .ImageryFeatures__Title {
          margin: 0 auto;
        }

        .ImageryFeatures__Title::before {
          left: -40px;
          width: 100px;
        }

        .ImageryFeatures__List {
          margin: 4rem auto 0 auto;
        }

        .ImageryFeatures__List li + li {
          margin-top: 6rem;
        }

        .ImageryFeatures__List p {
          margin-top: 4rem;
          max-width: 700px;
        }
      }

      [data-bulk-title]::before {
        content: attr(data-bulk-title);
        position: absolute;
        color: #202120;
        top: 0;
        left: 0;
        transform: translateY(-70%);
        font-size: 20vw;
        text-transform: uppercase;
        font-weight: 700;
        width: 100%;
        text-align: center;
      }

      [data-floating-title] {
        position: relative;
      }

      [data-floating-title]::after {
        content: attr(data-floating-title);
        position: absolute;
        top: 50%;
        color: rgba(32, 33, 32, 1);
        font-size: var(--text-size-stat-title);
        font-weight: var(--text-size-stat-title__weight);
        text-transform: uppercase;
        z-index: -1;
      }

      [data-floating-position-1]::after {
        left: 10%;
      }

      [data-floating-position-2]::after {
        left: 20%;
      }

      [data-floating-position-3]::after {
        left: 30%;
      }

      [data-floating-position-4]::after {
        left: 40%;
        white-space: nowrap;
      }

      .uppercase {
        text-transform: uppercase;
      }

      .Footer {
        color: #d7d7db;
      }

      .Footer p {
        font-size: 0.75rem;
        font-weight: bold;
      }

      .Footer a {
        color: #d7d7db;
      }

      .Footer ul {
        list-style-type: none;
        margin: 0;
        padding: 0;
      }

      .Footer__Top ul li + li {
        margin-top: var(--spacing);
      }

      .Footer__Top ul a {
        font-size: 0.875rem;
        text-decoration: none;
      }

      .Footer__Top {
        display: grid;
        grid-column-gap: 1.25rem;
        grid-template-columns: repeat(2, 1fr);
        grid-row-gap: 48px;
        padding: 60px var(--spacing-triple);
      }

      .Footer__Bottom {
        background: var(--color-surface-subdued);
        padding: 30px var(--spacing-triple);
      }

      .Footer__Bottom a {
        text-decoration: none;
        margin: 0 5px;
      }

      .Footer__Bottom ul {
        display: inline-block;
        text-align: center;
      }
      .Footer__Bottom li {
        display: inline-block;
        padding-top: 5px;
      }

      @media screen and (min-width: 730px) {
        .Footer__Top {
          grid-template-columns: repeat(6, 1fr);
          padding: 60px 86px;
        }
        .Footer__Bottom {
          padding: 36px 88px;
        }
        .Footer__Bottom ul {
          grid-template-columns: repeat(6, 1fr);
        }
      }
    </style>
</head>
<body>
<div class="animation" id="animation"></div>
<div class="LinedBackground">
<header>
<div class="PageTitle">
<div class="PageTitle__Logo">
<a href="https://www.shopify.com">
<img src="https://cdn.shopify.com/static/imagery-landing/shopify_monotone_white.svg" alt="Shopify Logo" />
</a>
</div>
</div>
<div class="PageHero">
<h1>
<span class="PageHero__Heading" aria-hidden="true">
Shopify's content delivery network
</span>
<span class="visually-hidden">
Shopify's content delivery network
</span>
</h1>
<div class="PageHero__Detail">
<p>Optimized, fast, and dynamic file transformation</p>
</div>
<div class="PageHero__Illustration"></div>
</div>
</header>
<main>

<div id="app" class="AppContainer"></div>
<div class="ImageryStats" data-bulk-title="Imagery">
<h2 class="visually-hidden">Stats</h2>
<div class="ImageryStats__Container">
<div class="ImageryStats__Item">
<div class="ImageryStats__Stat">20 billion</div>
<div class="ImageryStats__Detail">Files hosted with Shopify</div>
</div>
<div class="ImageryStats__Item">
<div class="ImageryStats__Stat">25 million</div>
<div class="ImageryStats__Detail">File requests per minute</div>
</div>
</div>
</div>
<div class="ImageryFeaturesContainer">
<div class="ImageryFeatures">
<h2 class="ImageryFeatures__Title">
<span class="uppercase" aria-hidden="true">Features</span>
<span class="visually-hidden">Features</span>
</h2>
<ul class="ImageryFeatures__List">
<li>
<h3 data-floating-title="compression" data-floating-position-1>
File compression.
</h3>
<p>
File size is automatically optimized when you upload to
Shopify. The change in file quality isn’t noticeable to the
human eye, but it’ll improve page load time.
</p>
</li>
<li>
<h3 data-floating-title="conversion" data-floating-position-2>
File format conversion.
</h3>
<p>
Shopify automatically detects which file formats are supported
on the client side and sends the best option. Example:
Converts images to WebP or AVIF where supported, but JPG as a fallback
for other clients.
</p>
</li>
<li>
<h3 data-floating-title="dynamic" data-floating-position-3>
Dynamic editing.
</h3>
<p>
Unlike destructive editing, dynamic editing allows you to crop
and transform your image without losing the original file.
Simply indicate the desired state using URL parameters. These
parameters will be interpreted and used to “on-the-fly”
transform the file. The original file remains preserved and
untouched.
</p>
</li>
<li>
<h3 data-floating-title="real time" data-floating-position-4>
All in real time.
</h3>
<p>
The compression, file format conversion, and dynamic editing
all happen in real time. Users have negligible to no wait time
as our servers around the world ensure ultra fast delivery.
</p>
</li>
</ul>
</div>
</div>
</main>
</div>
<footer class="Footer">
<div class="Footer__Top">
<div class="Footer__Block">
<p>News and updates</p>
<ul>
<li>
<a href="https://shopify.dev/changelog">Developer changelog</a>
</li>
<li><a href="https://www.shopifystatus.com/">Shopify status</a></li>
</ul>
</div>
<div class="Footer__Block">
<p>Blogs</p>
<ul>
<li>
<a href="https://www.shopify.com/partners/blog">Partner blog</a>
</li>
<li><a href="https://shopify.engineering/">Engineering blog</a></li>
<li><a href="https://ux.shopify.com/">UX Blog</a></li>
<li><a href="https://www.shopify.com/blog">Shopify Blog</a></li>
</ul>
</div>
<div class="Footer__Block">
<p>Community</p>
<ul>
<li>
<a href="https://community.shopify.com/c/App-Partner-Platform/ct-p/appdev">Developer forums</a
              >
</li>
<li><a href="https://discord.gg/shopifydevs">Devs Discord</a></li>
<li>
<a href="https://www.facebook.com/shopifypartners/">Facebook group</a
              >
</li>
</ul>
</div>
<div class="Footer__Block">
<p>Social</p>
<ul>
<li><a href="https://www.twitch.tv/shopifydevs">Twitch</a></li>
<li><a href="https://www.youtube.com/c/shopifydevs">YouTube</a></li>
<li><a href="https://twitter.com/ShopifyDevs">Twitter</a></li>
</ul>
</div>
<div class="Footer__Block">
<p>Events</p>
<ul>
<li><a href="https://unite.shopify.com/">Unite</a></li>
<li><a href="https://events.shopify.com/partners">Meetups</a></li>
<li>
<a href="https://events.shopify.com/partnertownhall">Partner Town Hall</a
              >
</li>
</ul>
</div>
<div class="Foter__Block">
<p>Legal</p>
<ul>
<li>
<a href="https://www.shopify.com/legal/terms">Terms of Service</a>
</li>
<li>
<a href="https://www.shopify.com/legal/api-terms">API Terms of Service</a
              >
</li>
<li>
<a href="https://www.shopify.com/legal/privacy">Privacy policy</a>
</li>
<li>
<a href="https://www.shopify.com/partners/terms">Partner program</a
              >
</li>
</ul>
</div>
</div>
<div class="Footer__Bottom">
<ul>
<li><a href="https://www.shopify.com/about">About Shopify</a></li>
<li><a href="https://www.shopify.com/plus">Shopify Plus</a></li>
<li><a href="https://www.shopify.com/tools">Shopify Tools</a></li>
<li><a href="https://www.shopify.com/careers">Careers</a></li>
<li><a href="https://investors.shopify.com/">Investors</a></li>
<li><a href="https://www.shopify.com/press">Press and media</a></li>
</ul>
</div>
</footer>
<script type="module">
      import {
        html,
        render,
        useState,
        useEffect,
        useRef,
      } from "https://cdn.shopify.com/static/imagery-landing/htm-preact-standalone-3.1.0.module.js";

      const basicPhotos = [
        {
          dimensions: {
            width: 1850,
            height: 1233,
          },
          url: "https://cdn.shopify.com/static/sample-images/garnished.jpeg",
        },
        {
          dimensions: {
            width: 1487,
            height: 2230,
          },
          url: "https://cdn.shopify.com/static/sample-images/bath.jpeg",
        },
        {
          dimensions: {
            width: 1487,
            height: 2230,
          },
          url: "https://cdn.shopify.com/static/sample-images/teapot.jpg",
        },
        {
          dimensions: {
            width: 1850,
            height: 1233,
          },
          url: "https://cdn.shopify.com/static/sample-images/shoes.jpeg",
        },
      ];

      const tools = [
        { name: "size", label: "Size", component: SizeForm },
        { name: "crop", label: "Crop", component: CropForm },
        { name: "padding", label: "Padding", component: PaddingForm },
      ];

      function App() {
        const [originalUrl, setOriginalUrl] = useState(basicPhotos[0].url);
        const [searchParams, setSearchParams] = useState(new URLSearchParams());
        const [imageDetails, setImageDetails] = useState(null);

        const [croppingMode, setCroppingMode] = useState(false);

        function commitCropUpdates(params) {
          if (params.length < 1) return;
          handleUpdateParams(params);
          setCroppingMode(false);
        }

        const [activeStockPhotoDetails, setActiveStockPhotoDetails] = useState(
          basicPhotos[0]
        );
        useEffect(() => {
          const activeStockPhoto = basicPhotos.find(
            (photo) => photo.url === originalUrl
          );
          setActiveStockPhotoDetails(activeStockPhoto);
        }, [originalUrl]);

        useEffect(() => {
          try {
            const url = new URL(originalUrl);
            if (!url.origin.includes("cdn.shopify") && !url.origin.includes("imagery4.myshopify"))
              throw new Error("Must be a Shopify CDN URL");
            (async () => {
              const image = await fetchImage(originalUrl, searchParams);
              if (image.error) return;
              setImageDetails(image);
            })();
          } catch (error) {}
        }, [originalUrl, searchParams]);

        useEffect(() => {}, [imageDetails]);

        function handleRemoveParams() {
          const newParams = new URLSearchParams();
          setSearchParams(newParams);
        }

        function handleUpdateParams(params) {
          const newParams = new URLSearchParams(searchParams);
          for (const [param, value] of params) {
            if (value == undefined || value === "") {
              newParams.delete(param);
            } else {
              newParams.set(param, value);
            }
          }
          setSearchParams(newParams);
        }

        function handleTemplateChange(template) {
          const newParams = new URLSearchParams();
          setSearchParams(newParams);
          setOriginalUrl(template);
        }

        function handleUpdateUrl({ target: { value } }) {
          try {
            const url = new URL(value);
            const base = `${url.origin}${url.pathname}`;
            const params = url.searchParams;
            params.delete("v");
            setActiveStockPhotoDetails(null);
            setOriginalUrl(base);
            setSearchParams(params);
          } catch (error) {}
        }

        const croppingTool = tools.find((tool) => tool.name === "crop");
        croppingTool.cropping = croppingMode;
        croppingTool.activateCrop = () => {
          setCroppingMode(true);
        };
        const cropping = searchParams.get("crop") === "region";

        return html`
          <div class="ImageEditor">
            <div class="EditorUrl">
              <div class="EditorUrl__Bar">
                <div class="EditorUrl__Icon"><${Icon} icon="globe" /></div>
                <label class="visually-hidden" for="image_url"
                  >Shopify image url</label
                >
                <input
                  id="image_url"
                  onChange=${handleUpdateUrl}
                  type="text"
                  name="image_url"
                  value=${originalUrl}
                  defaultValue=${originalUrl}
                />
              </div>
              <div class="EditorUrl__HelpText">
                <span
                  >Paste one of your Shopify hosted images, or use our
                  samples.</span
                >
              </div>
            </div>
            <div class="Preview">
              <div class="Templates">
                ${basicPhotos.map(
                  ({ url }) =>
                    html`<${ExampleImage}
                      src=${url}
                      active=${url === originalUrl}
                      onClick=${handleTemplateChange}
                    />`
                )}
              </div>
              <div
                class="Preview__Image ${originalUrl === basicPhotos[0].url
                  ? "active"
                  : null}"
              >
                ${croppingMode
                  ? html`
                      <div class="CropToolContainer">
                        <${CropTool}
                          src=${originalUrl}
                          params=${searchParams}
                          onChange=${commitCropUpdates}
                        />
                      </div>
                    `
                  : html`
                      <${Preview}
                        original=${originalUrl}
                        params=${searchParams}
                        updateParams=${handleUpdateParams}
                      />
                    `}
              </div>
            </div>

            <div class="CodePreview">
              <${Outputs}
                original=${originalUrl}
                params=${searchParams}
                imageDetails=${imageDetails}
              />
            </div>
            <div class="Tools">
              <${ImageDetails}
                details=${imageDetails}
                stockDetails=${activeStockPhotoDetails}
              />
              <div class="Tools__Inputs">
                <${Tools}
                  updateParams=${handleUpdateParams}
                  params=${searchParams}
                  tools=${tools}
                />
              </div>
              <div class="ResetButtonContainer">
                <button class="ResetButton" onClick=${handleRemoveParams}>
                  <${Icon} icon="reset" />Reset
                </button>
              </div>
            </div>
          </div>
        `;
      }

      function ImageDetails({ details, stockDetails }) {
        if (!details) return;
        const {
          dimensions: { width, height },
          type,
          size,
          originalSize,
          originalType,
        } = details;
        const [, ext] = type.split("/");

        let stockWidth, stockHeight;
        if (stockDetails) {
          stockWidth = stockDetails.dimensions.width;
          stockHeight = stockDetails.dimensions.height;
        }

        const sizeDiff = originalSize ?  (1 - size / originalSize) * 100 : null;
        const origType = originalType ? originalType.split("/")[1] : null;

        const optimizedDiffMarkup =
          sizeDiff && sizeDiff > 1
            ? html`<div class="Details__OptimizedDiff">
                <div class="Details__OptimizedDiffIcon">
                  <${Icon} icon="arrow" />
                </div>
                <div class="Details__OptimizedDiffPercentage">
                  ${sizeDiff.toFixed(0)}%
                </div>
              </div>`
            : null;

        const formatBytesMarkup = originalSize ? formatBytes(originalSize) : null;
        const extMarkup = origType ?  ` • ${origType.toUpperCase()}`: null;
        const dimensionMarkup = stockWidth && stockHeight ? ` • ${stockWidth} × ${" "} ${stockHeight}`: null;

        return html`
          <div class="Details">
            ${optimizedDiffMarkup && formatBytesMarkup && extMarkup
              ? html`
                  <div class="Details__Before">
                    <div class="Details__OptimizedTitle">Original file</div>
                    <div class="Details__OptimizedInfo">
                      ${formatBytesMarkup}
                      ${extMarkup}
                      ${dimensionMarkup}
                    </div>
                  </div>
                `
              : null}
            <div class="Details__Optimized">
              <div class="Details__OptimizedWrapper">
                <div class="Details__OptimizedTitle">
                  <div>Optimized file</div>
                  <div>${optimizedDiffMarkup}</div>
                </div>
                <div class="Details__OptimizedInfo">
                  ${formatBytes(size)} • ${ext.toUpperCase()} • ${width} ×
                  ${" "} ${height}
                </div>
              </div>
            </div>
          </div>
        `;
      }

      function ExampleImage({ src, active, onClick }) {
        function handleClick() {
          onClick(src);
        }
        const url = new URL(src);
        const [filename, ext] = url.pathname.split("/").pop().split(".");
        const params = new URLSearchParams({
          width: 200,
          height: 200,
          crop: "center",
        });
        return html`
          <button
            class=${`ExampleImageButton ${active && "active"}`}
            onClick=${handleClick}
            aria-label="Preview with ${filename}"
          >
            <img src=${`${src}?${params}`} alt="" />
          </button>
        `;
      }

      function Outputs({ original, params, imageDetails }) {
        const [activeTab, setActiveTab] = useState("url");
        return html`
          <div class="CodePreview__Tabs">
            <button
              class=${activeTab === "url" && "active"}
              onClick=${() => setActiveTab("url")}
            >
              URL
            </button>
            <button
              class=${activeTab === "liquid" && "active"}
              onClick=${() => setActiveTab("liquid")}
            >
              Liquid
            </button>
            <button
              class=${activeTab === "hydrogen" && "active"}
              onClick=${() => setActiveTab("hydrogen")}
            >
              Hydrogen
            </button>
          </div>
          <div class="CodePreview__Preview">
            ${activeTab === "url"
              ? html`
                  <div class="CodeBlock">
                    <${SegmentedUrl}
                      url=${original}
                      params=${params}
                      groups=${[
                        ["width", "height"],
                        [
                          "crop",
                          "crop_top",
                          "crop_left",
                          "crop_width",
                          "crop_height",
                        ],
                      ]}
                    />
                    <${CopyButton} text=${`${original}?${params}`} />
                  </div>
                `
              : null}
            ${activeTab === "liquid"
              ? html`
                  <${CodeBlock}
                    code=${liquidImage(original, params, imageDetails)}
                    language="liquid"
                  />
                `
              : null}
            ${activeTab === "hydrogen"
              ? html`
                  <${CodeBlock}
                    code=${hydrogenImage(`${original}?${params}`, imageDetails)}
                    language="tsx"
                  />
                `
              : null}
          </div>
        `;
      }

      function Preview({ original, params }) {
        const imageSource = `${original}?${params}`;
        const filename = new URL(original).pathname.split("/").pop();
        return html`
          <div class="Preview__ImageContainer">
            <img src=${imageSource} alt="Result for ${filename}" />
          </div>
        `;
      }

      function Tools({ updateParams, params: defaultValues, tools }) {
        const [activeTool, setActiveTool] = useState("size");

        function handleUpdateParams(params) {
          updateParams(params);
        }

        return tools.map(({ name, component: Component, ...rest }) => {
          return html`
            <div>
              <${Component}
                updateParams=${handleUpdateParams}
                defaultValues=${defaultValues}
                ...${rest}
              />
            </div>
          `;
        });
      }

      function SizeForm({ updateParams, defaultValues }) {
        function handleUpdateParams({ target: { name, value } }) {
          updateParams([[name, value]]);
        }

        return html`
          <div>
            <${TextField}
              id="tool_width"
              label="Width"
              name="width"
              type="number"
              onChange=${handleUpdateParams}
              value=${defaultValues?.get("width")}
            />
            <${TextField}
              id="tool_height"
              label="Height"
              name="height"
              type="number"
              onChange=${handleUpdateParams}
              value=${defaultValues?.get("height")}
            />
          </div>
        `;
      }

      function PaddingForm({ updateParams, defaultValues }) {
        function handleColorChange({ target: { value } }) {
          updateParams([["pad_color", value.replace("#", "")]]);
        }

        function handleClearValue(event) {
          event.stopPropagation();
          updateParams([["pad_color"]]);
        }

        const paddingColor = defaultValues.get("pad_color");
        const color = paddingColor ? `#${paddingColor}` : "Add...";

        return html`
          <label class="PaddingInput">
            <div class="visually-hidden">
              <input
                class="visually-hidden"
                value=${color}
                type="color"
                onChange=${handleColorChange}
              />
            </div>
            <span title="pad_color" class="PaddingInput__Label"
              >Padding color</span
            >
            <div class="PaddingInput__Wrapper">
              <div
                class="PaddingInput__Color ${paddingColor
                  ? ""
                  : "PaddingInput__Color--NoValue"}"
                style=${{ "--padding-color": color }}
              >
                ${color}
              </div>
              ${paddingColor
                ? html`<button
                    class="PaddingInput__Cancel"
                    onClick=${handleClearValue}
                    aria-label="Remove padding"
                  >
                    <${Icon} icon="cancel" />
                  </button>`
                : null}
            </div>
          </label>
        `;
      }

      function TextField({ id, type, label, name, value, disabled, onChange }) {
        return html`
          <div class="Tools__InputWrapper">
            <label title=${name} for=${id}>${label}</label>
            <input
              id=${id}
              type=${type || "text"}
              min="1"
              name=${name}
              onChange=${onChange}
              disabled=${Boolean(disabled)}
              placeholder="–"
              value=${value}
            />
          </div>
        `;
      }

      const cropModeOptions = [
        { label: "None", value: "" },
        { label: "Center", value: "center" },
        { label: "Top", value: "top" },
        { label: "Bottom", value: "bottom" },
        { label: "Left", value: "left" },
        { label: "Right", value: "right" },
        // { label: 'Region', value: 'region' },
      ];
      function CropForm({
        updateParams,
        defaultValues,
        activateCrop,
        cropping,
      }) {
        const [cropMode, setCropMode] = useState(null);
        function handleCropModeChange({ target: { value } }) {
          if (value === "none") return updateParams([["crop"]]);
          let paramsToUpdate = [["crop", value]];
          if (value !== "region") {
            paramsToUpdate = [
              ...paramsToUpdate,
              ["crop_top"],
              ["crop_left"],
              ["crop_width"],
              ["crop_height"],
            ];
          }
          updateParams(paramsToUpdate);
        }

        function handleCropChange({ target: { name, value } }) {
          updateParams([[name, value]]);
        }

        let values = {};
        for (const [param, value] of defaultValues) {
          values[param] = value;
        }

        setCropMode(defaultValues.get("crop"));
        useEffect(() => {
          if (!cropping && cropMode === "region") activateCrop();
        }, [cropMode]);

        const cropRegion = defaultValues.get("crop") === "region";
        return html`
          <div>
            <div class="Tools__InputWrapper">
              <label for="tool_crop_mode">Crop</label>
              <${Select}
                id="tool_crop_mode"
                value=${defaultValues.get("crop")}
                options=${cropModeOptions}
                onChange=${handleCropModeChange}
              />
            </div>
            ${cropRegion
              ? html`
                  <div class="Tools__InputWrapper">
                    <button
                      class="EditRegionButton"
                      onClick=${activateCrop}
                      disabled=${cropping}
                    >
                      Edit region
                    </button>
                  </div>

                  <${TextField}
                    type="number"
                    id="tool_crop_left"
                    label="Crop left"
                    name="crop_left"
                    onChange=${handleCropChange}
                    disabled=${!cropRegion}
                    value=${defaultValues?.get("crop_left")}
                  />
                  <${TextField}
                    type="number"
                    id="tool_crop_top"
                    label="Crop top"
                    name="crop_top"
                    onChange=${handleCropChange}
                    disabled=${!cropRegion}
                    value=${defaultValues?.get("crop_top")}
                  />

                  <${TextField}
                    type="number"
                    id="tool_crop_width"
                    label="Crop width"
                    name="crop_width"
                    onChange=${handleCropChange}
                    disabled=${!cropRegion}
                    value=${defaultValues?.get("crop_width")}
                  />
                  <${TextField}
                    type="number"
                    id="tool_crop_height"
                    label="Crop Height"
                    name="crop_height"
                    onChange=${handleCropChange}
                    disabled=${!cropRegion}
                    value=${defaultValues?.get("crop_height")}
                  />
                `
              : null}
          </div>
        `;
      }

      function Select({ id, value, options, onChange }) {
        const optionsMarkup = options.map(({ label, value }) => {
          return html` <option value=${value}>${label}</option> `;
        });
        return html`
          <div class="Select">
            <select id=${id} value=${value} onChange=${onChange}>
              ${optionsMarkup}
            </select>
            <div class="Select__Icon"><${Icon} icon="selector" /></div>
          </div>
        `;
      }

      function CropTool({ src, params, onChange }) {
        const cropToolRef = useRef(null);
        const croppr = useRef(null);
        const [cropPosition, setCropPosition] = useState([]);
        const [changes, setChanges] = useState([]);
        function commitCropChange() {
          onChange(changes);
        }
        useEffect(() => {
          function handleImageCropChange(value) {
            const hasValues = Object.values(value).every(
              (value) => !isNaN(value)
            );
            if (!hasValues) return;
            const { x, y, width, height } = value;
            setChanges([
              ["crop_left", x],
              ["crop_top", y],
              ["crop_width", width],
              ["crop_height", height],
            ]);
          }

          function setupCroppr() {
            if (croppr.current) {
              croppr.current.destroy();
            }

            const image = new Image();
            image.src = src;
            image.onload = () => {
              cropToolRef.current.appendChild(image);
              const scaledImageWidth = image.width;
              const scaledImageHeight = image.height;
              croppr.current = new Croppr(image, {
                onCropEnd: handleImageCropChange,
                startSize: [50, 50, "%"],
                onInitialize(instance) {
                  instance.resizeTo(
                    scaledImageWidth / 2,
                    scaledImageHeight / 2,
                    [0, 0]
                  );
                  instance.moveTo(scaledImageWidth / 4, scaledImageHeight / 4);
                  setCropPosition([instance.box.x2, instance.box.y2]);
                },
                onCropMove() {
                  setCropPosition([
                    croppr.current.box.x2,
                    croppr.current.box.y2,
                  ]);
                },
              });
            };
          }
          setupCroppr();
        }, [src]);

        const cropButtonTop = cropPosition[1] ? `${cropPosition[1]}px` : "100%";
        const cropButtonLeft = cropPosition[0] ? `${cropPosition[0]}px` : "0";
        return html`<div style="position: relative">
          <div
            style=${{ position: "relative", zIndex: 0 }}
            ref=${cropToolRef}
          ></div>
          <div
            style=${{
              position: "absolute",
              top: cropButtonTop,
              left: cropButtonLeft,
            }}
            class="CropButtonWrapper"
          >
            <button class="CropButton" onClick=${commitCropChange}>Done</button>
          </div>
        </div>`;
      }

      function CodeBlock({ code, language }) {
        const codeContainerRef = useRef(null);
        useEffect(() => {
          const preTag = document.createElement("pre");
          const codeTag = document.createElement("code");
          codeTag.setAttribute("class", `language-${language}`);
          codeTag.textContent = code;
          preTag.appendChild(codeTag);
          codeContainerRef.current.replaceChildren(preTag);
          Prism.highlightAll();
        }, [code]);

        return html`<div class="CodeBlock">
          <div class="CodeBlock__Code" ref=${codeContainerRef}></div>
          <${CopyButton} text=${code} />
        </div>`;
      }

      function SegmentedUrl({ url, params, groups }) {
        let paramGroups = [];

        for (const group of groups) {
          const newParams = new URLSearchParams();
          for (const item of group) {
            const value = params.get(item);
            if (value) newParams.append(item, params.get(item));
          }
          paramGroups.push(newParams);
        }

        // anything not manually grouped should be its own group
        const groupedParamList = groups.flat();
        for (const [param, value] of params) {
          if (!groupedParamList.includes(param)) {
            const newParams = new URLSearchParams();
            newParams.append(param, value);
            paramGroups.push(newParams);
          }
        }

        paramGroups = paramGroups.filter((param) => {
          return Array.from(param.entries()).length > 0;
        });

        return html`<span class="SegmentedUrl CodeBlock__Code"
          >${url}${ paramGroups.length > 0 ? "?" : ""}${paramGroups.map(
            (group, index) =>
              html`${index > 0 ? "&" : ""}<span>${group.toString()}</span>`
          )}</span
        >`;
      }

      function Icon({ icon }) {
        switch (icon) {
          case "reset":
            return html`<svg
              viewBox="0 0 20 20"
              xmlns="http://www.w3.org/2000/svg"
            >
              <path
                d="M17 9a1 1 0 01-1-1c0-.551-.448-1-1-1H5.414l1.293 1.293a.999.999 0 11-1.414 1.414l-3-3a.999.999 0 010-1.414l3-3a.997.997 0 011.414 0 .999.999 0 010 1.414L5.414 5H15c1.654 0 3 1.346 3 3a1 1 0 01-1 1zM3 11a1 1 0 011 1c0 .551.448 1 1 1h9.586l-1.293-1.293a.999.999 0 111.414-1.414l3 3a.999.999 0 010 1.414l-3 3a.999.999 0 11-1.414-1.414L14.586 15H5c-1.654 0-3-1.346-3-3a1 1 0 011-1z"
              />
            </svg>`;
          case "copy":
            return html`<svg
              viewBox="0 0 16 16"
              xmlns="http://www.w3.org/2000/svg"
            >
              <path
                fill-rule="evenodd"
                d="M7 1a1 1 0 0 0 0 2h2a1 1 0 0 0 0-2H7zM4 2a1 1 0 0 1 1 1 1 1 0 0 0 1 1h4a1 1 0 0 0 1-1 1 1 0 1 1 2 0v10a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V3a1 1 0 0 1 1-1z"
                clip-rule="evenodd"
              />
            </svg>`;
          case "selector":
            return html`<svg
              viewBox="0 0 20 20"
              xmlns="http://www.w3.org/2000/svg"
            >
              <path
                d="M10 14a.997.997 0 01-.707-.293l-5-5a.999.999 0 111.414-1.414L10 11.586l4.293-4.293a.999.999 0 111.414 1.414l-5 5A.997.997 0 0110 14z"
              />
            </svg>`;
          case "numbers":
            return html`<svg
              viewBox="0 0 20 20"
              xmlns="http://www.w3.org/2000/svg"
            >
              <path
                fill-rule="evenodd"
                d="M12.586 6H7.414c-.89 0-1.337-1.077-.707-1.707l2.586-2.586a1 1 0 0 1 1.414 0l2.586 2.586c.63.63.184 1.707-.707 1.707zm-5.172 8h5.172c.89 0 1.337 1.077.707 1.707l-2.586 2.586a1 1 0 0 1-1.414 0l-2.586-2.586c-.63-.63-.184-1.707.707-1.707z"
                clip-rule="evenodd"
              />
            </svg>`;
          case "globe":
            return html`<svg
              viewBox="0 0 20 20"
              xmlns="http://www.w3.org/2000/svg"
            >
              <path
                fill-rule="evenodd"
                d="M3.07 6a8.025 8.025 0 014.262-3.544A12.802 12.802 0 005.595 6H3.07zm-.818 2A8.015 8.015 0 002 10c0 .69.088 1.36.252 2h2.89A13.886 13.886 0 015 10c0-.704.051-1.371.143-2H2.252zm4.916 0C7.06 8.62 7 9.286 7 10c0 .713.061 1.38.168 2h5.664c.107-.62.168-1.287.168-2 0-.714-.061-1.38-.168-2H7.168zm7.69 0c.09.629.142 1.296.142 2s-.051 1.371-.143 2h2.891c.165-.64.252-1.31.252-2s-.087-1.36-.252-2h-2.89zm2.072-2h-2.525a12.805 12.805 0 00-1.737-3.544A8.025 8.025 0 0116.93 6zm-4.638 0H7.708c.324-.865.725-1.596 1.124-2.195.422-.633.842-1.117 1.168-1.452.326.335.746.82 1.168 1.452.4.599.8 1.33 1.124 2.195zm-1.124 10.195c.4-.599.8-1.33 1.124-2.195H7.708c.324.865.725 1.596 1.124 2.195.422.633.842 1.117 1.168 1.452.326-.335.746-.82 1.168-1.452zM3.07 14h2.525a12.802 12.802 0 001.737 3.544A8.025 8.025 0 013.07 14zm9.762 3.305a12.9 12.9 0 01-.164.24A8.025 8.025 0 0016.93 14h-2.525a12.805 12.805 0 01-1.573 3.305zM20 10c0 5.52-4.472 9.994-9.99 10h-.022C4.47 19.994 0 15.519 0 10 0 4.477 4.477 0 10 0s10 4.477 10 10z"
              />
            </svg>`;
          case "cancel":
            return html`<svg
              viewBox="0 0 20 20"
              xmlns="http://www.w3.org/2000/svg"
            >
              <path
                d="M11.414 10l4.293-4.293a.999.999 0 10-1.414-1.414L10 8.586 5.707 4.293a.999.999 0 10-1.414 1.414L8.586 10l-4.293 4.293a.999.999 0 101.414 1.414L10 11.414l4.293 4.293a.997.997 0 001.414 0 .999.999 0 000-1.414L11.414 10z"
              />
            </svg>`;
          case "arrow":
            return html`<svg
              xmlns="http://www.w3.org/2000/svg"
              viewBox="0 0 20 20"
            >
              <path
                fill-rule="evenodd"
                d="m10.707 17.707 5-5a.999.999 0 1 0-1.414-1.414L11 14.586V3a1 1 0 1 0-2 0v11.586l-3.293-3.293a.999.999 0 1 0-1.414 1.414l5 5a.999.999 0 0 0 1.414 0"
                clip-rule="evenodd"
                opacity=".8"
              />
            </svg>`;
          default:
            return null;
        }
      }

      function CopyButton({ text }) {
        const textareaRef = useRef(null);

        async function copyText() {
          try {
            await navigator.clipboard.writeText(text);
          } catch (error) {
            try {
              textareaRef.select();
              document.execCommand("copy");
            } catch (error2) {
              console.log(`Text not copied`, error, error2);
            }
          }
        }

        return html`
          <button class="CopyButton" onClick=${copyText}>
            <${Icon} icon="copy" /><span>Copy</span>
          </button>
          <label class="visually-hidden" for="copy-textarea">
            Copy text to clipboard
          </label>
          <textarea
            id="copy-textarea"
            class="visually-hidden"
            ref=${textareaRef}
            value=${text}
          ></textarea>
        `;
      }

      /** Render App */

      render(html`<${App} />`, document.getElementById("app"));

      /** Utility */

      async function fetchImage(url, params) {
        try {
          new URL(url);
        } catch (error) {
          return { error };
        }
        const response = await fetch(`${url}?${params}`, {
          headers: {
            Accept: "image/avif,image/webp,image/apng,image/*,*/*;q=0.8", // Default accept headers for Chrome
          },
        });
        const blob = await response.blob();
        const blobUrl = URL.createObjectURL(blob);

        const imageDimensions = await new Promise((resolve, reject) => {
          const tempImage = new Image();
          tempImage.src = blobUrl;

          tempImage.addEventListener(
            "load",
            ({ target: { width, height } }) => {
              resolve({ width, height });
            }
          );
          tempImage.addEventListener("error", (error) => reject({ error }));
        });

        const { type, size } = blob;

        return {
          type,
          size,
          originalSize: parseInt(response.headers.get("Source-Length")),
          originalType: response.headers.get("Source-Type"),
          dimensions: imageDimensions,
          url: blobUrl,
          blob,
        };
      }

      function getLiquidObject(url) {
        const urlObject = url.split("/")[9];

        const formattableUrlObjects = ["articles", "collections", "products"];

        if (formattableUrlObjects.includes(urlObject)) {
          return urlObject.slice(0, -1);
        }

        return "product";
      }

      function liquidImage(url, params, imageDetails) {
        const liquidParams = new URLSearchParams();
        for (const [param, value] of params) {
          if (!["width", "height"].includes(param)) {
            liquidParams.set(param, `'${value}'`);
          } else {
            liquidParams.set(param, value);
          }
        }
        if (
          params.get("pad_color") &&
          !(params.get("width") || params.get("height"))
        ) {
          const { width } = imageDetails.dimensions;
          liquidParams.set("width", width);
        }

        const liquidParamArray = Array.from(liquidParams);
        return `{{ ${getLiquidObject(url)} | image_url${
          liquidParamArray.length > 0 ? ": " : ""
        }${liquidParamArray
          .map(([param, value]) => `${param}: ${value}`)
          .join(", ")} }}`;
      }

      function hydrogenImage(url, imageDetails) {
        const urlObject = new URL(url);
        const params = urlObject.searchParams;
        const width = params.get("width") || imageDetails?.dimensions?.width;
        const height = params.get("height") || imageDetails?.dimensions?.height;
        const widthProp = width ? ` width={${width}}` : "";
        const heightProp = height ? ` height={${height}}` : "";

        return `import {Image} from '@shopify/hydrogen';
      export default function Component() {
        return <Image src="${urlObject.origin}${urlObject.pathname}${ Array.from(params).length > 0 ? "?" : ""}${params}"${widthProp}${heightProp} />;
      }
      `;
      }

      function formatBytes(bytes, decimals) {
        if (bytes == 0) return "0 Bytes";
        var k = 1024,
          dm = decimals || 2,
          sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"],
          i = Math.floor(Math.log(bytes) / Math.log(k));
        return (
          parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i]
        );
      }
    </script>
</body>
</html>

Latest requests

# Url Url Source Date
1 https://eightsleep.dexecure.net/ 2024-05-18 22:26:54
2 https://thebolditalic.com/?gi=14c5… 2024-05-18 22:26:53
3 https://thebolditalic.com/?gi=2e2e… 2024-05-18 22:26:52
4 https://thebolditalic.com/?gi=5d49… 2024-05-18 22:26:51
5 https://thebolditalic.com/?gi=083f… 2024-05-18 22:26:51
6 https://thebolditalic.com/?gi=3fc6… 2024-05-18 22:26:50
7 https://thebolditalic.com/?gi=1717… 2024-05-18 22:26:50
8 https://thebolditalic.com/?gi=ed38… 2024-05-18 22:26:49
9 https://thebolditalic.com/?gi=89d4… 2024-05-18 22:26:49
10 https://thebolditalic.com/?gi=243d… 2024-05-18 22:26:48
11 https://thebolditalic.com/?gi=50a7… 2024-05-18 22:26:47
12 https://storage.googleapis.com/chi… 2024-05-18 22:26:45
13 https://thebolditalic.com/?gi=f7ed… 2024-05-18 22:26:45
14 https://thebolditalic.com/?gi=eb21… 2024-05-18 22:26:45
15 https://thebolditalic.com/?gi=c492… 2024-05-18 22:26:44
16 https://thebolditalic.com/?gi=71b9… 2024-05-18 22:26:43
17 https://thebolditalic.com/?gi=b8a1… 2024-05-18 22:26:42
18 https://thebolditalic.com/?gi=7568… 2024-05-18 22:26:41
19 https://csapagy06284.fitnell.com/ 2024-05-18 22:26:41
20 https://thebolditalic.com/?gi=4579… 2024-05-18 22:26:41