@telepath-computer/television 0.1.216 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/THIRD-PARTY-NOTICES.txt +336 -0
  2. package/dist/canonical/v1/frozen.json +6 -0
  3. package/dist/canonical/v1/styles.css +7 -2
  4. package/dist/canonical/v2/components.js +1 -0
  5. package/dist/canonical/v2/fonts/Hind-Variable.933e9900.woff2 +0 -0
  6. package/dist/canonical/v2/styles.css +1153 -0
  7. package/dist/cli.cjs +1763 -761
  8. package/dist/onboarding/README.md +16 -14
  9. package/dist/onboarding/business-ops/about.html +4 -3
  10. package/dist/onboarding/business-ops/retention-vs-releases.html +25 -32
  11. package/dist/onboarding/business-ops/revenue-vs-goals.html +26 -31
  12. package/dist/onboarding/productivity/about.html +4 -3
  13. package/dist/onboarding/productivity/company-todos/THIRD-PARTY-NOTICES.txt +29 -0
  14. package/dist/onboarding/productivity/company-todos/index.html +20 -20
  15. package/dist/onboarding/productivity/company-todos/task.css +4 -5
  16. package/dist/onboarding/productivity/company-todos/task.js +1 -1
  17. package/dist/onboarding/productivity/meeting-prep.html +12 -12
  18. package/dist/onboarding/productivity/priorities-today.html +10 -10
  19. package/dist/onboarding/productivity/todays-calendar/calendar.css +1 -1
  20. package/dist/onboarding/productivity/todays-calendar/index.html +12 -14
  21. package/dist/onboarding/research/about.html +4 -3
  22. package/dist/onboarding/research/open-model-research.html +35 -35
  23. package/dist/onboarding/research/todays-news.html +72 -72
  24. package/dist/onboarding/tv-guide/welcome.html +178 -0
  25. package/dist/skills/television/SKILL.md +122 -67
  26. package/dist/skills/television/theming.md +1087 -0
  27. package/dist/skills/tv-calendar/SKILL.md +3 -1
  28. package/dist/skills/tv-calendar/calendar.css +1 -1
  29. package/dist/skills/tv-table/SKILL.md +5 -2
  30. package/dist/skills/tv-tasks/SKILL.md +19 -15
  31. package/dist/skills/tv-tasks/task.css +4 -26
  32. package/dist/skills/tv-tasks/task.js +1 -1
  33. package/dist/themes/README.md +5 -0
  34. package/dist/themes/aquarium/README.md +48 -0
  35. package/dist/themes/aquarium/iframe-background.js +137 -0
  36. package/dist/themes/aquarium/manifest.json +7 -0
  37. package/dist/themes/aquarium/theme.css +46 -0
  38. package/dist/themes/blueprint/README.md +31 -0
  39. package/dist/themes/blueprint/manifest.json +6 -0
  40. package/dist/themes/blueprint/theme.css +115 -0
  41. package/dist/themes/clouds/README.md +15 -0
  42. package/dist/themes/clouds/THIRD-PARTY-NOTICES.txt +81 -0
  43. package/dist/themes/clouds/manifest.json +6 -0
  44. package/dist/themes/clouds/theme.css +62 -0
  45. package/dist/themes/clouds/wallpaper-dark.webp +0 -0
  46. package/dist/themes/clouds/wallpaper.webp +0 -0
  47. package/dist/themes/crt-phosphor/README.md +31 -0
  48. package/dist/themes/crt-phosphor/manifest.json +6 -0
  49. package/dist/themes/crt-phosphor/theme.css +105 -0
  50. package/dist/themes/nord/README.md +9 -0
  51. package/dist/themes/nord/THIRD-PARTY-NOTICES.txt +29 -0
  52. package/dist/themes/nord/manifest.json +5 -0
  53. package/dist/themes/nord/theme.css +28 -0
  54. package/dist/themes/swiss/README.md +5 -0
  55. package/dist/themes/swiss/manifest.json +5 -0
  56. package/dist/themes/swiss/theme.css +34 -0
  57. package/dist/themes/tokyo-night/README.md +17 -0
  58. package/dist/themes/tokyo-night/THIRD-PARTY-NOTICES.txt +235 -0
  59. package/dist/themes/tokyo-night/manifest.json +5 -0
  60. package/dist/themes/tokyo-night/theme.css +31 -0
  61. package/dist/themes/tokyo-night/wallpaper.webp +0 -0
  62. package/dist/views/artifact-missing/index.html +40 -68
  63. package/dist/views/markdown/THIRD-PARTY-NOTICES.txt +26 -0
  64. package/dist/views/markdown/index.html +49 -23
  65. package/dist/web/THIRD-PARTY-NOTICES.txt +26 -0
  66. package/dist/web/assets/artifact-bridge-BEUWXKq7.js +1 -0
  67. package/dist/web/assets/{artifactMissing-BiK_l3Ts.js → artifactMissing-CUs1ZwDx.js} +1 -1
  68. package/dist/web/assets/main-3UimTRLK.css +1 -0
  69. package/dist/web/assets/main-CSSzbG7q.js +704 -0
  70. package/dist/web/assets/{urlUnsupported-RGIrhMDe.js → urlUnsupported-powR79PT.js} +1 -1
  71. package/dist/web/index.html +40 -5
  72. package/dist/web/views/artifact-missing/index.html +40 -68
  73. package/dist/web/views/url-unsupported/index.html +40 -68
  74. package/package.json +1 -1
  75. package/dist/onboarding/tv-guide/welcome/assets/television.svg +0 -71
  76. package/dist/onboarding/tv-guide/welcome/index.html +0 -203
  77. package/dist/skills/tv-theme/SKILL.md +0 -449
  78. package/dist/web/assets/artifact-bridge-3Trk-B2L.js +0 -1
  79. package/dist/web/assets/clouds-CAYIArXj.jpg +0 -0
  80. package/dist/web/assets/main-Blg8fuaV.css +0 -1
  81. package/dist/web/assets/main-V14r9Psb.js +0 -596
@@ -20,11 +20,13 @@ instead of referencing `/skills/tv-calendar/...` URLs.
20
20
  For a Television HTML directory artifact, copy both files into the registered directory next to `index.html` and reference them relatively:
21
21
 
22
22
  ```html
23
- <link rel="stylesheet" href="/canonical/v1/styles.css" />
23
+ <link rel="stylesheet" href="/canonical/v2/styles.css?authoredForAppVersion=<version>" />
24
24
  <link rel="stylesheet" href="./calendar.css" />
25
25
  <script type="module" src="./calendar.js"></script>
26
26
  ```
27
27
 
28
+ For a running server, read the exact release `version` from `tv status`; when working in a Television checkout, read the exact version from the checkout root `package.json`. Copy that exact release version unchanged into `authoredForAppVersion`. A missing version or the `0.0.0` development sentinel does not identify a release, so omit the metadata.
29
+
28
30
  For a standalone single-file HTML artifact, inline the contents of
29
31
  `calendar.css` and `calendar.js` directly into `<style>` and
30
32
  `<script type="module">` tags so the output stays self-contained.
