Jay V 5 месяцев назад
Родитель
Сommit
3359417378

+ 2 - 0
cloud/app/src/asset/lander/check.svg

@@ -0,0 +1,2 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z"/></svg>
+

+ 2 - 0
cloud/app/src/asset/lander/copy.svg

@@ -0,0 +1,2 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 512 512"><rect width="336" height="336" x="128" y="128" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32" rx="57" ry="57"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="m383.5 128l.5-24a56.16 56.16 0 0 0-56-56H112a64.19 64.19 0 0 0-64 64v216a56.16 56.16 0 0 0 56 56h24"/></svg>
+

+ 0 - 0
cloud/app/src/asset/screenshot-github.webp → cloud/app/src/asset/lander/screenshot-github.png


+ 0 - 0
cloud/app/src/asset/screenshot-splash.webp → cloud/app/src/asset/lander/screenshot-splash.png


+ 0 - 0
cloud/app/src/asset/screenshot-vscode.webp → cloud/app/src/asset/lander/screenshot-vscode.png


BIN
cloud/app/src/asset/lander/screenshot.png


+ 18 - 0
cloud/app/src/asset/logo-ornate-light.svg

@@ -0,0 +1,18 @@
+<svg width="288" height="50" viewBox="0 0 288 50" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M8 16.5H24V33H8V16.5Z" fill="black" fill-opacity="0.15"/>
+<path d="M48 16.5H64V33H48V16.5Z" fill="black" fill-opacity="0.15"/>
+<path d="M120 16.5H136V33H120V16.5Z" fill="black" fill-opacity="0.15"/>
+<path d="M160 16.5H176V33H160V16.5Z" fill="black" fill-opacity="0.15"/>
+<path d="M192 16.5H208V33H192V16.5Z" fill="black" fill-opacity="0.15"/>
+<path d="M232 16.5H248V33H232V16.5Z" fill="black" fill-opacity="0.15"/>
+<path d="M264 0H288V8.5H272V16.5H288V25H272V33H288V41.5H264V0Z" fill="black" fill-opacity="0.95"/>
+<path d="M248 0H224V41.5H248V33H232V8.5H248V0Z" fill="black" fill-opacity="0.95"/>
+<path d="M256 8.5H248V33H256V8.5Z" fill="black" fill-opacity="0.95"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M184 0H216V41.5H184V0ZM208 8.5H192V33H208V8.5Z" fill="black" fill-opacity="0.95"/>
+<path d="M144 8.5H136V41.5H144V8.5Z" fill="black" fill-opacity="0.55"/>
+<path d="M136 0H112V41.5H120V8.5H136V0Z" fill="black" fill-opacity="0.55"/>
+<path d="M80 0H104V8.5H88V16.5H104V25H88V33H104V41.5H80V0Z" fill="black" fill-opacity="0.55"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M40 0H72V41.5H48V49.5H40V0ZM64 8.5H48V33H64V8.5Z" fill="black" fill-opacity="0.55"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H32V41.5955H0V0ZM24 8.5H8V33H24V8.5Z" fill="black" fill-opacity="0.55"/>
+<path d="M152 0H176V8.5H160V33H176V41.5H152V0Z" fill="black" fill-opacity="0.95"/>
+</svg>

+ 3 - 3
cloud/app/src/entry-server.tsx

@@ -1,5 +1,5 @@
 // @refresh reload
-import { createHandler, StartServer } from "@solidjs/start/server";
+import { createHandler, StartServer } from "@solidjs/start/server"
 
 export default createHandler(() => (
   <StartServer
@@ -11,11 +11,11 @@ export default createHandler(() => (
           <link rel="icon" href="/favicon.ico" />
           {assets}
         </head>
-        <body data-color-mode="dark">
+        <body>
           <div id="app">{children}</div>
           {scripts}
         </body>
       </html>
     )}
   />
-));
+))

+ 301 - 79
cloud/app/src/routes/index.css

