Thanks to visit codestin.com
Credit goes to levels.io

, not inside the chart sections, so they need naming separately. */ .blogstats, .evchart, .folchart, .sentchart, .subchart, .tagwall, .vcfund, .fol-tip, .sc-tip, .svot-tip, .vcf-tip { font-variant-numeric: tabular-nums; } * { box-sizing: border-box; } html { background: var(--bg); } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; background: var(--bg); color: var(--fg); line-height: 1.65; margin: 0; } .theme-toggle { position: absolute; top: 1rem; right: 1rem; z-index: 10; background: none; border: none; cursor: pointer; font-size: 1.2rem; line-height: 1; padding: .35rem; color: var(--muted); opacity: .7; } .theme-toggle:hover { opacity: 1; } .subscribe-top { position: absolute; top: 1rem; right: 3.2rem; z-index: 10; background: var(--accent); color: #fff; font-size: .8rem; font-weight: 600; padding: .35em .85em; border-radius: 6px; border: 1px solid var(--accent); } /* Same hover treatment as .catch-emails-button: outlined ghost style. */ .subscribe-top:hover { background: none; color: var(--accent); text-decoration: none; } .wrap { max-width: 42rem; margin: 0 auto; padding: 3rem 1.25rem 5rem; } a { color: var(--accent); text-decoration: none; } a:hover { text-decoration: underline; } header.site { margin-bottom: 2.5rem; } header.site h1 { font-size: 1.4rem; margin: 0; } header.site h1 a { color: var(--fg); } header.site p { color: var(--muted); margin: .35rem 0 0; } .year { color: var(--heading); font-weight: 700; margin: 2.5rem 0 .75rem; font-size: 1.4em; } /* Date separator inside a post list — centred label with a hairline rule running out to each side. Used for both granularities (month when the list is dense, year when it isn't), so the two never look different. Distinct from .year, which is a section heading (Pinned, Related posts). */ .datesep { display: flex; align-items: center; gap: .75em; color: var(--muted); font-weight: 700; font-size: 1em; margin: 1.7rem 0 .6rem; } .datesep::before, .datesep::after { content: ""; flex: 1 1 0; height: 1px; background: var(--border); } .datesep a { color: inherit; text-decoration: none; } .datesep a:hover { color: var(--accent); text-decoration: underline; } .meta a { color: inherit; text-decoration: none; } .meta a:hover { color: var(--accent); text-decoration: underline; } .posts + .datesep { margin-top: 1.4rem; } ul.posts { list-style: none; padding: 0; margin: 0; } ul.posts li { display: flex; gap: 1rem; padding: .35rem 0; align-items: baseline; } ul.posts li time { color: var(--muted); font-variant-numeric: tabular-nums; font-size: .85rem; white-space: nowrap; flex: none; width: 4.6rem; } ul.posts li a { font-weight: 700; } ul.posts li .from-x { color: var(--muted); font-size: .85em; flex: none; margin-left: auto; } .draft-badge { font-size: .68em; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: #b45309; background: #fef3c7; border-radius: 4px; padding: .1em .4em; flex: none; } html[data-theme="dark"] .draft-badge { color: #fcd34d; background: #3a2e0c; } .draft-notice { font-size: .9em; font-weight: 600; color: #b45309; background: #fef3c7; border-radius: 6px; padding: .4em .7em; margin: 0 0 1rem; display: inline-block; } html[data-theme="dark"] .draft-notice { color: #fcd34d; background: #3a2e0c; } /* "Covers" toggle: clickable thumbnail (or empty placeholder) after the date. */ ul.posts li .cover-link { flex: none; line-height: 0; } ul.posts li .cover-thumb { display: block; width: 72px; height: 48px; object-fit: cover; border-radius: 4px; background: var(--border); transition: opacity .15s; } ul.posts.covers li { align-items: center; } /* Cap listing titles at 2 lines so a long title never wraps to 3+ and leaves the date/thumbnail floating in the middle. Full title shows on hover + the post page. */ ul.posts.covers li a:not(.cover-link) { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; min-width: 0; } /* Thumb + title behave as one link: hovering either underlines the title and dims the thumb; clicking dims it further. */ ul.posts li:has(.cover-link:hover) a:not(.cover-link) { text-decoration: underline; } ul.posts li .cover-link:hover .cover-thumb, ul.posts li:has(a:not(.cover-link):hover) .cover-thumb { opacity: .75; } ul.posts li .cover-link:active .cover-thumb, ul.posts li:has(a:not(.cover-link):active) .cover-thumb { opacity: .5; } .sort-sep { margin-left: 1rem; } .notice { text-align: center; font-size: 1.3rem; font-weight: bold; line-height: 1.5; margin: 1rem 0 2.5rem; color: var(--heading); } .notice a { color: var(--accent); } article h1.title { font-size: 2rem; line-height: 1.2; margin: 0 0 .25rem; } article .meta { color: var(--muted); font-size: .9rem; margin-bottom: .75rem; } article .meta .meta-link { color: inherit; text-decoration: none; } article .meta .meta-link:hover { text-decoration: underline; } .post-tags { margin-bottom: 2rem; display: flex; flex-wrap: wrap; gap: .5rem; } .post-tags a { font-size: .8rem; font-weight: 600; color: var(--accent); border: 1px solid var(--border); border-radius: 999px; padding: .15rem .7rem; } .post-tags a:hover { text-decoration: none; background: var(--accent); color: #fff; border-color: var(--accent); } .tags-index { margin-top: 1.5rem; } .tags-index .tag-count { color: var(--muted); font-weight: 400; } .tags-index a:hover .tag-count { color: #fff; } article img, article video { max-width: 100%; height: auto; border-radius: 4px; display: block; margin: 1.75rem auto; } /* Ghost image figures: kill the browser's default 40px figure margin + center */ article figure.kg-image-card, article figure.kg-card-hascaption { margin: 1.75rem 0; text-align: center; } article figure.kg-image-card img, article figure.kg-card-hascaption img { margin: .5rem auto; } article figcaption { color: var(--muted); font-size: .85rem; margin-top: .4rem; text-align: center; } article img { background-color: var(--code-bg); background-image: linear-gradient(90deg, transparent, rgba(128,128,128,.12), transparent); background-size: 200% 100%; animation: img-shimmer 1.4s ease-in-out infinite; } article video { width: 100%; background: #000; } @keyframes img-shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } } figure.kg-embed-card { margin: 2rem 0; } figure.kg-embed-card iframe { max-width: 100%; } figure.kg-embed-card .twitter-tweet, figure.kg-embed-card .twitter-tweet-rendered { width: 100% !important; max-width: 100% !important; margin-left: auto; margin-right: auto; } figure.kg-embed-card .twitter-tweet-rendered iframe { width: 100% !important; max-width: 100% !important; } /* YouTube/Vimeo oembed iframes ship with fixed width/height attrs. Many old posts have them BARE (not wrapped in figure.kg-embed-card), so the generic `article iframe{height:auto}` rule below collapsed them to the 150px iframe default — the "weirdly short" players. Target both wrapped and bare embeds and give them a responsive 16:9 box. */ figure.kg-embed-card iframe[src*="youtube.com/embed"], figure.kg-embed-card iframe[src*="player.vimeo.com"], article iframe[src*="youtube.com/embed"], article iframe[src*="youtube-nocookie.com/embed"], article iframe[src*="player.vimeo.com"] { width: 100%; height: auto; aspect-ratio: 16/9; display: block; margin: 1.5rem auto; } figure.kg-bookmark-card { position: relative; display: flex; margin: 1.5rem 0; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; min-height: 116px; } .kg-bookmark-container { position: absolute; inset: 0; z-index: 1; } .kg-bookmark-content { padding: 1rem 1.1rem; flex: 1 1 auto; min-width: 0; } .kg-bookmark-title { font-weight: 600; color: var(--fg); } .kg-bookmark-description { color: var(--muted); font-size: .9rem; margin-top: .4rem; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .kg-bookmark-metadata { margin-top: .6rem; font-size: .8rem; color: var(--muted); display: flex; align-items: center; gap: .5rem; } .kg-bookmark-icon { width: 18px; height: 18px; border-radius: 3px; background: none; animation: none; } .kg-bookmark-thumbnail { flex: 0 0 34%; max-width: 200px; } .kg-bookmark-thumbnail img { width: 100%; height: 100%; object-fit: cover; display: block; border-radius: 0; } @media (max-width: 480px) { .kg-bookmark-thumbnail { display: none; } } /* Internal "embed the post" card — replaces a tweet embed that's also a post here. */ figure.post-embed-card { display: flex; margin: 2rem 0; } .pec-link { display: flex; flex: 1; min-width: 0; text-decoration: none; overflow: hidden; border: 1px solid var(--border); border-radius: 8px; min-height: 96px; transition: border-color .1s; } .pec-link:hover { border-color: var(--accent); } .pec-content { padding: 1rem 1.1rem; flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; justify-content: center; } .pec-label { font-size: .72rem; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); margin-bottom: .35rem; } .pec-title { font-weight: 600; color: var(--fg); line-height: 1.3; } .pec-thumb { flex: 0 0 34%; max-width: 200px; } .pec-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; } @media (max-width: 480px) { .pec-thumb { display: none; } } article h2 { margin-top: 2rem; } article pre { background:var(--code-bg); padding:1rem; overflow:auto; border-radius:6px; } article code { background:var(--code-bg); padding:.15em .35em; border-radius:3px; font-size:.9em; } article pre code { background:none; padding:0; } article blockquote { border-left:3px solid var(--border); margin:1.5rem 0; padding:.2rem 0 .2rem 1rem; color:var(--quote); } article table { border-collapse: collapse; width: 100%; margin: 1.5rem 0; font-size: .92em; display: block; overflow-x: auto; } article th, article td { text-align: left; padding: .5rem .7rem; border-bottom: 1px solid var(--border); white-space: nowrap; } article th { font-weight: 700; color: var(--heading); border-bottom: 2px solid var(--border); } article tbody tr:hover { background: var(--code-bg); } article tr.exited-row { background: #dcfce7; } article tr.exited-row:hover { background: #c7f0d6; } html[data-theme="dark"] article tr.exited-row { background: #14321f; } html[data-theme="dark"] article tr.exited-row:hover { background: #1b432a; } article blockquote > :first-child { margin-top:0; } article blockquote > :last-child { margin-bottom:0; } .backlink { display:inline-block; margin-bottom:2rem; color:var(--muted); } /* Post page top row: backlink left, edit button pushed to the right. */ .post-topbar { display:flex; justify-content:space-between; align-items:baseline; gap:1rem; } .post-topbar .backlink { margin-bottom:2rem; } .edit-link { color: var(--accent); font-size: .82em; white-space: nowrap; } ul.posts li .edit-link { flex: none; margin-left: auto; } ul.posts li .from-x ~ .edit-link { margin-left: .5rem; } footer.site { margin-top: 4rem; color: var(--muted); font-size: .85rem; } .site-search { margin: 0 0 1rem; } /* Same visual language as the newsletter email box: white field, blue ring, soft shadow (email box keeps its flat right edge for the button). */ .site-search input { width: 100%; padding: 19px; font-size: 15px; color: #3c484e; background: #fff; border: none; border-radius: 6px; outline: none; box-shadow: 0 0 0 1px var(--accent), 0 2px 4px 0 rgb(0 0 0 / 7%), 0 1px 1.5px 0 rgb(0 0 0 / 5%); } .site-search input:focus { box-shadow: 0 0 0 2px var(--accent), 0 2px 4px 0 rgb(0 0 0 / 7%); } /* Dark mode: dark field, quiet border (the white field + blue ring glared). */ html[data-theme="dark"] .site-search input { background: var(--bg); color: var(--fg); box-shadow: 0 0 0 1px var(--border); } html[data-theme="dark"] .site-search input:focus { box-shadow: 0 0 0 1px var(--accent); } .topnav { color: var(--muted); font-size: .85rem; margin: 0 0 1.6rem; } .search-sort { color: var(--muted); font-size: .85rem; margin-top: .5rem; } .search-sort-filter { margin-top: .15rem; } .search-sort strong { color: var(--fg); } ul.search-results { list-style: none; padding: 0; margin: 1.5rem 0 0; } ul.search-results li { margin: 0 0 1.5rem; } ul.search-results li a { font-weight: 700; } ul.search-results time { display: block; color: var(--muted); font-size: .8rem; margin-top: .15rem; } .snippet { color: var(--muted); margin: .3rem 0 0; font-size: .92rem; } .site-credit { margin-top: .8rem; font-size: .8em; color: var(--muted); } .post-source { margin-top: 2rem; color: var(--muted); } .post-ps { margin-top: 0; color: var(--fg); } hr { background-color: var(--hr); height: 1px; width: 100%; border-style: none; margin: 28px 0; } .post-nav { display: flex; justify-content: space-between; gap: 1.5rem; } .post-nav .prev, .post-nav .next { flex: 0 1 50%; } .post-nav .next { margin-left: auto; } .post-nav a { font-weight: 700; display: flex; align-items: center; gap: .4em; } .post-nav .next a { justify-content: flex-end; } .post-nav .chev { flex: none; font-size: 1.4em; color: inherit; line-height: 1; } .post-nav .t { text-align: center; } /* Pie chart of category breakdown (SVG with hoverable slices) */ .projects-pie-wrap { display: flex; justify-content: center; margin: 1.5rem 0; } .projects-pie { width: 380px; height: 290px; max-width: 100%; filter: drop-shadow(0 1px 4px rgba(0,0,0,.1)); } html[data-theme="dark"] .projects-pie { filter: drop-shadow(0 1px 6px rgba(0,0,0,.4)); } .projects-pie path, .projects-pie circle { cursor: default; transition: opacity 80ms; } .projects-pie path:hover, .projects-pie circle:hover { opacity: .85; } .pie-label { fill: var(--fg); font-size: 9px; font-weight: 600; dominant-baseline: central; pointer-events: none; } .pie-lead { stroke: var(--muted); stroke-width: 0.6; fill: none; } .chart-legend { display: flex; flex-wrap: wrap; gap: .5rem 1rem; margin-top: .8rem; font-size: .85em; color: var(--muted); } .chart-legend .legend-item { display: inline-flex; align-items: baseline; gap: .35rem; white-space: nowrap; } /* /ask-test chat UI */ .ask-form { display: flex; gap: .5rem; margin: 1rem 0 .5rem; } .ask-form input { flex: 1; padding: .6rem .8rem; font-size: 1rem; border: 1px solid var(--border); border-radius: 8px; background: var(--bg); color: var(--fg); font-family: inherit; } .ask-form input:focus { outline: 2px solid var(--accent); outline-offset: 1px; } .ask-form button { padding: .6rem 1.2rem; font-size: 1rem; font-weight: 700; background: var(--accent); color: #fff; border: none; border-radius: 8px; cursor: pointer; font-family: inherit; } .ask-form button:disabled { opacity: .5; cursor: wait; } .ask-suggest { font-size: .85em; color: var(--muted); margin-bottom: 1.2rem; } .ask-out { font-size: 1rem; line-height: 1.65; padding: 1rem 1.2rem; background: var(--code-bg); border-radius: 10px; min-height: 3rem; display: none; } .ask-out.show { display: block; } .ask-out.loading::before { content: 'Thinking'; color: var(--muted); font-style: italic; } .ask-out.loading::after { content: '...'; color: var(--muted); font-style: italic; display: inline-block; width: 0; overflow: hidden; vertical-align: bottom; animation: think-dots 1.4s steps(4, end) infinite; } @keyframes think-dots { to { width: 1.6em; } } .ask-out.error { background: rgba(239,68,68,.1); color: #b91c1c; } .ask-meta { font-size: .75em; color: var(--muted); margin-top: .6rem; padding-top: .6rem; border-top: 1px dashed var(--border); } /* Mobile: stop the page from gaining horizontal scroll, without forcing the .wrap container to 100% (which broke the centered 42rem desktop layout). iOS Safari sometimes ignores overflow-x on body alone — set it on html too. */ html, body { overflow-x: hidden; max-width: 100%; } /* Belt-and-suspenders: clamp every chart container to its parent so an oversized SVG / inline-styled bar can't push the page wider on mobile. */ .projects-pie-wrap, .projects-pie, .projects-chart, .chart-bar, .projects-timeline, .tl-bars, .tl-axis, .projects-scoreboard { max-width: 100%; box-sizing: border-box; } /* Long project names / pasted URLs in notes were also a culprit. */ .proj-name, .proj-note, .scorecard-names { word-break: break-word; overflow-wrap: anywhere; } article img, article video, article iframe { max-width: 100%; height: auto; } article pre, article code { max-width: 100%; overflow-x: auto; word-wrap: break-word; } /* Cursor-following tooltip for SVG charts (pie slices have no ::after). */ .chart-tooltip { position: absolute; background: rgba(20,22,26,.92); color: #fff; padding: 5px 10px; border-radius: 6px; font-size: 12px; line-height: 1.4; white-space: nowrap; pointer-events: none; opacity: 0; transition: opacity 60ms; z-index: 1000; font-weight: 500; } /* Bar-segment instant tooltip (HTML so ::after works, like the timeline tiles). */ .bar-seg { position: relative; } .bar-seg::after { content: attr(data-tip); position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%) translateY(-6px); background: rgba(20,22,26,.92); color: #fff; padding: 5px 10px; border-radius: 6px; font-size: 12px; line-height: 1.4; white-space: nowrap; pointer-events: none; opacity: 0; transition: opacity 60ms; z-index: 5; font-weight: 500; } .bar-seg:hover::after { opacity: 1; } /* /projects scoreboard + list */ .projects-scoreboard { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1rem; margin: 1.75rem 0 2.25rem; align-items: stretch; } .scorecard { padding: 1.2rem 1.2rem 1.1rem; border-radius: 12px; border: 1px solid rgba(0,0,0,.06); display: flex; flex-direction: column; } .scorecard.good { background: #dcfce7; } /* pastel green */ .scorecard.made { background: #fef3c7; } /* pastel yellow */ .scorecard.failed { background: #fee2e2; } /* pastel red */ .scorecard.nonprofit { background: #ede9fe; } /* pastel purple */ .scorecard.newproj { background: #dbeafe; } /* pastel blue */ .scorecard.exited { background: #fce7f3; } /* pastel pink (success-exit) */ /* If there's an odd number of cards, let the last one span both columns. */ .scorecard:last-child:nth-child(odd) { grid-column: 1 / -1; } html[data-theme="dark"] .scorecard { border-color: rgba(255,255,255,.06); } html[data-theme="dark"] .scorecard.good { background: #102b1f; } html[data-theme="dark"] .scorecard.made { background: #2b2616; } html[data-theme="dark"] .scorecard.failed { background: #2b1818; } html[data-theme="dark"] .scorecard.nonprofit { background: #1f1a2e; } html[data-theme="dark"] .scorecard.newproj { background: #11202e; } html[data-theme="dark"] .scorecard.exited { background: #2b1424; } .scorecard-head { font-size: .72em; color: rgba(0,0,0,.6); text-transform: uppercase; letter-spacing: .08em; font-weight: 700; } html[data-theme="dark"] .scorecard-head { color: rgba(255,255,255,.65); } .scorecard-count { font-size: 2.6em; font-weight: 800; line-height: 1; margin: .35rem 0 .15rem; color: rgba(0,0,0,.85); display: flex; align-items: baseline; gap: .4rem; } html[data-theme="dark"] .scorecard-count { color: rgba(255,255,255,.95); } .scorecard-pct { font-size: .42em; font-weight: 500; color: rgba(0,0,0,.5); letter-spacing: .02em; } html[data-theme="dark"] .scorecard-pct { color: rgba(255,255,255,.55); } .scorecard-names { margin-top: .9rem; padding-top: .9rem; border-top: 1px dashed rgba(0,0,0,.1); display: flex; flex-wrap: wrap; gap: .4rem; } html[data-theme="dark"] .scorecard-names { border-top-color: rgba(255,255,255,.1); } .name-pill { display: inline-block; padding: .22em .7em; border-radius: 999px; background: rgba(255,255,255,.75); color: rgba(0,0,0,.85); font-size: .82em; line-height: 1.5; border: 1px solid rgba(0,0,0,.06); text-decoration: none; font-weight: 500; transition: background 80ms, color 80ms, border-color 80ms; } html[data-theme="dark"] .name-pill { background: rgba(255,255,255,.08); color: rgba(255,255,255,.92); border-color: rgba(255,255,255,.06); } a.name-pill:hover { background: rgba(0,0,0,.85); color: #fff; border-color: transparent; text-decoration: none; } html[data-theme="dark"] a.name-pill:hover { background: rgba(255,255,255,.9); color: #15171a; } @media (max-width: 640px) { .projects-scoreboard { grid-template-columns: 1fr; } } .projects-list { margin-top: 1.5rem; } .proj-year { font-weight: 700; font-size: 1.25em; margin: 1.6em 0 .6em; } .proj-entry { display: flex; gap: .55rem; align-items: baseline; padding: .3rem 0; } .proj-date { color: var(--muted); font-variant-numeric: tabular-nums; width: 4.5em; flex: none; font-size: .9em; } .proj-name { font-weight: 700; } .proj-name a { color: var(--fg); } .proj-status { color: var(--muted); font-size: .82em; margin-left: auto; flex: none; } .proj-post { font-size: .9em; margin: -.15em 0 .6em 0; } .proj-post a { color: var(--accent); font-weight: 600; } .proj-note { color: var(--quote); font-size: .92em; margin: .15em 0 .5em 0; line-height: 1.55; } .cat-dot { font-size: 1.1em; line-height: 1; flex: none; } .cat-dot.good { color: #16a34a; } .cat-dot.made { color: #d97706; } .cat-dot.failed { color: #ef4444; } .cat-dot.nonprofit { color: #8b5cf6; } .cat-dot.newproj { color: #2563eb; } .cat-dot.exited { color: #ec4899; } /* 100%-stacked bar visualising the same breakdown */ .projects-chart { margin: 0 0 2rem; } .chart-bar { display: flex; height: 40px; border-radius: 8px; overflow: hidden; border: 1px solid rgba(0,0,0,.06); } html[data-theme="dark"] .chart-bar { border-color: rgba(255,255,255,.06); } .bar-seg { display: flex; align-items: center; justify-content: center; font-size: .82em; font-weight: 700; color: #fff; min-width: 2px; transition: opacity 80ms, transform 80ms; cursor: pointer; } .bar-seg:hover { opacity: .85; } .bar-seg.dim { opacity: .25; } .bar-seg.active { transform: scaleY(1.08); box-shadow: inset 0 0 0 2px rgba(255,255,255,.6); } /* Click-to-filter (multi-select): timeline gets `filtering` + a `sel-` class per chosen category; only those categories' tiles stay visible. Click a legend item or bar segment; shift-click to combine several. */ .projects-timeline.filtering .tl-seg { display: none; } .projects-timeline.filtering.sel-good .tl-seg.good { display: block; } .projects-timeline.filtering.sel-made .tl-seg.made { display: block; } .projects-timeline.filtering.sel-failed .tl-seg.failed { display: block; } .projects-timeline.filtering.sel-nonprofit .tl-seg.nonprofit { display: block; } .projects-timeline.filtering.sel-newproj .tl-seg.newproj { display: block; } .projects-timeline.filtering.sel-exited .tl-seg.exited { display: block; } .bar-seg.good { background: #16a34a; } .bar-seg.made { background: #d97706; } .bar-seg.failed { background: #ef4444; } .bar-seg.nonprofit { background: #8b5cf6; } .bar-seg.newproj { background: #2563eb; } .bar-seg.exited { background: #ec4899; } .chart-legend { display: flex; flex-wrap: wrap; gap: .5rem 1rem; margin-top: .8rem; font-size: .85em; color: var(--muted); } .legend-item { display: inline-flex; align-items: center; gap: .35rem; cursor: pointer; transition: opacity .1s; } .chart-legend.has-active .legend-item { opacity: .4; } .chart-legend.has-active .legend-item.active { opacity: 1; } .chart-legend .legend-item.active strong { text-decoration: underline; } .chart-hint { font-size: .8em; color: var(--muted); opacity: .7; margin: .5rem 0 0; } .legend-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; box-shadow: inset 0 0 0 .5px rgba(0,0,0,.25); } .legend-dot.good { background: #16a34a; } .legend-dot.made { background: #d97706; } .legend-dot.failed { background: #ef4444; } .legend-dot.nonprofit { background: #8b5cf6; } .legend-dot.newproj { background: #2563eb; } .legend-dot.exited { background: #ec4899; } .legend-item strong { color: var(--fg); font-weight: 700; } /* Timeline (one column per year, stacked tiles per project) */ .projects-timeline { margin: 1.25rem 0 2rem; } .tl-controls { display: flex; gap: .8rem; align-items: center; margin: 0 0 .5rem; font-size: .85em; color: var(--muted); flex-wrap: wrap; } .tl-toggle { display: inline-flex; align-items: center; gap: .4em; cursor: pointer; user-select: none; } .tl-toggle input { cursor: pointer; margin: 0; } /* "Only show launches" hides every continuation tile (overrides filter rules too). */ .projects-timeline.launches-only .tl-seg:not(.tl-seg-launch) { display: none !important; } .tl-bars { display: flex; align-items: flex-end; gap: 1px; /* height set inline so PHP can scale to the busiest year */ border-bottom: 1px solid var(--border); padding: 0 0 1px; } .tl-col { flex: 1; display: flex; flex-direction: column-reverse; gap: 1px; min-width: 4px; height: 100%; justify-content: flex-start; } .tl-seg { flex: none; height: 4px; border-radius: 1px; display: block; position: relative; transition: filter 80ms; } /* Non-transforming hover so the child tooltip isn't stretched. */ .tl-seg:hover { filter: brightness(1.4) saturate(1.15); z-index: 2; box-shadow: 0 0 0 1px rgba(255,255,255,.4); } html[data-theme="dark"] .tl-seg:hover { box-shadow: 0 0 0 1px rgba(0,0,0,.4); } /* Instant tooltip on hover — native title has ~1s delay. */ .tl-seg::after { content: attr(data-tip); position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%) translateY(-6px); background: rgba(20,22,26,.92); color: #fff; padding: 5px 10px; border-radius: 6px; font-size: 12px; line-height: 1.4; white-space: nowrap; pointer-events: none; opacity: 0; transition: opacity 60ms; z-index: 5; font-weight: 500; } .tl-seg:hover::after { opacity: 1; } .tl-seg.good { background: #16a34a; } .tl-seg.made { background: #d97706; } .tl-seg.failed { background: #ef4444; } .tl-seg.nonprofit { background: #8b5cf6; } .tl-seg.newproj { background: #2563eb; } .tl-seg.exited { background: #ec4899; } /* Revenue chart — same visual language as the timeline above it. */ .projects-revenue { margin: 1.25rem 0 2rem; } .rev-title { font-size: .85em; color: var(--muted); margin: 0 0 .5rem; } .rev-bars { display: flex; align-items: flex-end; gap: 1px; border-bottom: 1px solid var(--border); padding: 0 0 1px; } .rev-col { flex: 1; display: flex; flex-direction: column-reverse; gap: 0; min-width: 4px; height: 100%; justify-content: flex-start; } /* Faint inset outline keeps white/light segments (Merch, X) visible on light theme. */ .rev-seg { flex: none; display: block; border-radius: 1px; position: relative; transition: filter 80ms; box-shadow: inset 0 0 0 .5px rgba(0,0,0,.18); } html[data-theme="dark"] .rev-seg { box-shadow: inset 0 0 0 .5px rgba(255,255,255,.12); } .rev-seg:hover { filter: brightness(1.4) saturate(1.15); z-index: 2; box-shadow: 0 0 0 1px rgba(255,255,255,.4); } html[data-theme="dark"] .rev-seg:hover { box-shadow: 0 0 0 1px rgba(0,0,0,.4); } .rev-seg::after { content: attr(data-tip); position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%) translateY(-6px); background: rgba(20,22,26,.92); color: #fff; padding: 5px 10px; border-radius: 6px; font-size: 12px; line-height: 1.4; white-space: nowrap; pointer-events: none; opacity: 0; transition: opacity 60ms; z-index: 5; font-weight: 500; } .rev-seg:hover::after { opacity: 1; } .tl-axis { display: flex; gap: 1px; font-size: .68em; color: var(--muted); margin-top: .4rem; } .tl-axis span { flex: 1; text-align: center; min-width: 4px; overflow: visible; white-space: nowrap; } /* When linked to from a pill, give the target row a subtle highlight + breathing room */ .proj-entry { scroll-margin-top: 1rem; transition: background 200ms; } .proj-entry:target { background: rgba(255,235,165,.35); border-radius: 6px; padding-left: .35rem; margin-left: -.35rem; } html[data-theme="dark"] .proj-entry:target { background: rgba(255,235,165,.08); } form.catch-emails { text-align: center; margin-top: 1.5rem; } /* Newsletter signup card on the front page, below the pinned box. */ .front-subscribe { margin: 1.5rem 0 0; padding: 1.25rem 1.25rem 1.5rem; border: 1px solid var(--border); border-radius: 8px; background: var(--code-bg); } /* Subscription-confirmed banner on top of the front page (/confirm). */ .confirm-banner { margin: 1rem 0 1.5rem; padding: 1rem 1.25rem; border: 2px solid var(--accent); border-radius: 8px; background: var(--code-bg); } .confirm-banner p { margin: .35em 0; } .front-subscribe p { margin: 0; text-align: center; color: var(--fg); } /* The lead line: inside the subscribe card on the blog, above it on the intro. */ .fs-lead { line-height: 1.45; text-wrap: balance; text-align: center; margin: 1.5rem 0 .75rem; color: var(--fg); } .fs-lead + .front-subscribe { margin-top: 0; } .front-subscribe .fs-note { max-width: 28rem; margin: .85rem auto 0; font-size: .8rem; color: var(--muted); line-height: 1.4; text-wrap: balance; } .front-subscribe form.catch-emails { margin-top: 1rem; margin-bottom: 0.5rem; } .catch-emails-text-box { outline:none; border:none; border-radius:6px; border-top-right-radius:0; border-bottom-right-radius:0; padding:12px; font-size:15px; vertical-align:middle; color:#3c484e; border-right:none; width:200px; box-shadow:0 0 0 1px var(--accent), 0 2px 4px 0 rgb(0 0 0 / 7%), 0 1px 1.5px 0 rgb(0 0 0 / 5%); } .catch-emails-button { cursor:pointer; background:var(--accent); appearance:none; color:#fff; padding:11px; font-weight:600; margin-left:0; font-size:15px; border-radius:6px; border-top-left-radius:0; border-bottom-left-radius:0; vertical-align:middle; border:1px solid var(--accent); z-index:1; position:relative; outline:1px solid var(--accent); width:130px; } .catch-emails-button:hover { background:none; color:var(--accent); border:1px solid var(--accent); } /* The intro keeps the chunkier 2px border it always had. */ #intro .catch-emails-button, #intro .catch-emails-button:hover { border-width:2px; } /* White on the accent measures 2.42:1 against the lighter dark-mode accent — well under the 4.5:1 AA wants for 15px/600 text. Dark text on the same blue is 7.41:1, so dark mode flips the label. */ html[data-theme="dark"] .catch-emails-button { color:#fff; } html[data-theme="dark"] .catch-emails-button:hover { color:var(--accent); } form.catch-emails .loading, form.catch-emails .error, form.catch-emails .success { display:none; } form.catch-emails.loading .not-subscribed, form.catch-emails.success .not-subscribed { display:none; } form.catch-emails.loading .loading { display:block; color:var(--muted); } form.catch-emails.error .error { display:block; color:#d33; margin-top:.5rem; } form.catch-emails.success .success { display:block; color:var(--accent); font-weight:600; } @media (max-width: 480px) { .catch-emails-text-box, .catch-emails-button { width:100%; margin:0 0 14px; display:block; border-radius:12px; } .catch-emails-button { margin:0; } } Subscribe
Sort: latest · oldest · views · blog views · views 30d · blog views 30d
Filter: all · blog · X posts · long form · presentations · podcasts
Pieter Levels

Hi. I’m @levelsio and you’ve found my blog!

I build internet startups by myself: Nomads.com, Remote OK, Hoodmaps, Photo AI and Interior AI. All bootstrapped without funding and just me coding everything on my laptop.

Since 2013 I write here about building businesses, traveling the world, AI and whatever else is on my mind.

Never miss a post: 182,443 people read my posts in full by email, before it spreads anywhere else

Subscribing you...
Subscribed! Check your inbox to confirm your email.

Or subscribe on Telegram →

Pinned

Never miss a post: 182,443 people read my posts in full by email, before it spreads anywhere else

Subscribing you...
Subscribed! Check your inbox to confirm your email.

Or subscribe on Telegram →