Compare commits

..

6 Commits

Author SHA1 Message Date
222e01d18e bump scripts 2026-04-14 09:33:45 -07:00
384ae63300 update README.md 2026-04-14 09:26:16 -07:00
eac8018a83 bump scripts 2026-04-14 09:23:14 -07:00
de546d6ff0 bump scripts 2026-04-14 08:56:59 -07:00
9965bd3529 update submodules 2026-04-14 03:09:20 -07:00
f0ae0b9ce1 hero bugfixes/improvements 2026-04-08 22:25:26 -07:00
9 changed files with 143 additions and 78 deletions

View File

@@ -1,3 +1 @@
![Badge](https://hitscounter.dev/api/hit?url=https%3A%2F%2Ftimmypidashev.dev&label=Visits&icon=eye-fill&color=%23198754)
<img src=".github/preview.jpeg" title="Preview"/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 742 KiB

View File

@@ -439,8 +439,15 @@ function GlitchCountdown({ seconds }: { seconds: number }) {
export default function Hero() {
const [phase, setPhase] = useState<
"intro" | "full" | "retired" | "countdown" | "glitch"
>("intro");
"intro" | "full" | "retired" | "countdown" | "glitch" | "void"
>(() => {
if (import.meta.env.DEV && typeof window !== "undefined") {
const p = new URLSearchParams(window.location.search);
if (p.has("debug-glitch")) return "glitch";
if (p.has("debug-countdown")) return "countdown";
}
return "intro";
});
const [fading, setFading] = useState(false);
const [cycle, setCycle] = useState(0);
const [countdown, setCountdown] = useState(150);
@@ -482,49 +489,81 @@ export default function Hero() {
}, [phase]);
// Glitch → transition into void
const glitchRef = useRef<HTMLDivElement>(null);
// Apply animation directly to each visible element (works on both desktop + mobile)
// On mobile, filter/transform on <body> doesn't reach fixed-position children,
// so we target the elements themselves
useEffect(() => {
if (phase !== "glitch") return;
// Apply glitch to all direct children of the layout wrapper
const wrapper = glitchRef.current?.closest("main")?.parentElement || document.body;
const style = document.createElement("style");
style.textContent = `
.hero-glitch-child {
animation: hero-glitch 3s ease-in forwards;
.hero-glitch-shake {
animation: hero-glitch-shake 3s ease-in forwards !important;
}
@keyframes hero-glitch {
0% { filter: none; transform: none; opacity: 1; }
5% { filter: hue-rotate(90deg) saturate(3); transform: skewX(2deg); }
10% { filter: invert(1); transform: skewX(-3deg) translateX(5px); }
15% { filter: hue-rotate(180deg) brightness(1.5); transform: scale(1.02); }
20% { filter: saturate(5) contrast(2); transform: skewX(1deg) translateY(-2px); }
25% { filter: invert(1) hue-rotate(270deg); transform: skewX(-2deg); }
30% { filter: brightness(2) saturate(0); transform: scale(0.98); }
40% { filter: hue-rotate(45deg) contrast(3); transform: translateX(-3px); }
50% { filter: invert(1) brightness(0.5); transform: skewX(4deg) skewY(1deg); }
60% { filter: saturate(0) brightness(1.8); transform: scale(1.01); }
70% { filter: hue-rotate(180deg) brightness(0.3); transform: none; }
80% { filter: contrast(5) saturate(0); transform: skewX(-1deg); }
90% { filter: brightness(0.1); transform: scale(0.99); opacity: 0.1; }
100% { filter: brightness(0); transform: none; opacity: 0; }
@keyframes hero-glitch-shake {
0% { transform: none; }
5% { transform: skewX(2deg); }
10% { transform: skewX(-3deg) translateX(5px); }
15% { transform: scale(1.02); }
20% { transform: skewX(1deg) translateY(-2px); }
25% { transform: skewX(-2deg); }
30% { transform: scale(0.98); }
40% { transform: translateX(-3px); }
50% { transform: skewX(4deg) skewY(1deg); }
60% { transform: scale(1.01); }
70% { transform: none; }
80% { transform: skewX(-1deg); }
90% { transform: none; }
100% { transform: none; }
}
.hero-glitch-filter {
animation: hero-glitch-filter 3s ease-in forwards !important;
position: fixed !important;
inset: 0 !important;
z-index: 99999 !important;
pointer-events: none !important;
}
@keyframes hero-glitch-filter {
0% { backdrop-filter: none; background: transparent; }
5% { backdrop-filter: hue-rotate(90deg) saturate(3); }
10% { backdrop-filter: invert(1); }
15% { backdrop-filter: hue-rotate(180deg) brightness(1.5); }
20% { backdrop-filter: saturate(5) contrast(2); }
25% { backdrop-filter: invert(1) hue-rotate(270deg); }
30% { backdrop-filter: brightness(2) saturate(0); }
40% { backdrop-filter: hue-rotate(45deg) contrast(3); }
50% { backdrop-filter: invert(1) brightness(0.5); }
60% { backdrop-filter: saturate(0) brightness(1.8); }
70% { backdrop-filter: hue-rotate(180deg) brightness(0.3); }
80% { backdrop-filter: contrast(5) saturate(0); }
90% { backdrop-filter: brightness(0); background: #000; }
100% { backdrop-filter: brightness(0); background: #000; }
}
`;
document.head.appendChild(style);
const children = Array.from(wrapper.children) as HTMLElement[];
children.forEach(child => child.classList.add("hero-glitch-child"));
// Overlay for backdrop-filter (color distortion — works on all platforms)
const overlay = document.createElement("div");
overlay.className = "hero-glitch-filter";
document.body.appendChild(overlay);
// Shake transforms on all layout elements
const targets = document.querySelectorAll<HTMLElement>(
"header, main, footer, nav"
);
targets.forEach(el => el.classList.add("hero-glitch-shake"));
// After glitch animation, transition to void phase
const timeout = setTimeout(() => {
children.forEach(child => child.classList.remove("hero-glitch-child"));
targets.forEach(el => el.classList.remove("hero-glitch-shake"));
overlay.remove();
style.remove();
setPhase("void");
}, 3000);
return () => {
clearTimeout(timeout);
children.forEach(child => child.classList.remove("hero-glitch-child"));
targets.forEach(el => el.classList.remove("hero-glitch-shake"));
overlay.remove();
style.remove();
};
}, [phase]);
@@ -587,7 +626,7 @@ export default function Hero() {
}
if (phase === "glitch") {
return <div ref={glitchRef} className="min-h-screen" />;
return <div className="min-h-screen" />;
}
if (phase === "countdown") {

View File

@@ -151,28 +151,20 @@ export default function VoidExperience({ token }: VoidExperienceProps) {
const [visitCount, setVisitCount] = useState<number | null>(null);
const [dissolving, setDissolving] = useState(false);
// Inject CSS + hide cursor + force fullscreen feel on mobile
// Inject CSS + hide cursor + hide layout chrome underneath
useEffect(() => {
const style = document.createElement("style");
style.textContent = GLITCH_CSS;
document.head.appendChild(style);
document.body.style.cursor = "none";
// Push mobile tab bar off-screen by making the page taller than viewport
const meta = document.querySelector('meta[name="viewport"]');
const origContent = meta?.getAttribute("content") || "";
meta?.setAttribute("content", "width=device-width, initial-scale=1, interactive-widget=resizes-content");
document.documentElement.style.overflow = "hidden";
document.body.style.overflow = "hidden";
// Scroll down slightly to trigger mobile browsers hiding the tab bar
window.scrollTo(0, 1);
return () => {
style.remove();
document.body.style.cursor = "";
document.documentElement.style.overflow = "";
document.body.style.overflow = "";
if (meta) meta.setAttribute("content", origContent);
};
}, []);
@@ -212,7 +204,7 @@ export default function VoidExperience({ token }: VoidExperienceProps) {
const corruption = getCorruption(activeSegment);
return (
<div className="fixed inset-0 bg-black" style={{ height: "100dvh" }}>
<div className="fixed inset-0 bg-black z-[9999]" style={{ height: "100dvh" }}>
{/* 3D Canvas — full glitch (transforms + filters) */}
<div className={`fixed inset-0 z-[10] ${getCanvasGlitch(activeSegment, dissolving)}`}>
<Canvas
@@ -225,17 +217,16 @@ export default function VoidExperience({ token }: VoidExperienceProps) {
</Canvas>
</div>
{/* Typewriter — filter-only glitch (no transform shift) */}
{/* Typewriter — glitch class applied to inner text, not the fixed container */}
{visitCount !== null && (
<div className={getTextGlitch(activeSegment, dissolving)}>
<VoidTypewriter
startSegment={0}
onPhaseComplete={handlePhaseComplete}
onSegmentChange={handleSegmentChange}
visitCount={visitCount}
corruption={corruption}
/>
</div>
<VoidTypewriter
startSegment={0}
onPhaseComplete={handlePhaseComplete}
onSegmentChange={handleSegmentChange}
visitCount={visitCount}
corruption={corruption}
glitchClass={getTextGlitch(activeSegment, dissolving)}
/>
)}
</div>
);

View File

@@ -11,6 +11,7 @@ interface VoidTypewriterProps {
onSegmentChange: (index: number) => void;
visitCount: number;
corruption: number;
glitchClass: string;
}
function getTextNodes(node: Node): Text[] {
@@ -25,7 +26,7 @@ function getTextNodes(node: Node): Text[] {
return nodes;
}
export default function VoidTypewriter({ startSegment, onPhaseComplete, onSegmentChange, visitCount, corruption }: VoidTypewriterProps) {
export default function VoidTypewriter({ startSegment, onPhaseComplete, onSegmentChange, visitCount, corruption, glitchClass }: VoidTypewriterProps) {
const containerRef = useRef<HTMLDivElement>(null);
const corruptionRef = useRef(corruption);
corruptionRef.current = corruption;
@@ -85,7 +86,7 @@ export default function VoidTypewriter({ startSegment, onPhaseComplete, onSegmen
<div className="fixed inset-0 z-[50] flex justify-center items-center pointer-events-none">
<div
ref={containerRef}
className="text-xl md:text-3xl font-bold text-center max-w-[85vw] md:max-w-[70vw] break-words text-white leading-relaxed"
className={`text-xl md:text-3xl font-bold text-center max-w-[85vw] md:max-w-[70vw] break-words text-white leading-relaxed ${glitchClass}`}
>
<Typewriter
key={`void-${startSegment}-${visitCount}`}

View File

@@ -44,14 +44,20 @@ Install the following programs. These will be needed to compile coreboot and fla
<Commands
description="Install prerequisite packages"
archCommand="sudo pacman -S base-devel curl git gcc-ada ncurses zlib nasm sharutils unzip flashrom"
debianCommand="sudo apt install build-essential curl git gnat libncurses-dev zlib1g-dev nasm sharutils unzip flashrom"
fedoraCommand="sudo dnf install @development-tools curl git gcc-gnat ncurses-devel zlib-devel nasm sharutils unzip flashrom"
gentooCommand="sudo emerge --ask sys-devel/base-devel net-misc/curl dev-vcs/git sys-devel/gcc ncurses dev-libs/zlib dev-lang/nasm app-arch/sharutils app-arch/unzip sys-apps/flashrom"
nixCommand="nix-env -i stdenv curl git gcc gnat ncurses zlib nasm sharutils unzip flashrom"
archCommand="sudo pacman -S base-devel curl git gcc-ada ncurses zlib nasm sharutils unzip flashrom usbutils chafa libwebp"
debianCommand="sudo apt install build-essential curl git gnat libncurses-dev zlib1g-dev nasm sharutils unzip flashrom usbutils chafa webp"
fedoraCommand="sudo dnf install @development-tools curl git gcc-gnat ncurses-devel zlib-devel nasm sharutils unzip flashrom usbutils chafa libwebp-tools"
gentooCommand="sudo emerge --ask sys-devel/base-devel net-misc/curl dev-vcs/git sys-devel/gcc ncurses dev-libs/zlib dev-lang/nasm app-arch/sharutils app-arch/unzip sys-apps/flashrom sys-apps/usbutils media-gfx/chafa media-libs/libwebp"
nixCommand="nix-env -i stdenv curl git gcc gnat ncurses zlib nasm sharutils unzip flashrom usbutils chafa libwebp"
client:load
/>
`usbutils` provides `lsusb` (used to verify the CH341A). `chafa` and the
`libwebp` tools are optional — the interactive script uses them to render
reference images inline in your terminal when supported (and to transcode
webp images to png if your chafa build doesn't support webp natively).
Without them the script falls back to printing a URL.
## Disassembling the Laptop
1. **Power off your laptop**: Make sure your T440p is completely powered off and unplugged from any power source.
2. **Remove the battery**: Flip the laptop over and remove the battery by sliding the latch to the unlock position and lifting it out.
@@ -59,24 +65,39 @@ Install the following programs. These will be needed to compile coreboot and fla
## Locating the EEPROM Chips
In order to flash the laptop, you will need to have access to two EEPROM chips located next to the sodimm RAM.
In order to flash the laptop, you will need to have access to two EEPROM chips
located next to the SODIMM RAM. They are different sizes and hold different
firmware — read them in the order shown below.
![EEPROM Chips Location](/blog/thinkpad-t440p-coreboot-guide/eeprom_chips_location.png)
The **4MB (top)** chip — smaller, farther from the CPU:
![4MB EEPROM chip location](/blog/thinkpad-t440p-coreboot-guide/eeprom_chip_4mb.webp)
The **8MB (bottom)** chip — larger, closer to the CPU, holds the Intel ME firmware:
![8MB EEPROM chip location](/blog/thinkpad-t440p-coreboot-guide/eeprom_chip_8mb.webp)
## Assembling the SPI Flasher
Place the SPI flasher ribbon cable into the correct slot and make sure its the 3.3v variant
![SPI Flasher Assembly](/blog/thinkpad-t440p-coreboot-guide/spi_flasher_assembly.png)
![SPI Flasher Assembly](/blog/thinkpad-t440p-coreboot-guide/spi_flasher_assembly.webp)
After the flasher is ready, connect it to your machine and ensure its ready to use:
After the flasher is ready, plug it into a USB port on your machine (leave the clip
unattached for now) and confirm the kernel sees it:
<Command
description="Ensure the CH341A flasher is being detected"
command="flashrom --programmer ch341a_spi"
description="Verify CH341A is on the USB bus"
command="lsusb | grep 1a86:5512"
/>
Flashrom should report that programmer initialization was a success.
A matching line (e.g. `Bus 001 Device 00X: ID 1a86:5512 QinHeng Electronics`)
confirms the programmer is plugged in and the host recognises it.
> Do **not** run `flashrom --programmer ch341a_spi` at this stage — with no
> chip clipped on, flashrom will report "No EEPROM/flash device found" and
> exit non-zero. That's expected, not a failure of the programmer. The chip
> probe happens in the next section, paired with the actual read.
## Extracting Original BIOS
@@ -89,11 +110,14 @@ the T440p will be done.
client:load
/>
Next, extract the original rom from both EEPROM chips. This is
done by attaching the programmer to the correct chip and running
the subsequent commands. It may take longer than expected, and
ensuring the bios was properly extracted is important before proceeding
further.
Next, extract the original ROM from both EEPROM chips. Do the **4MB (top)
chip first** — it's the smaller of the two, so reads finish faster and any
setup issues (clip alignment, pin 1, voltage) surface quickly. Then move
the clip to the **8MB (bottom) chip**.
Each chip is read twice so the two reads can be diffed to catch flaky
contact. The reads can take a while (tens of seconds to a couple of
minutes per pass) — that's normal.
<CommandSequence
commands={[
@@ -115,11 +139,18 @@ further.
client:load
/>
> **If flashrom errors with "Multiple flash chip definitions match":**
> Your chip's silicon ID matches several part variants (common for Winbond
> W25Q* parts). Re-run the command with `-c <chipname>` to disambiguate, e.g.
> `sudo flashrom --programmer ch341a_spi -c W25Q32JV -r 4mb_backup1.bin`.
> Use the same `-c` value for every subsequent read/write on that chip.
> The newest variant in the list is usually a safe default.
If the diff checks pass, combine both files into one ROM.
<Command
description="Combine 4MB & 8MB into one ROM"
command="cat 8mb_backup_1.bin 4mb_backup1.bin > t440p-original.rom"
command="cat 8mb_backup1.bin 4mb_backup1.bin > t440p-original.rom"
client:load
/>
@@ -140,7 +171,7 @@ a new bios image.
client:load
/>
We will need to build `idftool`, which will be used to export all necessary blobs
We will need to build `ifdtool`, which will be used to export all necessary blobs
from our original bios, and `cbfstool`, which will be used to extract __mrc.bin__(a blob
from a haswell chromebook peppy image).
@@ -258,9 +289,14 @@ Then open the configuration menu:
Key settings to configure:
- **Mainboard** &rarr; Mainboard vendor: **Lenovo** &rarr; Mainboard model: **ThinkPad T440p**
- **Chipset** &rarr; Add Intel descriptor.bin, ME firmware, and GbE configuration (set paths to your blobs)
- **Chipset** &rarr; Add haswell MRC file (set path to mrc.bin)
- **Payload** &rarr; Choose your preferred payload (GRUB2, SeaBIOS, or edk2)
(`CONFIG_BOARD_LENOVO_THINKPAD_T440P=y`)
- **Chipset** &rarr; Add Intel descriptor.bin (`CONFIG_HAVE_IFD_BIN`), ME firmware (`CONFIG_HAVE_ME_BIN`),
and GbE configuration (`CONFIG_HAVE_GBE_BIN`) — set paths to your extracted blobs.
- **Chipset** &rarr; Add Haswell MRC file (`CONFIG_HAVE_MRC` / `CONFIG_MRC_FILE`) — set path to `mrc.bin`.
- **Payload** &rarr; Choose your preferred payload: GRUB2 (`CONFIG_PAYLOAD_GRUB2`), SeaBIOS
(`CONFIG_PAYLOAD_SEABIOS`), or Tianocore/edk2 (`CONFIG_PAYLOAD_TIANOCORE`).
- **Devices** &rarr; (optional, GT730M only) Enable `CONFIG_VGA_BIOS_DGPU` and set
`CONFIG_VGA_BIOS_DGPU_FILE` to your extracted GT730M VBIOS (PCI ID `10de,1292`).
## Building and Flashing
@@ -344,7 +380,7 @@ Reboot to apply `iomem=relaxed`
<Command
description="Flash the original bios"
command="sudo flashrom -p internal:laptop=force_I_want_a_brick -r ~/t440p-coreboot/t440p-original.rom"
command="sudo flashrom -p internal:laptop=force_I_want_a_brick -w ~/t440p-coreboot/t440p-original.rom"
/>
And that about wraps it up! If you liked the guide, leave a reaction or comment any changes or fixes