diff --git a/STRUCTURE.md b/STRUCTURE.md index 2ab1b8d..9769788 100644 --- a/STRUCTURE.md +++ b/STRUCTURE.md @@ -184,6 +184,35 @@ they're after instead of landing on the page and hunting. anchor/link it can't resolve; treat a clean build as the actual pass/fail check for this list, since hand-checked slugs are easy to get subtly wrong (trailing punctuation, duplicate-heading suffixes, etc). +- **Marking content "advanced" for the Simplify toggle** — the header's "Essentials" / "Advanced" + segmented control (both labels always visible, on every page) hides content marked + `data-advanced="true"`, at one of two granularities. Each spot that should hide is marked + directly, in its own markdown source — there's no derived/shared list, so a new advanced entry + needs tagging in every place it should disappear from: + - **A homepage keyword-link row** — the bolded keyword plus its row of related links (e.g. + `functions.md#decorators` or `collections.md#sets`, in `index.md`) — append + `{: data-advanced="true" }` on its own line directly after the row, at the same indentation, + with no blank line before it (attr_list attaches it to that paragraph, which + `.simplify-active [data-advanced]` then hides). + - **The matching heading on the actual content page** — e.g. `functions.md`'s + `## Decorators { data-advanced="true" }` — append `{ data-advanced="true" }` directly on the + heading line (same attr_list convention as `data-card-link="skip"` above). This hides that + heading, everything up to the next heading of the same or higher level, and its + integrated-TOC sidebar entry, on that page specifically. Tag the homepage row and the + content-page heading independently — `docs/javascripts/essentials_toggle.js` doesn't infer one + from the other, by design (simpler and more robust than deriving a map at runtime). + - **A whole homepage card** (e.g. the OpenCV card) — append `{: data-advanced="card" }` the + same way, right after the card's first paragraph (the icon + title link, e.g. + `[__OpenCV__](...)`). `.simplify-active .grid.cards > ul > li:has(> p[data-advanced="card"])` + in `extra.css` walks up from that paragraph to hide the whole enclosing `
  • `. This one has + no content-page equivalent — it marks a whole linked page, not a section within one, so + there's nothing on that page itself to hide. + Which value to use, and what counts as advanced/niche vs. core, is a per-editor judgment call + — there's no test enforcing it either way. See `docs/javascripts/essentials_toggle.js` for the + toggle mechanism. A page can still show a *link* to a hidden section (e.g. `collections.md`'s + own cheat-sheet table links to `#tuples` even though the "Tuples" heading is hidden) — + following such a link automatically switches back to Complete and reveals the target, so this + doesn't need special-casing when adding new advanced content. ### Admonitions (`??? type "..."`) diff --git a/docs/about.md b/docs/about.md index 61cedc9..31d08b2 100644 --- a/docs/about.md +++ b/docs/about.md @@ -6,11 +6,25 @@ description: Why Python Field Guide exists, who built it, and how to send feedba
    +## What is Python? + +Python is a general-purpose language built for code that's easy to read back later, even by someone who didn't write it. There's no compiling: write a `.py` file, run it directly. + +It shows up everywhere — web backends, data analysis and machine learning, automating repetitive tasks, scientific computing, quick glue scripts. Several of these are covered on this site's [Libraries](index.md#utilities) pages. + +The fundamentals every language shares — variables, conditionals, loops, functions, classes — read closer to plain English in Python, so you spend your effort learning to think like a programmer instead of fighting a stricter syntax. Once those fundamentals are solid, they carry over to whatever language you pick up next. + +Python is often the fastest language to write *correct* code in, even though it's not the fastest to *run*, which is why it's such a common first choice for a new project. + +
    + +
    + ## Why I built this My name's Luka, I'm a software engineer and Intro Python teacher. -I built this from scratch — it started as a few quick-reference explanations for students working on their first programs, and evolved into this site. +*Python Field Guide* is a free, in-browser reference — most code blocks are editable and runnable directly on the page. It started as a few quick-reference explanations for students working on their first programs, and evolved into this site. I couldn't find a site my students would consistently use that had: @@ -21,6 +35,8 @@ I couldn't find a site my students would consistently use that had: - quick intuitive navigation - clean, minimal UI +It's built for learners — self-taught, students in an intro course, or anyone who wants one combined reference to work through start to finish, instead of a scattered pile of search results. + I'm hoping this can be a helpful cheatsheet for others to quickly reference syntax and structures.
    diff --git a/docs/collections.md b/docs/collections.md index 8469a53..87e7d61 100644 --- a/docs/collections.md +++ b/docs/collections.md @@ -760,7 +760,7 @@ flowchart LR
    -## Tuples +## Tuples { data-advanced="true" } A tuple stores multiple items, in order, written in parentheses. They are **immutable** so the items can't be changed once its created. @@ -1033,7 +1033,7 @@ The **negative index** starts counting down from the end instead, starting at `-
    -## Sets +## Sets { data-advanced="true" } A set stores multiple items, in no particular order, inside a single variable — written with curly braces. diff --git a/docs/foundations.md b/docs/foundations.md index ef83018..f3a6aab 100644 --- a/docs/foundations.md +++ b/docs/foundations.md @@ -18,7 +18,7 @@ description: >- - [Print debugging](errors.md#print-debugging) - [Isolate the problem](errors.md#isolate-the-problem) - **Try building something small.** Once you've read through [Conditionals](conditionals.md) and [Loops](loops.md) you already have enough to write a program. -- The homepage FAQ has more on [using AI to help you learn](index.md#faq). +- The homepage has more on [using AI to help you learn](index.md).
    @@ -44,7 +44,7 @@ flowchart LR classDef terminal fill:none,stroke-width:2px class start,stop terminal - classDef blackbox fill:#000,color:#fff,stroke:#fff,stroke-width:1px + classDef blackbox stroke-width:1px class black blackbox ``` @@ -57,38 +57,19 @@ flowchart LR
    ```mermaid -flowchart TB - -subgraph top[" "] -direction LR -black@{ shape: procs, label: "your program" } -stop@{ shape: dbl-circ, label: "end ■" } -start(("start ▶")) -start --> black --> stop -end - -p1["print(...)"] -p2["print(...)"] -p3["print(...)"] - -black -.-> p1 -black -.-> p2 -black -.-> p3 - -style top fill:none,stroke:none - -classDef terminal fill:none,stroke-width:2px -class start,stop terminal +flowchart LR + black@{ shape: procs, label: "your program\nprint(...)\nprint(...)\nprint(...)" } + stop@{ shape: dbl-circ, label: "end ■" } + start(("start ▶")) + start --> black --> stop -classDef blackbox fill:#000,color:#fff,stroke:#fff,stroke-width:1px -class black blackbox + classDef terminal fill:none,stroke-width:2px + class start,stop terminal -classDef plain fill:none,stroke:none -class p1,p2,p3 plain + classDef blackbox stroke-width:1px + class black blackbox ``` -

    FIG: print() checkpoints along a running program

    -
    Code editors have an **output** window at the bottom that shows the print statements as the program runs. @@ -100,41 +81,13 @@ Code editors have an **output** window at the bottom that shows the print statem
    ```mermaid -flowchart TB - -subgraph code[" "] -direction LR - -p("print") -o("(") -s("data") -c(")") - -p -.- o -.- s -.- c - -end - -f("the function name `print`") -pa("opening parenthesis") -st("the data you want to see: a word, number, variable, etc") -pc("closing parenthesis") - -f --> p -pa --> o -st --> s -pc --> c - -classDef plain fill:none,stroke:none -class f,pa,st,pc plain +flowchart LR +p("print") -.- o("(") -.- s("value you want to see") -.- c(")") classDef punct stroke:none class p,o,c punct - -style code fill:none,stroke:none ``` -

    FIG: the parts of a print() statement

    -
    ```python-ref @@ -416,47 +369,13 @@ The text inside the parentheses — `"What's your first name? "` — is the **pr
    ```mermaid -flowchart TB - -subgraph code[" "] -direction LR - -n("name") -eq("=") -i("input") -o("(") -s(""What's your name? "") -c(")") - -n -.- eq -.- i -.- o -.- s -.- c - -end - -nl("the variable to save the answer in") -eql("the assignment operator") -il("the function name `input`") -pa("opening parenthesis") -sl("the prompt: a message shown before waiting") -pc("closing parenthesis") - -nl --> n -eql --> eq -il --> i -pa --> o -sl --> s -pc --> c - -classDef plain fill:none,stroke:none -class nl,eql,il,pa,sl,pc plain +flowchart LR +n("variable_name") -.- eq("=") -.- i("input") -.- o("(") -.- q1(""") -.- s("Prompt the user for the value you want") -.- q2(""") -.- c(")") classDef punct stroke:none -class eq,i,o,c punct - -style code fill:none,stroke:none +class eq,i,o,c,q1,q2 punct ``` -

    FIG: the parts of an input() statement

    -
    **Prompt format:** diff --git a/docs/functions.md b/docs/functions.md index e4ebd7b..8eef957 100644 --- a/docs/functions.md +++ b/docs/functions.md @@ -323,7 +323,7 @@ describe(species="ball", length_ft=5)
    -## Scope +## Scope { data-advanced="true" } A variable created inside a function is **local** — it only exists while that function is running, and isn't visible outside it. @@ -394,7 +394,7 @@ def show_species():
    -## Recursion +## Recursion { data-advanced="true" } A function can call itself — this is called **recursion**, an alternative to a loop for problems that break down into smaller versions of themselves. @@ -460,7 +460,7 @@ Every recursive function needs two parts:
    -## Decorators +## Decorators { data-advanced="true" } **`@decorator`** lets you add behavior to a function without editing the function's own code — write the behavior once, then apply it to as many functions as you want. It's written as `@decorator_name`, placed directly above a `def`, and takes one function in, returning a function out[^callable]. @@ -470,7 +470,7 @@ A decorator can run its own code around a function call by returning a different ```python def decorator_name(func): # func is the function being decorated (here it's "describe()") - def wrapper(): # desfines a new function that runs in place of func from now on + def wrapper(): # defines a new function that runs in place of func from now on print("looking up a snake...") func() # calls the original, still reachable through func print("found it") diff --git a/docs/index.md b/docs/index.md index e56fa10..f97fba7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,6 +9,79 @@ hide: # Python Field Guide { .pt-visually-hidden } +??? ai "Using AI while learning to code" + + === "Why still learn to code yourself?" + +
    + + | | Learn to do it yourself | Have AI do it for you | + |---|---|---| + | **Writing & struggling with code** | :material-check:{ .pt-icon-success } Productive struggle is what **builds understanding**
    • You can solve the problem **again** on your own
    • **Adapt** the answer, and **catch** when it's wrong
    | :material-close:{ .pt-icon-fail } Being handed the answer skips [the friction that builds understanding](https://bjorklab.psych.ucla.edu/wp-content/uploads/sites/13/2016/04/EBjork_RBjork_2011.pdf)
    • If you skip that struggle, you **won't develop the knowledge** to solve the problem again, adapt the answer, or recognize when it's wrong.
    | + | **Reading & verifying code** | :material-check:{ .pt-icon-success } **Understanding programming fundamentals** makes AI more useful — you can read and evaluate code you didn't write
    • Spot mistakes
    • Understand **why** a solution works
    • Communicate your problem to AI more effectively
    | :material-close:{ .pt-icon-fail } Inefficient communication with AI **if you don't fully understand **what's going on, and AI code can look correct while being wrong or insecure
    • You can't tell **why** a solution works
    • Researchers are already documenting this skill gap in [students who rely on AI code generation](https://dl.acm.org/doi/10.1145/3617367) before they've [built their own foundation](https://dl.acm.org/doi/10.1145/3624720)
    • One [Stanford study](https://dl.acm.org/doi/10.1145/3576915.3623157) found developers using AI wrote **less** secure code — but were **more** confident it was secure
    | + | **On the job** | :material-check:{ .pt-icon-success } Employers are still hiring for understanding, not prompting
    • Code review, debugging, and interviews all test whether you can reason about code and judge whether it's correct
    • Learning to program on your own turns AI into a **tool you can direct and verify**, instead of one you're assuming got it right
    | :material-close:{ .pt-icon-fail } AI will likely be available at work too — but it isn't what will get you hired | + +
    + + === "How to use AI to support your learning?" + + ```mermaid + swimlane-beta TB + accTitle: Using AI to learn + accDescr: AI walks you through setup before you start. You attempt each problem yourself with autocomplete off, then check whether you can explain why it works and whether it actually holds up — if either check fails, you describe what you tried rather than handing over your code, so AI explains the concept instead of fixing it, and you try again. Passing both checks means you understand it well enough for a practice problem, or for the code review, debugging, and interviews that are what your job actually tests. + + subgraph You + you_help_setup(Need help setting up your computer to start programming?) + you_have_project(Do you have a project to work on?) + you_attempt(Attempt it yourself, autocomplete off so you understand everything that's there, utilize debug strategies) + you_writeup(Stuck? Write out your problem: what you expect to happen, what you're observing, what you've tried already, see if this helps you process or rethink your strategy) + you_finish("Learning to program means making mistakes, getting stuck, and eventually figuring things out and forming a mental model. That's the process that builds skills you can rely on to continue solving problems and detecting issues — ready for code review, debugging, and interviews, where AI won't be tested, your reasoning will. ") + end + + subgraph AI + ai_help_setup(AI can help guide you through setup on your exact device — which could make getting started more accessible for beginners) + ai_suggest_problem(Suggests a problem based on your skill level, interests, and what you're trying to improve) + explain(With that context AI can provide more targeted, efficient help. Don't just paste the code/error, ask AI to explain what a line does, or what an error means — an explanation to help you understand concepts, be clear you do NOT want it to write code, just explain) + end + + + ai_help_setup ~~~ you_help_setup + ai_suggest_problem ~~~ you_have_project + you_help_setup -->|Yes| ai_help_setup + you_help_setup -->|No| you_have_project + ai_help_setup --> you_have_project + + you_have_project -->|No| ai_suggest_problem + + you_have_project -->|Yes| you_attempt + + ai_suggest_problem --> you_attempt + you_attempt <--> you_writeup + you_attempt -->|Done!| you_finish + you_writeup --> |still stuck?| explain + explain --> you_attempt + explain ~~~ you_finish + + %% Edge label text colored red if it leads into the AI lane, green + %% if it leads into the You lane, matching the lane colors. This is + %% linkStyle's "color" property (not background) — the one styling + %% mechanism mermaid applies from inside its own closed shadow + %% root, so it's the only thing that actually reaches the label. + linkStyle 0 color:#a33f3f + linkStyle 1 color:#3f6b52 + linkStyle 3 color:#3f6b52 + linkStyle 4 color:#a33f3f + linkStyle 7 color:#3f6b52 + linkStyle 8 color:#a33f3f + + classDef you fill:#3f6b521f,stroke:#3f6b52,stroke-width:2px,color:#3f6b52 + classDef ai fill:#a33f3f1a,stroke:#a33f3f,stroke-width:2px,color:#a33f3f + class you_writeup,you_help_setup,you_have_project,you_attempt,you_finish you + class explain,ai_help_setup,ai_suggest_problem ai + style You fill:#3f6b521f,stroke:#3f6b52,color:#3f6b52 + style AI fill:#a33f3f1a,stroke:#a33f3f,color:#a33f3f + ``` +
    @@ -33,17 +106,19 @@ hide: [**`how to write and run .py file`**](workspace.md#step-2-write-and-run-a-python-file): [`file naming`](workspace.md#step-2-write-and-run-a-python-file) - [**`Terminal`**](workspace.md#using-the-terminal-optional): - [`cd`](workspace.md#using-the-terminal-optional) - [`ls`](workspace.md#using-the-terminal-optional) - [`pwd`](workspace.md#using-the-terminal-optional) - [`shortcuts`](workspace.md#using-the-terminal-optional) + [**`Terminal`**](workspace.md#using-the-terminal): + [`cd`](workspace.md#using-the-terminal) + [`ls`](workspace.md#using-the-terminal) + [`pwd`](workspace.md#using-the-terminal) + [`shortcuts`](workspace.md#using-the-terminal) + {: data-advanced="true" } - [**`virtual environments`**](workspace.md#virtual-environments-optional): - [`activate`](workspace.md#virtual-environments-optional) - [`pip`](workspace.md#virtual-environments-optional) - [`requirements.txt`](workspace.md#virtual-environments-optional) - [`venv`](workspace.md#virtual-environments-optional) + [**`virtual environments`**](workspace.md#virtual-environments): + [`activate`](workspace.md#virtual-environments) + [`pip`](workspace.md#virtual-environments) + [`requirements.txt`](workspace.md#virtual-environments) + [`venv`](workspace.md#virtual-environments) + {: data-advanced="true" } - :material-cube-outline:{ .lg .middle } [__Foundations__](foundations.md) @@ -209,6 +284,7 @@ hide: [`sum`](collections.md#arithmetic_1) [`tuple`](collections.md#create_2) [`unpacking`](collections.md#packing-and-unpacking) + {: data-advanced="true" } [**`sets`**](collections.md#sets): [`add`](collections.md#update_1) @@ -229,6 +305,7 @@ hide: [`sum`](collections.md#arithmetic_2) [`update`](collections.md#update_1) [`| & - ^`](collections.md#combine) + {: data-advanced="true" }
    @@ -316,8 +393,10 @@ hide: [**`scope`**](functions.md#scope): [`local vs global`](functions.md#local-vs-global-variables) + {: data-advanced="true" } [**`recursion`**](functions.md#recursion) + {: data-advanced="true" } [**`decorators`**](functions.md#decorators): [`arguments`](functions.md#accepting-arguments) @@ -325,6 +404,7 @@ hide: [`original function`](functions.md#returning-the-original-function) [`stacking`](functions.md#advanced-uses) [`wrapping`](functions.md#wrapping-the-call) + {: data-advanced="true" } - :material-package-variant:{ .lg .middle } [__Classes__](oop.md) @@ -339,6 +419,7 @@ hide: [`@classmethod`](oop.md#classmethod) [`@property`](oop.md#property) [`@staticmethod`](oop.md#staticmethod) + {: data-advanced="true" } [**`inheritance`**](oop.md#inheritance): [`adding attributes and methods`](oop.md#adding-attributes-and-methods) @@ -349,6 +430,7 @@ hide: [**`polymorphism`**](oop.md#polymorphism): [`polymorphism via inheritance`](oop.md#polymorphism-via-inheritance) [`same method name, unrelated classes`](oop.md#same-method-name-unrelated-classes) + {: data-advanced="true" }
    @@ -453,7 +535,7 @@ hide: # Add-On Libraries -
    +
    #### Testing { .pt-homepage-heading }
    @@ -484,6 +566,7 @@ hide: - :material-format-list-group:{ .lg .middle } [__collections__](libraries/collections.md) [:material-language-python:](libraries/collections.md){ .pt-lib-badge .pt-lib-badge--builtin title="Built-in — included with Python" } + {: data-advanced="card" } Specialized containers with advanced functionality. @@ -601,6 +684,7 @@ hide: - :material-matrix:{ .lg .middle } [__NumPy__](libraries/numpy.md) [:material-download-outline:](libraries/numpy.md){ .pt-lib-badge .pt-lib-badge--third-party title="Third-party — install separately with pip" } + {: data-advanced="card" } Fast numeric arrays, with math applied to a whole array at once instead of item by item. @@ -613,6 +697,7 @@ hide: - :material-table:{ .lg .middle } [__pandas__](libraries/pandas.md) [:material-download-outline:](libraries/pandas.md){ .pt-lib-badge .pt-lib-badge--third-party title="Third-party — install separately with pip" } + {: data-advanced="card" } Tabular data: rows and columns, like a spreadsheet, built on top of NumPy. @@ -754,13 +839,14 @@ hide:
    -
    +
    #### Computer vision { .pt-homepage-heading }
    - :material-face-recognition:{ .lg .middle } [__OpenCV__](libraries/opencv.md) [:material-download-outline:](libraries/opencv.md){ .pt-lib-badge .pt-lib-badge--third-party title="Third-party — install separately with pip" } + {: data-advanced="card" } Real-time image and video analysis, built directly on NumPy arrays: color spaces, edge detection, face detection. @@ -802,89 +888,3 @@ hide:
    -### FAQ - -??? ai "Why learn to code yourself, if AI can do it for you?" - -
    - - | | Learn to do it yourself | Have AI do it for you | - |---|---|---| - | **Writing & struggling with code** | :material-check:{ .pt-icon-success } **Productive struggle** is what builds understanding
    • You can solve the problem again on your own
    • Adapt the answer, and catch when it's wrong
    | :material-close:{ .pt-icon-fail } Being handed the answer skips [the friction that builds understanding](https://bjorklab.psych.ucla.edu/wp-content/uploads/sites/13/2016/04/EBjork_RBjork_2011.pdf)
    • If you skip that struggle, you *won't develop the knowledge* to solve the problem again, adapt the answer, or recognize when it's wrong.
    | - | **Reading & verifying code** | :material-check:{ .pt-icon-success } Understanding programming fundamentals makes AI more useful — you can read code you didn't write, and check it before you trust it
    • Spot mistakes
    • Understand *why* a solution works
    • Communicate your problem to AI more effectively
    | :material-close:{ .pt-icon-fail } Inefficient communication with AI if you don't fully understand what's going on, and AI code can look correct while being **wrong and insecure**
    • You can't tell *why* a solution works
    • Researchers are already documenting this skill gap in [students who rely on AI code generation](https://dl.acm.org/doi/10.1145/3617367) before they've [built their own foundation](https://dl.acm.org/doi/10.1145/3624720)
    • One [Stanford study](https://dl.acm.org/doi/10.1145/3576915.3623157) found developers using AI wrote *less* secure code — but were *more* confident it was secure
    | - | **On the job** | :material-check:{ .pt-icon-success } Employers are still hiring for understanding, not prompting
    • Code review, debugging, and interviews all test whether you can reason about code
    • And judge whether it's correct
    • Learning to program on your own turns AI into a tool you can direct and verify, instead of one you're assuming got it right
    | :material-close:{ .pt-icon-fail } AI will likely be available at work too — but it isn't what's being tested | - -
    - -??? ai "How to use AI to support your learning" - - ```mermaid - swimlane-beta TB - accTitle: Using AI to learn - accDescr: AI walks you through setup before you start. You attempt each problem yourself with autocomplete off, then check whether you can explain why it works and whether it actually holds up — if either check fails, you describe what you tried rather than handing over your code, so AI explains the concept instead of fixing it, and you try again. Passing both checks means you understand it well enough for a practice problem, or for the code review, debugging, and interviews that are what your job actually tests. - - subgraph You - you_help_setup(Need help setting up your computer to start programming?) - you_have_project(Do you have a project to work on?) - you_attempt(Attempt it yourself, autocomplete off so you understand everything that's there, utilize debug strategies) - you_writeup(Stuck? Write out your problem: what you expect to happen, what you're observing, what you've tried already, see if this helps you process or rethink your strategy) - you_finish("Learning to program means making mistakes, getting stuck, and eventually figuring things out and forming a mental model. That's the process that builds skills you can rely on to continue solving problems and detecting issues — ready for code review, debugging, and interviews, where AI won't be tested, your reasoning will. ") - end - - subgraph AI - ai_help_setup(AI can help guide you through setup on your exact device — which could make getting started more accessible for beginners) - ai_suggest_problem(Suggests a problem based on your skill level, interests, and what you're trying to improve) - explain(With that context AI can provide more targeted, efficient help. Don't just paste the code/error, ask AI to explain what a line does, or what an error means — an explanation to help you understand concepts, be clear you do NOT want it to write code, just explain) - end - - - ai_help_setup ~~~ you_help_setup - ai_suggest_problem ~~~ you_have_project - you_help_setup -->|Yes| ai_help_setup - you_help_setup -->|No| you_have_project - ai_help_setup --> you_have_project - - you_have_project -->|No| ai_suggest_problem - - you_have_project -->|Yes| you_attempt - - ai_suggest_problem --> you_attempt - you_attempt <--> you_writeup - you_attempt -->|Done!| you_finish - you_writeup --> |still stuck?| explain - explain --> you_attempt - explain ~~~ you_finish - - %% Edge label text colored red if it leads into the AI lane, green - %% if it leads into the You lane, matching the lane colors. This is - %% linkStyle's "color" property (not background) — the one styling - %% mechanism mermaid applies from inside its own closed shadow - %% root, so it's the only thing that actually reaches the label. - linkStyle 0 color:#a33f3f - linkStyle 1 color:#3f6b52 - linkStyle 3 color:#3f6b52 - linkStyle 4 color:#a33f3f - linkStyle 7 color:#3f6b52 - linkStyle 8 color:#a33f3f - - classDef you fill:#3f6b521f,stroke:#3f6b52,stroke-width:2px,color:#3f6b52 - classDef ai fill:#a33f3f1a,stroke:#a33f3f,stroke-width:2px,color:#a33f3f - class you_writeup,you_help_setup,you_have_project,you_attempt,you_finish you - class explain,ai_help_setup,ai_suggest_problem ai - style You fill:#3f6b521f,stroke:#3f6b52,color:#3f6b52 - style AI fill:#a33f3f1a,stroke:#a33f3f,color:#a33f3f - ``` - -

    FIG: when to use AI while learning to program

    - -??? info "What is Python, and what is this guide?" - - **Readable, and quick to write.** *Python* is a general-purpose language built for code that's easy to read back later — even by someone who didn't write it. No compiling: write a `.py` file, run it directly. - - - **Shows up everywhere** — web backends, data analysis and machine learning, automating repetitive tasks, scientific computing, quick glue scripts. Several of these are covered on this site's [Libraries](#utilities) pages. - - **The skills transfer.** Variables, conditionals, loops, functions, classes — the fundamentals every language shares — read closer to plain English here, so you spend your effort learning to *think* like a programmer instead of fighting a stricter syntax. Once solid, those fundamentals carry over to whatever language you pick up next. - - **Often the fastest language to write *correct* code in** — even though it's not the fastest to *run* — which is why it's such a common first choice for a new project. - - **This guide.** *Python Field Guide* is a free, in-browser reference — most code blocks are editable and runnable directly on the page. - - - **For learners** — self-taught, students in an intro course, or anyone who wants one combined reference to work through start to finish, instead of a scattered pile of search results. diff --git a/docs/javascripts/essentials_toggle.js b/docs/javascripts/essentials_toggle.js new file mode 100644 index 0000000..b672de8 --- /dev/null +++ b/docs/javascripts/essentials_toggle.js @@ -0,0 +1,254 @@ +(function () { + // "Essentials / Advanced" toggle: hides data-advanced content. On a + // homepage row, data-advanced="true" hides that row; the same attribute + // on a content page's own heading (e.g. functions.md's `## Decorators`) + // hides that section plus its TOC entry — marked independently in each + // place, no shared map. data-advanced="card" hides a whole homepage + // card (see extra.css); no content-page equivalent, since it marks a + // linked page rather than a section. Shows on every page; state + // persists via localStorage. + const STORAGE_KEY = "pt-simplify-active"; + + // pt-lib--N sizes each library box for its full card count; hiding cards + // in Essentials mode leaves boxes too wide. Recompute the visible count + // into --pt-lib-span so extra.css can override pt-lib--N while active. + function updateLibrarySpans() { + document.querySelectorAll(".pt-category--wide").forEach(function (box) { + const cards = box.querySelectorAll(".grid.cards > ul > li"); + let visible = 0; + cards.forEach(function (li) { + if (getComputedStyle(li).display !== "none") visible++; + }); + if (visible > 0) box.style.setProperty("--pt-lib-span", Math.min(visible, 4)); + }); + } + + // Hides/restores a heading and its whole section — every sibling up to + // the next heading of the same or higher level. + function setSectionHidden(heading, hidden) { + heading.hidden = hidden; + const level = Number(heading.tagName[1]); + let el = heading.nextElementSibling; + while (el && !(/^H[1-6]$/.test(el.tagName) && Number(el.tagName[1]) <= level)) { + el.hidden = hidden; + el = el.nextElementSibling; + } + + // Many pages wrap a whole ## section in
    for + // its own card-style border/background (raw HTML in the markdown, not + // generated). Hiding the heading and its siblings above leaves that + // wrapper behind as an empty card, so hide it too when the heading is + // its first child. + const wrapper = heading.parentElement; + if (wrapper && wrapper.classList.contains("pfg-section") && wrapper.firstElementChild === heading) { + wrapper.hidden = hidden; + } + } + + // toc.integrate puts headings in the same nav as site links — hide the + // matching
  • too, so there's no dead link to hidden content. + function setTocEntryHidden(id, hidden) { + // href gets rewritten to a full URL after hydration; match by suffix. + document.querySelectorAll('a.md-nav__link[href$="#' + id + '"]').forEach(function (link) { + const item = link.closest(".md-nav__item"); + if (item) item.hidden = hidden; + }); + } + + function applyAdvancedHeadings(active) { + document.querySelectorAll('.md-typeset [data-advanced="true"]').forEach(function (el) { + if (!/^H[1-6]$/.test(el.tagName)) return; + setSectionHidden(el, active); + if (el.id) setTocEntryHidden(el.id, active); + }); + } + + function applyState(container, active) { + document.body.classList.toggle("simplify-active", active); + updateLibrarySpans(); + applyAdvancedHeadings(active); + container.dataset.active = active ? "simplified" : "advanced"; + container.querySelectorAll(".pt-simplify-option").forEach(function (option) { + option.setAttribute("aria-pressed", String(option.dataset.mode === container.dataset.active)); + }); + } + + // Two always-visible options with a sliding highlight, not one button + // whose text changes. + function getOrCreateToggle() { + let container = document.getElementById("pt-simplify-toggle"); + if (container) return container; + + const paletteForm = document.querySelector('[data-md-component="palette"]'); + if (!paletteForm) return null; + + container = document.createElement("div"); + container.id = "pt-simplify-toggle"; + container.className = "pt-simplify-toggle"; + container.setAttribute("role", "group"); + container.setAttribute("aria-label", "Content level"); + + const highlight = document.createElement("span"); + highlight.className = "pt-simplify-highlight"; + highlight.setAttribute("aria-hidden", "true"); + + const essentials = document.createElement("button"); + essentials.type = "button"; + essentials.className = "pt-simplify-option"; + essentials.dataset.mode = "simplified"; + essentials.textContent = "Essentials"; + essentials.title = "Show only what you need to write your first programs"; + + const advanced = document.createElement("button"); + advanced.type = "button"; + advanced.className = "pt-simplify-option"; + advanced.dataset.mode = "advanced"; + advanced.textContent = "Advanced"; + advanced.title = "Show all site content"; + + container.append(highlight, advanced, essentials); + paletteForm.insertAdjacentElement("beforebegin", container); + + container.addEventListener("click", function (event) { + const option = event.target.closest(".pt-simplify-option"); + if (!option) return; + const next = option.dataset.mode === "simplified"; + localStorage.setItem(STORAGE_KEY, String(next)); + applyState(container, next); + }); + + return container; + } + + // Light/dark, same two-option format as above — replaces Material's + // native single-knob switch, whose knob was the only clickable spot. + // Native radios stay in the DOM (hidden); their own JS still applies + // and persists the scheme, we just flip `checked` and dispatch change. + function getOrCreateThemeToggle() { + let container = document.getElementById("pt-theme-toggle"); + if (container) return container; + + const paletteForm = document.querySelector('[data-md-component="palette"]'); + if (!paletteForm) return null; + + const lightRadio = paletteForm.querySelector('input[data-md-color-scheme="default"]'); + const darkRadio = paletteForm.querySelector('input[data-md-color-scheme="slate"]'); + if (!lightRadio || !darkRadio) return null; + + paletteForm.hidden = true; + + container = document.createElement("div"); + container.id = "pt-theme-toggle"; + container.className = "pt-simplify-toggle pt-theme-toggle"; + container.setAttribute("role", "group"); + container.setAttribute("aria-label", "Color theme"); + + const highlight = document.createElement("span"); + highlight.className = "pt-simplify-highlight"; + highlight.setAttribute("aria-hidden", "true"); + + const light = document.createElement("button"); + light.type = "button"; + light.className = "pt-simplify-option pt-theme-option"; + light.dataset.scheme = "default"; + light.title = "Switch to light mode"; + light.setAttribute("aria-label", "Switch to light mode"); + + const dark = document.createElement("button"); + dark.type = "button"; + dark.className = "pt-simplify-option pt-theme-option"; + dark.dataset.scheme = "slate"; + dark.title = "Switch to dark mode"; + dark.setAttribute("aria-label", "Switch to dark mode"); + + container.append(highlight, dark, light); + paletteForm.insertAdjacentElement("beforebegin", container); + + container.addEventListener("click", function (event) { + const option = event.target.closest(".pt-theme-option"); + if (!option) return; + const radio = option.dataset.scheme === "slate" ? darkRadio : lightRadio; + if (!radio.checked) { + radio.checked = true; + radio.dispatchEvent(new Event("change", { bubbles: true })); + } + applyThemeState(container); + }); + + // Sync to whatever scheme Material's own JS actually lands on, not + // just what we clicked. + lightRadio.addEventListener("change", function () { + applyThemeState(container); + }); + darkRadio.addEventListener("change", function () { + applyThemeState(container); + }); + + return container; + } + + function applyThemeState(container) { + const scheme = document.body.getAttribute("data-md-color-scheme"); + container.dataset.active = scheme === "slate" ? "dark" : "light"; + container.querySelectorAll(".pt-theme-option").forEach(function (option) { + option.setAttribute("aria-pressed", String(option.dataset.scheme === scheme)); + }); + } + + // A visible link can point at a hidden section (e.g. collections.md's + // cheat-sheet table links to #tuples while "Tuples" itself is hidden) — + // reveal the target instead of landing on nothing. + // + // Uses hashchange rather than a click listener: a real mouse click on an + // anchor races with, and in Chromium beats, a capturing click handler — + // it worked for a scripted .click() in manual testing but silently + // failed for an actual pointer click. hashchange fires after the + // navigation commits either way. + function revealHashTargetIfHidden() { + if (!location.hash || !document.body.classList.contains("simplify-active")) return; + + const target = document.getElementById(location.hash.slice(1)); + if (!target || !target.hidden) return; + + const container = document.getElementById("pt-simplify-toggle"); + if (!container) return; + localStorage.setItem(STORAGE_KEY, "false"); + applyState(container, false); + } + + function setUpAdvancedLinkRecovery() { + if (window.__ptHashRecoveryBound) return; + window.__ptHashRecoveryBound = true; + window.addEventListener("hashchange", revealHashTargetIfHidden); + } + + function setUpSimplifyToggle() { + const container = getOrCreateToggle(); + if (!container) return; + + setUpAdvancedLinkRecovery(); + + const themeToggle = getOrCreateThemeToggle(); + if (themeToggle) applyThemeState(themeToggle); + + // ?simplified=true/false on a link forces and saves that state, e.g. + // sharing a pre-simplified link. + const override = new URLSearchParams(window.location.search).get("simplified"); + if (override !== null) localStorage.setItem(STORAGE_KEY, override !== "false" ? "true" : "false"); + + applyState(container, localStorage.getItem(STORAGE_KEY) === "true"); + + // Also covers loading a URL whose hash already points at a hidden + // section, not just navigating there via a same-page click. + revealHashTargetIfHidden(); + } + + // navigation.instant swaps page content via JS without a full reload, so + // DOMContentLoaded only fires once. document$ is Material's own + // observable that emits on every page change, instant or not. + if (window.document$) { + window.document$.subscribe(setUpSimplifyToggle); + } else { + document.addEventListener("DOMContentLoaded", setUpSimplifyToggle); + } +})(); diff --git a/docs/libraries/index.md b/docs/libraries/index.md index 3bbb279..9383c3a 100644 --- a/docs/libraries/index.md +++ b/docs/libraries/index.md @@ -11,7 +11,7 @@ Libraries allow us to apply Python to real tasks. These are a few popular ones,
    -
    +
    #### Testing { .pt-homepage-heading }
    @@ -42,6 +42,7 @@ Libraries allow us to apply Python to real tasks. These are a few popular ones, - :material-format-list-group:{ .lg .middle } [__collections__](collections.md) [:material-language-python:](collections.md){ .pt-lib-badge .pt-lib-badge--builtin title="Built-in — included with Python" } + {: data-advanced="card" } Specialized containers: counting items, grouping with defaults, named tuples, fast queues. @@ -159,6 +160,7 @@ Libraries allow us to apply Python to real tasks. These are a few popular ones, - :material-matrix:{ .lg .middle } [__NumPy__](numpy.md) [:material-download-outline:](numpy.md){ .pt-lib-badge .pt-lib-badge--third-party title="Third-party — install separately with pip" } + {: data-advanced="card" } Fast numeric arrays, with math applied to a whole array at once instead of item by item. @@ -171,6 +173,7 @@ Libraries allow us to apply Python to real tasks. These are a few popular ones, - :material-table:{ .lg .middle } [__pandas__](pandas.md) [:material-download-outline:](pandas.md){ .pt-lib-badge .pt-lib-badge--third-party title="Third-party — install separately with pip" } + {: data-advanced="card" } Tabular data: rows and columns, like a spreadsheet, built on top of NumPy. @@ -312,13 +315,14 @@ Libraries allow us to apply Python to real tasks. These are a few popular ones,
    -
    +
    #### Computer vision { .pt-homepage-heading }
    - :material-face-recognition:{ .lg .middle } [__OpenCV__](opencv.md) [:material-download-outline:](opencv.md){ .pt-lib-badge .pt-lib-badge--third-party title="Third-party — install separately with pip" } + {: data-advanced="card" } Real-time image and video analysis, built directly on NumPy arrays: color spaces, edge detection, face detection. diff --git a/docs/oop.md b/docs/oop.md index 33a6442..f6c0f08 100644 --- a/docs/oop.md +++ b/docs/oop.md @@ -231,9 +231,9 @@ ball.describe() # "a 5 ft ball python"
    -## Method decorators +## Method decorators { data-advanced="true" } -Python provides three built-in [decorators](functions.md#decorators) for methods that change how the method is called and add functionality: +Python provides 3 built-in [decorators](functions.md#decorators) for methods that change how the method is called and add functionality: ```python-ref class Snake: @@ -458,7 +458,7 @@ boa.describe() # "a heavy-bodied constrictor" — Boa's version replaces it
    -## Polymorphism +## Polymorphism { data-advanced="true" } **Polymorphism** ("many forms") means the same method or function name behaves differently depending on which object it's called on — so you can call `.describe()` on any snake-like object without needing to know exactly which one it is. diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index 8a09076..c323b8a 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -24,12 +24,19 @@ --pt-heading-h3: #33724C; --pt-ref-keyword: #825E25; --pt-danger: #933331; - --pt-code-bg: #23221E; - --pt-code-border: #35342E; - --pt-code-fg: #F2EDE0; - --pt-code-string: #C9E4D2; - --pt-code-keyword: #E8C9A8; - --pt-code-comment: #97917F; + /* Runnable python blocks share the same light panel look as + .pyodide-reference in light mode (not a dark terminal here — that's + dark-mode-only, see the slate scheme below). */ + --pt-code-bg: var(--pt-panel); + --pt-code-border: var(--pt-hairline); + --pt-code-fg: var(--pt-ink); + --pt-code-string: var(--pt-accent); + --pt-code-keyword: var(--pt-ref-keyword); + --pt-code-comment: var(--pt-text-secondary); + + --pt-shimmer-gold: #D9C48C; + --pt-shimmer-sage: #B9CDAE; + --pt-shimmer-clay: #CBA989; } [data-md-color-scheme="slate"] { @@ -48,10 +55,11 @@ --pt-ref-keyword: #C99B5A; --pt-danger: #CB7671; - /* Same dark terminal panel as the light scheme (not inverted) — the - runnable block should read as dark in dark mode too, matching the - .pyodide-reference block, which already stays dark here since it tracks - --pt-panel. */ + /* Runnable python blocks keep a dedicated dark terminal panel here + (unlike the light scheme above, which now matches --pt-panel) — this + dark look was the original intent and .pyodide-reference already reads + dark in dark mode too since it tracks --pt-panel, so the two still + match visually. */ --pt-code-bg: #23221E; --pt-code-border: #35342E; --pt-code-fg: #F2EDE0; @@ -181,8 +189,29 @@ html:focus-within::-webkit-scrollbar-thumb { background-position: 0 0, 14px 18px, 0% 50%; } +/* Same decorative scale-ring + shimmer treatment, light (default) scheme. + Ring colors swap from dark mode's purple/indigo jewel tones to the site's + own gold/green palette (--pt-ref-keyword, --pt-accent), since purple reads + as an unrelated outlier against cream rather than a deliberate accent the + way it does against slate's near-black background. Shimmer hues likewise + swap to warm gold/sage/clay (--pt-shimmer-*) instead of indigo/purple/green + — colors close in value to --pt-bg so it stays a faint sheen, not a band. */ +[data-md-color-scheme="default"] .md-header { + background-image: + radial-gradient(circle at 100% 50%, transparent 14px, rgba(130, 94, 37, 0.07) 15px, rgba(8, 84, 42, 0.07) 16px, transparent 17px), + radial-gradient(circle at 100% 50%, transparent 14px, rgba(8, 84, 42, 0.07) 15px, rgba(130, 94, 37, 0.07) 16px, transparent 17px), + linear-gradient(120deg, + color-mix(in srgb, var(--pt-shimmer-gold) 18%, transparent), + color-mix(in srgb, var(--pt-shimmer-sage) 18%, transparent), + color-mix(in srgb, var(--pt-shimmer-clay) 18%, transparent), + color-mix(in srgb, var(--pt-shimmer-gold) 18%, transparent)); + background-size: 28px 36px, 28px 36px, 320% 320%; + background-position: 0 0, 14px 18px, 0% 50%; +} + @media (prefers-reduced-motion: no-preference) { - [data-md-color-scheme="slate"] .md-header { + [data-md-color-scheme="slate"] .md-header, + [data-md-color-scheme="default"] .md-header { animation: pt-header-shimmer 22s ease-in-out infinite; } } @@ -235,6 +264,13 @@ html:focus-within::-webkit-scrollbar-thumb { text-decoration: none; } +/* Tab labels aren't prose links — Material wraps each one in an purely + for keyboard/anchor navigation between tabs, not as a clickable reference, + so the link-in-text-block underline doesn't apply here. */ +.md-typeset .tabbed-labels a { + text-decoration: none; +} + .md-nav__link--active, .md-nav__item .md-nav__link--active, .md-nav__item--active > .md-nav__link { @@ -645,6 +681,118 @@ input:checked + .md-consent__settings { margin: 0.3rem 0 0.1rem; } +/* "Essentials / Advanced" toggle (docs/javascripts/essentials_toggle.js) + — both options always visible, sliding highlight behind the active + one, next to the palette toggle. Shows on every page. */ +.pt-simplify-toggle { + position: relative; + display: inline-flex; + align-items: center; + height: 1.2rem; + margin: 0.4rem 0.2rem; + /* currentColor, not a fixed accent — border/highlight/text below track + the theme's own ink/bg shades. */ + border: 0.05rem solid currentColor; + border-radius: 1rem; + overflow: hidden; +} + +.pt-simplify-highlight { + position: absolute; + top: 0; + left: 0; + width: 50%; + height: 100%; + /* Square outer edge gets clipped to the track's own rounding via + overflow: hidden above; only the inner (middle) edge needs rounding. */ + border-radius: 0 1rem 1rem 0; + background-color: currentColor; + transition: transform 0.2s ease; +} + +.pt-simplify-toggle[data-active="simplified"] .pt-simplify-highlight, +.pt-simplify-toggle[data-active="light"] .pt-simplify-highlight { + border-radius: 1rem 0 0 1rem; + transform: translateX(100%); +} + +.pt-simplify-option { + position: relative; + z-index: 1; + flex: 1; + padding: 0 0.5rem; + border: none; + background: none; + color: currentColor; + font-family: inherit; + font-size: 0.6rem; + font-weight: 700; + letter-spacing: 0.02em; + white-space: nowrap; + cursor: pointer; +} + +/* Active option's text sits on the currentColor highlight, so it flips + to the bg color for contrast; inactive stays plain currentColor. */ +.pt-simplify-toggle[data-active="simplified"] .pt-simplify-option[data-mode="simplified"], +.pt-simplify-toggle[data-active="advanced"] .pt-simplify-option[data-mode="advanced"], +.pt-simplify-toggle[data-active="light"] .pt-simplify-option[data-scheme="default"], +.pt-simplify-toggle[data-active="dark"] .pt-simplify-option[data-scheme="slate"] { + color: var(--pt-bg); +} + +/* Several of Material's own rules (.md-header__option, .md-typeset + details, .md-option:checked + label) beat the plain [hidden] { display: + none } UA rule on specificity, so an element we hide via JS (.hidden = + true) can silently stay visible — hit this three times now (the palette + form, admonitions under a hidden heading, the old knob). One global + override instead of chasing each element type. */ +[hidden] { + display: none !important; +} + +/* Light/dark toggle — shares .pt-simplify-toggle/-highlight/-option, but + each option is a fixed choice (sun = light, moon = dark) rather than + one element showing whichever mode is current. Native Material markup + is hidden but stays in the DOM (its own JS still applies the scheme). */ +.pt-theme-option { + padding: 0 0.35rem; +} + +.pt-theme-option::before { + content: ""; + display: block; + width: 0.7rem; + height: 0.7rem; + background-color: currentColor; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-size: contain; + mask-size: contain; + -webkit-mask-position: center; + mask-position: center; +} + +.pt-theme-option[data-scheme="default"]::before { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 7a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5m0 2a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3m0-7 2.39 3.42C13.65 5.15 12.84 5 12 5s-1.65.15-2.39.42zM3.34 7l4.16-.35A7.2 7.2 0 0 0 5.94 8.5c-.44.74-.69 1.5-.83 2.29zm.02 10 1.76-3.77a7.131 7.131 0 0 0 2.38 4.14zM20.65 7l-1.77 3.79a7.02 7.02 0 0 0-2.38-4.15zm-.01 10-4.14.36c.59-.51 1.12-1.14 1.54-1.86.42-.73.69-1.5.83-2.29zM12 22l-2.41-3.44c.74.27 1.55.44 2.41.44.82 0 1.63-.17 2.37-.44z'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 7a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5m0 2a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3m0-7 2.39 3.42C13.65 5.15 12.84 5 12 5s-1.65.15-2.39.42zM3.34 7l4.16-.35A7.2 7.2 0 0 0 5.94 8.5c-.44.74-.69 1.5-.83 2.29zm.02 10 1.76-3.77a7.131 7.131 0 0 0 2.38 4.14zM20.65 7l-1.77 3.79a7.02 7.02 0 0 0-2.38-4.15zm-.01 10-4.14.36c.59-.51 1.12-1.14 1.54-1.86.42-.73.69-1.5.83-2.29zM12 22l-2.41-3.44c.74.27 1.55.44 2.41.44.82 0 1.63-.17 2.37-.44z'/%3E%3C/svg%3E"); +} + +.pt-theme-option[data-scheme="slate"]::before { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m17.75 4.09-2.53 1.94.91 3.06-2.63-1.81-2.63 1.81.91-3.06-2.53-1.94L12.44 4l1.06-3 1.06 3zm3.5 6.91-1.64 1.25.59 1.98-1.7-1.17-1.7 1.17.59-1.98L15.75 11l2.06-.05L18.5 9l.69 1.95zm-2.28 4.95c.83-.08 1.72 1.1 1.19 1.85-.32.45-.66.87-1.08 1.27C15.17 23 8.84 23 4.94 19.07c-3.91-3.9-3.91-10.24 0-14.14.4-.4.82-.76 1.27-1.08.75-.53 1.93.36 1.85 1.19-.27 2.86.69 5.83 2.89 8.02a9.96 9.96 0 0 0 8.02 2.89m-1.64 2.02a12.08 12.08 0 0 1-7.8-3.47c-2.17-2.19-3.33-5-3.49-7.82-2.81 3.14-2.7 7.96.31 10.98 3.02 3.01 7.84 3.12 10.98.31'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m17.75 4.09-2.53 1.94.91 3.06-2.63-1.81-2.63 1.81.91-3.06-2.53-1.94L12.44 4l1.06-3 1.06 3zm3.5 6.91-1.64 1.25.59 1.98-1.7-1.17-1.7 1.17.59-1.98L15.75 11l2.06-.05L18.5 9l.69 1.95zm-2.28 4.95c.83-.08 1.72 1.1 1.19 1.85-.32.45-.66.87-1.08 1.27C15.17 23 8.84 23 4.94 19.07c-3.91-3.9-3.91-10.24 0-14.14.4-.4.82-.76 1.27-1.08.75-.53 1.93.36 1.85 1.19-.27 2.86.69 5.83 2.89 8.02a9.96 9.96 0 0 0 8.02 2.89m-1.64 2.02a12.08 12.08 0 0 1-7.8-3.47c-2.17-2.19-3.33-5-3.49-7.82-2.81 3.14-2.7 7.96.31 10.98 3.02 3.01 7.84 3.12 10.98.31'/%3E%3C/svg%3E"); +} + +.simplify-active [data-advanced] { + display: none; +} + +/* data-advanced="card" hides the entire card, not just the marked paragraph + — walk up from whichever paragraph carries it to the enclosing
  • . */ +.simplify-active .grid.cards > ul > li:has(> p[data-advanced="card"]) { + display: none; +} + .pt-category-grid { display: grid; grid-template-columns: repeat(2, 1fr); @@ -683,6 +831,14 @@ input:checked + .md-consent__settings { .pt-category--wide.pt-lib--2 { grid-column: span 2; } .pt-category--wide.pt-lib--3 { grid-column: span 3; } .pt-category--wide.pt-lib--4 { grid-column: span 4; } + + /* pt-lib--N above is sized for the full card count; Essentials mode + hides some, leaving boxes too wide. essentials_toggle.js recomputes the + visible count into --pt-lib-span, overriding pt-lib--N here (same + specificity, later in source) while active. */ + .simplify-active .pt-category--wide { + grid-column: span var(--pt-lib-span, 4); + } } @media (max-width: 45em) { diff --git a/docs/workspace.md b/docs/workspace.md index 43b05bf..31a77cc 100644 --- a/docs/workspace.md +++ b/docs/workspace.md @@ -147,7 +147,7 @@ That's it! You've written and run your first Python program. From here, you can
    -## Using the terminal *(optional)* +## Using the terminal { data-advanced="true" } The terminal is a text-based way to navigate your computer's files and run programs. @@ -232,7 +232,7 @@ It's good for running Python files that are already finished — either your own
    -## Virtual environments *(optional)* +## Virtual environments { data-advanced="true" } Sometimes you'll want to install [external libraries](./libraries/index.md) for your project. A **virtual environment** keeps each project's installed libraries in their own separate folder instead of installing them onto your computer. @@ -245,7 +245,7 @@ Sometimes you'll want to install [external libraries](./libraries/index.md) for **To setup and run a virtual environment:** -0. [Open the terminal](#using-the-terminal-optional) and navigate to your project folder +0. [Open the terminal](#using-the-terminal) and navigate to your project folder 1. Create a `venv` folder holding a private copy of Python and its libraries. This only needs to happen the first time you run your project. @@ -300,7 +300,7 @@ Sometimes you'll want to install [external libraries](./libraries/index.md) for python script.py # or python3 ``` - No different from [running a file from the terminal](#using-the-terminal-optional) — as + No different from [running a file from the terminal](#using-the-terminal) — as long as the virtual environment is active, `python`/`pip` automatically point at its copy of Python and its libraries instead of your system-wide one. diff --git a/mkdocs.yml b/mkdocs.yml index 57610b5..70837cb 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -133,6 +133,7 @@ extra_javascript: - javascripts/copyright_year.js - javascripts/external_links.js - javascripts/homepage_header_title.js + - javascripts/essentials_toggle.js - javascripts/a11y_patches.js - https://unpkg.com/mermaid@11/dist/mermaid.min.js - javascripts/mermaid_config.js diff --git a/tests/test_accessibility_browser.py b/tests/test_accessibility_browser.py index 6a7fc6c..827b892 100644 --- a/tests/test_accessibility_browser.py +++ b/tests/test_accessibility_browser.py @@ -37,10 +37,10 @@ def _select_scheme(page, scheme): """Flip Material's palette to `scheme` ('default' = light, 'slate' = dark). - Material keeps the palette radio out of the normal visibility flow (the visible - control is a styled sibling label), so Playwright's actionability checks never see - it as clickable. Dispatching the click directly gets the same "input change fires, - Material's own JS reacts" result without depending on which label is visible when. + docs/javascripts/essentials_toggle.js replaces the visible light/dark control with its + own buttons and hides Material's native radio entirely, so Playwright can't click it + as a normal user control. Clicking it directly still fires the same input change that + Material's own JS (and our buttons) rely on to apply and persist the scheme. """ page.evaluate( "(s) => document.querySelector(`input[data-md-color-scheme=\"${s}\"]`).click()", diff --git a/tests/test_accessibility_keyboard.py b/tests/test_accessibility_keyboard.py index cd1b164..8bc21ac 100644 --- a/tests/test_accessibility_keyboard.py +++ b/tests/test_accessibility_keyboard.py @@ -59,25 +59,33 @@ def test_no_positive_tabindex(page, site_url, path): def test_palette_toggle_is_keyboard_reachable(page, site_url): - """The dark/light toggle must be operable without a mouse. Material renders the radios - visually-hidden behind a styled label; this checks they're still real inputs (not - `hidden` / `display:none`) and that Tab actually lands on one.""" + """The dark/light toggle must be operable without a mouse. docs/javascripts/ + essentials_toggle.js replaces Material's native radios+labels with its own + `.pt-theme-option` buttons (a two-segment sun/moon control, matching the + Essentials/Complete toggle) and hides the native form — so this checks the + *replacement* buttons are real, labeled, visible controls and that Tab reaches + one, rather than the native radios (which are now deliberately hidden).""" page.goto(site_url) - inputs = page.evaluate( - """() => [...document.querySelectorAll('input[name=__palette]')].map((i) => ({ - hidden: i.hasAttribute('hidden'), - display: getComputedStyle(i).display, + buttons = page.evaluate( + """() => [...document.querySelectorAll('.pt-theme-option')].map((b) => ({ + hidden: b.hasAttribute('hidden'), + display: getComputedStyle(b).display, + label: b.getAttribute('aria-label') || '', }))""" ) - assert len(inputs) >= 2, "expected at least two palette radios (light + dark)" - assert all(not i["hidden"] and i["display"] != "none" for i in inputs), ( - "a palette radio is hidden from keyboard/AT users entirely" + assert len(buttons) >= 2, "expected at least two theme option buttons (light + dark)" + assert all(not b["hidden"] and b["display"] != "none" for b in buttons), ( + "a theme option button is hidden from keyboard/AT users entirely" ) + assert all(b["label"] for b in buttons), "a theme option button has no accessible name" + for _ in range(25): page.keyboard.press("Tab") - if page.evaluate("() => document.activeElement?.name === '__palette'"): + if page.evaluate( + "() => document.activeElement?.classList.contains('pt-theme-option')" + ): return - raise AssertionError("Tab never reached the palette toggle within 25 stops") + raise AssertionError("Tab never reached the theme toggle within 25 stops") # Interactive things this repo styles itself (as opposed to Material's header/search/palette diff --git a/tests/test_essentials_toggle.py b/tests/test_essentials_toggle.py new file mode 100644 index 0000000..129e242 --- /dev/null +++ b/tests/test_essentials_toggle.py @@ -0,0 +1,151 @@ +"""The "Essentials / Advanced" Simplify toggle +(docs/javascripts/essentials_toggle.js, docs/stylesheets/extra.css). + +Covers: the default (Advanced) state, that ?simplified=true both hides marked +content and carries the state onto a content page's own heading + TOC entry, and the +link-recovery behavior — a page can still show a *link* to a hidden section (e.g. +collections.md's cheat-sheet table links to #tuples even though the "Tuples" heading +below it is hidden in Essentials mode); clicking it should flip back to Advanced and +reveal the target rather than silently doing nothing. + +Browser tier — same setup as test_accessibility_browser.py (`playwright install chromium`). +""" + + +def test_advanced_content_visible_by_default(page, site_url): + page.goto(site_url) + is_simplified = page.evaluate("() => document.body.classList.contains('simplify-active')") + assert not is_simplified, "Simplify should not be active by default" + + # A row-level marker: the "sets" keyword-link row under Collections. + hidden = page.evaluate( + """() => { + const row = document.querySelector('p[data-advanced="true"]'); + return row ? getComputedStyle(row).display === 'none' : null; + }""" + ) + assert hidden is False, "a data-advanced row should be visible when not simplified" + + +def test_simplified_query_param_hides_marked_row(page, site_url): + page.goto(f"{site_url}/?simplified=true") + is_simplified = page.evaluate("() => document.body.classList.contains('simplify-active')") + assert is_simplified, "?simplified=true should activate Simplify mode" + + hidden = page.evaluate( + """() => { + const row = document.querySelector('p[data-advanced="true"]'); + return row ? getComputedStyle(row).display === 'none' : null; + }""" + ) + assert hidden is True, "a data-advanced row should be hidden once simplified" + + +def test_simplified_state_carries_to_content_page_heading_and_toc(page, site_url): + """functions.md's own '## Decorators { data-advanced="true" }' heading (and its + integrated-TOC entry) should hide too — carried over from the homepage's marker via + localStorage, with no need to visit the homepage first in this same test.""" + page.goto(f"{site_url}/functions/?simplified=true") + + result = page.evaluate( + """() => { + const heading = document.getElementById('decorators'); + const tocLink = document.querySelector('a.md-nav__link[href$="#decorators"]'); + const tocItem = tocLink ? tocLink.closest('.md-nav__item') : null; + return { + headingHidden: heading ? heading.hidden : null, + tocItemHidden: tocItem ? tocItem.hidden : null, + }; + }""" + ) + assert result["headingHidden"] is True, "Decorators heading should be hidden" + assert result["tocItemHidden"] is True, "Decorators' TOC entry should be hidden too" + + +def test_admonition_inside_a_hidden_section_is_actually_hidden(page, site_url): + """Regression: Material's `.md-typeset details { display: flow-root }` beats the + plain `[hidden] { display: none }` UA rule on specificity, so setting `.hidden = true` + on an admonition inside a hidden section didn't actually hide it — it stayed on + screen as a bordered box even though its heading was gone. Fixed with a blanket + `[hidden] { display: none !important }` in extra.css.""" + page.goto(f"{site_url}/collections/?simplified=true") + + hidden_and_shown = page.evaluate( + """() => [...document.querySelectorAll('.md-typeset details')] + .filter((d) => d.hidden) + .map((d) => getComputedStyle(d).display !== 'none')""" + ) + assert hidden_and_shown, "expected at least one admonition inside a hidden section" + assert not any(hidden_and_shown), ( + "an admonition has .hidden = true but still computes a visible display" + ) + + +def test_pfg_section_wrapper_is_hidden_with_its_heading(page, site_url): + """Regression: many pages wrap a whole ## section in raw + `
    ` for its own card-style border/background (not + generated — written directly in the markdown). setSectionHidden only hid the + heading and its flow siblings, which sit *inside* that wrapper — the wrapper + itself was never touched, so it stayed on screen as an empty bordered card + once everything inside it was hidden.""" + page.goto(f"{site_url}/collections/?simplified=true") + + result = page.evaluate( + """() => { + const heading = document.getElementById('tuples'); + const wrapper = heading.closest('.pfg-section'); + return { + wrapperFound: !!wrapper, + wrapperHidden: wrapper ? wrapper.hidden : null, + wrapperDisplay: wrapper ? getComputedStyle(wrapper).display : null, + }; + }""" + ) + assert result["wrapperFound"], "expected #tuples to sit inside a .pfg-section wrapper" + assert result["wrapperHidden"] is True, "the .pfg-section wrapper should be hidden too" + assert result["wrapperDisplay"] == "none", "the wrapper is still rendering as an empty card" + + +def test_link_to_hidden_section_recovers_to_advanced(page, site_url): + """collections.md's own cheat-sheet table (near the top) links to #tuples even + while the "Tuples" heading itself is hidden by data-advanced — clicking that visible + link should flip the toggle back to Advanced and reveal the section, rather than + landing on a hidden target and doing nothing.""" + page.goto(f"{site_url}/collections/?simplified=true") + + tuples_link = page.locator('table a[href$="#tuples"]') + assert tuples_link.count() > 0, "expected the cheat-sheet table's #tuples link to exist" + + before = page.evaluate("() => document.getElementById('tuples').hidden") + assert before is True, "Tuples section should start hidden while simplified" + + tuples_link.first.click() + + after = page.evaluate( + """() => ({ + tuplesHidden: document.getElementById('tuples').hidden, + simplifyActive: document.body.classList.contains('simplify-active'), + toggleActive: document.getElementById('pt-simplify-toggle')?.dataset.active, + stored: localStorage.getItem('pt-simplify-active'), + })""" + ) + assert after["tuplesHidden"] is False, "clicking the link should reveal the Tuples section" + assert after["simplifyActive"] is False, "clicking the link should turn Simplify off" + assert after["toggleActive"] == "advanced", "the toggle's own state should flip to Advanced" + assert after["stored"] == "false", "the flip should persist to localStorage" + + +def test_link_recovery_ignores_toc_links_to_visible_sections(page, site_url): + """Sanity check the recovery handler isn't overly broad: clicking an ordinary link to + a section that's already visible shouldn't touch Simplify state at all.""" + page.goto(f"{site_url}/collections/?simplified=true") + + lists_link = page.locator('a.md-nav__link[href$="#lists"]') + assert lists_link.count() > 0 + + lists_link.first.click() + + still_simplified = page.evaluate( + "() => document.body.classList.contains('simplify-active')" + ) + assert still_simplified, "a link to an already-visible section should not flip the toggle"