diff --git a/css/site.css b/css/site.css index cfb9c53..ac165ac 100644 --- a/css/site.css +++ b/css/site.css @@ -1,735 +1,656 @@ /* ============================================================ - The OC Guy — Odoo Community tips and tricks - Direction: "the sentence is the layout" (seed 1084447843) - Palette: navy field, pink ink. No Odoo plum, no teal, no violet. + The OC Guy — editorial rebuild + Design language from the VITA reference: paper ground, black + ink, hairline rules, didone serif display, tiny tracked caps + for labels, italic serif meta, boxed modules, 3-column cards. ============================================================ */ :root { - /* colour roles */ - --bg: #1a2039; - --surface: #212846; - --ink: #f9a3c0; - --muted: #b9879c; - --accent: #f9a3c0; - --accent-ink: #1a2039; + --paper: #fbfaf7; + --ink: #111110; + --muted: #6c6a66; + --rule: rgba(17, 17, 16, 0.16); + --rule-strong: rgba(17, 17, 16, 0.42); + --tint: #f2f0ec; - /* type steps — five, no more */ - --t-small: clamp(13px, 0.8rem + 0.1vw, 14px); - --t-body: clamp(17px, 1rem + 0.25vw, 19px); - --t-statement: clamp(26px, 2.35vw, 36px); - --t-head: clamp(32px, 1.3rem + 3.2vw, 64px); - --t-display: clamp(38px, 6.1vw, 90px); + --serif: "Playfair Display", "Times New Roman", Georgia, serif; + --sans: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif; - --pad: clamp(16px, 3vw, 32px); - --gap-section: clamp(72px, 7.5vw, 112px); + --pad: clamp(16px, 3.2vw, 40px); + --max: 1180px; + --gap: clamp(24px, 3vw, 44px); - --font: "General Sans", ui-sans-serif, system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif; - --ease: cubic-bezier(0.2, 0.8, 0.2, 1); + --step-xs: 11px; + --step-sm: 13px; + --step-body: 16px; + --step-lead: clamp(17px, 1.1vw, 19px); + --step-h3: clamp(19px, 1.5vw, 23px); + --step-h2: clamp(30px, 3.4vw, 46px); + --step-display: clamp(40px, 6.4vw, 92px); } *, *::before, *::after { box-sizing: border-box; } -html { - -webkit-text-size-adjust: 100%; - scroll-behavior: smooth; -} +html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; } body { margin: 0; - background: var(--bg); + background: var(--paper); color: var(--ink); - font-family: var(--font); - font-size: var(--t-body); - line-height: 1.45; - font-weight: 400; - overflow-x: hidden; + font-family: var(--sans); + font-size: var(--step-body); + line-height: 1.55; } img { display: block; max-width: 100%; height: auto; } a { color: inherit; } button, input { font: inherit; color: inherit; } -:focus-visible { - outline: 2px solid var(--ink); - outline-offset: 4px; -} -::selection { background: var(--ink); color: var(--bg); } +:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; } +::selection { background: var(--ink); color: var(--paper); } .sr-only { - position: absolute; width: 1px; height: 1px; - padding: 0; margin: -1px; overflow: hidden; - clip: rect(0 0 0 0); white-space: nowrap; border: 0; + position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; + overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; } .skip { position: fixed; top: 8px; left: 8px; z-index: 60; - padding: 10px 14px; background: var(--ink); color: var(--bg); - font-size: var(--t-small); text-decoration: none; - transform: translateY(-160%); - transition: transform 240ms var(--ease); + padding: 10px 16px; background: var(--ink); color: var(--paper); + font-size: var(--step-sm); text-decoration: none; + transform: translateY(-160%); transition: transform 220ms ease; } .skip:focus { transform: translateY(0); } -/* ---------- top bar: wordmark left, links spread right ---------- */ +/* ---------- masthead ---------- */ -.topbar { - display: flex; - align-items: baseline; - justify-content: space-between; - gap: 24px; - padding: 24px var(--pad) 0; +.masthead { + max-width: var(--max); + margin: 0 auto; + padding: clamp(20px, 3vw, 34px) var(--pad) 18px; + display: grid; + grid-template-columns: 1fr auto; + align-items: end; + gap: 14px; + border-bottom: 1px solid var(--rule); } -.mark { - font-size: var(--t-small); - font-weight: 500; +.wordmark { + font-family: var(--serif); + font-weight: 700; + font-size: clamp(40px, 6.2vw, 76px); + letter-spacing: -0.035em; + line-height: 0.86; + text-transform: uppercase; text-decoration: none; - letter-spacing: -0.01em; } .nav { display: flex; flex-wrap: wrap; - gap: clamp(12px, 2.4vw, 40px); - font-size: var(--t-small); + align-items: center; + gap: clamp(12px, 1.8vw, 26px); + font-size: var(--step-xs); + font-weight: 500; + letter-spacing: 0.16em; + text-transform: uppercase; } -.nav a { text-decoration: none; color: var(--muted); } -.nav a:hover, .nav a:focus-visible { color: var(--ink); } -.nav .nav-action { color: var(--ink); } +.nav a { text-decoration: none; padding: 6px 0; } +.nav a:hover { text-decoration: underline; text-underline-offset: 5px; } -/* ---------- hero ---------- */ - -.hero { - min-height: 92svh; - display: flex; - flex-direction: column; - justify-content: center; - padding: clamp(56px, 9vh, 110px) var(--pad) 32px; +.nav-cta { + grid-column: 2; + justify-self: end; + margin-top: 10px; + padding: 9px 18px; + border: 1px solid var(--ink); + font-size: var(--step-xs); + font-weight: 600; + letter-spacing: 0.16em; + text-transform: uppercase; + text-decoration: none; + transition: background-color 160ms ease, color 160ms ease; } +.nav-cta:hover, .nav-cta:focus-visible { background: var(--ink); color: var(--paper); } -.hero-part { white-space: nowrap; } +/* ---------- lead ---------- */ -.hero-line { - margin: 0; - display: flex; - flex-wrap: wrap; - align-items: baseline; - gap: 0 0.2em; - font-size: var(--t-display); - font-weight: 700; - letter-spacing: -0.035em; - line-height: 0.94; -} - -/* the photograph set into the sentence, one line tall */ -.slot { - display: inline-block; - width: 2.8em; - height: 0.76em; - vertical-align: 0; - overflow: hidden; - background: var(--surface); -} -.slot img { - width: 100%; height: 100%; - object-fit: cover; - object-position: 50% 52%; -} - -.hero-foot { - margin-top: clamp(40px, 7vh, 88px); +.lead { + max-width: var(--max); + margin: 0 auto; + padding: var(--gap) var(--pad) clamp(36px, 5vw, 64px); display: grid; - grid-template-columns: repeat(12, 1fr); - gap: var(--pad); - align-items: end; + grid-template-columns: 3fr 7fr; + grid-template-areas: + "side media" + "side caption"; + column-gap: var(--gap); + row-gap: 22px; } -.hero-say { - grid-column: 5 / 11; - margin: 0; - max-width: 34em; - font-size: var(--t-body); - line-height: 1.5; -} -.hero-foot .btn { grid-column: 11 / 13; justify-self: end; } -/* ---------- buttons: one shape, square, filled ---------- */ +.lead-side { grid-area: side; display: flex; flex-direction: column; gap: clamp(22px, 3vw, 36px); } + +.box { + border: 1px solid var(--rule-strong); + padding: 22px 20px 20px; +} +.box-kicker { + margin: 0 0 12px; + font-size: var(--step-xs); + font-weight: 600; + letter-spacing: 0.16em; + text-transform: uppercase; +} +.box-lede { + margin: 0 0 18px; + font-family: var(--serif); + font-style: italic; + font-size: var(--step-lead); + line-height: 1.45; +} .btn { display: inline-flex; align-items: center; justify-content: center; - min-height: 48px; - padding: 0 26px; - background: var(--accent); - color: var(--accent-ink); - font-size: var(--t-small); - font-weight: 500; + min-height: 44px; + padding: 0 22px; + background: var(--ink); + color: var(--paper); + border: 1px solid var(--ink); + border-radius: 0; + font-size: var(--step-xs); + font-weight: 600; + letter-spacing: 0.16em; + text-transform: uppercase; text-decoration: none; - border: 0; - border-radius: 0; cursor: pointer; - transition: transform 200ms var(--ease), background-color 200ms var(--ease); + transition: background-color 160ms ease, color 160ms ease; } -.btn:hover { background: var(--ink); transform: translateY(-2px); } -.btn:active { transform: translateY(0); } +.btn:hover, .btn:focus-visible { background: transparent; color: var(--ink); } -/* ---------- intro: statement with inline thumbnails ---------- */ - -.intro { - padding: clamp(56px, 6vw, 96px) var(--pad) clamp(80px, 9vw, 144px); - display: flex; - justify-content: center; -} - -.statement { - margin: 0; - max-width: 21em; - font-size: var(--t-statement); - line-height: 1.18; - letter-spacing: -0.015em; - font-weight: 500; -} - -.thumb { - display: inline-block; - width: 1.7em; - height: 1em; - margin: 0 0.12em; - vertical-align: -0.1em; - overflow: hidden; - border-radius: 0.06em; - background: var(--surface); -} -.thumb img { width: 100%; height: 100%; object-fit: cover; } -.thumb img { object-position: 50% 40%; } - -/* ---------- open: map to full width ---------- */ - -.open { padding-bottom: var(--gap-section); overflow: clip; } - -.map-figure { margin: 0; overflow: clip; } - -.map-figure img { - width: 100%; - max-width: 100%; - height: clamp(220px, 30vw, 420px); - object-fit: cover; - object-position: 50% 45%; - background: var(--surface); -} - -.map-figure figcaption { - padding: 14px var(--pad) 0; - font-size: var(--t-small); - color: var(--muted); - max-width: 52em; -} - -.open-copy { - margin-top: clamp(48px, 6vw, 88px); - padding: 0 var(--pad); - display: grid; - grid-template-columns: repeat(12, 1fr); - gap: var(--pad); -} -.open-copy h2 { - grid-column: 1 / 7; - margin: 0; - font-size: var(--t-head); - font-weight: 700; - letter-spacing: -0.03em; - line-height: 0.98; -} -.open-copy p { - grid-column: 8 / 13; - margin: 0; - max-width: 30em; - line-height: 1.55; -} - -.open-figure { - grid-column: 10 / 13; - margin: clamp(32px, 4vw, 56px) 0 0; -} -.open-figure img { - width: 100%; - aspect-ratio: 4 / 5; - object-fit: cover; - object-position: 50% 38%; - background: var(--surface); -} - -/* ---------- claims: sentences with switches ---------- */ - -.claims { - background: var(--surface); - padding: clamp(24px, 3vw, 40px) 0 calc(var(--gap-section) * 0.6); -} - -.band { - border: 0; - background: transparent; - transition: background-color 400ms var(--ease); - padding: 0 var(--pad); -} -.band[data-on="true"] { background: var(--bg); } - -.band-switch { - display: grid; - grid-template-columns: 1fr auto; - align-items: center; - gap: 24px; - width: 100%; - min-height: clamp(112px, 17vh, 168px); - padding: 18px 0; - background: none; - border: 0; - text-align: left; - cursor: pointer; -} - -.band-text { - font-size: var(--t-statement); - font-weight: 500; - letter-spacing: -0.03em; - line-height: 1; - color: var(--muted); - transition: color 300ms var(--ease); -} -.band[data-on="true"] .band-text { color: var(--ink); } - -.track { - position: relative; - display: block; - width: 2.4em; - height: 1.15em; - min-width: 52px; - min-height: 26px; - background: var(--bg); - transition: background-color 300ms var(--ease); -} -.band[data-on="true"] .track { background: var(--ink); } - -.knob { - position: absolute; - top: 0.14em; - left: 0.14em; - width: calc(1.15em - 0.28em); - height: calc(1.15em - 0.28em); - min-width: 18px; - min-height: 18px; - background: var(--muted); - transition: transform 300ms var(--ease), background-color 300ms var(--ease); -} -.band[data-on="true"] .knob { - transform: translateX(1.25em); - background: var(--bg); -} - -.band-value { - margin: 0; - max-width: 46em; - padding: 0 0 22px; - font-size: var(--t-body); - line-height: 1.5; - color: var(--muted); - opacity: 0.6; - transition: opacity 300ms var(--ease), color 300ms var(--ease); -} -.band[data-on="true"] .band-value { opacity: 1; color: var(--ink); } - -.band-value code { - font-family: var(--font); - font-weight: 500; - color: var(--ink); -} - -/* ---------- tips: statement left, table pushed right ---------- */ - -.tips { padding: var(--gap-section) 0; } - -.tips h2 { - margin: 0 0 96px; - padding-left: var(--pad); - padding-right: var(--pad); - font-size: var(--t-head); - font-weight: 700; - letter-spacing: -0.035em; - line-height: 0.96; - max-width: 15em; -} -.tips h2::first-line { padding-left: 3vw; } - -.tips-grid { - display: grid; - grid-template-columns: repeat(12, 1fr); - gap: var(--pad); - padding: 0 var(--pad); -} - -.tips-side { grid-column: 1 / 6; } - -.tips-note { - margin: 0 0 40px; - max-width: 24em; - color: var(--muted); - line-height: 1.55; -} - -/* the live thing */ -.picker { - background: var(--surface); - padding: clamp(18px, 2vw, 28px); - max-width: 30em; -} -.picker-q { - margin: 0 0 16px; - font-size: var(--t-small); - color: var(--muted); -} -.chips { - display: flex; - flex-wrap: wrap; - gap: 8px; -} -.chip { - padding: 10px 14px; - min-height: 40px; - background: var(--bg); - color: var(--ink); - border: 0; - border-radius: 0; - font-size: var(--t-small); - cursor: pointer; - transition: background-color 180ms var(--ease), color 180ms var(--ease), transform 180ms var(--ease); -} -.chip:hover { transform: translateY(-1px); } -.chip[aria-pressed="true"] { background: var(--ink); color: var(--bg); } - -.picker-out { - margin: 20px 0 0; - font-size: var(--t-body); - line-height: 1.4; - min-height: 2.8em; - color: var(--ink); -} -.picker-out .mod { font-weight: 700; } -.picker-out .arrow { color: var(--muted); } - -.tips-figure { - margin: clamp(28px, 4vw, 48px) 0 0; - width: min(100%, 22em); -} -.tips-figure img { - width: 100%; - aspect-ratio: 4 / 5; - object-fit: cover; - object-position: 50% 42%; - background: var(--surface); -} - -.tips-list { - grid-column: 8 / 13; +.side-list { margin: 0; padding: 0; list-style: none; + border-top: 1px solid var(--rule); + font-size: var(--step-sm); + line-height: 1.5; } -.tips-list li { - display: grid; - grid-template-columns: 7.5em 1fr; - gap: 4px 16px; - padding-bottom: 26px; - font-size: var(--t-body); -} -.tips-list li:nth-child(4) { padding-top: 18px; } - -.tip-kind { color: var(--muted); font-size: var(--t-small); padding-top: 3px; } -.tip-what { line-height: 1.35; } -.tip-where { - grid-column: 2; +.side-list li { padding: 12px 0; border-bottom: 1px solid var(--rule); } +.side-kicker { + display: block; + font-size: var(--step-xs); + letter-spacing: 0.16em; + text-transform: uppercase; color: var(--muted); - font-size: var(--t-small); - text-decoration: none; + margin-bottom: 3px; } -a.tip-where { color: var(--ink); text-underline-offset: 3px; text-decoration: underline; } -a.tip-where:hover { color: var(--bg); background: var(--ink); } +.side-list a { text-decoration: underline; text-underline-offset: 3px; } -/* ---------- about: portrait as drop cap ---------- */ +.lead-media { grid-area: media; } +.lead-media img { width: 100%; aspect-ratio: 16 / 11; object-fit: cover; object-position: 50% 42%; } -.about { - padding: var(--gap-section) var(--pad); +.lead-caption { grid-area: caption; max-width: 36em; } + +.meta { + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: 14px; + margin: 0 0 12px; + font-size: var(--step-xs); + letter-spacing: 0.16em; + text-transform: uppercase; + color: var(--muted); +} +.meta span:first-child { color: var(--ink); font-weight: 600; } + +h1 { + margin: 0 0 12px; + font-family: var(--serif); + font-weight: 500; + font-size: clamp(26px, 3.1vw, 42px); + line-height: 1.1; + letter-spacing: -0.015em; +} + +.standfirst { + margin: 0; + font-family: var(--serif); + font-size: var(--step-lead); + line-height: 1.55; + color: #2a2926; +} + +/* ---------- section frame ---------- */ + +.section { + max-width: var(--max); + margin: 0 auto; + padding: clamp(34px, 4.5vw, 56px) var(--pad) 0; +} + +.section-head { display: grid; - grid-template-columns: repeat(12, 1fr); - grid-template-areas: - "head head head head head head head head head head head head" - "bio bio bio bio bio bio bio bio bio . . ." - "facts facts facts facts . . . . . . . ."; - gap: 0 var(--pad); + grid-template-columns: auto 1fr auto; + align-items: baseline; + column-gap: 16px; + padding-bottom: 14px; + border-bottom: 1px solid var(--ink); + margin-bottom: clamp(22px, 3vw, 36px); } -.about-facts { - grid-area: facts; - margin: 40px 0 0; - max-width: 32em; + +.initial { + font-family: var(--serif); + font-size: clamp(54px, 7.4vw, 96px); + line-height: 0.7; + font-weight: 400; +} + +.section-title { + margin: 0; + font-family: var(--sans); + font-size: var(--step-xs); + font-weight: 600; + letter-spacing: 0.18em; + text-transform: uppercase; +} +.section-title em { + font-family: var(--serif); + font-style: italic; + font-size: var(--step-h3); + font-weight: 400; + letter-spacing: 0; + text-transform: none; + margin-left: 8px; +} + +.more { + font-size: var(--step-xs); + font-weight: 600; + letter-spacing: 0.16em; + text-transform: uppercase; + text-decoration: none; + border-bottom: 1px solid var(--ink); + padding-bottom: 2px; + justify-self: end; +} +.more:hover, .more:focus-visible { background: var(--ink); color: var(--paper); } + +/* ---------- cards ---------- */ + +.cards { + display: grid; + grid-template-columns: repeat(3, 1fr); + border-top: 1px solid var(--rule); +} + +.card { display: flex; flex-direction: column; gap: 10px; - font-size: var(--t-small); - color: var(--muted); + padding: 20px clamp(12px, 1.8vw, 24px) 22px; + text-decoration: none; + border-bottom: 1px solid var(--rule); + transition: background-color 200ms ease; } -.about-facts a { color: var(--ink); } +.card + .card { border-left: 1px solid var(--rule); } +.card:hover, .card:focus-visible { background: var(--tint); } -.about h2 { - grid-area: head; - margin: 0 0 28px; - font-size: var(--t-head); - font-weight: 700; - letter-spacing: -0.03em; - line-height: 1; -} - -.bio { - grid-area: bio; - margin: 0; - max-width: 26em; - font-size: var(--t-statement); - line-height: 1.22; - letter-spacing: -0.01em; - font-weight: 400; -} -.dropcap { - float: left; - width: 5em; - height: 5em; - margin: 0.1em 0.5em 0.2em 0; +.card-img { + width: 100%; + aspect-ratio: 4 / 3; object-fit: cover; - object-position: 30% 40%; - background: var(--surface); + object-position: 50% 45%; + background: var(--tint); } -/* ---------- ask: a sentence with a blank ---------- */ - -.ask { - background: var(--surface); - padding: var(--gap-section) var(--pad); - max-width: 100%; +.card-cat { + display: block; + padding: 7px 0; + border-top: 1px solid var(--rule); + border-bottom: 1px solid var(--rule); + text-align: center; + font-size: var(--step-xs); + font-weight: 600; + letter-spacing: 0.16em; + text-transform: uppercase; } -.ask-line { + +.card h3 { + margin: 2px 0 0; + font-family: var(--serif); + font-weight: 500; + font-size: var(--step-h3); + line-height: 1.25; + text-align: center; +} + +.card-desc { margin: 0; - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 0.22em; - font-size: var(--t-head); - font-weight: 700; - letter-spacing: -0.03em; - line-height: 1.1; -} -.ask-line label { flex: 0 0 auto; } -.ask-line input { - flex: 1 1 7em; - min-width: 6em; - height: 1.2em; - padding: 0 0.2em; - background: var(--surface); - border: 0; - border-radius: 0; - font-size: 0.5em; - font-weight: 500; - letter-spacing: 0; -} -.ask-line input::placeholder { color: var(--muted); } -.ask-send { - flex: 0 0 auto; - height: 1.05em; - padding: 0 0.4em; - background: var(--accent); - color: var(--accent-ink); - border: 0; - border-radius: 0; - font-size: 0.5em; - font-weight: 500; - letter-spacing: 0; - cursor: pointer; - transition: background-color 180ms var(--ease), transform 180ms var(--ease); -} -.ask-send:hover { background: var(--ink); transform: translateY(-1px); } - -.ask-note { - margin: 28px 0 0; - font-size: var(--t-small); + font-size: var(--step-sm); + line-height: 1.5; color: var(--muted); - max-width: 40em; + text-align: center; } -/* ---------- footer: invitation over a cropped name ---------- */ - -.foot { - background: var(--ink); - color: var(--bg); - padding: clamp(56px, 7vw, 96px) var(--pad) 0; - overflow: hidden; - min-height: 88svh; - display: flex; - flex-direction: column; +.card-meta { + margin-top: auto; + padding-top: 6px; + font-family: var(--serif); + font-style: italic; + font-size: var(--step-sm); + color: var(--muted); + text-align: center; } -.foot-top { +.card:hover h3, .card:focus-visible h3 { text-decoration: underline; text-underline-offset: 4px; } + +/* ---------- featured module ---------- */ + +.feature { display: grid; - grid-template-columns: repeat(12, 1fr); - gap: var(--pad); - align-items: start; + grid-template-columns: 4fr 7fr; + border: 1px solid var(--ink); } -.foot-invite { - grid-column: 1 / 8; + +.feature-plate { + padding: clamp(20px, 2.4vw, 30px); + border-right: 1px solid var(--ink); + background: var(--tint); +} + +.plate-kicker { + margin: 0 0 18px; + font-size: var(--step-xs); + font-weight: 600; + letter-spacing: 0.16em; + text-transform: uppercase; + color: var(--muted); +} + +.plate-name { margin: 0; - font-size: var(--t-head); - font-weight: 700; - letter-spacing: -0.035em; - line-height: 0.98; - max-width: 12em; + font-family: var(--serif); + font-size: clamp(34px, 4vw, 56px); + line-height: 0.95; + letter-spacing: -0.02em; } -.foot-top .btn { - grid-column: 10 / 13; - justify-self: end; - background: var(--bg); + +.plate-facts { + margin: 22px 0 0; + padding: 0; + list-style: none; + border-top: 1px solid var(--rule-strong); + font-size: var(--step-sm); +} +.plate-facts li { + display: flex; + justify-content: space-between; + gap: 12px; + padding: 9px 0; + border-bottom: 1px solid var(--rule); +} +.plate-facts span { + font-size: var(--step-xs); + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--muted); +} + +.plate-note { + margin: 18px 0 0; + font-family: var(--serif); + font-style: italic; + font-size: var(--step-sm); + line-height: 1.45; + color: var(--muted); +} + +.feature-body { padding: clamp(22px, 2.8vw, 36px); } + +.feature-name { + margin: 0 0 12px; + font-family: var(--serif); + font-weight: 500; + font-size: clamp(24px, 2.6vw, 34px); + line-height: 1.15; +} + +.feature-body > p { margin: 0 0 20px; max-width: 40em; } + +.benefits { + margin: 0 0 24px; + padding: 0; + list-style: none; + border-top: 1px solid var(--rule); +} +.benefits li { + padding: 12px 0; + border-bottom: 1px solid var(--rule); + font-size: var(--step-sm); + line-height: 1.5; + color: var(--muted); +} +.benefits strong { + display: block; + margin-bottom: 2px; + font-family: var(--serif); + font-size: var(--step-body); + font-weight: 500; color: var(--ink); } -.foot-top .btn:hover { background: var(--surface); color: var(--ink); } -.foot-links { - margin-top: clamp(40px, 6vw, 80px); - display: flex; - flex-wrap: wrap; - gap: 24px; - font-size: var(--t-body); +.feature-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 16px; } +.feature-note { + font-family: var(--serif); + font-style: italic; + font-size: var(--step-sm); + color: var(--muted); } -.foot-links a { text-decoration: underline; text-underline-offset: 4px; } -.foot-links a:hover { text-decoration-thickness: 2px; } -.foot-credits { - margin: 24px 0 0; - font-size: var(--t-small); - line-height: 1.5; - max-width: 62em; - opacity: 0.9; +/* ---------- community news ---------- */ + +.news { margin: 0; padding: 0; list-style: none; border-top: 1px solid var(--rule); } + +.news li { + display: grid; + grid-template-columns: 120px 1fr 190px; + gap: 20px; + padding: 20px 0; + border-bottom: 1px solid var(--rule); +} + +.news-date { + font-family: var(--serif); + font-style: italic; + font-size: var(--step-sm); + color: var(--muted); + padding-top: 4px; +} + +.news-body h3 { + margin: 0 0 6px; + font-family: var(--serif); + font-weight: 500; + font-size: var(--step-h3); + line-height: 1.2; +} +.news-body h3 a { text-decoration: none; } +.news-body h3 a:hover { text-decoration: underline; text-underline-offset: 4px; } +.news-body p { margin: 0; font-size: var(--step-sm); line-height: 1.5; color: var(--muted); max-width: 52em; } + +.news-source { + font-size: var(--step-xs); + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--muted); + text-align: right; + padding-top: 6px; + overflow-wrap: anywhere; +} + +/* ---------- ask ---------- */ + +.ask { + max-width: var(--max); + margin: clamp(40px, 6vw, 72px) auto 0; + padding: 0 var(--pad); +} +.ask-inner { + border-top: 1px solid var(--ink); + border-bottom: 1px solid var(--ink); + padding: clamp(26px, 3vw, 40px) 0; + display: grid; + grid-template-columns: 3fr 7fr; + gap: var(--gap); + align-items: start; +} +.ask h2 { + margin: 0; + font-family: var(--serif); + font-weight: 500; + font-size: var(--step-h2); + line-height: 1.05; +} +.ask-lede { margin: 8px 0 0; font-family: var(--serif); font-style: italic; color: var(--muted); } + +.ask-form { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; } +.ask-form label { + flex: 0 0 100%; + font-size: var(--step-xs); + letter-spacing: 0.16em; + text-transform: uppercase; + color: var(--muted); +} +.ask-form input { + flex: 1 1 240px; + min-height: 44px; + padding: 0 14px; + background: transparent; + border: 1px solid var(--rule-strong); + border-radius: 0; + font-size: var(--step-body); +} +.ask-form input:focus-visible { border-color: var(--ink); } + +/* ---------- footer ---------- */ + +.foot { + margin-top: clamp(48px, 7vw, 88px); + background: var(--ink); + color: #f4f2ee; + padding: clamp(32px, 4vw, 52px) var(--pad) 24px; } -.foot-credits a { text-underline-offset: 2px; } .foot-name { - margin: auto 0 0; - font-size: clamp(64px, 20vw, 280px); + max-width: var(--max); + margin: 0 auto 26px; + font-family: var(--serif); font-weight: 700; - letter-spacing: -0.05em; - line-height: 0.8; - white-space: nowrap; - transform: translateY(22%); - user-select: none; + font-size: clamp(44px, 9vw, 116px); + line-height: 0.86; + letter-spacing: -0.035em; + text-transform: uppercase; } +.foot-cols { + max-width: var(--max); + margin: 0 auto; + display: grid; + grid-template-columns: 1fr auto; + gap: 24px; + padding-top: 20px; + border-top: 1px solid rgba(255, 255, 255, 0.25); +} + +.foot-note { margin: 0; max-width: 34em; font-family: var(--serif); font-style: italic; font-size: var(--step-sm); line-height: 1.5; } + +.foot-links { display: flex; flex-wrap: wrap; gap: 18px; font-size: var(--step-xs); letter-spacing: 0.16em; text-transform: uppercase; } +.foot-links a { text-decoration: none; border-bottom: 1px solid rgba(255, 255, 255, 0.4); padding-bottom: 2px; } +.foot-links a:hover { border-color: #fff; } + +.foot-credits { + max-width: var(--max); + margin: 22px auto 0; + font-size: var(--step-xs); + line-height: 1.6; + color: rgba(255, 255, 255, 0.62); +} +.foot-credits a { color: rgba(255, 255, 255, 0.82); } + /* ---------- motion ---------- */ html.js [data-rv] { opacity: 0; - transform: translateY(22px); - transition: opacity 620ms var(--ease), transform 620ms var(--ease); + transform: translateY(14px); + transition: opacity 520ms ease, transform 520ms ease; } html.js [data-rv].rv-in { opacity: 1; transform: none; } -html.js [data-rv="2"] { transition-delay: 70ms; } -html.js [data-rv="3"] { transition-delay: 140ms; } -html.js [data-rv="4"] { transition-delay: 210ms; } - -/* signature move: the shutter on the hero photograph */ -html.js .slot { clip-path: inset(0 100% 0 0); } -html.js .hero-line.shutter .slot { - clip-path: inset(0 0 0 0); - transition: clip-path 1100ms cubic-bezier(0.16, 1, 0.3, 1); -} -html.js .slot img { transform: scale(1.12); transition: transform 1600ms cubic-bezier(0.16, 1, 0.3, 1); } -html.js .hero-line.shutter .slot img { transform: scale(1); } @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } html.js [data-rv] { opacity: 1; transform: none; transition: none; } - html.js .slot, html.js .hero-line.shutter .slot { clip-path: none; transition: none; } - html.js .slot img, html.js .hero-line.shutter .slot img { transform: none; transition: none; } - .map-figure img { transform: none !important; } } -/* ---------- narrow screens ---------- */ +/* ---------- tablet ---------- */ @media (max-width: 900px) { - .hero-foot { grid-template-columns: 1fr; gap: 24px; } - .hero-say { grid-column: 1 / -1; } - .hero-foot .btn { grid-column: 1 / -1; justify-self: start; } + .lead { + grid-template-columns: 1fr; + grid-template-areas: "media" "caption" "side"; + } + .lead-side { flex-direction: row; flex-wrap: wrap; } + .box { flex: 1 1 240px; } + .side-list { flex: 1 1 260px; } - .open-copy h2, - .open-copy p, - .open-figure { grid-column: 1 / -1; } - .open-figure { width: min(100%, 24em); } + .feature { grid-template-columns: 1fr; } + .feature-plate { border-right: 0; border-bottom: 1px solid var(--ink); } - .tips-side, - .tips-list { grid-column: 1 / -1; } - .tips-note { margin-bottom: 28px; } + .news li { grid-template-columns: 96px 1fr; } + .news-source { grid-column: 2; text-align: left; padding-top: 0; } - .about h2, .bio, .about-facts { grid-column: 1 / -1; } - - .foot-invite { grid-column: 1 / -1; } - .foot-top .btn { grid-column: 1 / -1; justify-self: start; margin-top: 28px; } - .foot { min-height: auto; } + .ask-inner { grid-template-columns: 1fr; } } -@media (max-width: 700px) { - .hero-line { - flex-direction: column; - align-items: flex-start; - gap: 14px; - font-size: clamp(26px, 7.6vw, 34px); - line-height: 1.04; - } - .slot { - width: 1.7em; - height: 0.7em; - vertical-align: 0; - } +/* ---------- phone ---------- */ - .about { - grid-template-areas: - "head head head head head head head head head head head head" - "bio bio bio bio bio bio bio bio bio bio bio bio" - "facts facts facts facts facts facts facts facts facts facts facts facts"; - gap: 0; - } - .about-facts { flex-direction: row; flex-wrap: wrap; gap: 6px 18px; margin: 28px 0 0; } +@media (max-width: 680px) { + .masthead { grid-template-columns: 1fr; align-items: start; gap: 12px; } + .nav { gap: 14px; } + .nav-cta { grid-column: 1; justify-self: start; margin-top: 0; } + + .lead-side { flex-direction: column; } + + .cards { grid-template-columns: 1fr; } + .card { padding: 18px 0 22px; } + .card + .card { border-left: 0; } + .card-img { aspect-ratio: 16 / 9; } + + .section-head { grid-template-columns: auto 1fr; row-gap: 10px; } + .more { grid-column: 1 / -1; justify-self: start; } + + .news li { grid-template-columns: 1fr; gap: 8px; } + .news-date, .news-source { padding-top: 0; } + + .ask-form input { flex: 1 1 100%; } + .ask-form .btn { width: 100%; } + + .foot-cols { grid-template-columns: 1fr; } } -@media (max-width: 620px) { - .topbar { padding-top: 18px; } - .nav { gap: 14px; font-size: 13px; } - .nav a:not(.nav-action):not(:first-child) { display: none; } - - .hero { padding-top: 96px; } - .hero-line { line-height: 0.98; } - .slot { width: 1.5em; height: 0.6em; } - - .statement { font-size: 22px; line-height: 1.25; } - .statement .thumb:nth-of-type(2) { display: none; } - - .band-switch { min-height: 132px; gap: 16px; } - .band-text { line-height: 1.06; } - - .tips h2 { margin-bottom: 56px; } - .tips-list li { grid-template-columns: 1fr auto; padding-bottom: 22px; } - .tip-kind { grid-column: 1; } - .tip-what { grid-column: 1 / -1; } - .tip-where { grid-column: 1 / -1; } - - .dropcap { width: 3.2em; height: 3.2em; } - .bio { font-size: 22px; line-height: 1.28; } - - .ask-line { font-size: var(--t-statement); } - .ask-line label { flex: 1 1 100%; } - .ask-line input { flex: 1 1 100%; order: 3; height: 2.4em; font-size: 0.8em; } - .ask-send { order: 4; height: 2em; font-size: 0.8em; } - - .foot-name { font-size: 26vw; } +/* ---------- touch targets on phones ---------- */ +@media (max-width: 680px) { + .nav a, + .more, + .foot-links a, + .side-list a, + .news-body h3 a { + display: inline-flex; + align-items: center; + min-height: 44px; + } + .nav-cta { min-height: 44px; } + .foot-links { gap: 10px 18px; } } diff --git a/index.html b/index.html index 5050fb1..ce565ee 100644 --- a/index.html +++ b/index.html @@ -3,232 +3,200 @@ -The OC Guy — Odoo Community tips and tricks - - - - - +The OC Guy — Odoo Community tips, modules and community news + + + + + -
- The OC Guy +
+ The OC Guy + Ask a question
- -
-

- Odoo Community - A 1689 engraved world map in two hemispheres, ringed by allegorical figures and ships - tips and tricks. -

-
-

Hey — welcome. Here you find the best tips and tricks for Odoo Community, - the open-source ERP small companies run themselves: install it, back it up, extend it, and - bolt AI on top. I am The OC Guy, Oliver Arnold.

- Read the tips +
+
+
+

Get the most out of Odoo Community

+

Install it, back it up, extend it, bolt AI on top — the notes I wish I had on day one.

+ Read the tips +
+
    +
  • Written by Oliver Arnold, 安雷威
  • +
  • Since 2024, in German, English and Spanish
  • +
  • Contact o.arnold@projektbox.de
  • +
-
- -
-

What Odoo Community is

-

- A full ERP whose licence costs nothing: - An engraving of four kinds of globe, opened to show their rings and axes - read every line, change it, run it on your own server - An engraved seventeenth-century printer's workshop: a hand press, workmen setting type - and it answers to you. -

-
- - -
-
- A 1794 wall map of the world in two hemispheres, engraved with notes on voyages and discoveries -
A 1794 double-hemisphere map: knowledge printed once, copied by hand, corrected by everyone who used it.
+
+ A 1794 wall map of the world engraved in two hemispheres, with marginal notes on voyages and discoveries
-
-

Written in the open, used in the open

-

Odoo Community is published under LGPL v3. No seat count, no trial that expires, no module you must - rent back. The people who make it stronger are the people who run it: localisation packs for your - country's taxes, modules for your industry, a patch for the bug you hit last week. That is the deal — - you inherit a working ERP, and you owe the next person what you learned.

-
- The 1689 world map again, closer: the engraved landmasses and sea routes of two hemispheres -
+ +
+

GuideOdoo Community

+

Welcome. Best tips and tricks for Odoo Community.

+

Hey — you are in the right place. This is where I write down how to run the open-source + ERP that small companies own themselves: the install that holds, the backup that restores, the module + that already exists, and the AI that sits on top of your records instead of guessing at them.

- -
-

What you get with Odoo Community

+
+
+ +

The latest in Tips & Tricks

+ View all tips +
-
- -

Zero per user, per month. Odoo Community is LGPL v3: you pay for a server and your own time, not for permission.

-
+
+ + A 1689 engraved world map in two hemispheres + Install +

Run Odoo from Docker Compose, not from source

+

Two containers, one file, a volume you can copy. The shortest path to a server you + understand and can move next year without a migration project.

+ 6 min read +
-
- -

PostgreSQL on your own machine. One pg_dump is the whole company, portable to any host you like.

-
+ + An engraved seventeenth-century printer's workshop with a hand press and workmen setting type + Backup +

Dump the database nightly and restore it monthly

+

A backup you have never restored is a hope, not a backup. One pg_dump, one cron line, + and a restore into a scratch copy that takes four minutes.

+ 4 min read +
-
- -

The source is public. Find the field, change the field, keep the change — no vendor to convince, no feature request to queue.

-
- -
- -

The OCA repositories and the Odoo app store carry thousands of modules, most of them free, written by people solving their own problem first.

-
- -
- -

The ERP keeps the truth — stock, invoices, orders. The model reads it and drafts with it. Boring, correct data in; useful answers out.

+ + An engraving of four kinds of globe on stands + Extend +

Take an OCA module before you write your own

+

Someone already solved your problem and maintains it for every series. Reading their + code teaches you the ORM faster than any tutorial.

+ 8 min read +
- -
-

Tips that save a weekend

-
-
-

Seven things worth knowing before your first install, and before every upgrade after it.

+
+
+ +

Featured Module of the month

+
- -
-

What does your company actually do?

-
- - - - - - -
-

Choose one, or several. The modules stack in the order you would install them.

-
- -
- An engraving of four kinds of globe on stands, opened to show their rings and axes -
+
+
+

Odoo Community · LGPL-3

+

Inventory

+
    +
  • Series 18.0
  • +
  • Depends on Product, UoM
  • +
  • Install Apps → Inventory
  • +
+

Module plate: the facts above come from the module manifest, not from a vendor page.

-
    -
  1. - Install - Run it from Docker Compose, not from source. - One file, two containers -
  2. -
  3. - Backup - Dump the database nightly and actually restore it monthly. - pg_dump, then psql -
  4. -
  5. - Upgrade - Stay on one major series, and migrate a copy before the live one. - Never in place -
  6. -
  7. - Extend - Take an OCA module before you write your own. - github.com/OCA -
  8. -
  9. - Extend - Keep your code in one addons path and never edit core files. - Inherit, do not patch -
  10. -
  11. - Debug - Answer "where does this field live" with the Odoo shell, not with grep. - odoo shell -
  12. -
  13. - Invoice - Load the localisation pack for your country before the first invoice. - l10n_<your country> -
  14. -
+
+

Inventory, before you buy anything

+

One module holds what you own, where it sits and what it is worth: stock moves, reordering rules, + lots and serial numbers, three warehouse steps if you need them, one if you do not.

+
    +
  • Real stock, not a spreadsheetEvery receipt, delivery and adjustment is a move with a date and a person behind it.
  • +
  • Barcode-ready on day oneThe Community module ships the operations your scanner needs; no paid add-on.
  • +
  • Multi-warehouse without a second systemLocations, routes and reordering rules live in the same database as your invoices.
  • +
+
+ Explore module + Free, in the Community edition +
+
- -
-

Who is typing

-

- An engraved printer's workshop: a hand press on the left, workmen setting type in the centre - I am Oliver. I run Odoo Community for my own company and write down what breaks. - No partner, no licences: own your ERP, bolt AI on. -

-

- Oliver Arnold, 安雷威 - o.arnold@projektbox.de -

+
+
+ +

The latest in Community news

+ View all community news +
+ +
    +
  • + +
    +

    RMA: returns, repairs and replacements inside Odoo

    +

    Return Merchandise Authorization for the whole flow — the customer sends it back, you decide repair + or credit, the stock move happens once.

    +
    + github.com/OCA/rma +
  • +
  • + +
    +

    Helpdesk modules that stay out of your way

    +

    Tickets, teams and SLAs without the enterprise edition: the OCA helpdesk repository keeps a branch + per supported series, so an upgrade does not orphan your support desk.

    +
    + github.com/OCA/helpdesk +
  • +
  • + +
    +

    Localisation is community work: Italy's charts, kept current

    +

    Tax reports, e-invoicing and the country chart of accounts, maintained by the people who file with + them. The same pattern exists for your country.

    +
    + github.com/OCA/l10n-italy +
  • +
-
-

Ask a question

-

- - - -

-

One question, one answer, by mail. No list, no tracking, no sequence of follow-ups.

+
+

Ask a question

+

One question, one answer, by mail. No list, no tracking, no follow-up sequence.

+
+ + + +
+
- diff --git a/js/site.js b/js/site.js index 547f699..f01aec8 100644 --- a/js/site.js +++ b/js/site.js @@ -1,6 +1,6 @@ /* The OC Guy — page behaviour. - Everything here degrades: without JS the page still reads, the bands still - show their titles, the tips still list, the form still shows. */ + Everything degrades: without JS the page is a plain, readable + document with working links and a mail form. */ (function () { 'use strict'; @@ -9,23 +9,10 @@ var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches; - /* ---------- signature move: the shutter on the hero photograph ---------- */ - var hero = document.querySelector('.hero-line'); - if (hero) { - if (reduce) { - hero.classList.add('shutter'); - } else { - requestAnimationFrame(function () { - setTimeout(function () { hero.classList.add('shutter'); }, 120); - }); - } - } - - /* ---------- entrance for the parts of every section ---------- */ - var targets = []; - document.querySelectorAll('.intro .statement, .open h2, .open p, .map-figure figcaption, .tips h2, .tips-note, .picker, .tips-list li, .about h2, .bio, .about-facts, .ask-line, .ask-note, .band-text, .foot-invite, .foot-links').forEach(function (el) { - targets.push(el); - }); + /* ---------- entrance for the blocks of each section ---------- */ + var targets = document.querySelectorAll( + '.lead-caption, .box, .side-list, .section-head, .card, .feature, .news li, .ask-inner' + ); if (!reduce && 'IntersectionObserver' in window) { var io = new IntersectionObserver(function (entries) { @@ -34,115 +21,56 @@ entry.target.classList.add('rv-in'); io.unobserve(entry.target); }); - }, { rootMargin: '0px 0px -12% 0px', threshold: 0.12 }); + }, { rootMargin: '0px 0px -8% 0px', threshold: 0.08 }); - targets.forEach(function (el, i) { - el.setAttribute('data-rv', String((i % 4) + 1)); + Array.prototype.forEach.call(targets, function (el) { + el.setAttribute('data-rv', '1'); io.observe(el); }); } else { - targets.forEach(function (el) { el.classList.add('rv-in'); }); + Array.prototype.forEach.call(targets, function (el) { el.classList.add('rv-in'); }); } - /* ---------- switches: five claims ---------- */ - document.querySelectorAll('.band-switch').forEach(function (btn) { - btn.addEventListener('click', function () { - var band = btn.closest('.band'); - var on = btn.getAttribute('aria-checked') === 'true'; - btn.setAttribute('aria-checked', String(!on)); - band.setAttribute('data-on', String(!on)); - }); - }); - - /* ---------- the live thing: pick what the company does ---------- */ - var chips = Array.prototype.slice.call(document.querySelectorAll('.chip')); - var out = document.querySelector('.picker-out'); - var installOrder = ['Sales', 'CRM', 'Purchase', 'Manufacturing', 'Inventory', 'Project', 'Timesheets', 'Expenses', 'Invoicing', 'Accounting']; - - function render(mods) { - if (!mods.length) { - out.textContent = 'Choose one, or several. The modules stack in the order you would install them.'; - return; - } - out.innerHTML = ''; - mods.forEach(function (name, i) { - var span = document.createElement('span'); - span.className = 'mod'; - span.textContent = name; - out.appendChild(span); - if (i < mods.length - 1) { - var arrow = document.createElement('span'); - arrow.className = 'arrow'; - arrow.textContent = ' → '; - out.appendChild(arrow); - } - }); - var note = document.createElement('span'); - note.className = 'arrow'; - note.textContent = ' — that order, top to bottom.'; - out.appendChild(note); - } - - chips.forEach(function (chip) { - chip.setAttribute('aria-pressed', 'false'); - chip.addEventListener('click', function () { - var pressed = chip.getAttribute('aria-pressed') === 'true'; - chip.setAttribute('aria-pressed', String(!pressed)); - - var picked = chips.filter(function (c) { return c.getAttribute('aria-pressed') === 'true'; }) - .reduce(function (all, c) { - c.dataset.modules.split(',').forEach(function (m) { - var name = m.trim(); - if (all.indexOf(name) === -1) all.push(name); - }); - return all; - }, []); - - picked.sort(function (a, b) { return installOrder.indexOf(a) - installOrder.indexOf(b); }); - render(picked); - }); - }); - - /* ---------- ask: one question, by mail ---------- */ - var send = document.querySelector('.ask-send'); + /* ---------- the ask form: one question, by mail ---------- */ + var form = document.querySelector('.ask-form'); var mail = document.getElementById('ask-mail'); - if (send && mail) { - send.addEventListener('click', function () { + + if (form && mail) { + form.addEventListener('submit', function (event) { + event.preventDefault(); var addr = (mail.value || '').trim(); - var body = 'My question about Odoo Community:\n\n\n'; - if (addr) body += '\n— reply to: ' + addr + '\n'; - var href = 'mailto:o.arnold@projektbox.de' - + '?subject=' + encodeURIComponent('Odoo Community question') - + '&body=' + encodeURIComponent(body); - if (addr && addr.indexOf('@') === -1) { - mail.focus(); + if (!addr || addr.indexOf('@') === -1) { mail.setAttribute('aria-invalid', 'true'); + mail.focus(); return; } mail.removeAttribute('aria-invalid'); - window.location.href = href; - }); - mail.addEventListener('keydown', function (e) { - if (e.key === 'Enter') send.click(); + var body = 'My question about Odoo Community:\n\n\n— reply to: ' + addr + '\n'; + window.location.href = 'mailto:o.arnold@projektbox.de' + + '?subject=' + encodeURIComponent('Odoo Community question') + + '&body=' + encodeURIComponent(body); }); } - /* ---------- one moving picture: the world map drifts against the scroll ---------- */ - var map = document.getElementById('worldmap'); - if (map && !reduce) { - var ticking = false; - var update = function () { - var rect = map.getBoundingClientRect(); - var vh = window.innerHeight || 800; - if (rect.bottom > -200 && rect.top < vh + 200) { - var progress = (rect.top + rect.height / 2 - vh / 2) / vh; - map.style.transform = 'translate3d(0,' + (progress * -26).toFixed(2) + 'px,0) scale(1.06)'; - } - ticking = false; - }; - window.addEventListener('scroll', function () { - if (!ticking) { ticking = true; requestAnimationFrame(update); } - }, { passive: true }); - update(); + /* ---------- which section the reader is in ---------- */ + var links = Array.prototype.slice.call(document.querySelectorAll('.nav a[href^="#"]')); + var sections = links + .map(function (a) { return document.querySelector(a.getAttribute('href')); }) + .filter(Boolean); + + if (sections.length && 'IntersectionObserver' in window) { + var seen = new IntersectionObserver(function (entries) { + entries.forEach(function (entry) { + if (!entry.isIntersecting) return; + links.forEach(function (a) { + if (a.getAttribute('href') === '#' + entry.target.id) { + a.setAttribute('aria-current', 'true'); + } else { + a.removeAttribute('aria-current'); + } + }); + }); + }, { rootMargin: '-45% 0px -50% 0px' }); + sections.forEach(function (s) { seen.observe(s); }); } })();