@@ -1,24 +1,43 @@
 [data-page="home"] {
-  --color-bg: oklch(0.2097 0.008 274.53);
-  --color-border: oklch(0.46 0.02 269.88);
-  --color-text: #ffffff;
-  --color-text-secondary: oklch(0.72 0.01 270.15);
-  --color-text-dimmed: hsl(224, 7%, 46%);
-  padding: var(--space-6);
+  --color-text: hsl(224, 10%, 10%);
+  --color-text-secondary: hsl(224, 7%, 46%);
+  --color-text-dimmed: hsl(224, 6%, 63%);
+
+  --color-border: hsl(224, 6%, 77%);
+}
+
+[data-page="home"] {
+  @media (prefers-color-scheme: dark) {
+    --color-text: hsl(0, 0%, 100%);
+    --color-text-secondary: hsl(224, 6%, 66%);
+    --color-text-dimmed: hsl(224, 7%, 46%);
+
+    --color-border: hsl(224, 6%, 36%);
+  }
+}
+
+[data-page="home"] {
+  --padding: 3rem;
+  --vertical-padding: 1.5rem;
+  --heading-font-size: 1.5625rem;
+
+  @media (max-width: 30rem) {
+    --padding: 1rem;
+    --vertical-padding: 0.75rem;
+    --heading-font-size: 1.375rem;
+  }
+
   font-family: var(--font-mono);
   color: var(--color-text);
+  padding: calc(var(--padding) + 1rem);
 
   a {
     color: var(--color-text);
     text-decoration: underline;
     text-underline-offset: var(--space-0-75);
+    text-decoration-thickness: 1px;
   }
 
-  background: var(--color-bg);
-  position: fixed;
-  overflow-y: scroll;
-  inset: 0;
-
   [data-component="content"] {
     max-width: 67.5rem;
     margin: 0 auto;
@@ -26,56 +45,98 @@
   }
 
   [data-component="top"] {
-    padding: var(--space-12);
+    padding: var(--padding);
     display: flex;
     flex-direction: column;
     align-items: start;
-    gap: var(--space-4);
+    gap: calc(var(--vertical-padding) / 2);
+
+    img {
+      height: auto;
+      width: clamp(200px, 70vw, 400px);
+    }
 
-    [data-slot="logo"] {
-      height: 70px;
+    [data-slot="logo dark"] {
+      display: none;
+    }
+
+    @media (prefers-color-scheme: dark) {
+      [data-slot="logo light"] {
+        display: none;
+      }
+      [data-slot="logo dark"] {
+        display: block;
+      }
     }
 
     [data-slot="title"] {
-      font-size: var(--font-size-2xl);
+      line-height: 1.25;
+      font-size: var(--heading-font-size);
       text-transform: uppercase;
     }
   }
 
   [data-component="cta"] {
-    height: var(--space-19);
     border-top: 2px solid var(--color-border);
     display: flex;
+    justify-content: flex-start;
+    align-items: stretch;
+
+    @media (max-width: 50rem) {
+      flex-direction: column;
+    }
 
     [data-slot="left"] {
-      display: flex;
-      padding: 0 var(--space-12);
+      flex: 0 0 auto;
+      text-align: center;
+      line-height: 1.4;
+      padding: var(--vertical-padding) var(--padding);
       text-transform: uppercase;
-      text-decoration: underline;
-      align-items: center;
-      justify-content: center;
-      text-underline-offset: var(--space-0-75);
-      border-right: 2px solid var(--color-border);
+
+      @media (max-width: 50rem) {
+        padding-bottom: calc(var(--vertical-padding) + 4px);
+      }
     }
 
     [data-slot="right"] {
       flex: 1;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      gap: var(--space-2-5);
-      padding: 0 var(--space-6);
+      padding: var(--vertical-padding) 1rem;
+      border-left: 2px solid var(--color-border);
+
+      @media (max-width: 50rem) {
+        border-left: none;
+        border-top: 2px solid var(--color-border);
+      }
     }
 
     [data-slot="command"] {
       all: unset;
       display: flex;
       align-items: center;
+      justify-content: center;
       cursor: pointer;
       color: var(--color-text-secondary);
-      font-size: var(--font-size-lg);
+      font-size: 1.125rem;
       font-family: var(--font-mono);
       gap: var(--space-2);
+      width: 100%;
+
+      & > span {
+        @media (max-width: 24rem) {
+          font-size: 0.875rem;
+        }
+        @media (max-width: 30rem) {
+          [data-slot="protocol"] {
+            display: none;
+          }
+        }
+        @media (max-width: 43rem) {
+          text-align: center;
+          span:first-child {
+            display: block;
+          }
+        }
+      }
     }
 
     [data-slot="highlight"] {
@@ -86,7 +147,7 @@
 
   [data-component="features"] {
     border-top: 2px solid var(--color-border);
-    padding: var(--space-12);
+    padding: var(--padding);
 
     [data-slot="list"] {
       padding-left: var(--space-4);
@@ -98,7 +159,7 @@
 
         strong {
           text-transform: uppercase;
-          font-weight: 600;
+          font-weight: 700;
         }
       }
 
@@ -120,24 +181,28 @@
     }
   }
 
-  [data-component="title"] {
-    letter-spacing: -0.03125rem;
-    text-transform: uppercase;
-    font-weight: 400;
-    font-size: var(--font-size-md);
-    flex-shrink: 0;
-    color: oklch(0.55 0.02 269.87);
-  }
-
   [data-component="method"] {
-    padding: var(--space-4) var(--space-6);
     display: flex;
+    padding: calc(var(--vertical-padding) / 2) calc(var(--padding) / 2) calc(var(--vertical-padding) / 2 + 0.125rem);
     flex-direction: column;
-    align-items: start;
-    gap: var(--space-3);
+    text-align: left;
+    gap: var(--space-2-5);
+
+    @media (max-width: 30rem) {
+      gap: 0.3125rem;
+    }
+
+    @media (max-width: 40rem) {
+      text-align: left;
+    }
 
     &:nth-child(2) {
       border-left: 2px solid var(--color-border);
+
+      @media (max-width: 40rem) {
+        border-left: none;
+        border-top: 2px solid var(--color-border);
+      }
     }
 
     &:nth-child(3) {
@@ -147,6 +212,23 @@
     &:nth-child(4) {
       border-top: 2px solid var(--color-border);
       border-left: 2px solid var(--color-border);
+
+      @media (max-width: 40rem) {
+        border-left: none;
+      }
+    }
+
+    [data-component="title"] {
+      letter-spacing: -0.03125rem;
+      text-transform: uppercase;
+      font-weight: normal;
+      font-size: 1rem;
+      flex-shrink: 0;
+      color: var(--color-text-dimmed);
+
+      @media (max-width: 30rem) {
+        font-size: 0.75rem;
+      }
     }
 
     [data-slot="button"] {
@@ -155,63 +237,176 @@
       display: flex;
       align-items: center;
       color: var(--color-text-secondary);
-      gap: var(--space-2);
+      gap: var(--space-2-5);
+      font-size: 1rem;
+
+      @media (max-width: 24rem) {
+        font-size: 0.875rem;
+      }
 
       strong {
         color: var(--color-text);
         font-weight: 500;
       }
+
+      @media (max-width: 40rem) {
+        justify-content: flex-start;
+      }
+
+      @media (max-width: 30rem) {
+        justify-content: center;
+      }
     }
   }
 
   [data-component="screenshots"] {
-    border-top: 2px solid var(--color-border);
+    --images-height: 600px;
     display: grid;
     grid-template-columns: 1fr 1fr;
-    gap: 0;
+    grid-template-rows: var(--images-height);
+    border-top: 2px solid var(--color-border);
 
-    [data-slot="left"] {
-      padding: var(--space-8) var(--space-6);
+    & > div.left {
       display: flex;
-      flex-direction: column;
-
-      img {
-        width: 100%;
-        height: auto;
-      }
+      grid-row: 1;
+      grid-column: 1;
     }
 
-    [data-slot="right"] {
+    & > div.right {
       display: grid;
       grid-template-rows: 1fr 1fr;
+      grid-row: 1;
+      grid-column: 2;
       border-left: 2px solid var(--color-border);
-    }
 
-    [data-slot="filler"] {
-      display: flex;
-      flex-grow: 1;
-      align-items: center;
-      justify-content: center;
+      & > div.row1 {
+        display: flex;
+        grid-row: 1;
+        border-bottom: 2px solid var(--color-border);
+        height: calc(var(--images-height) / 2);
+      }
+
+      & > div.row2 {
+        display: flex;
+        grid-row: 2;
+        height: calc(var(--images-height) / 2);
+      }
     }
 
-    [data-slot="cell"] {
-      padding: var(--space-8) var(--space-6);
+    figure {
+      flex: 1;
       display: flex;
       flex-direction: column;
-      gap: var(--space-4);
+      gap: calc(var(--padding) / 4);
+      padding: calc(var(--padding) / 2);
+      border-width: 0;
+      border-style: solid;
+      border-color: var(--color-border);
+      min-height: 0;
+      overflow: hidden;
+
+      & > div, figcaption {
+        display: flex;
+        align-items: center;
+      }
 
-      &:nth-child(2) {
+      & > div {
+        flex: 1;
+        min-height: 0;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+      }
+
+      a {
+        display: flex;
+        flex: 1;
+        min-height: 0;
+        align-items: center;
+        justify-content: center;
+        width: 100%;
+        height: 100%;
+      }
+
+      figcaption {
+        letter-spacing: -0.03125rem;
+        text-transform: uppercase;
+        color: var(--color-text-dimmed);
+        flex-shrink: 0;
+
+        @media (max-width: 30rem) {
+          font-size: 0.75rem;
+        }
+      }
+    }
+
+    & > div.left figure {
+      height: var(--images-height);
+      box-sizing: border-box;
+    }
+
+    & > div.right figure {
+      height: calc(var(--images-height) / 2);
+      box-sizing: border-box;
+    }
+
+    & > div.left img {
+      width: 100%;
+      height: 100%;
+      min-width: 0;
+      object-fit: contain;
+    }
+
+    & > div.right img {
+      width: 100%;
+      height: calc(100% - 2rem);
+      object-fit: contain;
+      display: block;
+    }
+
+    @media (max-width: 30rem) {
+      & {
+        --images-height: auto;
+        grid-template-columns: 1fr;
+        grid-template-rows: auto auto;
+      }
+
+      & > div.left {
+        grid-row: 1;
+        grid-column: 1;
+      }
+
+      & > div.right {
+        grid-row: 2;
+        grid-column: 1;
+        border-left: none;
         border-top: 2px solid var(--color-border);
+
+        & > div.row1,
+        & > div.row2 {
+          height: auto;
+        }
+      }
+
+      & > div.left figure,
+      & > div.right figure {
+        height: auto;
       }
 
-      img {
-        width: 80%;
+      & > div.left img,
+      & > div.right img {
+        width: 100%;
         height: auto;
+        max-height: none;
       }
     }
   }
 
   [data-component="copy-status"] {
+    @media (max-width: 43rem) {
+      display: none;
+    }
+
     [data-slot="copy"] {
       display: block;
       width: var(--space-4);
@@ -227,7 +422,7 @@
       display: none;
       width: var(--space-4);
       height: var(--space-4);
-      color: white;
+      color: var(--color-text);
 
       [data-copied] & {
         display: block;
@@ -237,20 +432,47 @@
 
   [data-component="footer"] {
     border-top: 2px solid var(--color-border);
-    display: grid;
-    grid-template-columns: 1fr 1fr 1fr;
-    font-size: var(--font-size-lg);
-    height: var(--space-20);
+    display: flex;
+    flex-direction: row;
 
     [data-slot="cell"] {
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      border-right: 2px solid var(--color-border);
+      flex: 1;
+      text-align: center;
       text-transform: uppercase;
+      padding: var(--vertical-padding) 0.5rem;
+    }
+
+    [data-slot="cell"] + [data-slot="cell"] {
+      border-left: 2px solid var(--color-border);
+    }
+
+    /* Small desktop: first two columns shrink to content, third expands */
+    @media (max-width: 57rem) {
+      [data-slot="cell"]:nth-child(1),
+      [data-slot="cell"]:nth-child(2) {
+        flex: 0 0 auto;
+        padding-left: calc(var(--padding) / 2);
+        padding-right: calc(var(--padding) / 2);
+      }
 
-      &:last-child {
-        border-right: none;
+      [data-slot="cell"]:nth-child(3) {
+        flex: 1;
+      }
+    }
+
+    /* Mobile: third column on its own row */
+    @media (max-width: 40rem) {
+      flex-wrap: wrap;
+
+      [data-slot="cell"]:nth-child(1),
+      [data-slot="cell"]:nth-child(2) {
+        flex: 1;
+      }
+
+      [data-slot="cell"]:nth-child(3) {
+        flex: 1 0 100%;
+        border-left: none;
+        border-top: 2px solid var(--color-border);
       }
     }
   }

+ 29 - 21
cloud/app/src/routes/index.tsx

@@ -1,10 +1,11 @@
 import { Title } from "@solidjs/meta"
 import { onCleanup, onMount } from "solid-js"
 import "./index.css"
-import logo from "../asset/logo-ornate-dark.svg"
-import IMG_SPLASH from "../asset/screenshot-splash.webp"
-import IMG_VSCODE from "../asset/screenshot-vscode.webp"
-import IMG_GITHUB from "../asset/screenshot-github.webp"
+import logoLight from "../asset/logo-ornate-light.svg"
+import logoDark from "../asset/logo-ornate-dark.svg"
+import IMG_SPLASH from "../asset/lander/screenshot-splash.png"
+import IMG_VSCODE from "../asset/lander/screenshot-vscode.png"
+import IMG_GITHUB from "../asset/lander/screenshot-github.png"
 import { IconCopy, IconCheck } from "../component/icon"
 import { createAsync, query, redirect, RouteDefinition } from "@solidjs/router"
 import { getActor, withActor } from "~/context/auth"
@@ -60,7 +61,8 @@ export default function Home() {
       <Title>opencode | AI coding agent built for the terminal</Title>
       <div data-component="content">
         <section data-component="top">
-          <img data-slot="logo" src={logo} alt="logo" />
+          <img data-slot="logo light" src={logoLight} alt="opencode logo light" />
+          <img data-slot="logo dark" src={logoDark} alt="opencode logo dark" />
           <h1 data-slot="title">The AI coding agent built for the terminal.</h1>
         </section>
 
@@ -145,24 +147,30 @@ export default function Home() {
         </section>
 
         <section data-component="screenshots">
-          <div data-slot="left">
-            <div data-component="title">opencode TUI with tokyonight theme</div>
-            <div data-slot="filler">
-              <img src={IMG_SPLASH} alt="opencode TUI with tokyonight theme" />
-            </div>
+          <div class="left">
+            <figure>
+              <figcaption>opencode TUI with the tokyonight theme</figcaption>
+              <a href="/docs/cli">
+                <img src={IMG_SPLASH} alt="opencode TUI with tokyonight theme" />
+              </a>
+            </figure>
           </div>
-          <div data-slot="right">
-            <div data-slot="cell">
-              <div data-component="title">opencode in VS Code</div>
-              <div data-slot="filler">
-                <img src={IMG_VSCODE} alt="opencode in VS Code" />
-              </div>
+          <div class="right">
+            <div class="row1">
+              <figure>
+                <figcaption>opencode in VS Code</figcaption>
+                <a href="/docs/ide">
+                  <img src={IMG_VSCODE} alt="opencode in VS Code" />
+                </a>
+              </figure>
             </div>
-            <div data-slot="cell">
-              <div data-component="title">opencode in GitHub</div>
-              <div data-slot="filler">
-                <img src={IMG_GITHUB} alt="opencode in GitHub" />
-              </div>
+            <div class="row2">
+              <figure>
+                <figcaption>opencode in GitHub</figcaption>
+                <a href="/docs/github">
+                  <img src={IMG_GITHUB} alt="opencode in GitHub" />
+                </a>
+              </figure>
             </div>
           </div>
         </section>

+ 1 - 1
cloud/app/src/style/base.css

@@ -1,6 +1,6 @@
 html {
-  color-scheme: dark;
   line-height: 1;
+  background-color: var(--color-bg);
 }
 
 body {

+ 47 - 47
cloud/app/src/style/token/color.css

@@ -1,53 +1,8 @@
-body {
+:root {
   --color-white: #ffffff;
   --color-black: #000000;
-}
-
-[data-color-mode="dark"] {
-  /* OpenCode theme colors */
-  --color-bg: #0c0c0e;
-  --color-bg-surface: #161618;
-  --color-bg-elevated: #1c1c1f;
-
-  --color-text: #ffffff;
-  --color-text-muted: #a1a1a6;
-  --color-text-disabled: #68686f;
-
-  --color-accent: #007aff;
-  --color-accent-hover: #0056b3;
-  --color-accent-active: #004085;
-
-  --color-success: #30d158;
-  --color-warning: #ff9f0a;
-  --color-danger: #ff453a;
-
-  --color-border: #38383a;
-  --color-border-muted: #2c2c2e;
-
-  /* Button colors */
-  --color-primary: var(--color-accent);
-  --color-primary-hover: var(--color-accent-hover);
-  --color-primary-active: var(--color-accent-active);
-  --color-primary-text: #ffffff;
 
-  --color-danger: #ff453a;
-  --color-danger-hover: #d70015;
-  --color-danger-active: #a50011;
-  --color-danger-text: #ffffff;
-
-  --color-warning: #ff9f0a;
-  --color-warning-hover: #cc7f08;
-  --color-warning-active: #995f06;
-  --color-warning-text: #000000;
-
-  /* Surface colors */
-  --color-surface: var(--color-bg-surface);
-  --color-surface-hover: var(--color-bg-elevated);
-  --color-border: var(--color-border);
-}
-
-[data-color-mode="light"] {
-  /* OpenCode light theme colors */
+  /* Default light theme colors */
   --color-bg: #ffffff;
   --color-bg-surface: #f5f5f7;
   --color-bg-elevated: #ffffff;
@@ -88,3 +43,48 @@ body {
   --color-surface-hover: var(--color-bg-elevated);
   --color-border: var(--color-border);
 }
+
+@media (prefers-color-scheme: dark) {
+  :root {
+    /* OpenCode dark theme colors */
+    --color-bg: #0c0c0e;
+    --color-bg-surface: #161618;
+    --color-bg-elevated: #1c1c1f;
+
+    --color-text: #ffffff;
+    --color-text-muted: #a1a1a6;
+    --color-text-disabled: #68686f;
+
+    --color-accent: #007aff;
+    --color-accent-hover: #0056b3;
+    --color-accent-active: #004085;
+
+    --color-success: #30d158;
+    --color-warning: #ff9f0a;
+    --color-danger: #ff453a;
+
+    --color-border: #38383a;
+    --color-border-muted: #2c2c2e;
+
+    /* Button colors */
+    --color-primary: var(--color-accent);
+    --color-primary-hover: var(--color-accent-hover);
+    --color-primary-active: var(--color-accent-active);
+    --color-primary-text: #ffffff;
+
+    --color-danger: #ff453a;
+    --color-danger-hover: #d70015;
+    --color-danger-active: #a50011;
+    --color-danger-text: #ffffff;
+
+    --color-warning: #ff9f0a;
+    --color-warning-hover: #cc7f08;
+    --color-warning-active: #995f06;
+    --color-warning-text: #000000;
+
+    /* Surface colors */
+    --color-surface: var(--color-bg-surface);
+    --color-surface-hover: var(--color-bg-elevated);
+    --color-border: var(--color-border);
+  }
+}