From e2019866b56970b75b219a542d93f6a6cee42052 Mon Sep 17 00:00:00 2001 From: ykiko Date: Sat, 26 Sep 2026 15:35:48 +0800 Subject: [PATCH] feat(theme): redesign the homepage around the mascot and the projects The homepage is the entry for every clice.io project, so it now says what the group builds and shows the projects, without repeating the same links. Hero: the mascot, redrawn standing quietly with her hands clasped (the bow keeps its gold C clasp), stands on the right so she no longer sits under the navbar logo. The headline and the introduction share one speech balloon whose tail is computed to reach her mouth. The introduction says what the tools do, and the only button is "Get started with clice". Projects: one manga page instead of three cards. catter, clice and kotatsu are panels with slanted gutters, drawn from the grid layout at runtime; an arrow labelled compile_commands.json crosses from catter to clice, and two arrows rise from kotatsu into both. Each panel has a narration box, a line-icon portrait, a one-sentence summary and links to its docs (kotatsu, which has none, to GitHub). Removed: the editors/platforms chips, the Q&A strip, the "Get involved" cards and the per-card GitHub links, which repeated the navbar and footer. Body text: M PLUS Rounded 1c carries only Japanese kanji, so Chinese paragraphs mixed its glyphs (and Japanese punctuation) with Noto Sans SC. Body text is now Nunito for Latin with Noto Sans SC for all Chinese. The footer uses the new logo instead of the old avatar. Images and font packages nothing references any more are deleted; the new hero's source and build script are in art/home, and art/logo also writes favicon.ico. --- .vitepress/theme/components/HomeLanding.vue | 1160 +++++++++++-------- .vitepress/theme/components/SiteFooter.vue | 12 +- .vitepress/theme/index.ts | 8 +- .vitepress/theme/style.css | 28 +- art/README.md | 15 +- art/home/build.sh | 23 + art/home/sources/hero-quiet-01.png | Bin 0 -> 1380433 bytes art/home/sources/hero-quiet-01.prompt.txt | 15 + art/logo/build.sh | 4 + package-lock.json | 24 +- package.json | 3 +- public/favicon.ico | Bin 0 -> 10312 bytes public/image.png | Bin 1272796 -> 0 bytes public/mascot/avatar.webp | Bin 45322 -> 0 bytes public/mascot/face-angry.webp | Bin 48906 -> 0 bytes public/mascot/face-proud.webp | Bin 44616 -> 0 bytes public/mascot/face-shy.webp | Bin 46588 -> 0 bytes public/mascot/face-skeptical.webp | Bin 32812 -> 0 bytes public/mascot/face-smiling.webp | Bin 34390 -> 0 bytes public/mascot/face-surprised.webp | Bin 38646 -> 0 bytes public/mascot/face-thinking.webp | Bin 46264 -> 0 bytes public/mascot/hero-home.webp | Bin 0 -> 89236 bytes public/mascot/hero-welcome.webp | Bin 145026 -> 0 bytes public/mascot/logo-mark-ink.webp | Bin 17734 -> 0 bytes public/mascot/logo-mark-mono.webp | Bin 6846 -> 0 bytes public/mascot/project-catter.webp | Bin 63912 -> 0 bytes public/mascot/project-clice.webp | Bin 78258 -> 0 bytes public/mascot/project-kotatsu.webp | Bin 71724 -> 0 bytes public/mascot/under-construction.webp | Bin 92254 -> 0 bytes public/mascot/wordmark-io.webp | Bin 27060 -> 0 bytes public/mascot/wordmark.webp | Bin 19362 -> 0 bytes 31 files changed, 745 insertions(+), 547 deletions(-) create mode 100755 art/home/build.sh create mode 100644 art/home/sources/hero-quiet-01.png create mode 100644 art/home/sources/hero-quiet-01.prompt.txt create mode 100644 public/favicon.ico delete mode 100644 public/image.png delete mode 100644 public/mascot/avatar.webp delete mode 100644 public/mascot/face-angry.webp delete mode 100644 public/mascot/face-proud.webp delete mode 100644 public/mascot/face-shy.webp delete mode 100644 public/mascot/face-skeptical.webp delete mode 100644 public/mascot/face-smiling.webp delete mode 100644 public/mascot/face-surprised.webp delete mode 100644 public/mascot/face-thinking.webp create mode 100644 public/mascot/hero-home.webp delete mode 100644 public/mascot/hero-welcome.webp delete mode 100644 public/mascot/logo-mark-ink.webp delete mode 100644 public/mascot/logo-mark-mono.webp delete mode 100644 public/mascot/project-catter.webp delete mode 100644 public/mascot/project-clice.webp delete mode 100644 public/mascot/project-kotatsu.webp delete mode 100644 public/mascot/under-construction.webp delete mode 100644 public/mascot/wordmark-io.webp delete mode 100644 public/mascot/wordmark.webp diff --git a/.vitepress/theme/components/HomeLanding.vue b/.vitepress/theme/components/HomeLanding.vue index c3a4bcb3e..39e355711 100644 --- a/.vitepress/theme/components/HomeLanding.vue +++ b/.vitepress/theme/components/HomeLanding.vue @@ -1,270 +1,463 @@ @@ -295,6 +488,7 @@ const platforms = [ /* hero */ .hero { + position: relative; display: grid; grid-template-columns: 1fr; gap: 0; @@ -317,7 +511,7 @@ const platforms = [ .hero-figure img { display: block; - width: 240px; + height: 300px; animation: sway 6s ease-in-out infinite; transform-origin: 50% 100%; } @@ -326,19 +520,11 @@ const platforms = [ padding: 28px 24px 32px; } -.kicker { - margin: 0 0 12px; - font-family: var(--clice-font-display); - font-weight: 900; - font-size: 14px; - letter-spacing: 0.04em; - color: var(--bow); -} - +/* She says the whole introduction: one balloon holds the headline and the + lead, filling the column so its edge (and the tail's root) sits by her. */ .bubble { position: relative; - display: inline-block; - padding: 14px 22px; + padding: 20px 24px 22px; border: var(--line) solid var(--line-color); border-radius: 18px; background: var(--paper); @@ -349,26 +535,29 @@ const platforms = [ background: var(--paper); } -.bubble::before, -.bubble::after { - content: ''; +.tail { position: absolute; - top: 22px; - left: -14px; - border-style: solid; - border-width: 0 14px 12px 0; - border-color: transparent var(--line-color) transparent transparent; + inset: 0; + width: 100%; + height: 100%; + pointer-events: none; + animation: fade-in 0.2s ease 0.5s both; } -.bubble::after { - left: -9px; - top: 24px; - border-width: 0 10px 8px 0; - border-color: transparent var(--paper) transparent transparent; +.tail-fill { + fill: var(--paper); +} + +.tail-line { + fill: none; + stroke: var(--line-color); + stroke-width: var(--line); + stroke-linejoin: round; } .bubble h1 { margin: 0; + text-wrap: balance; font-family: var(--clice-font-display); font-weight: 900; font-size: 30px; @@ -377,8 +566,7 @@ const platforms = [ } .lead { - margin: 20px 0 0; - max-width: 560px; + margin: 12px 0 0; font-size: 16px; line-height: 1.7; color: var(--ink-2); @@ -392,7 +580,9 @@ const platforms = [ } .btn { - display: inline-block; + display: inline-flex; + align-items: center; + gap: 8px; padding: 8px 22px; border: var(--line) solid var(--line-color); border-radius: 999px; @@ -425,93 +615,51 @@ const platforms = [ color: #1d212d; } -.btn.alt { - background: var(--straw); - color: #2b3040; -} - +/* Side by side the copy leads on the left and she stands on the right, so + she doesn't sit directly under the navbar logo. */ @media (min-width: 768px) { .hero { - grid-template-columns: 340px 1fr; + grid-template-columns: 1fr 340px; } .hero-figure { + order: 2; border-bottom: none; - border-right: var(--line) solid var(--line-color); + border-left: var(--line) solid var(--line-color); padding: 32px 24px 0; } .hero-figure img { - width: 280px; + height: 460px; } .hero-copy { - padding: 40px 40px 44px; display: flex; flex-direction: column; justify-content: center; + padding: 40px 40px 44px; } .bubble h1 { - font-size: 38px; + font-size: 32px; } } -/* works with / runs on */ - -.support { - margin-top: 26px; - padding-top: 18px; - border-top: var(--line-thin) dashed var(--line-color); - display: flex; - flex-direction: column; - gap: 10px; -} - -.support-row { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 8px; -} - -.support-label { - min-width: 78px; - font-family: var(--clice-font-display); - font-weight: 900; - font-size: 12px; - letter-spacing: 0.04em; - color: var(--ink-3); -} - -.chip { - display: inline-flex; - align-items: center; - gap: 6px; - padding: 0 10px; - border: var(--line-thin) solid var(--line-color); - border-radius: 999px; - background: var(--paper); - font-size: 12px; - font-weight: 700; - line-height: 24px; - color: var(--ink); - text-decoration: none; -} - -.chip.arch { - background: var(--straw-soft); -} +@media (min-width: 960px) { + .bubble { + padding: 26px 32px 28px; + } -.chip.link:hover { - border-color: var(--bow); - color: var(--bow); + .bubble h1 { + font-size: 38px; + } } /* section titles */ .section-title { position: relative; + scroll-margin-top: calc(var(--vp-nav-height) + 24px); margin: 56px 0 20px; padding: 4px 0 4px 16px; font-family: var(--clice-font-display); @@ -532,341 +680,358 @@ const platforms = [ box-sizing: border-box; } -/* projects */ +/* projects: one manga page, a panel per project */ -.projects { +.manga-page { + position: relative; display: grid; - grid-template-columns: 1fr; - gap: 20px; + grid-template-columns: minmax(0, 1fr); + gap: 40px; } -@media (min-width: 768px) { - .projects { - grid-template-columns: repeat(3, minmax(0, 1fr)); +@media (min-width: 960px) { + .manga-page { + grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); + grid-template-areas: + 'catter clice' + 'kotatsu kotatsu'; + gap: 34px 38px; + } + + .panel.catter { + grid-area: catter; + } + + .panel.clice { + grid-area: clice; + } + + .panel.kotatsu { + grid-area: kotatsu; } } -.project { - position: relative; - display: flex; - flex-direction: column; - padding: 22px 22px 26px; - text-decoration: none; - color: inherit; - overflow: hidden; - transition: - transform 0.12s ease, - box-shadow 0.12s ease; +/* Panel order follows the story: the build, then clice, then what holds + them up. */ +.panel.catter { + order: 1; } -.project::before { - content: ''; - position: absolute; - inset: 0 auto 0 0; - width: 10px; - border-right: var(--line) solid var(--line-color); - background: var(--accent); +.panel.clice { + order: 2; } -.project.bow { - --accent: var(--bow); +.panel.kotatsu { + order: 3; } -.project.aqua { - --accent: var(--aqua); +.page-frames, +.page-arrows { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + overflow: visible; + pointer-events: none; } -.project.straw { - --accent: var(--straw); +.page-frames path { + fill: var(--panel); + stroke: var(--line-color); + stroke-width: 2.5; + stroke-linejoin: round; } -.project:hover { - transform: translate(-2px, -2px); - box-shadow: 6px 6px 0 var(--shadow-color); +.dark .page-frames path { + fill: var(--paper-2); } -.project .art { - margin: -22px -22px 16px -22px; - padding-left: 10px; - height: 170px; - border-bottom: var(--line) solid var(--line-color); - background: - var(--screentone) 0 0 / 10px 10px, - #ffffff; - display: flex; - justify-content: center; - align-items: flex-end; - overflow: hidden; +.page-arrows { + z-index: 2; } -.project .art img { - display: block; - height: 150px; - width: auto; - transition: transform 0.15s ease; +.page-arrows .halo { + fill: var(--panel); + stroke: var(--panel); + stroke-width: 9; + stroke-linejoin: round; + stroke-linecap: round; } -.project:hover .art img { - transform: translateY(-4px); +.dark .page-arrows .halo { + fill: var(--paper-2); + stroke: var(--paper-2); } -.project-head { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 8px 10px; - margin-left: 8px; +.page-arrows .shaft { + fill: none; + stroke: var(--ink); + stroke-width: 3; + stroke-linecap: round; } -.project h3 { - margin: 0; - font-family: var(--clice-font-display); - font-weight: 900; - font-size: 24px; - color: var(--ink); +.page-arrows .head { + fill: var(--ink); + stroke: var(--ink); + stroke-width: 1.5; + stroke-linejoin: round; } -.tag { - padding: 0 10px; +/* What one project hands to the next, pinned to the arrow between them. */ +.arrow-label { + position: absolute; + z-index: 3; + padding: 1px 8px; border: var(--line-thin) solid var(--line-color); - border-radius: 999px; - background: var(--straw-soft); - font-size: 12px; + border-radius: 4px; + background: var(--panel); + font-family: var(--vp-font-family-mono); font-weight: 700; - line-height: 20px; + font-size: 11px; + line-height: 18px; color: var(--ink); white-space: nowrap; + transform: translate(-50%, -50%) rotate(-3deg); } -.project p { - margin: 12px 0 0 8px; - font-size: 14px; - line-height: 1.65; - color: var(--ink-2); - flex-grow: 1; +.dark .arrow-label { + background: var(--paper-2); } -.arrow { - position: absolute; - right: 16px; - bottom: 10px; - font-family: var(--clice-font-display); - font-weight: 900; - font-size: 20px; - color: var(--bow); - transition: transform 0.12s ease; +.panel { + position: relative; + z-index: 1; + padding: 20px 22px 18px; } -.project:hover .arrow { - transform: translateX(4px); +/* Until the frames are measured (and without JS) a plain border stands in. */ +.manga-page:not(.framed) .panel { + border: var(--line) solid var(--line-color); + border-radius: var(--radius); + background: var(--panel); } -/* q&a comic */ +.dark .manga-page:not(.framed) .panel { + background: var(--paper-2); +} -.qa { - display: flex; - flex-direction: column; - gap: 28px; +.bow { + --accent: var(--bow); } -.qa-row { - display: grid; - grid-template-columns: 1fr; - grid-template-areas: - 'caption' - 'face' - 'answer'; - gap: 14px; +.aqua { + --accent: var(--aqua); } -.caption { - grid-area: caption; - display: flex; - align-items: center; - gap: 12px; - padding: 10px 16px; - border: var(--line) solid var(--line-color); - border-radius: 2px; - background: var(--paper-3); - box-shadow: var(--hard-shadow-sm); +.straw { + --accent: var(--straw); +} + +/* A narration box, square-cornered, in the top corner of the panel. */ +.narration { width: fit-content; max-width: 100%; + margin: 0 0 16px; + padding: 6px 12px; + border: var(--line-thin) solid var(--line-color); + border-left: 6px solid var(--accent); + background: var(--paper); + font-size: 13px; + line-height: 1.55; + color: var(--ink); } -.caption .q { - flex: 0 0 auto; - width: 26px; - height: 26px; - border: var(--line) solid var(--line-color); - border-radius: 999px; - background: var(--ribbon); - font-family: var(--clice-font-display); - font-weight: 900; - font-size: 13px; - line-height: 22px; - text-align: center; - color: #ffffff; - transform: rotate(-8deg); +.panel-main { + display: flex; + align-items: flex-start; + gap: 20px; } -.caption p { - margin: 0; - font-family: var(--clice-font-display); - font-weight: 700; - font-size: 15px; - color: var(--ink); +/* catter faces clice across the gutter, so its portrait sits on the right. */ +.panel.catter .panel-main { + flex-direction: row-reverse; } -.qa-row .face { - grid-area: face; - position: relative; - width: 160px; - height: 160px; - overflow: hidden; - background: #ffffff; +.panel-body { + flex: 1; + min-width: 0; } -.qa-row .face img { +.line-icon { display: block; - width: 100%; - height: 100%; - object-fit: cover; - object-position: center 20%; + fill: none; + stroke: var(--ink); + stroke-width: 2; + stroke-linecap: round; + stroke-linejoin: round; } -.num { - position: absolute; - left: 0; - top: 0; - width: 26px; - height: 26px; - border-right: var(--line) solid var(--line-color); - border-bottom: var(--line) solid var(--line-color); - border-radius: 0 0 var(--radius) 0; - background: var(--straw); - font-family: var(--clice-font-display); - font-weight: 900; - font-size: 13px; - line-height: 26px; - text-align: center; - color: #2b3040; +.line-icon :deep(.dot) { + fill: var(--ink); + stroke: none; } -.answer { - grid-area: answer; +/* A character's portrait frame; it tilts a little under the pointer. */ +.portrait { position: relative; - padding: 18px 22px 20px; + flex-shrink: 0; + display: grid; + place-items: center; + width: 68px; + height: 68px; border: var(--line) solid var(--line-color); - border-radius: 18px; - background: var(--panel); - box-shadow: var(--hard-shadow); -} - -.dark .answer { - background: var(--paper-2); + border-radius: 50%; + background: var(--paper); + box-shadow: 3px 3px 0 var(--accent); + transition: transform 0.15s ease; } -.answer::before, -.answer::after { - content: ''; - position: absolute; - top: -14px; - left: 28px; - border-style: solid; - border-width: 0 0 14px 14px; - border-color: transparent transparent var(--line-color) transparent; +.portrait .line-icon { + width: 34px; + height: 34px; } -.answer::after { - top: -9px; - left: 30px; - border-width: 0 0 10px 10px; - border-color: transparent transparent var(--panel) transparent; +.lit .portrait { + transform: rotate(-6deg); } -.dark .answer::after { - border-bottom-color: var(--paper-2); +.project-head { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 6px 10px; } -.answer h3 { - margin: 0 0 8px; +.panel h3 { + margin: 0; font-family: var(--clice-font-display); font-weight: 900; - font-size: 20px; + font-size: 28px; + line-height: 1.2; +} + +.panel h3 a { color: var(--ink); + text-decoration: none; } -.answer p { - margin: 0; +/* The title link covers the whole panel. */ +.stretch::after { + content: ''; + position: absolute; + inset: 0; +} + +.tag { + padding: 0 10px; + border: var(--line-thin) solid var(--line-color); + border-radius: 999px; + background: var(--straw-soft); + font-size: 12px; + font-weight: 700; + line-height: 20px; + color: var(--ink); + white-space: nowrap; +} + +.summary { + margin: 12px 0 0; font-size: 14px; - line-height: 1.7; + line-height: 1.65; color: var(--ink-2); } -.answer p + p { - margin-top: 8px; +/* The whole panel is the link; this only says where it goes. */ +.more { + display: inline-block; + margin-top: 12px; + font-family: var(--clice-font-display); + font-weight: 900; + font-size: 13px; + color: var(--bow); + transition: transform 0.12s ease; } -@media (min-width: 768px) { - .qa-row { - grid-template-columns: 160px 1fr; +.lit .more { + transform: translateX(4px); +} + +/* kotatsu's panel runs the full width: its name on the left, the + description beside it. */ +@media (min-width: 960px) { + .panel.kotatsu .panel-body { + display: grid; + grid-template-columns: minmax(0, 2fr) minmax(0, 3fr); grid-template-areas: - '. caption' - 'face answer'; - column-gap: 28px; - row-gap: 12px; - align-items: start; + 'head summary' + 'head more'; + column-gap: 32px; } - .qa-row.flip { - grid-template-columns: 1fr 160px; - grid-template-areas: - 'caption .' - 'answer face'; + .panel.kotatsu .project-head { + grid-area: head; + align-self: center; } - .qa-row.flip .caption { - justify-self: end; + .panel.kotatsu .summary, + .panel.kotatsu .more { + padding-left: 28px; + border-left: var(--line-thin) dashed var(--line-color); } - .answer::before, - .answer::after { - top: 26px; - left: -14px; - border-width: 0 14px 12px 0; - border-color: transparent var(--line-color) transparent transparent; + .panel.kotatsu .summary { + grid-area: summary; + margin: 0; + align-self: end; } - .answer::after { - top: 28px; - left: -9px; - border-width: 0 10px 8px 0; - border-color: transparent var(--panel) transparent transparent; + .panel.kotatsu .more { + grid-area: more; + justify-self: start; + margin: 0; + padding-top: 12px; } +} - .dark .answer::after { - border-right-color: var(--paper-2); - border-bottom-color: transparent; +/* Narrow screens: the portrait sits beside the name only, and everything + below uses the panel's full width. */ +@media (max-width: 639px) { + .panel-main { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + gap: 0 14px; } - .qa-row.flip .answer::before, - .qa-row.flip .answer::after { - left: auto; - right: -14px; - border-width: 12px 0 0 14px; - border-color: transparent transparent transparent var(--line-color); + .panel.catter .panel-main { + grid-template-columns: minmax(0, 1fr) auto; } - .qa-row.flip .answer::after { - right: -9px; - top: 28px; - border-width: 8px 0 0 10px; - border-color: transparent transparent transparent var(--panel); + .panel.catter .portrait { + grid-column: 2; + grid-row: 1; } - .dark .qa-row.flip .answer::after { - border-left-color: var(--paper-2); - border-right-color: transparent; + .panel-body { + display: contents; + } + + .project-head { + align-self: center; + } + + .summary, + .more { + grid-column: 1 / -1; + } + + .portrait { + width: 56px; + height: 56px; + } + + .portrait .line-icon { + width: 28px; + height: 28px; } } @@ -896,6 +1061,12 @@ const platforms = [ } } +@keyframes fade-in { + from { + opacity: 0; + } +} + @keyframes sway { 0%, 100% { @@ -910,6 +1081,7 @@ const platforms = [ @media (prefers-reduced-motion: reduce) { .hero, .bubble, + .tail, .hero-figure img { animation: none; } diff --git a/.vitepress/theme/components/SiteFooter.vue b/.vitepress/theme/components/SiteFooter.vue index 50a073dd9..c3fe4d17a 100644 --- a/.vitepress/theme/components/SiteFooter.vue +++ b/.vitepress/theme/components/SiteFooter.vue @@ -46,7 +46,7 @@ const other = computed(() => (zh.value ? { text: 'English', href: '/' } : { text