From 8f9210d6bed236e70f95df0ebbf579410a58968c Mon Sep 17 00:00:00 2001 From: kstyagi23 Date: Sat, 5 Sep 2026 07:16:04 +0530 Subject: [PATCH] Refactor terminology and update UI for archiving feature - Changed button text and messages from "crawl" to "archive" for clarity. - Updated HTML elements to reflect the new terminology and improve user guidance. - Modified footer content to emphasize local-first archiving and provide source link. - Added a link to the project's repository in the index page and test to ensure it appears. --- README.md | 2 + app/static/app.css | 1115 +++++++++++++++++--------------------- app/static/app.js | 6 +- app/templates/index.html | 106 ++-- tests/test_main.py | 1 + 5 files changed, 543 insertions(+), 687 deletions(-) diff --git a/README.md b/README.md index bd7c20e..77e4e8a 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ SiteHarbor is a standalone Python web-capture service. It creates a bounded offline archive of a public website, preserves discovered static assets, rewrites supported local links, records a manifest, and keeps completed ZIP files outside the public web root. +Source: + It is intentionally designed as a local or trusted-network application. It uses SQLite for durable job state and does not require Redis, a queue server, or object storage. ## What It Does diff --git a/app/static/app.css b/app/static/app.css index f5188f8..d4fedd8 100644 --- a/app/static/app.css +++ b/app/static/app.css @@ -1,24 +1,32 @@ :root { - color-scheme: dark; - --paper: #f4f4f0; - --ink: #f4f6f1; - --muted: #9b9f98; - --quiet: #666b65; - --page: #1b1c1b; - --black: #050605; - --black-soft: #0a0c0a; - --forest: #063b2c; - --forest-deep: #02271e; - --mint: #8eeab8; - --mint-bright: #c3f8d8; - --line: rgba(238, 247, 239, 0.13); - --line-strong: rgba(238, 247, 239, 0.22); - --danger: #f0978e; - --amber: #e7c77b; - --blue: #9ac4f7; - --radius: 18px; - --shadow: 0 36px 95px rgba(0, 0, 0, 0.32); - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + color-scheme: light; + --canvas: #f3f5f1; + --surface: #ffffff; + --surface-muted: #edf1ed; + --surface-strong: #e4ebe5; + --ink: #17201a; + --ink-soft: #334038; + --muted: #6b776f; + --quiet: #8a958d; + --line: #d8dfd8; + --line-strong: #c4cec5; + --night: #18221b; + --night-soft: #223128; + --night-line: rgba(239, 246, 239, 0.16); + --accent: #2168d8; + --accent-dark: #1855b4; + --accent-soft: #e6efff; + --success: #18714d; + --success-soft: #e3f4ea; + --danger: #b3473d; + --danger-soft: #fae9e6; + --warning: #996514; + --warning-soft: #fff4d6; + --radius-lg: 22px; + --radius-md: 14px; + --radius-sm: 9px; + --shadow: 0 18px 48px rgba(27, 42, 32, 0.08); + font-family: "Aptos", "Segoe UI", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif; } * { @@ -27,7 +35,8 @@ html { min-width: 320px; - background: var(--page); + background: var(--canvas); + scroll-behavior: smooth; } body { @@ -35,24 +44,7 @@ body { min-height: 100vh; margin: 0; color: var(--ink); - background: - radial-gradient(circle at 86% -8%, rgba(122, 255, 181, 0.035), transparent 24rem), - radial-gradient(circle at 10% 100%, rgba(0, 0, 0, 0.24), transparent 32rem), - var(--page); -} - -body::before { - position: fixed; - z-index: -1; - inset: 0; - content: ""; - pointer-events: none; - opacity: 0.23; - background-image: - linear-gradient(rgba(255, 255, 255, 0.025) 1px, transparent 1px), - linear-gradient(90deg, rgba(255, 255, 255, 0.018) 1px, transparent 1px); - background-size: 48px 48px; - mask-image: linear-gradient(to bottom, black, transparent 71%); + background: var(--canvas); } button, @@ -64,174 +56,159 @@ button { cursor: pointer; } +a { + color: inherit; +} + button:focus-visible, input:focus-visible, summary:focus-visible, a:focus-visible { - outline: 2px solid var(--mint-bright); + outline: 3px solid rgba(33, 104, 216, 0.34); outline-offset: 3px; } +.skip-link { + position: fixed; + z-index: 20; + top: 12px; + left: 12px; + padding: 10px 13px; + border-radius: var(--radius-sm); + color: #fff; + background: var(--accent-dark); + text-decoration: none; + transform: translateY(-160%); + transition: transform 160ms ease; +} + +.skip-link:focus { + transform: translateY(0); +} + .site-shell { - width: min(1220px, calc(100% - 40px)); + width: min(1200px, calc(100% - 48px)); margin: 0 auto; } .site-header { - position: relative; display: grid; - grid-template-columns: minmax(220px, 1fr) auto minmax(180px, 1fr); + grid-template-columns: minmax(210px, 1fr) auto minmax(250px, 1fr); align-items: center; gap: 28px; - min-height: 108px; - border-bottom: 1px solid rgba(255, 255, 255, 0.1); -} - -.site-header::after { - position: absolute; - bottom: -1px; - left: 0; - width: 118px; - height: 2px; - content: ""; - background: var(--mint); + min-height: 82px; + border-bottom: 1px solid var(--line); } .brand { - display: inline-grid; - grid-template-columns: auto 1fr; + display: inline-flex; align-items: center; justify-self: start; - gap: 12px; - color: var(--ink); + gap: 11px; text-decoration: none; } .brand-mark { position: relative; display: grid; - width: 38px; - height: 38px; + width: 31px; + height: 31px; place-items: center; overflow: hidden; - border: 1px solid rgba(152, 242, 187, 0.48); - border-radius: 11px; - background: - linear-gradient(135deg, rgba(142, 234, 184, 0.22), rgba(6, 59, 44, 0.45)), - #0a2118; - box-shadow: inset 0 1px 0 rgba(238, 255, 244, 0.16); + border-radius: 9px; + background: var(--night); } -.brand-mark::before { +.brand-mark::before, +.brand-mark::after { position: absolute; - width: 23px; - height: 23px; - border: 1px solid rgba(197, 255, 218, 0.28); - border-radius: 50%; content: ""; } -.brand-mark i { - position: absolute; - display: block; - z-index: 1; - width: 4px; - height: 4px; +.brand-mark::before { + width: 13px; + height: 13px; + border: 2px solid #fff; border-radius: 50%; - background: var(--mint); - box-shadow: 0 0 8px rgba(142, 234, 184, 0.7); } -.brand-mark i:nth-child(1) { - top: 10px; - left: 10px; -} - -.brand-mark i:nth-child(2) { - right: 10px; - bottom: 10px; - opacity: 0.75; -} - -.brand-mark i:nth-child(3) { - top: 10px; - right: 10px; - width: 3px; - height: 3px; - opacity: 0.55; +.brand-mark::after { + right: 6px; + bottom: 6px; + width: 5px; + height: 5px; + border-radius: 50%; + background: #8db5ff; } .brand-copy { display: grid; - gap: 3px; + gap: 2px; } -.brand-kicker, -.service-panel-label, -.footer-kicker, -.footer-index { - color: var(--quiet); - font-size: 0.58rem; - font-weight: 780; - letter-spacing: 0.14em; -} - -.brand strong { - color: var(--paper); - font-size: 1.16rem; +.brand-copy strong { + color: var(--ink); + font-size: 1rem; font-weight: 760; - letter-spacing: -0.05em; + letter-spacing: -0.04em; +} + +.brand-copy span { + color: var(--quiet); + font-size: 0.64rem; + font-weight: 650; } .site-nav { display: flex; align-items: center; - gap: 4px; - padding: 5px; - border: 1px solid rgba(235, 246, 235, 0.1); - border-radius: 999px; - background: rgba(1, 4, 2, 0.3); + gap: 20px; } .site-nav a { - padding: 7px 10px; - border-radius: 999px; - color: #aeb6ad; - font-size: 0.68rem; - font-weight: 650; + color: var(--muted); + font-size: 0.75rem; + font-weight: 680; text-decoration: none; - transition: color 160ms ease, background 160ms ease; + transition: color 150ms ease; } .site-nav a:hover { - color: var(--mint-bright); - background: rgba(142, 234, 184, 0.1); + color: var(--accent-dark); } -.service-panel { - display: grid; - justify-items: end; - gap: 6px; +.header-tools { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 17px; +} + +.source-link { + padding: 7px 10px; + border: 1px solid var(--line-strong); + border-radius: 7px; + color: var(--ink-soft); + font-size: 0.69rem; + font-weight: 700; + text-decoration: none; + transition: border-color 150ms ease, color 150ms ease, background 150ms ease; +} + +.source-link:hover { + border-color: #9fbff4; + color: var(--accent-dark); + background: var(--accent-soft); } .service-status { - display: flex; + display: inline-flex; align-items: center; - gap: 8px; - color: #c3cbc2; - font-size: 0.72rem; -} - -.service-status[data-online="false"] > i { - background: var(--danger); - box-shadow: 0 0 0 4px rgba(240, 151, 142, 0.08), 0 0 13px rgba(240, 151, 142, 0.55); -} - -.eyebrow { - color: var(--mint); - font-size: 0.65rem; - font-weight: 780; - letter-spacing: 0.16em; + gap: 7px; + color: var(--muted); + font-size: 0.68rem; + font-weight: 620; + white-space: nowrap; } .service-status > i, @@ -241,21 +218,25 @@ a:focus-visible { width: 7px; height: 7px; border-radius: 50%; - background: var(--mint); - box-shadow: 0 0 0 4px rgba(142, 234, 184, 0.08), 0 0 13px rgba(142, 234, 184, 0.72); + background: var(--success); +} + +.service-status[data-online="false"] > i { + background: var(--danger); } main { - padding: 53px 0 84px; + padding: 48px 0 76px; } .workbench { display: grid; - grid-template-columns: minmax(0, 1.06fr) minmax(420px, 0.94fr); + grid-template-columns: minmax(0, 0.88fr) minmax(460px, 1.12fr); overflow: hidden; - min-height: 630px; - border: 1px solid rgba(255, 255, 255, 0.14); - background: var(--black); + min-height: 580px; + border: 1px solid var(--line); + border-radius: var(--radius-lg); + background: var(--surface); box-shadow: var(--shadow); } @@ -270,37 +251,21 @@ main { flex-direction: column; justify-content: space-between; overflow: hidden; - padding: clamp(34px, 5.2vw, 68px); - background: - radial-gradient(ellipse 92% 88% at 22% 90%, rgba(25, 205, 137, 0.45), transparent 61%), - radial-gradient(ellipse 80% 78% at 86% 19%, rgba(7, 91, 67, 0.78), transparent 66%), - linear-gradient(135deg, #064632 0%, var(--forest-deep) 58%, #04271e 100%); + padding: clamp(34px, 5vw, 62px); + color: #eaf0eb; + background: var(--night); } .observatory::before { position: absolute; - top: -38%; - left: -31%; - width: 106%; - aspect-ratio: 1; - border: 1px solid rgba(204, 255, 222, 0.12); + top: -126px; + right: -126px; + width: 290px; + height: 290px; + border: 1px solid rgba(230, 242, 233, 0.12); border-radius: 50%; content: ""; - box-shadow: - 0 0 0 59px rgba(189, 255, 216, 0.025), - 0 0 0 120px rgba(189, 255, 216, 0.018); -} - -.observatory::after { - position: absolute; - right: -28%; - bottom: -38%; - width: 82%; - aspect-ratio: 1; - border: 1px solid rgba(202, 255, 222, 0.11); - border-radius: 50%; - content: ""; - box-shadow: 0 0 0 48px rgba(202, 255, 222, 0.025); + box-shadow: 0 0 0 42px rgba(230, 242, 233, 0.025); } .observatory-copy, @@ -311,7 +276,15 @@ main { } .eyebrow { - margin: 0 0 12px; + margin: 0 0 11px; + color: var(--accent); + font-size: 0.63rem; + font-weight: 780; + letter-spacing: 0.13em; +} + +.observatory .eyebrow { + color: #a9c7ff; } h1, @@ -324,64 +297,63 @@ p { h1, h2, h3 { - color: var(--paper); + color: var(--ink); letter-spacing: -0.055em; } h1 { - max-width: 525px; - margin-bottom: 21px; - font-size: clamp(3.05rem, 5.25vw, 5.35rem); - font-weight: 710; - line-height: 0.91; + max-width: 500px; + margin-bottom: 20px; + color: #f6f9f7; + font-size: clamp(3rem, 4.8vw, 4.8rem); + font-weight: 730; + line-height: 0.95; } h1 em { - color: var(--mint-bright); + color: #a9c7ff; font-style: normal; } h2 { margin-bottom: 0; - font-size: clamp(1.8rem, 2.9vw, 2.55rem); - font-weight: 710; - line-height: 0.98; + font-size: clamp(1.72rem, 2.5vw, 2.35rem); + font-weight: 730; + line-height: 1; } h3 { - margin-bottom: 7px; - font-size: 0.98rem; - font-weight: 700; + margin-bottom: 6px; + font-size: 0.96rem; + font-weight: 740; } .lede { - max-width: 480px; + max-width: 430px; margin-bottom: 0; - color: rgba(233, 250, 239, 0.75); - font-size: 0.96rem; - line-height: 1.67; + color: #bdc8c0; + font-size: 0.91rem; + line-height: 1.65; } .metric-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; - margin: 37px 0; + margin: 35px 0; } .metric-card { - min-height: 102px; - padding: 17px; - border: 1px solid rgba(231, 255, 239, 0.2); - border-radius: 10px; - background: rgba(2, 27, 20, 0.22); - backdrop-filter: blur(7px); + min-height: 88px; + padding: 15px; + border: 1px solid var(--night-line); + border-radius: var(--radius-sm); + background: rgba(255, 255, 255, 0.045); } .metric-card.metric-primary { - border-color: rgba(249, 255, 249, 0.76); - color: #0f261b; - background: var(--paper); + border-color: #b9cdf1; + background: #e9f0fc; } .metric-value, @@ -391,58 +363,57 @@ h3 { .metric-value { overflow: hidden; - color: #eafff0; - font-size: clamp(1.34rem, 2vw, 1.72rem); - font-weight: 740; + color: #f4f8f5; + font-size: clamp(1.25rem, 1.9vw, 1.62rem); + font-weight: 760; letter-spacing: -0.06em; text-overflow: ellipsis; white-space: nowrap; } .metric-primary .metric-value { - color: #11261a; + color: #183e78; } .metric-label { margin-top: 6px; - color: rgba(226, 251, 233, 0.7); - font-size: 0.65rem; - font-weight: 670; - letter-spacing: 0.06em; + color: #aab8af; + font-size: 0.59rem; + font-weight: 720; + letter-spacing: 0.08em; text-transform: uppercase; } .metric-primary .metric-label { - color: #547062; + color: #58719a; } .observatory-foot { display: flex; align-items: flex-start; - gap: 12px; - max-width: 455px; + gap: 10px; + max-width: 430px; padding-top: 17px; - border-top: 1px solid rgba(234, 255, 241, 0.2); + border-top: 1px solid var(--night-line); } .observatory-foot .pulse-dot { width: 6px; height: 6px; margin-top: 5px; + background: #a9c7ff; } .observatory-foot p { margin: 0; - color: rgba(230, 249, 236, 0.66); - font-size: 0.73rem; + color: #aebbb2; + font-size: 0.71rem; line-height: 1.55; } .capture-console { - padding: clamp(31px, 4.2vw, 53px); - background: - linear-gradient(135deg, rgba(255, 255, 255, 0.025), transparent 28%), - var(--black); + padding: clamp(32px, 4.5vw, 54px); + background: var(--surface); } .console-head { @@ -453,48 +424,47 @@ h3 { margin-bottom: 31px; } -.console-head .eyebrow { - color: #a6ad9f; -} - .console-step { - padding-top: 4px; - color: #747a72; - font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; - font-size: 0.67rem; - letter-spacing: 0.08em; + padding: 5px 8px; + border: 1px solid var(--line); + border-radius: 6px; + color: var(--quiet); + font-family: "Cascadia Mono", "SFMono-Regular", Consolas, monospace; + font-size: 0.6rem; + font-weight: 700; + letter-spacing: 0.06em; } .field-label, .limit-grid label > span, .text-rule > span { display: block; - margin-bottom: 9px; - color: #c5cbc3; - font-size: 0.73rem; - font-weight: 690; + margin-bottom: 8px; + color: var(--ink-soft); + font-size: 0.7rem; + font-weight: 720; } .url-input-wrap { display: flex; align-items: center; - border: 1px solid #41463f; - border-radius: 9px; - background: #101210; - transition: border-color 160ms ease, box-shadow 160ms ease, background 160ms ease; + border: 1px solid var(--line-strong); + border-radius: var(--radius-sm); + background: #fbfcfa; + transition: border-color 150ms ease, box-shadow 150ms ease; } .url-input-wrap:focus-within { - border-color: var(--mint); - background: #121611; - box-shadow: 0 0 0 4px rgba(142, 234, 184, 0.1); + border-color: var(--accent); + box-shadow: 0 0 0 4px rgba(33, 104, 216, 0.1); } .url-input-wrap > span { padding-left: 15px; - color: var(--mint); - font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; - font-size: 1rem; + color: var(--accent); + font-family: "Cascadia Mono", "SFMono-Regular", Consolas, monospace; + font-size: 0.95rem; + font-weight: 700; } .url-input-wrap input { @@ -505,34 +475,34 @@ h3 { outline: 0; color: var(--ink); background: transparent; - font-size: 0.96rem; + font-size: 0.93rem; } .url-input-wrap input::placeholder, .text-rule input::placeholder { - color: #656a64; + color: #9aa49d; } .quick-settings { display: grid; - gap: 10px; - margin-top: 18px; + gap: 9px; + margin-top: 17px; } .switch-row, .connection-control { border: 1px solid var(--line); - border-radius: 10px; - background: rgba(255, 255, 255, 0.022); + border-radius: var(--radius-sm); + background: var(--surface-muted); } .switch-row { display: flex; align-items: center; justify-content: space-between; - gap: 17px; - min-height: 75px; - padding: 14px 15px; + gap: 18px; + min-height: 72px; + padding: 13px 15px; } .switch-row strong, @@ -544,17 +514,17 @@ h3 { .switch-row strong, .connection-control strong { - margin-bottom: 4px; - color: #e3e7e1; - font-size: 0.82rem; - font-weight: 680; + margin-bottom: 3px; + color: var(--ink-soft); + font-size: 0.79rem; + font-weight: 730; } .switch-row small, .connection-control small { - max-width: 320px; - color: #7e847d; - font-size: 0.7rem; + max-width: 330px; + color: var(--muted); + font-size: 0.68rem; line-height: 1.4; } @@ -572,59 +542,58 @@ h3 { .switch > span { position: relative; display: block; - width: 42px; - height: 23px; - border: 1px solid #525850; + width: 39px; + height: 22px; + border: 1px solid #b4c0b6; border-radius: 999px; - background: #222622; - transition: border-color 160ms ease, background 160ms ease; + background: #d6ddd7; + transition: border-color 150ms ease, background 150ms ease; } .switch > span::after { position: absolute; top: 3px; left: 3px; - width: 15px; - height: 15px; + width: 14px; + height: 14px; border-radius: 50%; - background: #acb3ab; + background: #fff; + box-shadow: 0 1px 3px rgba(23, 32, 26, 0.18); content: ""; - transition: transform 160ms ease, background 160ms ease; + transition: transform 150ms ease; } .switch input:checked + span { - border-color: rgba(142, 234, 184, 0.82); - background: rgba(55, 146, 90, 0.57); + border-color: var(--accent); + background: var(--accent); } .switch input:checked + span::after { - transform: translateX(17px); - background: var(--mint-bright); + transform: translateX(16px); } .switch input:disabled + span { cursor: not-allowed; - opacity: 0.38; + opacity: 0.5; } .connection-control { - position: relative; display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 11px 16px; - padding: 14px 15px 13px; + padding: 13px 15px; } .connection-value { align-self: center; - color: #a8afa6; - font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; - font-size: 0.68rem; + color: var(--muted); + font-family: "Cascadia Mono", "SFMono-Regular", Consolas, monospace; + font-size: 0.66rem; } .connection-value output { - color: var(--mint); - font-weight: 700; + color: var(--accent-dark); + font-weight: 800; } .connection-control input[type="range"] { @@ -632,7 +601,7 @@ h3 { height: 4px; grid-column: 1 / -1; margin: 2px 0 0; - accent-color: var(--mint); + accent-color: var(--accent); cursor: pointer; } @@ -644,21 +613,21 @@ h3 { .setting-panel { border: 1px solid var(--line); - border-radius: 10px; - background: rgba(255, 255, 255, 0.012); + border-radius: var(--radius-sm); + background: #fbfcfa; } .setting-panel summary { display: flex; align-items: center; justify-content: space-between; - gap: 15px; - min-height: 52px; - padding: 0 15px; - color: #d9ddd7; + gap: 14px; + min-height: 49px; + padding: 0 14px; + color: var(--ink-soft); cursor: pointer; - font-size: 0.79rem; - font-weight: 660; + font-size: 0.75rem; + font-weight: 720; list-style: none; } @@ -667,9 +636,7 @@ h3 { } .setting-panel summary::after { - order: 3; - margin-left: 2px; - color: #8f978d; + color: var(--muted); content: "+"; font-size: 1rem; font-weight: 400; @@ -685,9 +652,9 @@ h3 { .summary-value { overflow: hidden; - color: #727970; - font-size: 0.66rem; - font-weight: 500; + color: var(--quiet); + font-size: 0.64rem; + font-weight: 560; text-align: right; text-overflow: ellipsis; white-space: nowrap; @@ -697,46 +664,46 @@ h3 { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 8px; - padding: 15px; + padding: 14px; } .limit-grid label > span { margin-bottom: 6px; - color: #8f968d; - font-size: 0.63rem; + color: var(--muted); + font-size: 0.61rem; } .limit-grid input, .text-rule input { width: 100%; min-width: 0; - border: 1px solid #3b403a; - border-radius: 7px; + border: 1px solid var(--line-strong); + border-radius: 6px; outline: 0; - color: #e9ede7; - background: #111310; - transition: border-color 160ms ease, box-shadow 160ms ease; + color: var(--ink); + background: #fff; + transition: border-color 150ms ease, box-shadow 150ms ease; } .limit-grid input { padding: 9px 7px; - font-size: 0.77rem; + font-size: 0.74rem; } .limit-grid input:focus, .text-rule input:focus { - border-color: var(--mint); - box-shadow: 0 0 0 3px rgba(142, 234, 184, 0.09); + border-color: var(--accent); + box-shadow: 0 0 0 3px rgba(33, 104, 216, 0.1); } .file-rules { - padding: 15px; + padding: 14px; } .file-rules > p { margin-bottom: 12px; - color: #858b83; - font-size: 0.7rem; + color: var(--muted); + font-size: 0.68rem; line-height: 1.45; } @@ -752,18 +719,18 @@ h3 { gap: 7px; min-height: 33px; padding: 0 8px; - border: 1px solid #373d36; - border-radius: 7px; - color: #bfc5bd; - background: #0f110f; - font-size: 0.69rem; + border: 1px solid var(--line); + border-radius: 6px; + color: var(--ink-soft); + background: #fff; + font-size: 0.67rem; } .asset-choices input { width: 13px; height: 13px; margin: 0; - accent-color: var(--mint); + accent-color: var(--accent); } .text-rule { @@ -773,18 +740,18 @@ h3 { .text-rule > span { margin-bottom: 6px; - color: #929890; - font-size: 0.66rem; + color: var(--muted); + font-size: 0.64rem; } .text-rule input { padding: 10px 11px; - font-size: 0.76rem; + font-size: 0.74rem; } .proxy-row { - min-height: 74px; - margin: 15px; + min-height: 72px; + margin: 14px; } .proxy-row.is-unavailable { @@ -795,19 +762,19 @@ h3 { min-height: 1.25em; margin: 13px 0 0; color: var(--danger); - font-size: 0.75rem; + font-size: 0.73rem; line-height: 1.4; } .form-message.success { - color: var(--mint); + color: var(--success); } .console-actions { display: flex; align-items: center; justify-content: space-between; - gap: 20px; + gap: 18px; margin-top: 13px; } @@ -817,21 +784,21 @@ h3 { justify-content: space-between; gap: 20px; flex: 0 0 auto; - min-height: 45px; - padding: 0 15px 0 17px; - border: 1px solid var(--paper); + min-height: 43px; + padding: 0 14px 0 16px; + border: 1px solid var(--accent); border-radius: 7px; - color: #112118; - background: var(--paper); - font-size: 0.79rem; - font-weight: 780; - transition: transform 160ms ease, background 160ms ease, box-shadow 160ms ease; + color: #fff; + background: var(--accent); + font-size: 0.75rem; + font-weight: 760; + transition: transform 150ms ease, background 150ms ease, box-shadow 150ms ease; } .capture-button:hover:not(:disabled) { + background: var(--accent-dark); + box-shadow: 0 8px 16px rgba(33, 104, 216, 0.22); transform: translateY(-1px); - background: var(--mint-bright); - box-shadow: 0 13px 26px rgba(142, 234, 184, 0.14); } .capture-button:disabled { @@ -840,22 +807,21 @@ h3 { } .capture-button span:last-child { - color: #315540; - font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; - font-size: 1rem; + font-family: "Cascadia Mono", "SFMono-Regular", Consolas, monospace; + font-size: 0.95rem; } .console-actions p { - max-width: 260px; + max-width: 270px; margin: 0; - color: #777d75; - font-size: 0.66rem; + color: var(--muted); + font-size: 0.64rem; line-height: 1.45; text-align: right; } .activity-section { - padding-top: 77px; + padding-top: 72px; } .section-heading { @@ -863,25 +829,24 @@ h3 { align-items: flex-end; justify-content: space-between; gap: 22px; - margin-bottom: 21px; + margin-bottom: 20px; } .section-heading .eyebrow { - margin-bottom: 10px; - color: #8d968b; + margin-bottom: 9px; } .section-heading h2 { - margin-bottom: 9px; - font-size: clamp(1.75rem, 2.4vw, 2.2rem); + margin-bottom: 8px; + font-size: clamp(1.65rem, 2.2vw, 2.1rem); } .section-heading p:not(.eyebrow) { - max-width: 580px; + max-width: 560px; margin: 0; - color: #969b95; - font-size: 0.79rem; - line-height: 1.52; + color: var(--muted); + font-size: 0.77rem; + line-height: 1.55; } .quiet-button, @@ -889,11 +854,11 @@ h3 { .icon-button { border: 1px solid var(--line-strong); border-radius: 7px; - color: #ced4cc; - background: rgba(255, 255, 255, 0.035); - font-size: 0.72rem; - font-weight: 670; - transition: border-color 160ms ease, color 160ms ease, background 160ms ease; + color: var(--ink-soft); + background: var(--surface); + font-size: 0.7rem; + font-weight: 700; + transition: border-color 150ms ease, color 150ms ease, background 150ms ease; } .quiet-button { @@ -905,20 +870,20 @@ h3 { .quiet-button:hover, .job-action:hover, .icon-button:hover { - border-color: rgba(142, 234, 184, 0.66); - color: var(--mint-bright); - background: rgba(69, 136, 94, 0.17); + border-color: #9fbff4; + color: var(--accent-dark); + background: var(--accent-soft); } .empty-state { display: flex; align-items: center; - gap: 16px; - min-height: 124px; - padding: 23px; - border: 1px dashed rgba(231, 238, 230, 0.22); - border-radius: var(--radius); - color: var(--muted); + gap: 15px; + min-height: 119px; + padding: 21px; + border: 1px dashed var(--line-strong); + border-radius: var(--radius-md); + background: rgba(255, 255, 255, 0.48); } .empty-state[hidden] { @@ -926,23 +891,23 @@ h3 { } .empty-state p { - max-width: 580px; + max-width: 560px; margin: 0; - color: #969d95; - font-size: 0.79rem; - line-height: 1.52; + color: var(--muted); + font-size: 0.76rem; + line-height: 1.5; } .empty-mark { display: grid; flex: 0 0 auto; - width: 38px; - height: 38px; + width: 36px; + height: 36px; place-items: center; - border: 1px solid rgba(142, 234, 184, 0.52); border-radius: 50%; - color: var(--mint); - font-size: 1.3rem; + color: var(--accent-dark); + background: var(--accent-soft); + font-size: 1.2rem; } .job-grid { @@ -953,14 +918,22 @@ h3 { .job-card { position: relative; + contain-intrinsic-size: auto 270px; + content-visibility: auto; overflow: hidden; - min-height: 259px; + min-height: 252px; padding: 19px; border: 1px solid var(--line); - border-radius: 12px; - background: - linear-gradient(135deg, rgba(255, 255, 255, 0.035), transparent 42%), - rgba(8, 10, 8, 0.58); + border-radius: var(--radius-md); + background: var(--surface); + box-shadow: 0 3px 10px rgba(27, 42, 32, 0.025); + transition: border-color 150ms ease, box-shadow 150ms ease, transform 150ms ease; +} + +.job-card:hover { + border-color: #c5d0c6; + box-shadow: 0 10px 24px rgba(27, 42, 32, 0.07); + transform: translateY(-1px); } .job-card::before { @@ -968,22 +941,23 @@ h3 { top: 0; left: 0; width: 100%; - height: 2px; + height: 3px; content: ""; - background: #5c635c; + background: #bdc8bf; } .job-card[data-state="running"]::before, .job-card[data-state="queued"]::before { - background: var(--blue); + background: var(--accent); } .job-card[data-state="ready"]::before { - background: var(--mint); + background: var(--success); } .job-card[data-state="failed"]::before, -.job-card[data-state="cancelled"]::before { +.job-card[data-state="cancelled"]::before, +.job-card[data-state="expired"]::before { background: var(--danger); } @@ -998,15 +972,15 @@ h3 { .job-topline { align-items: center; justify-content: space-between; - gap: 14px; + gap: 12px; } .job-host { overflow: hidden; - color: #f0f2ef; - font-size: 0.94rem; - font-weight: 700; - letter-spacing: -0.025em; + color: var(--ink); + font-size: 0.91rem; + font-weight: 760; + letter-spacing: -0.03em; text-overflow: ellipsis; white-space: nowrap; } @@ -1014,57 +988,60 @@ h3 { .state-pill { flex: 0 0 auto; padding: 5px 7px; - border: 1px solid rgba(202, 211, 200, 0.25); + border: 1px solid var(--line-strong); border-radius: 999px; - color: #aab1a8; - font-size: 0.61rem; - font-weight: 720; + color: var(--muted); + font-size: 0.57rem; + font-weight: 760; letter-spacing: 0.07em; text-transform: uppercase; } .state-pill.ready { - border-color: rgba(142, 234, 184, 0.45); - color: var(--mint); + border-color: #a9d8bd; + color: var(--success); + background: var(--success-soft); } .state-pill.failed, .state-pill.cancelled, .state-pill.expired { - border-color: rgba(240, 151, 142, 0.45); + border-color: #edc0ba; color: var(--danger); + background: var(--danger-soft); } .state-pill.running, .state-pill.queued { - border-color: rgba(154, 196, 247, 0.48); - color: var(--blue); + border-color: #b9cff2; + color: var(--accent-dark); + background: var(--accent-soft); } .job-url { overflow: hidden; - margin: 5px 0 16px; - color: #7e857c; - font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; - font-size: 0.67rem; + margin: 6px 0 16px; + color: var(--quiet); + font-family: "Cascadia Mono", "SFMono-Regular", Consolas, monospace; + font-size: 0.64rem; text-overflow: ellipsis; white-space: nowrap; } .job-phase { - min-height: 37px; + min-height: 35px; margin: 0 0 12px; - color: #c9cec7; - font-size: 0.77rem; + color: var(--ink-soft); + font-size: 0.75rem; line-height: 1.45; } .job-progress { overflow: hidden; - height: 3px; + height: 4px; margin: 0 0 15px; border-radius: 999px; - background: rgba(235, 243, 235, 0.1); + background: var(--surface-strong); } .job-progress > span { @@ -1072,16 +1049,17 @@ h3 { width: var(--progress, 0%); height: 100%; border-radius: inherit; - background: var(--blue); - transition: width 260ms ease; + background: var(--accent); + transition: width 220ms ease; } .job-card[data-state="ready"] .job-progress > span { - background: var(--mint); + background: var(--success); } .job-card[data-state="failed"] .job-progress > span, -.job-card[data-state="cancelled"] .job-progress > span { +.job-card[data-state="cancelled"] .job-progress > span, +.job-card[data-state="expired"] .job-progress > span { background: var(--danger); } @@ -1109,8 +1087,8 @@ h3 { .job-stats strong { overflow: hidden; - color: #e8ece6; - font-size: 0.82rem; + color: var(--ink); + font-size: 0.8rem; text-overflow: ellipsis; white-space: nowrap; } @@ -1118,8 +1096,9 @@ h3 { .job-stats span, .job-meta span:first-child { margin-top: 3px; - color: #777e75; - font-size: 0.59rem; + color: var(--quiet); + font-size: 0.57rem; + font-weight: 650; letter-spacing: 0.05em; text-transform: uppercase; } @@ -1129,12 +1108,12 @@ h3 { gap: 10px; min-height: 18px; margin-bottom: 9px; - color: #929991; - font-size: 0.67rem; + color: var(--muted); + font-size: 0.65rem; } .job-meta span:last-child { - max-width: 55%; + max-width: 56%; overflow: hidden; text-align: right; text-overflow: ellipsis; @@ -1150,17 +1129,17 @@ h3 { .job-tags span { padding: 3px 6px; - border: 1px solid rgba(207, 218, 206, 0.15); + border: 1px solid var(--line); border-radius: 999px; - color: #879087; - font-size: 0.58rem; + color: var(--muted); + font-size: 0.57rem; } .job-warning { overflow: hidden; margin: 0 0 10px; - color: var(--amber); - font-size: 0.67rem; + color: var(--warning); + font-size: 0.65rem; line-height: 1.35; text-overflow: ellipsis; white-space: nowrap; @@ -1180,19 +1159,20 @@ h3 { } .job-action.primary { - border-color: rgba(142, 234, 184, 0.7); - color: #10251a; - background: var(--mint); + border-color: var(--accent); + color: #fff; + background: var(--accent); } .job-action.primary:hover { - color: #092015; - background: var(--mint-bright); + border-color: var(--accent-dark); + color: #fff; + background: var(--accent-dark); } .job-action.danger { - border-color: rgba(240, 151, 142, 0.36); - color: #efaaa3; + border-color: #e1b4ae; + color: var(--danger); } .job-action:disabled { @@ -1201,89 +1181,36 @@ h3 { } .site-footer { - margin-top: 7px; - border-top: 1px solid rgba(255, 255, 255, 0.1); -} - -.footer-top { - display: grid; - grid-template-columns: minmax(0, 0.86fr) minmax(0, 1.54fr); - gap: clamp(34px, 6vw, 88px); - padding: 43px 0 37px; -} - -.footer-kicker { - margin: 0 0 13px; - color: var(--mint); -} - -.footer-intro h2 { - max-width: 390px; - margin: 0 0 14px; - font-size: clamp(1.55rem, 2.6vw, 2.2rem); - line-height: 1.03; -} - -.footer-intro > p:last-child { - max-width: 420px; - margin: 0; - color: #99a19a; - font-size: 0.78rem; - line-height: 1.62; -} - -.footer-points { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); -} - -.footer-point { - min-width: 0; - padding: 0 20px; -} - -.footer-point:first-child { - padding-left: 0; -} - -.footer-point + .footer-point { - border-left: 1px solid var(--line); -} - -.footer-index { - display: block; - margin-bottom: 15px; - color: #768177; - font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; -} - -.footer-point h3 { - margin: 0 0 8px; - color: #e4e9e2; - font-size: 0.87rem; - letter-spacing: -0.025em; -} - -.footer-point p { - margin: 0; - color: #838b83; - font-size: 0.7rem; - line-height: 1.58; -} - -.footer-bottom { display: flex; + align-items: center; justify-content: space-between; - gap: 24px; - padding: 16px 0 27px; + gap: 22px; + padding: 22px 0 30px; border-top: 1px solid var(--line); - color: #687168; - font-size: 0.64rem; + color: var(--muted); + font-size: 0.68rem; line-height: 1.5; } -.footer-bottom span:last-child { - text-align: right; +.site-footer p { + margin: 0; +} + +.footer-links { + display: flex; + align-items: center; + gap: 16px; + white-space: nowrap; +} + +.footer-links a { + color: var(--accent-dark); + font-weight: 720; + text-decoration: none; +} + +.footer-links a:hover { + text-decoration: underline; } .report-dialog { @@ -1291,15 +1218,14 @@ h3 { max-height: min(760px, calc(100vh - 44px)); padding: 0; border: 1px solid var(--line-strong); - border-radius: 15px; + border-radius: var(--radius-md); color: var(--ink); - background: #111310; - box-shadow: 0 30px 100px rgba(0, 0, 0, 0.67); + background: var(--surface); + box-shadow: 0 24px 70px rgba(27, 42, 32, 0.2); } .report-dialog::backdrop { - background: rgba(0, 0, 0, 0.72); - backdrop-filter: blur(5px); + background: rgba(24, 34, 27, 0.36); } .dialog-head { @@ -1307,17 +1233,17 @@ h3 { align-items: flex-start; justify-content: space-between; gap: 20px; - padding: 24px 25px 18px; + padding: 23px 24px 18px; border-bottom: 1px solid var(--line); } .dialog-head .eyebrow { - color: var(--mint); + margin-bottom: 8px; } .dialog-head h2 { margin: 0; - font-size: 1.7rem; + font-size: 1.65rem; } .icon-button { @@ -1329,21 +1255,21 @@ h3 { .report-content { overflow: auto; max-height: calc(min(760px, 100vh - 44px) - 102px); - padding: 23px 25px 28px; + padding: 22px 24px 28px; } .report-summary { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 9px; - margin-bottom: 21px; + margin-bottom: 20px; } .report-summary div { padding: 12px; border: 1px solid var(--line); border-radius: 8px; - background: rgba(255, 255, 255, 0.025); + background: var(--surface-muted); } .report-summary strong, @@ -1353,16 +1279,16 @@ h3 { .report-summary strong { overflow: hidden; - color: var(--mint); - font-size: 1rem; + color: var(--accent-dark); + font-size: 0.98rem; text-overflow: ellipsis; white-space: nowrap; } .report-summary span { margin-top: 4px; - color: #7f867d; - font-size: 0.61rem; + color: var(--muted); + font-size: 0.59rem; letter-spacing: 0.04em; text-transform: uppercase; } @@ -1370,8 +1296,8 @@ h3 { .report-list { margin: 0; padding-left: 18px; - color: #c6ccc4; - font-size: 0.78rem; + color: var(--ink-soft); + font-size: 0.76rem; line-height: 1.55; } @@ -1381,30 +1307,24 @@ h3 { .report-subhead { margin: 21px 0 9px; - color: var(--mint); - font-size: 0.7rem; - letter-spacing: 0.1em; + color: var(--accent-dark); + font-size: 0.68rem; + letter-spacing: 0.08em; text-transform: uppercase; } -@media (prefers-reduced-motion: no-preference) { - .pulse-dot, - .service-status > i { - animation: pulse 2.2s ease-in-out infinite; +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + scroll-behavior: auto !important; + transition-duration: 0.01ms !important; } } -@keyframes pulse { - 50% { - opacity: 0.45; - transform: scale(0.76); - } -} - -@media (max-width: 930px) { +@media (max-width: 980px) { .site-header { grid-template-columns: minmax(0, 1fr) auto; - min-height: 90px; } .site-nav { @@ -1416,68 +1336,39 @@ h3 { } .observatory { - min-height: 490px; - } - - .capture-console { - min-height: 0; - } - - .footer-top { - grid-template-columns: 1fr; - gap: 33px; + min-height: 460px; } } @media (max-width: 680px) { .site-shell { - width: min(100% - 28px, 1220px); + width: min(100% - 28px, 1200px); } .site-header { - min-height: 80px; + min-height: 72px; + gap: 14px; } - .brand-mark { - width: 33px; - height: 33px; - } - - .brand-mark::before { - width: 19px; - height: 19px; - } - - .brand-mark i:nth-child(1) { - top: 8px; - left: 8px; - } - - .brand-mark i:nth-child(2) { - right: 8px; - bottom: 8px; - } - - .brand-mark i:nth-child(3) { - top: 8px; - right: 8px; - } - - .brand-kicker, - .service-panel-label { + .brand-copy span, + .service-status span { display: none; } - .service-status { - font-size: 0.66rem; + .header-tools { + gap: 10px; + } + + .source-link { + padding: 6px 8px; } main { - padding: 31px 0 61px; + padding: 28px 0 56px; } .workbench { - min-height: 0; + border-radius: var(--radius-md); } .observatory, @@ -1486,11 +1377,11 @@ h3 { } .observatory { - min-height: 495px; + min-height: 0; } h1 { - font-size: clamp(2.85rem, 14vw, 4.1rem); + font-size: clamp(2.65rem, 13vw, 3.85rem); } .metric-grid { @@ -1498,8 +1389,8 @@ h3 { } .metric-card { - min-height: 94px; - padding: 14px; + min-height: 82px; + padding: 13px; } .console-head { @@ -1515,7 +1406,8 @@ h3 { } .console-actions, - .section-heading { + .section-heading, + .site-footer { align-items: flex-start; flex-direction: column; } @@ -1526,11 +1418,11 @@ h3 { } .activity-section { - padding-top: 57px; + padding-top: 56px; } .section-heading { - gap: 16px; + gap: 15px; } .job-grid { @@ -1541,37 +1433,14 @@ h3 { grid-template-columns: repeat(2, minmax(0, 1fr)); } - .footer-top { - padding: 34px 0 30px; + .site-footer { + gap: 10px; } - .footer-points { - grid-template-columns: 1fr; - gap: 0; - } - - .footer-point, - .footer-point:first-child { - padding: 18px 0; - } - - .footer-point:first-child { - padding-top: 0; - } - - .footer-point + .footer-point { - border-top: 1px solid var(--line); - border-left: 0; - } - - .footer-bottom { - align-items: flex-start; - flex-direction: column; - gap: 5px; - } - - .footer-bottom span:last-child { - text-align: left; + .footer-links { + flex-wrap: wrap; + gap: 8px 14px; + white-space: normal; } } @@ -1580,11 +1449,7 @@ h3 { grid-template-columns: 1fr; } - .observatory { - min-height: 0; - } - .summary-value { - max-width: 125px; + max-width: 122px; } } diff --git a/app/static/app.js b/app/static/app.js index 17f8e38..54c3777 100644 --- a/app/static/app.js +++ b/app/static/app.js @@ -606,7 +606,7 @@ form.addEventListener("submit", async (event) => { setMessage(""); submitButton.disabled = true; - submitButton.querySelector("span").textContent = "Creating crawl"; + submitButton.querySelector("span").textContent = "Preparing archive"; try { const job = await request("/api/captures", { method: "POST", @@ -625,14 +625,14 @@ form.addEventListener("submit", async (event) => { }), }); upsertJob(job); - setMessage("Crawl queued. This browser will reconnect to it automatically.", "success"); + setMessage("Archive queued. This browser will reconnect to it automatically.", "success"); urlInput.value = ""; void loadPublicStats(); } catch (error) { setMessage(error.message); } finally { submitButton.disabled = false; - submitButton.querySelector("span").textContent = "Start private crawl"; + submitButton.querySelector("span").textContent = "Start archive"; } }); diff --git a/app/templates/index.html b/app/templates/index.html index 570a3b5..a21931b 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -3,7 +3,7 @@ - + Website Downloader & Offline Archive Tool | {{ app_name }} @@ -32,22 +33,29 @@ +