Family Home Lab: portal, dsh (chat+plugins), transcriber, music/media tools, home dash
This commit is contained in:
330
portal/static/app.css
Normal file
330
portal/static/app.css
Normal file
@@ -0,0 +1,330 @@
|
||||
/* ============================================================
|
||||
Family Home Lab — Components & Layout
|
||||
Builds strictly from tokens.css variables per DESIGN.md.
|
||||
============================================================ */
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--font-family);
|
||||
background: var(--canvas-soft);
|
||||
color: var(--ink-secondary);
|
||||
font: var(--type-body-md);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
a { color: var(--primary); text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
|
||||
/* -------------------------------------------------- Login */
|
||||
.login-screen {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--secondary);
|
||||
color: var(--surface);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding: var(--space-lg);
|
||||
}
|
||||
/* Subtle starfield/sticker dots */
|
||||
.login-screen::before {
|
||||
content: "";
|
||||
position: absolute; inset: 0;
|
||||
background-image:
|
||||
radial-gradient(rgba(255,255,255,.16) 1px, transparent 1.5px),
|
||||
radial-gradient(rgba(255,255,255,.1) 1px, transparent 1.5px);
|
||||
background-size: 44px 44px, 71px 71px;
|
||||
background-position: 0 0, 17px 21px;
|
||||
pointer-events: none;
|
||||
}
|
||||
.login-wrap { position: relative; z-index: 1; width: 100%; max-width: 400px; }
|
||||
.login-greeting {
|
||||
margin: 0 0 var(--space-xxl);
|
||||
font: var(--type-display-1);
|
||||
color: var(--surface);
|
||||
letter-spacing: -1.5px;
|
||||
}
|
||||
.auth-card {
|
||||
background: var(--surface);
|
||||
border-radius: var(--rounded-lg);
|
||||
box-shadow: var(--shadow-level-2);
|
||||
padding: var(--space-xl) var(--space-xl);
|
||||
color: var(--ink-secondary);
|
||||
}
|
||||
.auth-title { font: var(--type-title); margin: 0 0 var(--space-md); color: var(--ink); }
|
||||
|
||||
/* -------------------------------------------------- Form bits */
|
||||
.form-field { margin-bottom: var(--space-md); }
|
||||
.form-field label {
|
||||
display: block;
|
||||
font: var(--type-eyebrow);
|
||||
letter-spacing: 0.125px;
|
||||
color: var(--ink-muted);
|
||||
margin-bottom: var(--space-xs);
|
||||
}
|
||||
.text-input {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
font: var(--type-body-sm);
|
||||
color: var(--ink);
|
||||
border: 1px solid rgb(221, 221, 221);
|
||||
border-radius: var(--rounded-xs);
|
||||
background: var(--surface);
|
||||
}
|
||||
.text-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary);
|
||||
box-shadow: var(--shadow-level-1), 0 0 0 3px rgba(0, 117, 222, 0.18);
|
||||
}
|
||||
.form-error {
|
||||
background: #fdecec;
|
||||
color: #b3261e;
|
||||
border: 1px solid #f5c6c4;
|
||||
border-radius: var(--rounded-sm);
|
||||
padding: var(--space-sm);
|
||||
font: var(--type-body-sm);
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------- Buttons */
|
||||
.button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-xs);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font: var(--type-button);
|
||||
text-decoration: none;
|
||||
transition: transform .08s ease, background .15s ease, box-shadow .15s ease;
|
||||
}
|
||||
.button:active { transform: scale(0.97); }
|
||||
button.button, input[type="submit"].button { font-family: inherit; }
|
||||
|
||||
.button-primary {
|
||||
background: var(--primary);
|
||||
color: var(--on-primary);
|
||||
border-radius: var(--rounded-full);
|
||||
padding: 12px 28px;
|
||||
}
|
||||
.button-primary:hover { background: var(--primary-active); color: var(--on-primary); text-decoration: none; }
|
||||
|
||||
.button-secondary {
|
||||
background: var(--surface);
|
||||
color: var(--ink);
|
||||
border-radius: var(--rounded-full);
|
||||
padding: 11px 26px;
|
||||
box-shadow: var(--shadow-level-1);
|
||||
border: 1px solid var(--hairline);
|
||||
}
|
||||
.button-secondary:hover { box-shadow: var(--shadow-level-1); }
|
||||
|
||||
.button-utility {
|
||||
background: var(--surface);
|
||||
color: var(--ink);
|
||||
border-radius: var(--rounded-md);
|
||||
padding: 4px 14px;
|
||||
border: 1px solid var(--hairline);
|
||||
font: var(--type-body-sm);
|
||||
}
|
||||
.button-utility:hover { background: var(--canvas-soft); }
|
||||
|
||||
.width-full { width: 100%; }
|
||||
|
||||
/* -------------------------------------------------- Nav bar */
|
||||
.nav-bar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 20;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-lg);
|
||||
background: var(--surface);
|
||||
border-bottom: 1px solid var(--hairline);
|
||||
padding: var(--space-sm) var(--space-xxl);
|
||||
}
|
||||
.nav-wordmark {
|
||||
font: var(--type-heading-2);
|
||||
color: var(--ink);
|
||||
letter-spacing: -0.25px;
|
||||
text-decoration: none;
|
||||
}
|
||||
.nav-wordmark:hover { text-decoration: none; }
|
||||
.nav-right { display: flex; align-items: center; gap: var(--space-md); }
|
||||
.nav-user { display: flex; align-items: center; gap: var(--space-xs); font: var(--type-body-sm); color: var(--ink-secondary); }
|
||||
.nav-avatar {
|
||||
width: 28px; height: 28px;
|
||||
border-radius: var(--rounded-full);
|
||||
background: var(--primary);
|
||||
color: var(--on-primary);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font: var(--type-button);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------- Layout */
|
||||
.page { max-width: var(--container-max); margin: 0 auto; padding: var(--space-xxl) var(--space-lg); }
|
||||
.greeting { font: var(--type-heading-1); color: var(--ink); margin: 0 0 var(--space-xxl); }
|
||||
|
||||
.section { margin-bottom: var(--space-xl); }
|
||||
.section-head { display: flex; align-items: center; gap: var(--space-sm); margin-bottom: var(--space-md); }
|
||||
.category-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font: var(--type-eyebrow);
|
||||
letter-spacing: 0.125px;
|
||||
background: var(--surface);
|
||||
border-radius: var(--rounded-full);
|
||||
padding: 4px 8px;
|
||||
border: 1px solid var(--hairline);
|
||||
}
|
||||
.pill-dot { width: 8px; height: 8px; border-radius: var(--rounded-full); }
|
||||
.section-title { font: var(--type-heading-2); color: var(--ink); margin: 0; }
|
||||
|
||||
/* -------------------------------------------------- Tool grid */
|
||||
.tool-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
@media (max-width: 840px) { .tool-grid { grid-template-columns: repeat(2, 1fr); } }
|
||||
@media (max-width: 600px) { .tool-grid { grid-template-columns: 1fr; } }
|
||||
|
||||
.tool-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm);
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--hairline);
|
||||
border-radius: var(--rounded-lg);
|
||||
padding: var(--space-lg);
|
||||
text-decoration: none;
|
||||
color: var(--ink-secondary);
|
||||
transition: box-shadow .18s ease, transform .18s ease;
|
||||
}
|
||||
.tool-card:hover {
|
||||
box-shadow: var(--shadow-level-1);
|
||||
transform: translateY(-1px);
|
||||
text-decoration: none;
|
||||
}
|
||||
.tool-card-top { display: flex; align-items: flex-start; justify-content: space-between; }
|
||||
.tool-icon {
|
||||
width: 40px; height: 40px;
|
||||
border-radius: var(--rounded-full);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
color: var(--surface);
|
||||
font: var(--type-title);
|
||||
flex: none;
|
||||
}
|
||||
.tool-card .tool-name { font: var(--type-title); color: var(--ink); margin: 0; }
|
||||
.tool-card .tool-desc { font: var(--type-body-sm); color: var(--ink-muted); margin: 0; flex: 1; }
|
||||
.tool-card-bottom { display: flex; align-items: center; justify-content: space-between; }
|
||||
.launch-chip { font: var(--type-caption); color: var(--ink-faint); }
|
||||
.tag-chip {
|
||||
font: var(--type-caption);
|
||||
color: var(--ink-muted);
|
||||
border: 1px solid var(--hairline);
|
||||
border-radius: var(--rounded-full);
|
||||
padding: 1px 8px;
|
||||
background: var(--surface-2);
|
||||
}
|
||||
.tag-chip-login { color: var(--sticker-orange); border-color: color-mix(in srgb, var(--sticker-orange) 40%, transparent); }
|
||||
.tag-chip-admin { color: var(--ink-muted); border-style: dashed; }
|
||||
|
||||
/* catalogue search */
|
||||
.dash-search {
|
||||
max-width: 480px;
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
.tool-grid .tool-card.hidden { display: none; }
|
||||
.section.hidden-section { display: none; }
|
||||
|
||||
/* status dot levels */
|
||||
.status-dot {
|
||||
width: 8px; height: 8px; border-radius: var(--rounded-full); display: inline-block;
|
||||
}
|
||||
.status-online { background: var(--sticker-green); }
|
||||
.status-starting { background: var(--sticker-orange); }
|
||||
.status-offline { background: var(--ink-faint); }
|
||||
|
||||
/* loading pulse for polling */
|
||||
.tool-grid.htmx-request .tool-card { opacity: .6; transition: opacity .2s; }
|
||||
|
||||
/* -------------------------------------------------- Tool embed */
|
||||
.tool-bar {
|
||||
position: sticky; top: 52px; z-index: 15;
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
gap: var(--space-md);
|
||||
background: var(--surface);
|
||||
border-bottom: 1px solid var(--hairline);
|
||||
padding: var(--space-sm) var(--space-xxl);
|
||||
}
|
||||
.embed-well {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--hairline);
|
||||
border-radius: var(--rounded-lg);
|
||||
overflow: hidden;
|
||||
height: calc(100vh - 170px);
|
||||
box-shadow: var(--shadow-level-1);
|
||||
}
|
||||
.embed-well iframe { width: 100%; height: 100%; border: 0; display: block; }
|
||||
|
||||
/* -------------------------------------------------- Link-out fallback */
|
||||
.linkout-card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--hairline);
|
||||
border-radius: var(--rounded-lg);
|
||||
padding: var(--space-xl);
|
||||
max-width: 520px;
|
||||
box-shadow: var(--shadow-level-1);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------- Partial rows (status) */
|
||||
.status-refresh {
|
||||
display: contents; /* keeps the HTMX swap invisible-pressure-free */
|
||||
}
|
||||
|
||||
/* -------------------------------------------------- Admin */
|
||||
.user-table { width: 100%; border-collapse: collapse; background: var(--surface); border-radius: var(--rounded-lg); overflow: hidden; border: 1px solid var(--hairline); }
|
||||
.user-table th, .user-table td { text-align: left; padding: var(--space-sm) var(--space-md); border-bottom: 1px solid var(--hairline); font: var(--type-body-sm); }
|
||||
.user-table th { background: var(--canvas-soft); color: var(--ink-muted); font: var(--type-eyebrow); letter-spacing: .125px; }
|
||||
.user-table tr:last-child td { border-bottom: none; }
|
||||
.badge { display: inline-block; font: var(--type-eyebrow); border-radius: var(--rounded-full); padding: 2px 8px; background: var(--canvas-soft); color: var(--ink-muted); }
|
||||
.badge-admin { background: var(--secondary); color: var(--surface); }
|
||||
|
||||
.admin-panel { display: grid; grid-template-columns: 2fr 1fr; gap: var(--space-xxl); align-items: start; }
|
||||
@media (max-width: 840px) { .admin-panel { grid-template-columns: 1fr; } }
|
||||
|
||||
/* -------------------------------------------------- Workspace / file list */
|
||||
.tool-detail { display: flex; flex-direction: column; gap: var(--space-lg); }
|
||||
.workspace {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--hairline);
|
||||
border-radius: var(--rounded-lg);
|
||||
padding: var(--space-lg) var(--space-xl);
|
||||
box-shadow: var(--shadow-level-1);
|
||||
}
|
||||
.workspace-head {
|
||||
display: flex; align-items: baseline; justify-content: space-between;
|
||||
gap: var(--space-md); margin-bottom: var(--space-sm);
|
||||
}
|
||||
.file-list { list-style: none; margin: 0; padding: 0; }
|
||||
.file-list li {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
gap: var(--space-md); padding: 10px 2px;
|
||||
border-bottom: 1px solid var(--hairline); font: var(--type-body-sm);
|
||||
}
|
||||
.file-list li:last-child { border-bottom: none; }
|
||||
.file-name { color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.file-meta { color: var(--ink-faint); font: var(--type-caption); white-space: nowrap; }
|
||||
|
||||
/* -------------------------------------------------- Minimal, generic */
|
||||
.muted { color: var(--ink-muted); }
|
||||
.faint { color: var(--ink-faint); }
|
||||
.margin-top { margin-top: var(--space-lg); }
|
||||
.stack > * + * { margin-top: var(--space-lg); }
|
||||
BIN
portal/static/fonts/inter-400.woff2
Normal file
BIN
portal/static/fonts/inter-400.woff2
Normal file
Binary file not shown.
BIN
portal/static/fonts/inter-500.woff2
Normal file
BIN
portal/static/fonts/inter-500.woff2
Normal file
Binary file not shown.
BIN
portal/static/fonts/inter-600.woff2
Normal file
BIN
portal/static/fonts/inter-600.woff2
Normal file
Binary file not shown.
BIN
portal/static/fonts/inter-700.woff2
Normal file
BIN
portal/static/fonts/inter-700.woff2
Normal file
Binary file not shown.
90
portal/static/tokens.css
Normal file
90
portal/static/tokens.css
Normal file
@@ -0,0 +1,90 @@
|
||||
/* ============================================================
|
||||
Family Home Lab — Design Tokens
|
||||
Source: DESIGN.md (Notion design language)
|
||||
Only structural/semantic tokens here. All values map 1:1.
|
||||
============================================================ */
|
||||
|
||||
/* Inter (@fontsource) — self-hosted substitute for NotionInter */
|
||||
@font-face { font-family: "Inter"; font-style: normal; font-weight: 400; font-display: swap; src: url("/static/fonts/inter-400.woff2") format("woff2"); }
|
||||
@font-face { font-family: "Inter"; font-style: normal; font-weight: 500; font-display: swap; src: url("/static/fonts/inter-500.woff2") format("woff2"); }
|
||||
@font-face { font-family: "Inter"; font-style: normal; font-weight: 600; font-display: swap; src: url("/static/fonts/inter-600.woff2") format("woff2"); }
|
||||
@font-face { font-family: "Inter"; font-style: normal; font-weight: 700; font-display: swap; src: url("/static/fonts/inter-700.woff2") format("woff2"); }
|
||||
|
||||
:root {
|
||||
/* --- Surface --- */
|
||||
--canvas-soft: #f6f5f4;
|
||||
--surface: #ffffff;
|
||||
--hairline: #e6e6e6;
|
||||
|
||||
/* --- Text --- */
|
||||
--ink: #000000;
|
||||
--ink-secondary: #31302e;
|
||||
--ink-muted: #615d59;
|
||||
--ink-faint: #a39e98;
|
||||
|
||||
/* --- Structural accents --- */
|
||||
--primary: #0075de; /* the ONLY action colour */
|
||||
--primary-active: #005bab;
|
||||
--on-primary: #ffffff;
|
||||
--secondary: #213183; /* deep indigo — login band + greeting only */
|
||||
|
||||
/* --- Sticker palette (decorative only) --- */
|
||||
--sticker-sky: #62aef0; /* chat */
|
||||
--sticker-purple: #d6b6f6; /* image */
|
||||
--sticker-pink: #ff64c8; /* video */
|
||||
--sticker-orange: #dd5b00; /* audio */
|
||||
--sticker-teal: #2a9d99; /* docs */
|
||||
--sticker-green: #1aae39; /* status: online/saved */
|
||||
--sticker-purple-deep: #391c57; /* illustration only */
|
||||
--sticker-orange-deep: #793400; /* illustration only */
|
||||
|
||||
/* --- Typography (Inter, self-hosted via @font-face) --- */
|
||||
--font-family: "Inter", -apple-system, system-ui, "Segoe UI",
|
||||
Helvetica, Arial, sans-serif;
|
||||
|
||||
/* Font-family is baked into each token below: the CSS `font` shorthand
|
||||
resets font-family to the UA default (serif) unless a family is written,
|
||||
and it cannot carry letter-spacing — so weights/sizes ship inline and the
|
||||
family is appended per token. */
|
||||
--type-display-1: 700 48px/1.05 var(--font-family);
|
||||
--type-heading-1: 700 32px/1.1 var(--font-family);
|
||||
--type-heading-2: 700 22px/1.27 var(--font-family);
|
||||
--type-title: 600 17px/1.4 var(--font-family);
|
||||
--type-body-md: 400 15px/1.5 var(--font-family);
|
||||
--type-body-sm: 400 14px/1.4 var(--font-family);
|
||||
--type-button: 500 15px/1.4 var(--font-family);
|
||||
--type-caption: 400 13px/1.43 var(--font-family);
|
||||
--type-eyebrow: 600 12px/1.33 var(--font-family);
|
||||
|
||||
/* --- Layout (base unit 8px) --- */
|
||||
--space-xxs: 4px;
|
||||
--space-xs: 8px;
|
||||
--space-sm: 12px;
|
||||
--space-md: 16px;
|
||||
--space-lg: 24px;
|
||||
--space-xl: 28px;
|
||||
--space-xxl: 32px;
|
||||
|
||||
--container-max: 1200px;
|
||||
|
||||
/* --- Elevation --- */
|
||||
--shadow-level-1: rgba(0, 0, 0, 0.01) 0 0.175px 1px,
|
||||
rgba(0, 0, 0, 0.02) 0 0.8px 3px,
|
||||
rgba(0, 0, 0, 0.027) 0 2px 8px,
|
||||
rgba(0, 0, 0, 0.04) 0 4px 18px;
|
||||
--shadow-level-2: rgba(0, 0, 0, 0.0122) 0 0.175px 1px,
|
||||
rgba(0, 0, 0, 0.024) 0 0.8px 3px,
|
||||
rgba(0, 0, 0, 0.032) 0 2px 8px,
|
||||
rgba(0, 0, 0, 0.047) 0 4px 18px,
|
||||
rgba(0, 0, 0, 0.05) 0 23px 52px;
|
||||
|
||||
/* --- Shapes --- */
|
||||
--rounded-xs: 4px;
|
||||
--rounded-sm: 5px;
|
||||
--rounded-md: 8px;
|
||||
--rounded-lg: 12px;
|
||||
--rounded-full: 9999px;
|
||||
}
|
||||
|
||||
/* Numerals: enable OpenType lnum */
|
||||
html { font-feature-settings: "lnum" 1; }
|
||||
Reference in New Issue
Block a user