@@ -1 +1 @@
1
- calendar-event{display:contents}calendar-event .event-block{pointer-events:none;z-index:2}calendar-event .event-block-inner{background:var(--event-bg-timed, var(--neutral-100));border-left:3px solid var(--event-border-color, var(--neutral-300));border-radius:4px;box-shadow:inset 0 0 0 .5px var(--neutral-200)}calendar-grid calendar-event .event-block{grid-column:calc(var(--day-index) + 1);grid-row:1 / -1;position:relative;z-index:0}calendar-grid calendar-event .event-block-inner{position:absolute;top:calc(var(--y-start) * 100%);height:calc((var(--y-end) - var(--y-start)) * 100%);left:calc(2px + var(--cascade-depth, 0) * 6px);right:2px;padding:var(--space-4) var(--space-8);overflow:hidden;pointer-events:auto;z-index:calc(1 + var(--cascade-depth, 0))}calendar-allday calendar-event .event-block{grid-column:calc(var(--day-start) + 1) / span var(--day-span);grid-row:var(--lane-index);padding:2px}calendar-allday calendar-event .event-block-inner{background:var(--event-bg-all-day, var(--event-bg-timed, var(--neutral-100)));border-left:var( --event-all-day-border, 3px solid var(--event-border-color, var(--neutral-300)) );padding:1px var(--space-8);display:flex;align-items:center;pointer-events:auto}calendar-event[color=red]{--event-bg-timed: var(--red-100);--event-bg-all-day: var(--red-200);--event-border-color: var(--red-500);--event-all-day-border: none}calendar-event[color=orange]{--event-bg-timed: var(--orange-100);--event-bg-all-day: var(--orange-200);--event-border-color: var(--orange-500);--event-all-day-border: none}calendar-event[color=yellow]{--event-bg-timed: var(--yellow-100);--event-bg-all-day: var(--yellow-200);--event-border-color: var(--yellow-500);--event-all-day-border: none}calendar-event[color=green]{--event-bg-timed: var(--green-100);--event-bg-all-day: var(--green-200);--event-border-color: var(--green-500);--event-all-day-border: none}calendar-event[color=blue]{--event-bg-timed: var(--blue-100);--event-bg-all-day: var(--blue-200);--event-border-color: var(--blue-500);--event-all-day-border: none}calendar-event[color=purple]{--event-bg-timed: var(--purple-100);--event-bg-all-day: var(--purple-200);--event-border-color: var(--purple-500);--event-all-day-border: none}calendar-event h3{margin:0;font-size:var(--text-sm);font-weight:600;color:var(--color-text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}calendar-time-axis{display:contents}calendar-time-axis .hour-label{grid-column:time-left / days-start;grid-row-start:calc(var(--hour-index) + 1);align-self:start;padding:var(--space-2) var(--space-8);font-size:round(calc(var(--text-sm) / var(--text-scale)),1px);color:var(--color-text-muted);text-align:right;white-space:nowrap;transform:translateY(-50%)}calendar-time-axis .hour-label[data-hour="0"]{visibility:hidden;width:0;height:0;padding:0;overflow:hidden}calendar-week{display:grid;grid-template-columns:[time-left] auto [days-start] repeat(var(--day-count, 0),minmax(0,1fr)) [days-end];grid-template-rows:[headers] auto [allday] auto [hours-start] repeat(var(--hour-count, 0),minmax(var(--hour-min-height, 4rem),1fr)) [hours-end];width:100%;height:100%;overflow:auto;background:var(--color-surface);font-family:inherit;color:var(--color-text)}calendar-week>calendar-headers,calendar-week>calendar-allday,calendar-week>calendar-grid{display:grid;grid-column:1 / -1;grid-template-columns:subgrid;min-width:0}calendar-week>calendar-headers{grid-row:headers / allday;position:sticky;top:0;z-index:3;background:var(--color-surface);border-top:.5px solid var(--color-border);border-bottom:1px solid var(--color-border)}calendar-headers>calendar-day{grid-column:calc(var(--day-index) + 1);display:flex;align-items:baseline;gap:var(--space-2);padding:var(--space-12);font-size:var(--text-sm);border-left:.5px solid var(--color-border-muted)}calendar-headers>calendar-day:last-of-type{border-right:.5px solid var(--color-border-muted)}calendar-headers>calendar-day .weekday{text-transform:uppercase;letter-spacing:.04em;color:var(--color-text-muted)}calendar-headers>calendar-day .date-num{color:var(--color-text)}calendar-week>calendar-allday{grid-row:allday / hours-start;grid-template-rows:repeat(var(--allday-lane-count, 1),auto);position:sticky;top:var(--headers-height, 0px);z-index:2;border-bottom:1px solid var(--color-border);background:var(--color-surface)}calendar-allday>calendar-cell{grid-column:calc(var(--day-index) + 1);grid-row:1;border-left:.5px solid var(--color-border-muted)}calendar-allday>calendar-cell:last-of-type{border-right:.5px solid var(--color-border-muted)}calendar-week>calendar-grid{grid-row:hours-start / hours-end;grid-template-rows:subgrid;position:relative}calendar-grid>calendar-column{grid-column:calc(var(--day-index) + 1);grid-row:1 / -1;border-left:.5px solid var(--color-border-muted);background-image:repeating-linear-gradient(to bottom,transparent 0,transparent calc(100% / var(--hour-count) - .5px),var(--color-border-muted) calc(100% / var(--hour-count) - .5px),var(--color-border-muted) calc(100% / var(--hour-count)))}calendar-grid>calendar-column:last-of-type{border-right:.5px solid var(--color-border-muted)}calendar-grid>.start-hour-marker{grid-column:2;grid-row-start:calc(var(--start-hour) + 1);width:0;height:0}calendar-grid>.now-line-faint,calendar-grid>.now-line{grid-row:1 / -1;position:relative;pointer-events:none;z-index:1}calendar-grid>.now-line-faint{grid-column:2 / -1}calendar-grid>.now-line{grid-column:calc(var(--day-index) + 1)}calendar-grid>.now-line-faint:before,calendar-grid>.now-line:before{content:"";position:absolute;left:0;right:0;top:calc(var(--y-fraction) * 100%);height:1px;transform:translateY(-.5px)}calendar-grid>.now-line-faint:before{background:#e7202240}calendar-grid>.now-line:before{background:#e72022}calendar-grid>.now-line:after{content:"";position:absolute;left:0;top:calc(var(--y-fraction) * 100%);width:.5rem;height:.5rem;border-radius:50%;background:#e72022;transform:translate(-50%,-50%)}
1
+ calendar-event{display:contents}calendar-event .event-block{pointer-events:none;z-index:2}calendar-event .event-block-inner{background:var(--event-bg-timed, var(--neutral-100));border-left:3px solid var(--event-border-color, var(--neutral-300));border-radius:4px;box-shadow:inset 0 0 0 .5px var(--neutral-200)}calendar-grid calendar-event .event-block{grid-column:calc(var(--day-index) + 1);grid-row:1 / -1;position:relative;z-index:0}calendar-grid calendar-event .event-block-inner{position:absolute;top:calc(var(--y-start) * 100%);height:calc((var(--y-end) - var(--y-start)) * 100%);left:calc(2px + var(--cascade-depth, 0) * 6px);right:2px;padding:var(--space-4) var(--space-8);overflow:hidden;pointer-events:auto;z-index:calc(1 + var(--cascade-depth, 0))}calendar-allday calendar-event .event-block{grid-column:calc(var(--day-start) + 1) / span var(--day-span);grid-row:var(--lane-index);padding:2px}calendar-allday calendar-event .event-block-inner{background:var(--event-bg-all-day, var(--event-bg-timed, var(--neutral-100)));border-left:var( --event-all-day-border, 3px solid var(--event-border-color, var(--neutral-300)) );padding:1px var(--space-8);display:flex;align-items:center;pointer-events:auto}calendar-event[color=red]{--event-bg-timed: var(--red-100);--event-bg-all-day: var(--red-200);--event-border-color: var(--red-500);--event-all-day-border: none}calendar-event[color=orange]{--event-bg-timed: var(--orange-100);--event-bg-all-day: var(--orange-200);--event-border-color: var(--orange-500);--event-all-day-border: none}calendar-event[color=yellow]{--event-bg-timed: var(--yellow-100);--event-bg-all-day: var(--yellow-200);--event-border-color: var(--yellow-500);--event-all-day-border: none}calendar-event[color=green]{--event-bg-timed: var(--green-100);--event-bg-all-day: var(--green-200);--event-border-color: var(--green-500);--event-all-day-border: none}calendar-event[color=blue]{--event-bg-timed: var(--blue-100);--event-bg-all-day: var(--blue-200);--event-border-color: var(--blue-500);--event-all-day-border: none}calendar-event[color=purple]{--event-bg-timed: var(--purple-100);--event-bg-all-day: var(--purple-200);--event-border-color: var(--purple-500);--event-all-day-border: none}calendar-event h3{margin:0;font-size:var(--text-sm);font-weight:600;color:var(--neutral-900);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}calendar-time-axis{display:contents}calendar-time-axis .hour-label{grid-column:time-left / days-start;grid-row-start:calc(var(--hour-index) + 1);align-self:start;padding:var(--space-2) var(--space-8);font-size:round(calc(var(--text-base) * 10 / 14),1px);color:var(--color-text-muted);text-align:right;white-space:nowrap;transform:translateY(-50%)}calendar-time-axis .hour-label[data-hour="0"]{visibility:hidden;width:0;height:0;padding:0;overflow:hidden}calendar-week{display:grid;grid-template-columns:[time-left] auto [days-start] repeat(var(--day-count, 0),minmax(0,1fr)) [days-end];grid-template-rows:[headers] auto [allday] auto [hours-start] repeat(var(--hour-count, 0),minmax(var(--hour-min-height, 4rem),1fr)) [hours-end];width:100%;height:100%;overflow:auto;background:var(--color-surface);font-family:inherit;color:var(--color-text)}calendar-week>calendar-headers,calendar-week>calendar-allday,calendar-week>calendar-grid{display:grid;grid-column:1 / -1;grid-template-columns:subgrid;min-width:0}calendar-week>calendar-headers{grid-row:headers / allday;position:sticky;top:0;z-index:3;background:var(--color-surface);border-top:.5px solid var(--color-border);border-bottom:1px solid var(--color-border)}calendar-headers>calendar-day{grid-column:calc(var(--day-index) + 1);display:flex;align-items:baseline;gap:var(--space-2);padding:var(--space-12);font-size:var(--text-sm);border-left:.5px solid var(--color-border)}calendar-headers>calendar-day:last-of-type{border-right:.5px solid var(--color-border)}calendar-headers>calendar-day .weekday{text-transform:uppercase;letter-spacing:.04em;color:var(--color-text-muted)}calendar-headers>calendar-day .date-num{color:var(--color-text)}calendar-week>calendar-allday{grid-row:allday / hours-start;grid-template-rows:repeat(var(--allday-lane-count, 1),auto);position:sticky;top:var(--headers-height, 0px);z-index:2;border-bottom:1px solid var(--color-border);background:var(--color-surface)}calendar-allday>calendar-cell{grid-column:calc(var(--day-index) + 1);grid-row:1;border-left:.5px solid var(--color-border)}calendar-allday>calendar-cell:last-of-type{border-right:.5px solid var(--color-border)}calendar-week>calendar-grid{grid-row:hours-start / hours-end;grid-template-rows:subgrid;position:relative}calendar-grid>calendar-column{grid-column:calc(var(--day-index) + 1);grid-row:1 / -1;border-left:.5px solid var(--color-border);background-image:repeating-linear-gradient(to bottom,transparent 0,transparent calc(100% / var(--hour-count) - .5px),var(--color-border) calc(100% / var(--hour-count) - .5px),var(--color-border) calc(100% / var(--hour-count)))}calendar-grid>calendar-column:last-of-type{border-right:.5px solid var(--color-border)}calendar-grid>.start-hour-marker{grid-column:2;grid-row-start:calc(var(--start-hour) + 1);width:0;height:0}calendar-grid>.now-line-faint,calendar-grid>.now-line{grid-row:1 / -1;position:relative;pointer-events:none;z-index:1}calendar-grid>.now-line-faint{grid-column:2 / -1}calendar-grid>.now-line{grid-column:calc(var(--day-index) + 1)}calendar-grid>.now-line-faint:before,calendar-grid>.now-line:before{content:"";position:absolute;left:0;right:0;top:calc(var(--y-fraction) * 100%);height:1px;transform:translateY(-.5px)}calendar-grid>.now-line-faint:before{background:#e7202240}calendar-grid>.now-line:before{background:#e72022}calendar-grid>.now-line:after{content:"";position:absolute;left:0;top:calc(var(--y-fraction) * 100%);width:.5rem;height:.5rem;border-radius:50%;background:#e72022;transform:translate(-50%,-50%)}
@@ -5,7 +5,9 @@ description: Author dense Airtable-style HTML record tables using the shared Tel
5
5
 
6
6
  # Authoring a record table
7
7
 
8
- Know the main `television` skill first. Re-read it only if it is not already in your context or you know it changed. It defines the general Television workflow and the canonical HTML house style. This skill only adds table-specific guidance.
8
+ Know the main `television` skill first. Re-read it only if it is not already in your context or you know it changed. It defines the general Television workflow and the canonical HTML house style (`<link rel="stylesheet" href="/canonical/v2/styles.css?authoredForAppVersion=<version>" />`). This skill only adds table-specific guidance.
9
+
10
+ For a running server, read the exact release `version` from `tv status`; when working in a Television checkout, read the exact version from the checkout root `package.json`. Copy that exact release version unchanged into `authoredForAppVersion`. A missing version or the `0.0.0` development sentinel does not identify a release, so omit the metadata.
9
11
 
10
12
  A dense, Airtable-style table of records. Lots of cells, mixed cell types,
11
13
  structured rows. It should look crisp at a glance and read like real software,
@@ -48,7 +50,8 @@ Use this page structure:
48
50
  </body>
49
51
  ```
50
52
 
51
- The page is white throughout. Hairline borders around the thead and between rows do the visual work.
53
+ Use `--color-surface` for the page background and `--color-text` for its text.
54
+ Hairline borders using `--color-border` around the thead and between rows do the visual work.
52
55
 
53
56
  ## Styling guidance
54
57
 
@@ -22,12 +22,14 @@ For a directory artifact, copy both next to `index.html` and load them with the
22
22
  canonical boilerplate:
23
23
 
24
24
  ```html
25
- <link rel="stylesheet" href="/canonical/v1/styles.css" />
26
- <script type="module" src="/canonical/v1/components.js"></script>
25
+ <link rel="stylesheet" href="/canonical/v2/styles.css?authoredForAppVersion=<version>" />
26
+ <script type="module" src="/canonical/v2/components.js"></script>
27
27
  <link rel="stylesheet" href="./task.css" />
28
28
  <script type="module" src="./task.js"></script>
29
29
  ```
30
30
 
31
+ For a running server, read the exact release `version` from `tv status`; when working in a Television checkout, read the exact version from the checkout root `package.json`. Copy that exact release version unchanged into `authoredForAppVersion`. A missing version or the `0.0.0` development sentinel does not identify a release, so omit the metadata.
32
+
31
33
  For a single-file artifact, inline `task.css`/`task.js` into `<style>` and
32
34
  `<script type="module">` tags instead. `components.js` gives you `<tv-icon>`;
33
35
  `task.js` registers `<tv-task-checkbox>` and `<tv-task-meta-due>`.
@@ -36,6 +38,13 @@ A task list is not a prose document: don't add `body` padding (the house
36
38
  style doc suggests it for prose) — `tv-task-list` and the page header supply
37
39
  their own gutters. Leave the body at zero.
38
40
 
41
+ Television manages light and dark appearance. Live canonical v2 supplies the
42
+ selected theme and shared defaults; the task kit inherits its surface, text,
43
+ muted-text, and border roles. Author no `data-theme` marker or separate dark
44
+ palette. Task colors use existing semantics: primary for checked checkboxes,
45
+ `--color-primary-text` for their marks, `--color-alert` for due today and the
46
+ default highlight, and `--color-danger` for overdue.
47
+
39
48
  ## Markup
40
49
 
41
50
  Author this shape — a page header, then a list of sections and rows:
@@ -103,8 +112,8 @@ actually in the section, and skip it when it adds nothing.
103
112
  One task row — a `<tv-task-checkbox>` followed by a `<tv-task-body>`. Give it a
104
113
  plain `id` if you'll wire interactivity. One per task.
105
114
 
106
- Add `highlighted` to emphasize a row: a soft wash, amber by default. The hue
107
- is one knob — `--task-highlight` — settable inline for a single row (`<tv-task highlighted style="--task-highlight: #7C5CFF">`)
115
+ Add `highlighted` to emphasize a row: a soft wash of the shared alert color by default. The hue
116
+ is one knob — `--task-highlight` — settable inline for a single row (`<tv-task highlighted style="--task-highlight: var(--color-primary)">`)
108
117
  or on an ancestor to retint every highlight in the list. Highlight only with
109
118
  a good reason: the user asked for it, or the emphasis clearly benefits them
110
119
  (the one row they should see first). At more than one or two per list the
@@ -137,7 +146,7 @@ it.
137
146
 
138
147
  ### `<tv-task-meta-due>`
139
148
  A due date: `<tv-task-meta-due date="YYYY-MM-DD">`. Give it only the machine
140
- date — it computes urgency (today → amber, overdue → red, upcoming → muted),
149
+ date — it computes urgency (today → alert, overdue → danger, upcoming → muted),
141
150
  writes the human label ("Today", "Tomorrow", "Jun 20"), and draws the calendar
142
151
  glyph. Never hand-format the date or set a color. At most one per task.
143
152
 
@@ -212,21 +221,16 @@ An overdue date and multiple tags — same markup, the component colors it:
212
221
  A highlighted row, retinted to match the artifact's accent:
213
222
 
214
223
  ```html
215
- <tv-task highlighted style="--task-highlight: #7C5CFF">
224
+ <tv-task highlighted style="--task-highlight: var(--color-primary)">
216
225
  <tv-task-checkbox></tv-task-checkbox>
217
226
  <tv-task-body><tv-task-title>Sign the lease by Friday</tv-task-title></tv-task-body>
218
227
  </tv-task>
219
228
  ```
220
229
 
221
- Dark mode — set `data-theme="dark"` on `<html>` (the page header and body
222
- background live outside the list, so scoping dark to the list would leave
223
- them light):
224
-
225
- ```html
226
- <html data-theme="dark">
227
- …
228
- </html>
229
- ```
230
+ The kit retains `--task-due-today`, `--task-highlight`, and `--task-meta-text`
231
+ for deliberate artifact customization. Prefer shared semantic tokens for their
232
+ values. Their defaults follow alert, the due-today color, and shared muted text
233
+ at 85% opacity, respectively; the kit does not redefine canonical color tokens.
230
234
 
231
235
  ## Guidelines
232
236
 
@@ -9,18 +9,11 @@
9
9
  */
10
10
 
11
11
  :root {
12
- /* The amber for a task due today. Not a canonical token, so it gets one
13
- named source here rather than being sprinkled as a literal. */
14
- --task-due-today: #EA880B;
12
+ /* Task accents follow the active canonical/theme semantic roles. */
13
+ --task-due-today: var(--color-alert);
15
14
  /* Highlight hue — the highlighted row's wash derives from this one knob.
16
15
  Set it on any ancestor (or the row itself) to retint. */
17
16
  --task-highlight: var(--task-due-today);
18
- /* Skill-local muted: a shade darker than the canonical --color-text-muted
19
- (neutral-500, L 0.553) so notes hold their own against the darker meta
20
- treatment below. Declared at :root, so it recolors ALL muted text on the
21
- page (canonical components included) — deliberate for a single-skill
22
- artifact; revisit if skills ever share a page. */
23
- --color-text-muted: oklch(0.52 0 0);
24
17
  /* The meta line recedes a step behind the note: 85% of muted. */
25
18
  --task-meta-text: color-mix(in srgb, var(--color-text-muted) 85%, transparent);
26
19
  }
@@ -40,7 +33,7 @@ tv-task-body {
40
33
 
41
34
  tv-task-title {
42
35
  display: block;
43
- font-weight: var(--font-weight-body);
36
+ font-weight: var(--font-weight-normal);
44
37
  line-height: 1.35;
45
38
  }
46
39
  /* Completed — tracks the checkbox's reflected host attribute live, not a
@@ -168,7 +161,7 @@ tv-task-section > header h2 {
168
161
  tv-task-count {
169
162
  color: var(--task-meta-text);
170
163
  font-size: var(--text-sm);
171
- font-weight: var(--font-weight-body);
164
+ font-weight: var(--font-weight-normal);
172
165
  }
173
166
 
174
167
  /* Empty state — shown when a section or list has no tasks. */
@@ -186,18 +179,3 @@ tv-task-placeholder {
186
179
  tv-task[highlighted] {
187
180
  background: color-mix(in srgb, var(--task-highlight) 10%, transparent);
188
181
  }
189
-
190
- /* ---- Dark mode ----
191
- The canonical theme is light-only, so the skill carries its own dark token
192
- set, applied when the document has data-theme="dark". */
193
- [data-theme="dark"] {
194
- --color-surface: var(--neutral-800);
195
- --color-surface-muted: var(--neutral-900);
196
- --color-text: var(--neutral-50);
197
- --color-text-muted: var(--neutral-400);
198
- /* Re-derive: custom properties resolve where declared, so without this the
199
- meta tone would keep the light-mode muted it was mixed from. */
200
- --task-meta-text: color-mix(in srgb, var(--color-text-muted) 85%, transparent);
201
- --color-border: rgba(255, 255, 255, 0.14);
202
- color-scheme: dark;
203
- }
@@ -157,7 +157,7 @@ const CHECKBOX_STYLES = `
157
157
  content: "";
158
158
  width: 4px;
159
159
  height: 7px;
160
- border: solid white;
160
+ border: solid var(--color-primary-text);
161
161
  border-width: 0 1.5px 1.5px 0;
162
162
  opacity: 0;
163
163
  transform: translateY(-0.5px) rotate(45deg) scale(0.75);
@@ -0,0 +1,5 @@
1
+ # Bundled themes
2
+
3
+ Each immediate child directory is an installed-theme package with `manifest.json`, `theme.css`, and any relative assets it needs. The server build validates this tree and copies it into the server and CLI distributions.
4
+
5
+ A serving boot copies each package into the user themes directory once. The installed folder then belongs to the user. The null theme is labelled `None` and has no package in this tree. Clouds is the default theme selected on a new installation.
@@ -0,0 +1,48 @@
1
+ # Aquarium
2
+
3
+ Authored by an agent (Claude) on 2026-09-17 as part of a batch of "fun themes" testing the theme system. Covers "background iframe" and "light and dark modes".
4
+
5
+ ## Intent
6
+
7
+ A fish tank behind the interface. Light appearance: sunlit lagoon, colourful fish, sun shafts. Dark appearance: deep sea, bioluminescent fish outlines, drifting jellyfish and plankton. Interface controls are frosted glass so the tank shows through without hurting legibility.
8
+
9
+ ## Why a script
10
+
11
+ Fish that flee the pointer and bubbles that burst where the user presses cannot be done in CSS. No application DOM access is needed, so the effect lives in the sandboxed background frame (`iframe-background.js`), not `main.js`.
12
+
13
+ ## How it works
14
+
15
+ - `manifest.json` declares `enableIframeBackgroundJS: true`.
16
+ - The wallpaper paints on `.app-main`, which is in front of the background frame. `theme.css` therefore sets `--app-wallpaper: transparent` — but only under `:root:has(#theme-iframe-background)`. Without the frame (script gate closed, loading) the CSS water gradient is the fallback.
17
+ - The script draws the water itself on a full-viewport canvas.
18
+ - Appearance: read once from the frame root's `data-theme` (see "Frame appearance and transparency" below).
19
+ - Pointer: accepts only `event.source === parent` and the five documented `television-theme-pointer-*` types. Move → fish within 150px steer away. Down → bubble burst + nearby fish startle. Cancel → stop fleeing.
20
+
21
+ ## Resource cost
22
+
23
+ One 2D canvas, capped at 30fps and devicePixelRatio 1.5; 22 fish, ≤90 bubbles, 5 jellyfish, 70 plankton points. Skips frames while `document.hidden`. Under `prefers-reduced-motion: reduce` it draws a single still frame and redraws only on resize. `shadowBlur` (dark mode glow) is the most expensive part; reduce fish count first if it ever matters.
24
+
25
+ ## Selector rules
26
+
27
+ - `:root:has(#theme-iframe-background)` — wallpaper fallback switch described above.
28
+ - `.sidebar { backdrop-filter }` with an 82%-opaque `--sidebar-background` lets water colour tint the sidebar.
29
+
30
+ ## Files
31
+
32
+ `theme.css`, `manifest.json`, `iframe-background.js`. No assets.
33
+
34
+ ## Maintenance
35
+
36
+ Every save in this folder destroys and recreates the frame, so fish positions reset — expected. Manifest changes need a registry refresh (`tv set-theme aquarium` does one).
37
+
38
+ ## Frame appearance and transparency
39
+
40
+ Television generates the frame document. It stamps `data-theme="light"` or `"dark"` on the document's root, supplies full size, zero margin, hidden overflow and a transparent ground, and keeps the document's `color-scheme` matched to the frame element. That match is what keeps the frame transparent. Television recreates the frame and reruns the script when appearance changes.
41
+
42
+ So the scripts here read `data-theme` from the root once at startup when they need it, install no appearance listener, and do not style the root element at all. Never set `color-scheme` on the frame document's root: a mismatch with the frame element makes the frame opaque, and an opaque overlay frame hides the whole application.
43
+
44
+ History: 1.0.0 predated this contract and, in dark appearance, rendered the frame as a solid white sheet. 1.0.1 worked around it from inside the script. 1.0.2 removes the workaround and follows the contract above.
45
+
46
+ ## Manifest colour scheme
47
+
48
+ `"colorScheme": "light dark"`: the theme adapts and follows the Light / Dark / System preference through its `[data-theme]` blocks. The theme sets no CSS `color-scheme`; the manifest is the only place the scheme is stated.
@@ -0,0 +1,137 @@
1
+ /* Aquarium background. Runs in Television's sandboxed background frame: no app DOM access, input arrives only as pointer messages. */
2
+ (() => {
3
+ "use strict";
4
+
5
+ const POINTER = {
6
+ move: "television-theme-pointer-move",
7
+ down: "television-theme-pointer-down",
8
+ up: "television-theme-pointer-up",
9
+ cancel: "television-theme-pointer-cancel",
10
+ click: "television-theme-pointer-click",
11
+ };
12
+ const FRAME_MS = 1000 / 30; // 30fps is plenty for drifting fish
13
+ const rand = (a, b) => a + Math.random() * (b - a);
14
+
15
+ function start() {
16
+ // Television's frame document already supplies full size, zero margin, hidden overflow, a transparent ground and the
17
+ // matching color-scheme. Leave the root element's styles alone: changing color-scheme there turns the frame opaque.
18
+ const canvas = document.createElement("canvas");
19
+ canvas.style.cssText = "display:block;width:100vw;height:100vh";
20
+ document.body.appendChild(canvas);
21
+ const ctx = canvas.getContext("2d");
22
+
23
+ // Television stamps the effective appearance on this document's root and recreates the frame when it changes,
24
+ // so one read at startup is enough and no listener is needed.
25
+ const dark = document.documentElement.getAttribute("data-theme") === "dark";
26
+ const still = matchMedia("(prefers-reduced-motion: reduce)");
27
+ let W = 0, H = 0, dpr = 1;
28
+ const pointer = { x: -9999, y: -9999, active: false };
29
+
30
+ function resize() {
31
+ dpr = Math.min(devicePixelRatio || 1, 1.5);
32
+ W = innerWidth; H = innerHeight;
33
+ canvas.width = Math.max(1, Math.round(W * dpr));
34
+ canvas.height = Math.max(1, Math.round(H * dpr));
35
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
36
+ }
37
+ addEventListener("resize", resize);
38
+ resize();
39
+
40
+ const DAY_FISH = ["#ff7a59", "#ffc145", "#ff5d8f", "#f4f1de", "#7bdff2", "#b388eb"];
41
+ const fish = Array.from({ length: 22 }, (_, i) => ({
42
+ x: rand(0, 1600), y: rand(60, 800), vx: rand(-1, 1) || 0.5, vy: 0,
43
+ size: rand(9, 22), hue: i % DAY_FISH.length, phase: rand(0, 6.28), speed: rand(0.5, 1.3), depth: rand(0.55, 1),
44
+ }));
45
+ const bubbles = Array.from({ length: 34 }, () => newBubble(true));
46
+ const jellies = Array.from({ length: 5 }, () => ({ x: rand(0, 1600), y: rand(0, 900), r: rand(16, 34), phase: rand(0, 6.28), hue: rand(170, 320) }));
47
+ const plankton = Array.from({ length: 70 }, () => ({ x: rand(0, 1600), y: rand(0, 900), p: rand(0, 6.28) }));
48
+
49
+ function newBubble(anywhere, x, y) {
50
+ return { x: x ?? rand(0, Math.max(W, 800)), y: y ?? (anywhere ? rand(0, Math.max(H, 600)) : Math.max(H, 600) + 10), r: rand(1.5, 5.5), vy: rand(0.4, 1.4), wob: rand(0, 6.28) };
51
+ }
52
+
53
+ addEventListener("message", (event) => {
54
+ if (event.source !== parent) return;
55
+ const d = event.data;
56
+ if (!d || typeof d.type !== "string") return;
57
+ if (d.type === POINTER.move) { pointer.x = d.clientX; pointer.y = d.clientY; pointer.active = true; }
58
+ else if (d.type === POINTER.down) {
59
+ pointer.x = d.clientX; pointer.y = d.clientY;
60
+ for (let i = 0; i < 9 && bubbles.length < 90; i++) bubbles.push(newBubble(false, d.clientX + rand(-10, 10), d.clientY + rand(-6, 6)));
61
+ for (const f of fish) { // a tap on the glass startles nearby fish
62
+ const dx = f.x - d.clientX, dy = f.y - d.clientY, dist = Math.hypot(dx, dy) || 1;
63
+ if (dist < 260) { f.vx += (dx / dist) * 5; f.vy += (dy / dist) * 3; }
64
+ }
65
+ } else if (d.type === POINTER.cancel) pointer.active = false;
66
+ });
67
+
68
+ function drawWater(dark, t) {
69
+ const g = ctx.createLinearGradient(0, 0, 0, H);
70
+ if (dark) { g.addColorStop(0, "#0b2545"); g.addColorStop(0.6, "#06142e"); g.addColorStop(1, "#02060f"); }
71
+ else { g.addColorStop(0, "#9be7f0"); g.addColorStop(0.55, "#3aa7d6"); g.addColorStop(1, "#1b5fa8"); }
72
+ ctx.fillStyle = g; ctx.fillRect(0, 0, W, H);
73
+ if (!dark) { // sun shafts
74
+ ctx.save(); ctx.globalCompositeOperation = "lighter";
75
+ for (let i = 0; i < 5; i++) {
76
+ const x = (W * (i + 0.5)) / 5 + Math.sin(t / 4000 + i) * 60, w = 50 + 25 * Math.sin(t / 3000 + i * 2);
77
+ const rg = ctx.createLinearGradient(0, 0, 0, H * 0.85);
78
+ rg.addColorStop(0, "rgba(255,255,230,0.16)"); rg.addColorStop(1, "rgba(255,255,230,0)");
79
+ ctx.fillStyle = rg; ctx.beginPath(); ctx.moveTo(x - w / 2, 0); ctx.lineTo(x + w / 2, 0); ctx.lineTo(x + w * 1.6 - 120, H * 0.85); ctx.lineTo(x - w * 1.6 - 120, H * 0.85); ctx.closePath(); ctx.fill();
80
+ }
81
+ ctx.restore();
82
+ }
83
+ }
84
+
85
+ function drawFish(f, dark, t) {
86
+ const dir = f.vx >= 0 ? 1 : -1, s = f.size * f.depth, wag = Math.sin(t / 110 * f.speed + f.phase) * 0.45;
87
+ ctx.save(); ctx.translate(f.x, f.y); ctx.scale(dir, 1); ctx.rotate(Math.max(-0.4, Math.min(0.4, f.vy * 0.25)));
88
+ ctx.globalAlpha = 0.45 + 0.55 * f.depth;
89
+ if (dark) { ctx.fillStyle = "rgba(10,25,50,0.9)"; ctx.strokeStyle = `hsla(${170 + f.hue * 25},90%,65%,0.85)`; ctx.lineWidth = 1.2; ctx.shadowColor = ctx.strokeStyle; ctx.shadowBlur = 8; }
90
+ else { ctx.fillStyle = DAY_FISH[f.hue]; }
91
+ ctx.beginPath(); ctx.ellipse(0, 0, s, s * 0.48, 0, 0, 6.2832); ctx.fill(); if (dark) ctx.stroke();
92
+ ctx.beginPath(); ctx.moveTo(-s * 0.85, 0); ctx.lineTo(-s * 1.7, -s * 0.55 + wag * s); ctx.lineTo(-s * 1.7, s * 0.55 + wag * s); ctx.closePath(); ctx.fill(); if (dark) ctx.stroke();
93
+ ctx.shadowBlur = 0; ctx.fillStyle = dark ? "rgba(200,255,250,0.9)" : "rgba(20,30,50,0.85)";
94
+ ctx.beginPath(); ctx.arc(s * 0.55, -s * 0.1, Math.max(1, s * 0.09), 0, 6.2832); ctx.fill();
95
+ ctx.restore();
96
+ }
97
+
98
+ function drawJelly(j, t) {
99
+ const pulse = 1 + 0.12 * Math.sin(t / 700 + j.phase);
100
+ ctx.save(); ctx.translate(j.x, j.y); ctx.globalAlpha = 0.55;
101
+ ctx.shadowColor = `hsla(${j.hue},90%,70%,0.9)`; ctx.shadowBlur = 22; ctx.fillStyle = `hsla(${j.hue},85%,72%,0.5)`;
102
+ ctx.beginPath(); ctx.ellipse(0, 0, j.r * pulse, j.r * 0.75 / pulse, 0, Math.PI, 0); ctx.closePath(); ctx.fill();
103
+ ctx.shadowBlur = 0; ctx.strokeStyle = `hsla(${j.hue},85%,78%,0.45)`; ctx.lineWidth = 1.2;
104
+ for (let k = -2; k <= 2; k++) { ctx.beginPath(); ctx.moveTo(k * j.r * 0.35, 0); for (let y = 4; y < j.r * 2.2; y += 4) ctx.lineTo(k * j.r * 0.35 + Math.sin(t / 400 + y / 9 + k + j.phase) * 4, y); ctx.stroke(); }
105
+ ctx.restore();
106
+ }
107
+
108
+ function step(t) {
109
+ for (const f of fish) {
110
+ f.vx += Math.cos(t / 2300 * f.speed + f.phase) * 0.01; f.vy += Math.sin(t / 1700 + f.phase) * 0.012;
111
+ if (pointer.active) { const dx = f.x - pointer.x, dy = f.y - pointer.y, dist = Math.hypot(dx, dy) || 1; if (dist < 150) { const k = (150 - dist) / 150; f.vx += (dx / dist) * k * 0.9; f.vy += (dy / dist) * k * 0.6; } }
112
+ const cruise = f.speed * (f.vx >= 0 ? 1 : -1); f.vx += (cruise - f.vx) * 0.02; f.vy *= 0.95;
113
+ f.x += f.vx * f.depth; f.y += f.vy * f.depth;
114
+ if (f.x < -50) f.x = W + 40; else if (f.x > W + 50) f.x = -40;
115
+ if (f.y < 40) f.vy += 0.05; else if (f.y > H - 40) f.vy -= 0.05;
116
+ }
117
+ for (let i = bubbles.length - 1; i >= 0; i--) { const b = bubbles[i]; b.y -= b.vy; b.wob += 0.04; b.x += Math.sin(b.wob) * 0.35; if (b.y < -10) { if (bubbles.length > 34) bubbles.splice(i, 1); else bubbles[i] = newBubble(false); } }
118
+ for (const j of jellies) { j.y -= 0.18 + 0.12 * Math.sin(t / 700 + j.phase); j.x += Math.sin(t / 2600 + j.phase) * 0.2; if (j.y < -80) { j.y = H + 80; j.x = rand(0, W); } }
119
+
120
+ drawWater(dark, t);
121
+ if (dark) { ctx.fillStyle = "rgba(160,255,240,0.55)"; for (const p of plankton) { const a = 0.5 + 0.5 * Math.sin(t / 900 + p.p); ctx.globalAlpha = a * 0.6; ctx.fillRect(p.x % Math.max(W, 1), (p.y + t / 140) % Math.max(H, 1), 1.5, 1.5); } ctx.globalAlpha = 1; for (const j of jellies) drawJelly(j, t); }
122
+ fish.sort((a, b) => a.depth - b.depth); for (const f of fish) drawFish(f, dark, t);
123
+ ctx.lineWidth = 1; for (const b of bubbles) { ctx.strokeStyle = dark ? "rgba(170,230,255,0.35)" : "rgba(255,255,255,0.7)"; ctx.fillStyle = dark ? "rgba(170,230,255,0.06)" : "rgba(255,255,255,0.18)"; ctx.beginPath(); ctx.arc(b.x, b.y, b.r, 0, 6.2832); ctx.fill(); ctx.stroke(); }
124
+ }
125
+
126
+ let last = 0;
127
+ function loop(t) {
128
+ requestAnimationFrame(loop);
129
+ if (document.hidden || t - last < FRAME_MS) return;
130
+ last = t; step(t);
131
+ }
132
+ if (still.matches) { step(0); addEventListener("resize", () => step(0)); }
133
+ else requestAnimationFrame(loop);
134
+ }
135
+
136
+ if (document.body) start(); else addEventListener("DOMContentLoaded", start, { once: true });
137
+ })();
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "Aquarium",
3
+ "version": "1.0.2",
4
+ "colorScheme": "light dark",
5
+ "authoredForAppVersion": "1.3.1",
6
+ "enableIframeBackgroundJS": true
7
+ }
@@ -0,0 +1,46 @@
1
+ /* Aquarium — a living tank behind the interface (iframe-background.js). Lagoon by day, deep sea by night. */
2
+ :root {
3
+ --accent: oklch(0.72 0.15 200);
4
+ --neutral: oklch(0.556 0.03 225);
5
+ --control-radius: 10px;
6
+ --panel-radius: 14px;
7
+ --color-link: var(--accent-600);
8
+ }
9
+
10
+ [data-theme="dark"] {
11
+ --accent: oklch(0.8 0.14 190);
12
+ --color-surface: oklch(0.22 0.04 235);
13
+ --color-surface-muted: oklch(0.18 0.04 238);
14
+ --color-link: var(--accent-300);
15
+ }
16
+
17
+ :root[data-television-document="app"] {
18
+ --frame-radius: 16px;
19
+ --tab-radius: var(--radius-pill);
20
+ --channel-radius: 10px;
21
+ /* Tank glass: frosted controls so the fish stay visible but labels stay readable. */
22
+ --wallpaper-overlay-background: var(--tint-surface);
23
+ --wallpaper-overlay-blur: 12px;
24
+ --wallpaper-overlay-border: 1px solid oklch(1 0 0 / 25%);
25
+ --page-inactive-opacity: 0.7;
26
+ /* The wallpaper is a water gradient that also serves when the script is off or still loading… */
27
+ --app-wallpaper: linear-gradient(to bottom, oklch(0.86 0.09 200), oklch(0.62 0.13 225) 60%, oklch(0.45 0.12 245));
28
+ --wallpaper-overlay-text-color: oklch(0.25 0.06 240);
29
+ --sidebar-background: oklch(from var(--color-surface-muted) l c h / 82%);
30
+ }
31
+
32
+ :root[data-television-document="app"][data-theme="dark"] {
33
+ --app-wallpaper: linear-gradient(to bottom, oklch(0.26 0.08 245), oklch(0.15 0.06 255) 60%, oklch(0.08 0.03 260));
34
+ --wallpaper-overlay-text-color: oklch(0.93 0.03 200);
35
+ --wallpaper-overlay-border: 1px solid oklch(1 0 0 / 12%);
36
+ }
37
+
38
+ /* …and becomes transparent once the background frame exists, because the wallpaper paints on .app-main in front of the frame and would hide it. */
39
+ :root[data-television-document="app"]:has(#theme-iframe-background) {
40
+ --app-wallpaper: transparent;
41
+ }
42
+
43
+ /* Let the water show faintly through the sidebar. */
44
+ :root[data-television-document="app"] .sidebar {
45
+ backdrop-filter: blur(14px);
46
+ }
@@ -0,0 +1,31 @@
1
+ # Blueprint
2
+
3
+ Authored by an agent (Claude) on 2026-09-17 as part of a batch of "fun themes" testing the theme system. Covers the brief's "single-mode theme" and "creative CSS" cases.
4
+
5
+ ## Intent
6
+
7
+ A cyanotype engineering drawing. One fixed look with no `data-theme` blocks. The manifest declares `"colorScheme": "dark"`, so Television applies the dark appearance wherever this theme is active, whatever the appearance preference says. The theme sets no CSS `color-scheme` anywhere; Television supplies it. White-line edges, square corners, monospace lettering, a drafting grid.
8
+
9
+ ## Choices
10
+
11
+ - Role tokens (`--color-surface`, `--color-text`, …) are stated at `:root` so they beat both foundation mode tables.
12
+ - `--alpha-active: 12%` is restated in `theme.css`. It dates from when a fixed look could run under light appearance; with `colorScheme: "dark"` the foundation's dark table always applies, so the restatement is now redundant but harmless. Hue slots (`--red`, …) are *not* restated: in light appearance they keep the slightly more saturated light-table values. Acceptable.
13
+ - Shadows are replaced: a blueprint has no depth. Panels get a 4px surface-coloured halo (`--shadow-md`) so popovers separate from the grid.
14
+ - `--font-sans` is monospace and unprefixed, so live-canonical artifacts are lettered the same way. Intentional cross-document reach.
15
+
16
+ ## Selector rules (all app-scoped)
17
+
18
+ - **Grid wallpaper** — four `linear-gradient` layers in `--app-wallpaper`; no image asset.
19
+ - **Part numbers** — `counter-reset` on `.sidebar-body`, `counter-increment` on `.channel-row`, shown in `.channel::before`. Numbering runs continuously through Pinned and Recent. Pure decoration; the DOM is untouched.
20
+ - **Selection callout** — selected channel is a dashed cyan outline with cyan text (`--channel-background-selected: transparent`), not a fill. Outline is on `.channel`, separate from the keyboard focus ring.
21
+ - **Uppercase lettering** — `.tab-label`, `.channel-group-label`, `.artifact-title`.
22
+ - **Dimension line** — `.page[selected]::before`, positioned in the gap above the frame. Relies on `.page` being a positioned, unclipped box; if a release changes that, the mark simply disappears or misplaces — delete the rule.
23
+ - **Title block** — `#foreground-overlay::after`, bottom-left, width derived from `--sidebar-width`. It sits above the interface without taking input. With a very long channel list it overlaps the last rows (85% opaque); accepted.
24
+
25
+ ## Files
26
+
27
+ `theme.css`, `manifest.json`. No assets, no JavaScript.
28
+
29
+ ## Maintenance
30
+
31
+ `.channel::before`, `.page[selected]::before` and `#foreground-overlay::after` were confirmed unused by Television 1.3.1 (computed `content: none`) before being claimed. Re-check on upgrade.
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "Blueprint",
3
+ "version": "1.0.0",
4
+ "colorScheme": "dark",
5
+ "authoredForAppVersion": "1.3.1"
6
+ }
@@ -0,0 +1,115 @@
1
+ /* Blueprint — a fixed cyanotype look: one appearance, stated at :root. */
2
+ :root {
3
+ --neutral: oklch(0.556 0.09 255);
4
+ --accent: oklch(0.86 0.12 205); /* pale cyan pencil */
5
+ --color-surface: oklch(0.3 0.11 258);
6
+ --color-surface-muted: oklch(0.26 0.1 258);
7
+ --color-text: oklch(0.95 0.025 225);
8
+ --color-text-muted: oklch(0.78 0.06 235);
9
+ --color-text-reversed: oklch(0.22 0.09 258);
10
+ --color-border: oklch(from var(--color-text) l c h / 35%);
11
+ --color-link: var(--accent);
12
+ /* The foundation's dark table only applies in dark appearance; restate its press strength for the fixed look. */
13
+ --alpha-active: 12%;
14
+
15
+ /* Technical lettering everywhere, artifacts included. */
16
+ --font-sans: ui-monospace, "SF Mono", SFMono-Regular, Menlo, "DejaVu Sans Mono", monospace;
17
+
18
+ /* Drafted with a ruler: no curves, white-line edges. */
19
+ --control-radius: 0px;
20
+ --panel-radius: 0px;
21
+ --surface-border-color: oklch(from var(--color-text) l c h / 55%);
22
+ --surface-outline-color: transparent;
23
+ --panel-border: 1px dashed var(--panel-border-color);
24
+ --shadow-sm: none;
25
+ --shadow-md: 0 0 0 4px oklch(from var(--color-surface) l c h / 60%);
26
+ --shadow-lg: var(--shadow-md);
27
+ --shadow-xl: none;
28
+ }
29
+
30
+ :root[data-television-document="app"] {
31
+ --frame-radius: 0px;
32
+ --tab-radius: 0px;
33
+ --channel-radius: 0px;
34
+ --page-snap-preview-radius: 0px;
35
+ --frame-border: 1px solid var(--frame-border-color);
36
+ --frame-titlebar-border-color: var(--frame-border-color);
37
+ --sidebar-background: oklch(0.27 0.105 258);
38
+ --sidebar-heading-font-size: var(--text-sm);
39
+ --channel-background-selected: transparent;
40
+ --channel-text-color-selected: var(--accent);
41
+ --tab-background-selected: var(--color-surface);
42
+ --tab-border-selected: 1px dashed var(--surface-border-color);
43
+ --wallpaper-overlay-text-color: var(--color-text);
44
+ --page-inactive-opacity: 0.4;
45
+
46
+ /* Drafting grid: 100px majors, 20px minors, drawn entirely with gradients. */
47
+ --app-wallpaper:
48
+ linear-gradient(oklch(1 0 0 / 16%) 1px, transparent 1px) 0 0 / 100px 100px,
49
+ linear-gradient(90deg, oklch(1 0 0 / 16%) 1px, transparent 1px) 0 0 / 100px 100px,
50
+ linear-gradient(oklch(1 0 0 / 6%) 1px, transparent 1px) 0 0 / 20px 20px,
51
+ linear-gradient(90deg, oklch(1 0 0 / 6%) 1px, transparent 1px) 0 0 / 20px 20px,
52
+ oklch(0.34 0.125 258);
53
+ }
54
+
55
+ /* Part numbers: a CSS counter labels every channel like a bill of materials. */
56
+ :root[data-television-document="app"] .sidebar-body {
57
+ counter-reset: part;
58
+ }
59
+ :root[data-television-document="app"] .channel-row {
60
+ counter-increment: part;
61
+ }
62
+ :root[data-television-document="app"] .channel::before {
63
+ content: counter(part, decimal-leading-zero) " ";
64
+ color: var(--color-text-muted);
65
+ margin-right: var(--space-6);
66
+ }
67
+ /* Selection is a drafted callout — dashed box and cyan ink — instead of a fill. */
68
+ :root[data-television-document="app"] .channel[aria-selected="true"] {
69
+ outline: 1px dashed var(--accent);
70
+ outline-offset: -1px;
71
+ }
72
+ :root[data-television-document="app"] .channel[aria-selected="true"]::before {
73
+ color: var(--accent);
74
+ }
75
+
76
+ /* Drawing-label lettering for tabs and group headings. */
77
+ :root[data-television-document="app"] :is(.tab-label, .channel-group-label, .artifact-title) {
78
+ text-transform: uppercase;
79
+ letter-spacing: 0.08em;
80
+ }
81
+
82
+ /* Dimension line: a measured arrow across the top edge of the selected page. The page is not clipped, so the mark sits in the gap above the frame. */
83
+ :root[data-television-document="app"] .page[selected]::before {
84
+ content: "◂──────── SELECTED ────────▸";
85
+ position: absolute;
86
+ top: -18px;
87
+ left: 0;
88
+ right: 0;
89
+ text-align: center;
90
+ font: 10px/1 var(--font-mono);
91
+ letter-spacing: 0.1em;
92
+ white-space: nowrap;
93
+ overflow: hidden;
94
+ color: oklch(from var(--color-text) l c h / 70%);
95
+ pointer-events: none;
96
+ }
97
+
98
+ /* Title block, as on every engineering drawing, in the usually empty bottom of the sidebar. Lives on the input-free foreground overlay. */
99
+ :root[data-television-document="app"] #foreground-overlay::after {
100
+ content: "TELEVISION / CHANNEL LAYOUT\A DWG NO. TV-001 SHEET 1 OF 1\A SCALE 1:1 DO NOT SCALE DRAWING";
101
+ white-space: pre;
102
+ position: absolute;
103
+ left: 12px;
104
+ bottom: 12px;
105
+ width: calc(var(--sidebar-width) - 24px);
106
+ box-sizing: border-box;
107
+ padding: 8px 10px;
108
+ font: 9px/1.7 var(--font-mono);
109
+ letter-spacing: 0.06em;
110
+ color: oklch(from var(--color-text) l c h / 75%);
111
+ border: 1px solid oklch(from var(--color-text) l c h / 50%);
112
+ box-shadow: inset 0 0 0 3px oklch(0.27 0.105 258), inset 0 0 0 4px oklch(from var(--color-text) l c h / 30%);
113
+ background: oklch(0.27 0.105 258 / 85%);
114
+ overflow: hidden;
115
+ }
@@ -0,0 +1,15 @@
1
+ # Clouds
2
+
3
+ Clouds gives Television a cool, airy appearance with sky photographs and restrained interface colors. Light appearance pairs white clouds and blue sky with pale gray surfaces; dark appearance uses deep blue clouds and stars. Translucent controls and soft shadows keep the interface readable while leaving the sky visible around the content.
4
+
5
+ The blue accent echoes the daylight photograph. Interface colors adapt the Gray, Neutral and Slate palettes from [Tailwind CSS](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/theme.css), under the [MIT License](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/LICENSE). Copyright (c) Tailwind Labs, Inc. Complete terms are included in `THIRD-PARTY-NOTICES.txt`.
6
+
7
+ ## Wallpapers and credits
8
+
9
+ The day photograph is `wallpaper.webp`; the night photograph is `wallpaper-dark.webp`.
10
+
11
+ Day: Photo by <a href="https://unsplash.com/@ljubazarkovic?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Ljubomir Žarković</a> on <a href="https://unsplash.com/photos/white-clouds-and-blue-sky-during-daytime-bt_ZtkCxLs4?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>.
12
+
13
+ Night: Photo by <a href="https://unsplash.com/@shotbycerqueira?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Shot by Cerqueira</a> on <a href="https://unsplash.com/photos/blue-and-white-starry-night-sky-NpF9JLGYfeQ?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>.
14
+
15
+ Both photographs are used under the [Unsplash License](https://unsplash.com/license), retrieved 2026-09-17. Complete license terms and photo attributions are included in `THIRD-PARTY-NOTICES.txt`.